Skip to content

Commit 943554a

Browse files
authored
Store external Vela blocks in named data (pytorch#21893)
Differential Revision: D114240131 Pull Request resolved: pytorch#21893
1 parent bd8b2be commit 943554a

20 files changed

Lines changed: 971 additions & 77 deletions

‎backends/arm/BUCK‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
13
# Copyright 2025-2026 Arm Limited and/or its affiliates.
24
#
35
# This source code is licensed under the BSD-style license found in the
@@ -87,6 +89,7 @@ fbcode_target(
8789
":arm_vela",
8890
"//executorch/backends/arm/tosa:specification",
8991
"//executorch/backends/arm/tosa:partitioner",
92+
"//executorch/exir/_serialize:lib",
9093
],
9194
)
9295

@@ -162,6 +165,7 @@ fbcode_target(
162165
],
163166
deps = [
164167
"fbsource//third-party/pypi/ethos-u-vela:ethos-u-vela",
168+
"//executorch/exir/_serialize:lib",
165169
],
166170
)
167171
fbcode_target(

‎backends/arm/CMakeLists.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
13
# Copyright 2023, 2025-2026 Arm Limited and/or its affiliates.
24
#
35
# This source code is licensed under the BSD-style license found in the

‎backends/arm/__init__.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
"EthosUBackend": ("executorch.backends.arm.ethosu", "EthosUBackend"),
2424
"EthosUCompileSpec": ("executorch.backends.arm.ethosu", "EthosUCompileSpec"),
2525
"EthosUPartitioner": ("executorch.backends.arm.ethosu", "EthosUPartitioner"),
26+
"VelaExternalBlockPlacements": (
27+
"executorch.backends.arm.ethosu",
28+
"VelaExternalBlockPlacements",
29+
),
2630
"VgfBackend": ("executorch.backends.arm.vgf", "VgfBackend"),
2731
"VgfCompileSpec": ("executorch.backends.arm.vgf", "VgfCompileSpec"),
2832
"VgfPartitioner": ("executorch.backends.arm.vgf", "VgfPartitioner"),

‎backends/arm/arm_vela.py‎

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
13
# Copyright 2023-2026 Arm Limited and/or its affiliates.
24
#
35
# This source code is licensed under the BSD-style license found in the
46
# LICENSE file in the root directory of this source tree.
57

68

9+
import hashlib
710
import os
811
import struct
912
import tempfile
1013

14+
from collections.abc import Mapping
15+
from dataclasses import dataclass
1116
from typing import List
1217

1318
import numpy as np
@@ -20,6 +25,33 @@
2025
has_vela = False
2126

2227

28+
_BLOCK_NAME_LENGTH = 16
29+
_BLOCK_HEADER = struct.Struct(f"<{_BLOCK_NAME_LENGTH}sIB11s")
30+
_BLOCK_ALIGNMENT = 16
31+
_STREAM_HEADER = "vela_bin_stream"
32+
_STREAM_FOOTER = "vela_end_stream"
33+
_EXTERNAL_REFERENCE = 1
34+
_RESERVED_BYTES = b"\x00" * 11
35+
36+
37+
@dataclass(frozen=True)
38+
class VelaExternalBlock:
39+
"""Named-data payload emitted by Vela compilation."""
40+
41+
key: str
42+
payload: bytes
43+
alignment: int
44+
placement: str
45+
46+
47+
@dataclass(frozen=True)
48+
class VelaCompileResult:
49+
"""Binary stream and external payloads produced by Vela."""
50+
51+
processed_bytes: bytes
52+
external_blocks: tuple[VelaExternalBlock, ...] = ()
53+
54+
2355
def _as_int32(value, name: str) -> int:
2456
"""Convert numpy scalars to signed int32 with a clear error on overflow."""
2557
arr = np.asarray(value)
@@ -65,16 +97,18 @@ def vela_compile(
6597
args: List[str],
6698
verbose: bool = False,
6799
intermediate_path: str | None = None,
68-
):
100+
block_placements: Mapping[str, str] | None = None,
101+
) -> VelaCompileResult:
69102
"""Compile a TOSA graph to a binary stream for ArmBackendEthosU using
70103
Vela.
71104
"""
72105
if not has_vela:
73106
raise RuntimeError(
74107
"ethos-u-vela pip package couldn't be imported. Make sure it's installed!"
75108
)
109+
resolved_block_placements: Mapping[str, str] = block_placements or {}
76110

77-
def run(dir: str) -> bytes:
111+
def run(dir: str) -> VelaCompileResult:
78112
tosaname = "out.tosa"
79113
tosa_path = os.path.join(dir, tosaname)
80114
with open(tosa_path, "wb") as f:
@@ -90,11 +124,10 @@ def run(dir: str) -> bytes:
90124

91125
np_path = os.path.join(dir, "output", "out_vela.npz")
92126

93-
blocks = b""
94127
with np.load(np_path, allow_pickle=False) as data:
95128
# Construct our modified output_blocks with data in a form easily
96129
# digested on the device side
97-
bin_blocks = {"vela_bin_stream": b""}
130+
bin_blocks = {_STREAM_HEADER: b""}
98131

99132
# copy command data through unmodified
100133
bin_blocks["cmd_data"] = data["cmd_data"].tobytes()
@@ -114,28 +147,58 @@ def run(dir: str) -> bytes:
114147
bin_blocks["inputs"] = vela_bin_pack_io("input", data)
115148
bin_blocks["outputs"] = vela_bin_pack_io("output", data)
116149

117-
bin_blocks["vela_end_stream"] = b""
150+
bin_blocks[_STREAM_FOOTER] = b""
151+
152+
unknown_blocks = resolved_block_placements.keys() - bin_blocks.keys()
153+
if unknown_blocks:
154+
raise ValueError(
155+
"External Vela block placements reference blocks that were not "
156+
f"emitted: {sorted(unknown_blocks)}"
157+
)
118158

119159
# Emit the NPZ regions as:
120160
# - 16 byte block name null terminated string (padded to 16 if name shorter)
121-
# - 4 bytes of int32 block length and 12 bytes of 0's
161+
# - 4 bytes of int32 block length, 1 byte external flag, and 11 reserved bytes
122162
# - block data (padded to 16 byte alignment at end)
123163
# Repeat for all blocks
164+
blocks = b""
165+
external_blocks: list[VelaExternalBlock] = []
124166
for key in bin_blocks.keys():
125167
block_name = bytes(key, "utf8")[:15]
126168
block_name = block_name + b"\x00" * (16 - len(block_name))
169+
block_data = bin_blocks[key]
170+
placement = resolved_block_placements.get(key)
171+
external_reference = 0
172+
if placement is not None:
173+
digest = hashlib.sha256(
174+
placement.encode("ascii") + b"\0" + block_data
175+
).hexdigest()
176+
external_blocks.append(
177+
VelaExternalBlock(
178+
key=digest,
179+
payload=block_data,
180+
alignment=_BLOCK_ALIGNMENT,
181+
placement=placement,
182+
)
183+
)
184+
block_data = digest.encode("ascii")
185+
external_reference = _EXTERNAL_REFERENCE
127186

128187
# We need the acual unpadded block lengths for hw setup
129-
block_length_bytes = struct.pack("<iiii", len(bin_blocks[key]), 0, 0, 0)
188+
block_header = _BLOCK_HEADER.pack(
189+
block_name,
190+
len(block_data),
191+
external_reference,
192+
_RESERVED_BYTES,
193+
)
130194

131195
# Pad block data to multiple of 16 bytes
132-
block_data = bin_blocks[key]
133196
block_data = block_data + b"\x00" * (15 - (len(block_data) - 1) % 16)
134197

135-
block = block_name + block_length_bytes + block_data
198+
block = block_header + block_data
136199
blocks = blocks + block
137200

138-
return blocks
201+
return VelaCompileResult(blocks, tuple(external_blocks))
139202

140203
if intermediate_path is not None:
141204
return run(intermediate_path)

‎backends/arm/ethosu/__init__.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
#
66

77
from .backend import EthosUBackend # noqa: F401
8-
from .compile_spec import EthosUCompileSpec # noqa: F401
8+
from .compile_spec import EthosUCompileSpec, VelaExternalBlockPlacements # noqa: F401
99
from .partitioner import EthosUPartitioner # noqa: F401
1010

11-
__all__ = ["EthosUBackend", "EthosUPartitioner", "EthosUCompileSpec"]
11+
__all__ = [
12+
"EthosUBackend",
13+
"EthosUPartitioner",
14+
"EthosUCompileSpec",
15+
"VelaExternalBlockPlacements",
16+
]

‎backends/arm/ethosu/backend.py‎

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
13
# Copyright 2025-2026 Arm Limited and/or its affiliates.
24
#
35
# This source code is licensed under the BSD-style license found in the
@@ -14,10 +16,11 @@
1416
import logging
1517
from typing import final, List
1618

17-
from executorch.backends.arm.arm_vela import vela_compile
19+
from executorch.backends.arm.arm_vela import vela_compile, VelaCompileResult
1820
from executorch.backends.arm.ethosu.compile_spec import EthosUCompileSpec
1921

2022
from executorch.backends.arm.tosa.backend import TOSABackend
23+
from executorch.exir._serialize._named_data_store import NamedDataStore
2124
from executorch.exir.backend.backend_details import BackendDetails, PreprocessResult
2225
from executorch.exir.backend.compile_spec_schema import CompileSpec
2326
from torch.export.exported_program import ExportedProgram
@@ -37,18 +40,18 @@ class EthosUBackend(BackendDetails):
3740

3841
@staticmethod
3942
def _compile_tosa_flatbuffer(
40-
tosa_flatbuffer: bytes, compile_spec: EthosUCompileSpec
41-
) -> bytes:
43+
tosa_flatbuffer: bytes,
44+
compile_spec: EthosUCompileSpec,
45+
) -> VelaCompileResult:
4246
"""Compile a TOSA flatbuffer into a target-specific binary stream.
4347
4448
Args:
4549
tosa_flatbuffer (bytes): Serialized TOSA graph produced by
4650
``TOSABackend``.
4751
compile_spec (EthosUCompileSpec): Compile specification providing
4852
Vela flags and intermediate paths.
49-
5053
Returns:
51-
bytes: Target-specific binary stream produced by Vela.
54+
VelaCompileResult: Binary stream and external payloads from Vela.
5255
5356
"""
5457
compile_flags = compile_spec.compiler_flags
@@ -70,13 +73,15 @@ def _compile_tosa_flatbuffer(
7073
)
7174

7275
# Pass on the TOSA flatbuffer to the vela compiler.
73-
binary = vela_compile(
76+
return vela_compile(
7477
tosa_flatbuffer,
7578
compile_flags,
7679
verbose=logger.getEffectiveLevel() <= logging.INFO,
7780
intermediate_path=compile_spec._get_intermediate_path(),
81+
block_placements=(
82+
compile_spec.external_block_placements.to_block_placements()
83+
),
7884
)
79-
return binary
8085

8186
@staticmethod
8287
def preprocess(
@@ -108,8 +113,21 @@ def preprocess(
108113
# which can be passed on to next compilation step.
109114
tosa_preprocess = TOSABackend._preprocess(edge_program, tosa_compile_spec)
110115

111-
binary = EthosUBackend._compile_tosa_flatbuffer(
116+
compile_result = EthosUBackend._compile_tosa_flatbuffer(
112117
tosa_preprocess.processed_bytes, compile_spec
113118
)
114-
115-
return PreprocessResult(processed_bytes=binary)
119+
if not compile_result.external_blocks:
120+
return PreprocessResult(processed_bytes=compile_result.processed_bytes)
121+
122+
data_store = NamedDataStore()
123+
for external_block in compile_result.external_blocks:
124+
data_store.add_named_data(
125+
external_block.key,
126+
external_block.payload,
127+
alignment=external_block.alignment,
128+
external_tag=external_block.placement,
129+
)
130+
return PreprocessResult(
131+
processed_bytes=compile_result.processed_bytes,
132+
data_store_output=data_store.get_named_data_store_output(),
133+
)

0 commit comments

Comments
 (0)