Skip to content

Commit 1186129

Browse files
mcr229facebook-github-bot
authored andcommitted
BC Fix Stage 2 fully migrate to constant data (#1960)
Summary: Pull Request resolved: #1960 This is part 2 of stage 2. We are updating the model generation to generate XN01 models. The change here is removing all usage of old constant data serialization (serializing data in flatbuffer) and moving completely to serializing data using the XNNHeader. This removes a few flatbuffer constructs like Buffer and also removes items from the XNNGraph like constant_buffer and mem_id_sizes Reviewed By: digantdesai, kirklandsign Differential Revision: D53495025 fbshipit-source-id: 23211d92a690daf8e439ddf46547f966befe96ba
1 parent 7973d7a commit 1186129

File tree

6 files changed

+52
-160
lines changed

6 files changed

+52
-160
lines changed

backends/xnnpack/operators/node_visitor.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
)
1818

1919
from executorch.backends.xnnpack.serialization.xnnpack_graph_schema import (
20-
Buffer,
20+
ConstantDataOffset,
2121
PerChannelQuant,
2222
PerTensorQuant,
2323
PerTokenDynamicQuant,
@@ -43,6 +43,12 @@
4343
torch.float32: XNNDatatype.xnn_datatype_fp32,
4444
}
4545

46+
from executorch.backends.xnnpack.serialization.xnnpack_graph_serialize import (
47+
_aligned_size,
48+
_pad_to,
49+
CONSTANT_TENSOR_ALIGNMENT,
50+
)
51+
4652

4753
class InputTypeToIndex:
4854
"""
@@ -78,9 +84,11 @@ def __init__(
7884
self,
7985
exported_program: ExportedProgram,
8086
external_ids: Dict,
87+
constant_data_bytes: bytearray,
8188
) -> None:
8289
self._external_ids = external_ids or {}
8390
self._exported_program = exported_program or None
91+
self._constant_data_bytes = constant_data_bytes
8492

8593
@property
8694
def external_ids(self) -> Dict:
@@ -317,7 +325,7 @@ def define_tensor(
317325
dims = [1] if len(dims) == 0 else dims
318326

319327
# constant values serialize data
320-
buffer_idx = self.get_serialized_buffer(
328+
buffer_idx = self.get_serialized_buffer_index(
321329
tensor,
322330
xnn_graph,
323331
vals_to_ids,
@@ -426,7 +434,7 @@ def convert_to_qc4w(inp: torch.Tensor) -> torch.Tensor:
426434

427435
return result
428436

429-
def get_serialized_buffer(
437+
def get_serialized_buffer_index(
430438
self,
431439
tensor: torch.fx.Node,
432440
xnn_graph: XNNGraph,
@@ -469,11 +477,7 @@ def get_serialized_buffer(
469477
)
470478
return 0
471479

472-
check_or_raise(
473-
len(xnn_graph.constant_buffer) == len(xnn_graph.mem_buffer_sizes),
474-
"Internal Error: const_buffer and buffer_sizes length mismatch",
475-
)
476-
buffer_idx = len(xnn_graph.constant_buffer)
480+
buffer_idx = len(xnn_graph.constant_data)
477481
const_val = get_param_tensor(self.exported_program, get_attr_node)
478482
assert const_val is not None and isinstance(const_val, torch.Tensor)
479483
const_val = const_val.contiguous()
@@ -501,9 +505,13 @@ def get_serialized_buffer(
501505
const_val.untyped_storage().data_ptr(),
502506
ctypes.POINTER(array_type),
503507
).contents
504-
buffer = Buffer(storage=bytes(array))
505-
xnn_graph.constant_buffer.append(buffer)
506-
xnn_graph.mem_buffer_sizes.append(const_val.untyped_storage().nbytes())
508+
509+
offset = len(self._constant_data_bytes)
510+
size = const_val.untyped_storage().nbytes()
511+
xnn_graph.constant_data.append(ConstantDataOffset(offset=offset, size=size))
512+
self._constant_data_bytes.extend(
513+
_pad_to(bytes(array), _aligned_size(size, CONSTANT_TENSOR_ALIGNMENT))
514+
)
507515

508516
return buffer_idx
509517

backends/xnnpack/serialization/schema.fbs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,9 @@ union XNNQuantParams {
3636
PerTokenDynamicQuant,
3737
}
3838

39-
// taken from executorch
40-
// Data buffer abstraction.
39+
// Deprecated buffer abstraction, const data buffers do not belong in flatbuffer
4140
table Buffer {
42-
storage:[ubyte] (force_align: 16);
41+
storage:[ubyte] (deprecated, force_align: 16);
4342
}
4443

4544
table PerChannelQuant {
@@ -324,18 +323,14 @@ table XNNGraph {
324323
// Ids of external outputs
325324
output_ids:[uint];
326325

327-
// Tables of constant data, used for constant Values (e.g.
328-
// data field of weight tensors). Each constant is assigned an index into the table
329-
// which are each individually aligned. 0 index is reserved to be pointed to by non-constant
330-
// Tensors. Exactly one of constant_buffer and constant_data must be non-empty
331-
constant_buffer:[Buffer];
326+
// Deprecated constant buffer storage in flatbuffer
327+
constant_buffer:[Buffer] (deprecated);
332328

333-
// the list index is memory buffer id, the value is the memory buffer size.
334-
mem_buffer_sizes: [uint];
329+
// Deprecated memory_buffer size tracking in flatbuffer
330+
mem_buffer_sizes: [uint] (deprecated);
335331

336332
// List of the constant data that follows the XNNGraph in this file. Each constant data is assigned an index into
337-
// the table. 0 index is reserved to be pointed to by non-constant Tensor. Exactly one of constant_buffer and
338-
// constant_data must be non-empty
333+
// the table. 0 index is reserved to be pointed to by non-constant Tensor.
339334
constant_data:[ConstantDataOffset];
340335
}
341336

backends/xnnpack/serialization/xnnpack_graph_schema.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,6 @@ class XValue:
431431
xvalue_union: "XValueUnion"
432432

433433

434-
@dataclass
435-
class Buffer:
436-
storage: bytes
437-
438-
439434
@dataclass
440435
class ConstantDataOffset:
441436
offset: int
@@ -452,7 +447,4 @@ class XNNGraph:
452447
input_ids: List[int]
453448
output_ids: List[int]
454449

455-
constant_buffer: List[Buffer]
456-
mem_buffer_sizes: List[int]
457-
458450
constant_data: List[ConstantDataOffset]

backends/xnnpack/serialization/xnnpack_graph_serialize.py

Lines changed: 12 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,10 @@
99
import tempfile
1010

1111
from dataclasses import dataclass, fields, is_dataclass
12-
from typing import ClassVar, List, Literal, Tuple
12+
from typing import ClassVar, Literal
1313

1414
import pkg_resources
15-
from executorch.backends.xnnpack.serialization.xnnpack_graph_schema import (
16-
Buffer,
17-
ConstantDataOffset,
18-
XNNGraph,
19-
)
15+
from executorch.backends.xnnpack.serialization.xnnpack_graph_schema import XNNGraph
2016
from executorch.exir._serialize._dataclass import _DataclassEncoder
2117

2218
from executorch.exir._serialize._flatbuffer import _flatc_compile
@@ -26,6 +22,9 @@
2622
# endian.
2723
_HEADER_BYTEORDER: Literal["little"] = "little"
2824

25+
# Constant Tensor alignment for serializaing XNNPACK payloads
26+
CONSTANT_TENSOR_ALIGNMENT = 16
27+
2928

3029
def sanity_check_xnngraph_dataclass(table, name: str = ""):
3130
"""
@@ -274,40 +273,6 @@ def _pad_to(data: bytes, length: int) -> bytes:
274273
return data
275274

276275

277-
def _extract_constant_data(
278-
constant_buffer: List[Buffer],
279-
tensor_alignment: int = 16,
280-
) -> Tuple[bytes, List[int]]:
281-
"""Copies the tensors from the provided list into a single buffer and tracks the offsets
282-
of each tensor.
283-
284-
constant_buffer: list of Buffers from which to extract constants from. Not modified.
285-
tensor_alignment: Alignment in bytes. The starting offset of each tensor in the
286-
constant segment will be aligned to this value. Default to 16.
287-
288-
Returns:
289-
A tuple of (constant segment, list of offsets for each tensor in the segment)
290-
"""
291-
constant_segment_data: bytearray = bytearray()
292-
constant_segment_offsets: List[int] = []
293-
current_offset: int = 0
294-
for i in range(len(constant_buffer)):
295-
buffer = constant_buffer[i]
296-
buffer_length = len(buffer.storage)
297-
pad_length = _padding_required(buffer_length, tensor_alignment)
298-
299-
# Append each constant buffer to the constant segment.
300-
constant_segment_data += buffer.storage
301-
# Add padding for all but the last tensor.
302-
if i < len(constant_buffer) - 1:
303-
constant_segment_data += b"\x00" * pad_length
304-
305-
# Append constant data offset.
306-
constant_segment_offsets.append(current_offset)
307-
current_offset += buffer_length + pad_length
308-
return bytes(constant_segment_data), constant_segment_offsets
309-
310-
311276
def pretty_print_xnngraph(xnnpack_graph_json: str):
312277
"""
313278
Pretty print the XNNGraph
@@ -335,7 +300,9 @@ def convert_to_flatbuffer(xnnpack_graph: XNNGraph) -> bytes:
335300
return output_file.read()
336301

337302

338-
def serialize_xnnpack_binary(xnnpack_graph: XNNGraph) -> bytes:
303+
def serialize_xnnpack_binary(
304+
xnnpack_graph: XNNGraph, constant_data_bytes: bytearray
305+
) -> bytes:
339306
"""Returns the runtime binary representation of the given XNNGraph.
340307
341308
Args:
@@ -344,56 +311,32 @@ def serialize_xnnpack_binary(xnnpack_graph: XNNGraph) -> bytes:
344311
Returns:
345312
The serialized form of the XNNGraph, ready for execution by XNNPACK Backend
346313
"""
347-
constant_tensor_alignment = 16
348-
349-
# Extract constant data from the graph
350-
constant_data, constant_data_offsets = _extract_constant_data(
351-
xnnpack_graph.constant_buffer, constant_tensor_alignment
352-
)
353-
354-
assert len(constant_data_offsets) == len(xnnpack_graph.mem_buffer_sizes)
355-
356-
for offset_idx in range(len(constant_data_offsets)):
357-
constant_data_offset = constant_data_offsets[offset_idx]
358-
constant_data_size = xnnpack_graph.mem_buffer_sizes[offset_idx]
359-
xnnpack_graph.constant_data.append(
360-
ConstantDataOffset(constant_data_offset, constant_data_size)
361-
)
362-
363-
# We are moving all constant data from the graph to the constant data section.
364-
# So we remove all constant buffers
365-
xnnpack_graph.constant_buffer = []
366-
xnnpack_graph.mem_buffer_sizes = []
367314

368315
# Convert the XNNGraph to a flatbuffer
369316
flatbuffer_payload = convert_to_flatbuffer(xnnpack_graph)
370317

371318
# size of flatbuffer data, padded to be `constant_tensor_alignment` byte aligned
372319
padded_flatbuffer_length: int = _aligned_size(
373320
input_size=len(flatbuffer_payload),
374-
alignment=constant_tensor_alignment,
321+
alignment=CONSTANT_TENSOR_ALIGNMENT,
375322
)
376323
# size of header to insert, padded to be `constant_tensor_alignment` byte aligned
377324
padded_header_length: int = _aligned_size(
378-
input_size=XNNHeader.EXPECTED_LENGTH,
379-
alignment=constant_tensor_alignment,
325+
input_size=XNNHeader.EXPECTED_LENGTH, alignment=CONSTANT_TENSOR_ALIGNMENT
380326
)
381327

382328
# Create the XNNPACK Header
383329
header: bytes = XNNHeader(
384330
flatbuffer_offset=padded_header_length,
385331
flatbuffer_size=len(flatbuffer_payload),
386332
constant_data_offset=padded_header_length + padded_flatbuffer_length,
387-
constant_data_size=len(constant_data),
333+
constant_data_size=len(constant_data_bytes),
388334
).to_bytes()
389335

390-
# Concatenate the header, flatbuffer data, and constant data
391-
# Constant data does not need to be padded to alignment because nothing follows it
392-
393336
return b"".join(
394337
[
395338
_pad_to(header, padded_header_length),
396339
_pad_to(flatbuffer_payload, padded_flatbuffer_length),
397-
constant_data,
340+
constant_data_bytes,
398341
]
399342
)

backends/xnnpack/test/serialization/test_serialization.py

Lines changed: 6 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7-
import os
8-
import random
97
import unittest
10-
from typing import List, Tuple
118

129
from executorch.backends.xnnpack.serialization.xnnpack_graph_schema import (
13-
Buffer,
10+
ConstantDataOffset,
1411
XNNGraph,
1512
)
1613

@@ -22,23 +19,6 @@
2219

2320

2421
class TestSerialization(unittest.TestCase):
25-
def _generate_random_const_buffers(
26-
self, num_tensors: int
27-
) -> Tuple[List[Buffer], List[int]]:
28-
"""
29-
Helper function to generate `num_tensor` buffers of random sizes and random contents,
30-
we return a tuple of (list_of_buffers, list_of_mem_sizes),
31-
"""
32-
buffers = []
33-
mem_sizes = []
34-
for _ in range(num_tensors):
35-
buffer_size = random.randint(1, 1000)
36-
buffer = bytearray(os.urandom(buffer_size))
37-
buffers.append(Buffer(storage=bytes(buffer)))
38-
mem_sizes.append(buffer_size)
39-
40-
return buffers, mem_sizes
41-
4222
def test_serialize_xnnpack_binary(self):
4323
xnn_graph = XNNGraph(
4424
version="0",
@@ -47,25 +27,18 @@ def test_serialize_xnnpack_binary(self):
4727
num_externs=0,
4828
input_ids=[],
4929
output_ids=[],
50-
constant_buffer=[Buffer(storage=b"")],
51-
mem_buffer_sizes=[0],
52-
constant_data=[],
30+
constant_data=[ConstantDataOffset(0, 0)],
5331
)
54-
buffers, sizes = self._generate_random_const_buffers(5)
55-
xnn_graph.constant_buffer.extend(buffers)
56-
xnn_graph.mem_buffer_sizes.extend(sizes)
57-
buffers = xnn_graph.constant_buffer
5832

59-
serialized_binary = serialize_xnnpack_binary(xnn_graph)
60-
offsets = xnn_graph.constant_data
33+
constant_data_bytes = b"\x00" * 24
34+
serialized_binary = serialize_xnnpack_binary(
35+
xnn_graph, bytearray(constant_data_bytes)
36+
)
6137

6238
# Check header
6339
self.assertEqual(serialized_binary[0:4], b"\x00\x00\x00\x00")
6440
self.assertEqual(serialized_binary[XNNHeader.MAGIC_OFFSET], b"XH00")
6541
flatbuffer_offset_bytes = serialized_binary[XNNHeader.FLATBUFFER_OFFSET_OFFSET]
66-
constant_data_offset_bytes = serialized_binary[
67-
XNNHeader.CONSTANT_DATA_OFFSET_OFFSET
68-
]
6942

7043
# Check flatbuffer is at flatbuffer offset
7144
flatbuffer_offset = int.from_bytes(
@@ -75,24 +48,3 @@ def test_serialize_xnnpack_binary(self):
7548
self.assertEqual(
7649
serialized_binary[flatbuffer_offset:][XNNHeader.MAGIC_OFFSET], b"XN01"
7750
)
78-
79-
# Check constant data
80-
# Check that constant buffers have been moved to constant data
81-
self.assertEqual(len(offsets), len(buffers))
82-
self.assertEqual(len(xnn_graph.constant_buffer), 0)
83-
84-
constant_data_offset = int.from_bytes(
85-
constant_data_offset_bytes, byteorder=_HEADER_BYTEORDER
86-
)
87-
constant_data_payload = serialized_binary[constant_data_offset:]
88-
89-
# We check that constant data indexes stored in the xnn_graph correctly index
90-
# into the correct buffer in the constant data section
91-
for idx in range(1, len(offsets)):
92-
offset = offsets[idx].offset
93-
size = offsets[idx].size
94-
95-
constant_data_bytes = constant_data_payload[offset : offset + size]
96-
constant_buffer_bytes = buffers[idx].storage
97-
98-
self.assertEqual(constant_data_bytes, constant_buffer_bytes)

0 commit comments

Comments
 (0)