Skip to content

Commit 6730dd6

Browse files
committed
Add tosa VGF encapsulated compilation target for ahead of time.
- Add a VgfQuantizer (same as TOSAQuantizer) - Add a VgfBackend and VgfPartitioner - Requires yet to be released converter_backend Signed-off-by: Rob Elliott <[email protected]> Change-Id: I764c32c33c503eb44200e9a7d98caa8fae8a4882
1 parent 6ed5513 commit 6730dd6

File tree

8 files changed

+221
-12
lines changed

8 files changed

+221
-12
lines changed

backends/arm/TARGETS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ python_library(
77
"ethosu_partitioner.py",
88
"tosa_backend.py",
99
"tosa_partitioner.py",
10+
"vgf_backend.py",
11+
"vgf_partitioner.py",
1012
],
1113
deps = [
1214
":arm_backend",

backends/arm/arm_backend.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@ def __init__(self):
2727
self.tosa_spec = None
2828
self.input_order = None
2929

30+
def vgf_compile_spec(
31+
self,
32+
compiler_flags: Optional[str] = "",
33+
) -> "ArmCompileSpecBuilder":
34+
"""
35+
Generate compile spec for VGF compatible targets
36+
37+
Args:
38+
compiler_flags: Extra compiler flags for converter_backend
39+
"""
40+
self.output_format = "vgf"
41+
self.compiler_flags = [
42+
compiler_flags,
43+
]
44+
self.tosa_spec = TosaSpecification.create_from_string("TOSA-0.80+MI")
45+
return self
46+
3047
def ethosu_compile_spec(
3148
self,
3249
target: str,
@@ -126,13 +143,16 @@ def build(self) -> List[CompileSpec]:
126143
# Always supply a TOSA version
127144
self.compile_spec = [CompileSpec("tosa_spec", str(self.tosa_spec).encode())]
128145

129-
if self.output_format == "vela":
130-
self.compile_spec += [
131-
CompileSpec("output_format", "vela".encode()),
132-
CompileSpec("compile_flags", " ".join(self.compiler_flags).encode()),
133-
]
134-
elif self.output_format == "tosa":
135-
self.compile_spec.append(CompileSpec("output_format", "tosa".encode()))
146+
# Add compile flags, these are backend specific, refer to the backend
147+
# documentation.
148+
self.compile_spec += [
149+
CompileSpec("compile_flags", " ".join(self.compiler_flags).encode()),
150+
]
151+
152+
# encode output format
153+
self.compile_spec.append(
154+
CompileSpec("output_format", self.output_format.encode())
155+
)
136156

137157
if self.path_for_intermediates is not None:
138158
self.compile_spec.append(
@@ -168,6 +188,13 @@ def is_ethosu(compile_spec: List[CompileSpec]) -> bool:
168188
return False
169189

170190

191+
def is_vgf(compile_spec: List[CompileSpec]) -> bool:
192+
for spec in compile_spec:
193+
if spec.key == "output_format":
194+
return spec.value.decode() == "vgf"
195+
return False
196+
197+
171198
def get_tosa_spec(compile_spec: List[CompileSpec]) -> TosaSpecification:
172199
for spec in compile_spec:
173200
if spec.key == "tosa_spec":

backends/arm/ethosu_backend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class EthosUBackend(BackendDetails):
3535

3636
@staticmethod
3737
def _compile_tosa_flatbuffer(
38-
tosa_flatbuffer: bytes, compile_spec: list[CompileSpec]
38+
tosa_flatbuffer: bytes, compile_spec: List[CompileSpec]
3939
) -> bytes:
4040
"""
4141
Static helper method to do the compilation of the TOSA flatbuffer

backends/arm/quantizer/arm_quantizer.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from executorch.backends.arm.arm_backend import (
2828
get_tosa_spec,
2929
is_ethosu,
30+
is_vgf,
3031
) # usort: skip
3132
from executorch.exir.backend.compile_spec_schema import CompileSpec
3233
from torch.ao.quantization.fake_quantize import (
@@ -52,6 +53,7 @@
5253
__all__ = [
5354
"TOSAQuantizer",
5455
"EthosUQuantizer",
56+
"VgfQuantizer",
5557
"get_symmetric_quantization_config",
5658
]
5759

@@ -358,3 +360,12 @@ def __init__(self, compile_spec: list[CompileSpec]) -> None:
358360

359361
tosa_spec = get_tosa_spec(compile_spec)
360362
super().__init__(tosa_spec)
363+
364+
365+
class VgfQuantizer(TOSAQuantizer):
366+
def __init__(self, compile_spec: list[CompileSpec]) -> None:
367+
if not is_vgf(compile_spec):
368+
raise RuntimeError("compile spec is not targeting VGF")
369+
370+
tosa_spec = get_tosa_spec(compile_spec)
371+
super().__init__(tosa_spec)

backends/arm/tosa_backend.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@
3535
logger = logging.getLogger(__name__)
3636

3737

38-
def _get_first_delegation_tag(graph_module) -> str | None:
39-
"""Get the first delegation tag from the graph_module or return None."""
38+
def arm_get_first_delegation_tag(graph_module) -> str:
39+
"""Get the first delegation tag from the graph_module or return empty string."""
4040
for node in graph_module.graph.nodes:
4141
tag = node.meta.get("delegation_tag")
4242
if tag:
4343
return tag
4444

4545
logger.debug("No delegation tag found in partition.")
46-
return None
46+
return ""
4747

4848

4949
@final
@@ -136,7 +136,7 @@ def preprocess( # noqa: C901
136136
)
137137

138138
if artifact_path:
139-
tag = _get_first_delegation_tag(graph_module)
139+
tag = arm_get_first_delegation_tag(graph_module)
140140
dbg_tosa_dump(
141141
tosa_graph,
142142
artifact_path,

backends/arm/vgf_backend.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Copyright 2025 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
# pyre-unsafe
7+
8+
#
9+
# Main implementation of AoT flow to partition and preprocess for VGF target
10+
# backends. This flow converts via TOSA, to an encoding of TOSA known as VGF
11+
# this form is used where the final JIT compile is performed on target (in the
12+
# runtime delegate executorch::runtime::BackendInterface::init
13+
#
14+
15+
import logging
16+
import os
17+
import subprocess
18+
import tempfile
19+
from typing import final, List
20+
21+
from executorch.backends.arm.tosa_backend import (
22+
arm_get_first_delegation_tag,
23+
TOSABackend,
24+
)
25+
from executorch.exir.backend.backend_details import BackendDetails, PreprocessResult
26+
from executorch.exir.backend.compile_spec_schema import CompileSpec
27+
from torch.export.exported_program import ExportedProgram
28+
29+
# debug functionality
30+
logger = logging.getLogger(__name__)
31+
32+
33+
@final
34+
class VgfBackend(BackendDetails):
35+
"""
36+
BackendDetails subclass for delegation to VGF compatible devices. This enables
37+
encapsulated TOSA on target device and JIT compilation on suitable platforms.
38+
"""
39+
40+
@staticmethod
41+
def _compile_tosa_flatbuffer(
42+
tosa_flatbuffer: bytes,
43+
compile_spec: List[CompileSpec],
44+
tag_name: str = "",
45+
) -> bytes:
46+
"""
47+
Static helper method to do the compilation of the TOSA flatbuffer
48+
representation to a target specific binary stream.
49+
"""
50+
compile_flags = []
51+
artifact_path = None
52+
for spec in compile_spec:
53+
if spec.key == "compile_flags":
54+
compile_flags.append(spec.value.decode())
55+
if spec.key == "debug_artifact_path":
56+
artifact_path = spec.value.decode()
57+
58+
# Pass on the TOSA flatbuffer to the vgf compiler.
59+
binary = vgf_compile(tosa_flatbuffer, compile_flags, artifact_path, tag_name)
60+
return binary
61+
62+
@staticmethod
63+
def preprocess(
64+
edge_program: ExportedProgram,
65+
compile_spec: List[CompileSpec],
66+
) -> PreprocessResult:
67+
logger.info(f"{VgfBackend.__name__} preprocess")
68+
69+
# deduce TOSA compile_spec from VGF compile spec. We get a new
70+
# compile spec list, containing only elements relevant for the
71+
# TOSABackend.
72+
tosa_compile_spec = TOSABackend.filter_tosa_compile_specs(compile_spec)
73+
74+
# Backends doesn't allow inheritance, as stated in comments in exir/backend/backend_api.py
75+
# ('All backend implementation are final...'), so use composition instead.
76+
# preprocess returns the serialized TOSA flatbuffer in .processed_bytes,
77+
# which can be passed on to next compilation step.
78+
tosa_preprocess = TOSABackend.preprocess(edge_program, tosa_compile_spec)
79+
80+
tag_name = arm_get_first_delegation_tag(edge_program.graph_module)
81+
82+
binary = VgfBackend._compile_tosa_flatbuffer(
83+
tosa_preprocess.processed_bytes, compile_spec, tag_name
84+
)
85+
86+
return PreprocessResult(processed_bytes=binary)
87+
88+
89+
def vgf_compile(
90+
tosa_flatbuffer: bytes,
91+
compile_flags: List[str],
92+
artifact_path: str | None = None,
93+
tag_name: str = "",
94+
):
95+
with tempfile.TemporaryDirectory() as tmpdir:
96+
97+
# We currently write out a flatbuffer as input to the converter
98+
tosaname = f"output_{tag_name}.tosa"
99+
tosa_path = os.path.join(tmpdir, tosaname)
100+
with open(tosa_path, "wb") as f:
101+
f.write(tosa_flatbuffer)
102+
103+
additional_flags = " ".join(compile_flags)
104+
vgf_path = tosa_path + ".vgf"
105+
conversion_command = (
106+
f"converter-backend {additional_flags} -i {tosa_path} -o {vgf_path}"
107+
)
108+
try:
109+
subprocess.run(
110+
[conversion_command], shell=True, check=True, capture_output=True
111+
)
112+
except subprocess.CalledProcessError as process_error:
113+
raise RuntimeError(
114+
f"Vgf compiler ('{conversion_command}') failed with error:\n \
115+
{process_error.stderr.decode()}\n \
116+
Stdout:\n{process_error.stdout.decode()}"
117+
)
118+
119+
if artifact_path is not None:
120+
logger.info(f"Emitting debug output to: {vgf_path=}")
121+
os.makedirs(artifact_path, exist_ok=True)
122+
cp = f"cp {vgf_path} {artifact_path}"
123+
subprocess.run(cp, shell=True, check=True, capture_output=False)
124+
125+
vgf_bytes = open(vgf_path, "rb").read()
126+
return vgf_bytes

backends/arm/vgf_partitioner.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2025 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
# pyre-unsafe
7+
8+
from typing import final, List, Optional, Sequence
9+
10+
from executorch.backends.arm.arm_backend import (
11+
is_vgf,
12+
) # usort: skip
13+
from executorch.backends.arm.tosa_partitioner import TOSAPartitioner
14+
from executorch.backends.arm.vgf_backend import VgfBackend
15+
from executorch.exir.backend.compile_spec_schema import CompileSpec
16+
from executorch.exir.backend.partitioner import DelegationSpec
17+
from torch.fx.passes.operator_support import OperatorSupportBase
18+
19+
20+
@final
21+
class VgfPartitioner(TOSAPartitioner):
22+
def __init__(
23+
self,
24+
compile_spec: List[CompileSpec],
25+
additional_checks: Optional[Sequence[OperatorSupportBase]] = None,
26+
) -> None:
27+
if not is_vgf(compile_spec):
28+
raise RuntimeError("compile spec is not targeting Vgf")
29+
30+
# Override the delegation spec for Vgf
31+
self.delegation_spec = DelegationSpec(VgfBackend.__name__, compile_spec)
32+
self.additional_checks = additional_checks

examples/arm/aot_arm_compiler.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222
get_tosa_spec,
2323
is_ethosu,
2424
is_tosa,
25+
is_vgf,
2526
)
2627
from executorch.backends.arm.ethosu_partitioner import EthosUPartitioner
2728
from executorch.backends.arm.quantizer import (
2829
EthosUQuantizer,
2930
get_symmetric_quantization_config,
3031
TOSAQuantizer,
32+
VgfQuantizer,
3133
)
3234
from executorch.backends.arm.tosa_partitioner import TOSAPartitioner
3335
from executorch.backends.arm.tosa_specification import TosaSpecification
@@ -36,6 +38,8 @@
3638
GenericModelEvaluator,
3739
MobileNetV2Evaluator,
3840
)
41+
42+
from executorch.backends.arm.vgf_partitioner import VgfPartitioner
3943
from executorch.devtools.backend_debug import get_delegation_info
4044
from executorch.devtools.bundled_program.config import MethodTestCase, MethodTestSuite
4145

@@ -145,6 +149,8 @@ def quantize(
145149
quantizer = EthosUQuantizer(compile_specs)
146150
elif is_tosa(compile_specs):
147151
quantizer = TOSAQuantizer(get_tosa_spec(compile_specs))
152+
elif is_vgf(compile_specs):
153+
quantizer = VgfQuantizer(compile_specs)
148154
else:
149155
raise RuntimeError("Unsupported compilespecs for quantization!")
150156

@@ -267,6 +273,7 @@ def forward(self, x: torch.Tensor, y: torch.Tensor):
267273
"ethos-u85-512",
268274
"ethos-u85-1024",
269275
"ethos-u85-2048",
276+
"vgf",
270277
"TOSA",
271278
]
272279

@@ -324,6 +331,8 @@ def get_compile_spec(
324331
memory_mode=memory_mode,
325332
extra_flags="--verbose-operators --verbose-cycle-estimate",
326333
)
334+
elif "vgf" in target:
335+
spec_builder = ArmCompileSpecBuilder().vgf_compile_spec()
327336

328337
if intermediates is not None:
329338
spec_builder.dump_intermediate_artifacts_to(intermediates)
@@ -635,6 +644,8 @@ def to_edge_TOSA_delegate(
635644
partitioner = EthosUPartitioner(compile_spec)
636645
elif is_tosa(compile_spec):
637646
partitioner = TOSAPartitioner(compile_spec)
647+
elif is_vgf(compile_spec):
648+
partitioner = VgfPartitioner(compile_spec)
638649
else:
639650
raise RuntimeError(f"Unhandled compile spec: {compile_spec}")
640651

0 commit comments

Comments
 (0)