Skip to content

Commit d6b1dcb

Browse files
committed
[ET-VK] Introduce VulkanDelegateHeader to manage constant data and shader data
Pull Request resolved: #2013 ## Context This changeset is essentially a mirror of #1523 in the XNNPACK delegate which is the first step to enabling constant weight data to be serialized outside the flatbuffer blob to speed up deserialization time for large models. `VulkanDelegateHeader` is introduced which will be used later on to help interpret what different sections of the serialized binary blob corresponds to. The primary difference compared to `XNNHeader` which was added for the XNNPACK delegate is that fields are added to support custom compute shaders that will be serialized with the model. ghstack-source-id: 215799821 @exported-using-ghexport Differential Revision: [D53957853](https://our.internmc.facebook.com/intern/diff/D53957853/)
1 parent d25a219 commit d6b1dcb

File tree

3 files changed

+210
-0
lines changed

3 files changed

+210
-0
lines changed

backends/vulkan/serialization/vulkan_graph_serialize.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import os
99
import tempfile
1010

11+
from dataclasses import dataclass
12+
from typing import ClassVar
13+
1114
# pyre-ignore[21]: Could not find module `executorch.exir._serialize._bindings`.
1215
import executorch.exir._serialize._bindings as bindings # @manual=//executorch/exir/_serialize:_bindings
1316

@@ -32,3 +35,96 @@ def convert_to_flatbuffer(vk_graph: VkGraph) -> bytes:
3235
output_path = os.path.join(d, "schema.bin")
3336
with open(output_path, "rb") as output_file:
3437
return output_file.read()
38+
39+
40+
@dataclass
41+
class VulkanDelegateHeader:
42+
# Defines the byte region that each component of the header corresponds to
43+
MAGIC_IX: ClassVar[slice] = slice(4, 8)
44+
HEADER_SIZE_IX: ClassVar[slice] = slice(8, 10)
45+
FLATBUFFER_OFFSET_IX: ClassVar[slice] = slice(10, 14)
46+
FLATBUFFER_SIZE_IX: ClassVar[slice] = slice(14, 18)
47+
BYTES_OFFSET_IX: ClassVar[slice] = slice(18, 22)
48+
BYTES_SIZE_IX: ClassVar[slice] = slice(22, 30)
49+
50+
# magic bytes that should be at the beginning of the header
51+
EXPECTED_MAGIC: ClassVar[bytes] = b"VH00"
52+
# The length of the header in bytes
53+
EXPECTED_LENGTH: ClassVar[int] = 30
54+
55+
# Instance attributes, @dataclass will turn these into constructor args
56+
flatbuffer_offset: int
57+
flatbuffer_size: int
58+
bytes_offset: int
59+
bytes_size: int
60+
61+
@staticmethod
62+
def from_bytes(data: bytes) -> "VulkanDelegateHeader":
63+
if len(data) > VulkanDelegateHeader.EXPECTED_LENGTH:
64+
raise ValueError(
65+
f"Expected header to be {VulkanDelegateHeader.EXPECTED_LENGTH} bytes, "
66+
f"but got {len(data)} bytes."
67+
)
68+
69+
magic_b: bytes = data[VulkanDelegateHeader.MAGIC_IX]
70+
71+
if magic_b != VulkanDelegateHeader.EXPECTED_MAGIC:
72+
raise ValueError(
73+
f"Expected magic bytes to be {VulkanDelegateHeader.EXPECTED_MAGIC}, "
74+
f"but got {magic_b}."
75+
)
76+
77+
length: int = int.from_bytes(
78+
data[VulkanDelegateHeader.HEADER_SIZE_IX], byteorder="little"
79+
)
80+
81+
if length != VulkanDelegateHeader.EXPECTED_LENGTH:
82+
raise ValueError(
83+
f"Expected header to be {VulkanDelegateHeader.EXPECTED_LENGTH} bytes, "
84+
f"but got {length} bytes."
85+
)
86+
87+
flatbuffer_offset_b: bytes = data[VulkanDelegateHeader.FLATBUFFER_OFFSET_IX]
88+
flatbuffer_size_b: bytes = data[VulkanDelegateHeader.FLATBUFFER_SIZE_IX]
89+
bytes_offset_b: bytes = data[VulkanDelegateHeader.BYTES_OFFSET_IX]
90+
bytes_size_b: bytes = data[VulkanDelegateHeader.BYTES_SIZE_IX]
91+
92+
return VulkanDelegateHeader(
93+
flatbuffer_offset=int.from_bytes(flatbuffer_offset_b, byteorder="little"),
94+
flatbuffer_size=int.from_bytes(flatbuffer_size_b, byteorder="little"),
95+
bytes_offset=int.from_bytes(bytes_offset_b, byteorder="little"),
96+
bytes_size=int.from_bytes(bytes_size_b, byteorder="little"),
97+
)
98+
99+
def is_valid(self) -> bool:
100+
if self.flatbuffer_size <= 0:
101+
return False
102+
103+
expected_offset = self.flatbuffer_offset + self.flatbuffer_size
104+
if self.bytes_offset < expected_offset:
105+
return False
106+
107+
if self.bytes_size < 0:
108+
return False
109+
110+
return True
111+
112+
def to_bytes(self) -> bytes:
113+
if not self.is_valid():
114+
raise ValueError("VulkanDelegateHeader instance contains invalid values")
115+
116+
data: bytes = (
117+
# 4 bytes of padding for magic bytes, this is so that the header magic
118+
# bytes is in the same position as the flatbuffer header magic bytes
119+
b"\x00\x00\x00\x00"
120+
+ self.EXPECTED_MAGIC
121+
+ self.EXPECTED_LENGTH.to_bytes(2, byteorder="little")
122+
+ self.flatbuffer_offset.to_bytes(4, byteorder="little")
123+
+ self.flatbuffer_size.to_bytes(4, byteorder="little")
124+
+ self.bytes_offset.to_bytes(4, byteorder="little")
125+
+ self.bytes_size.to_bytes(8, byteorder="little")
126+
)
127+
128+
assert len(data) == VulkanDelegateHeader.EXPECTED_LENGTH
129+
130+
return data

backends/vulkan/test/TARGETS

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,13 @@ python_unittest(
2020
"//executorch/kernels/portable:custom_ops_generated_lib",
2121
],
2222
)
23+
24+
python_unittest(
25+
name = "test_vulkan_delegate_header",
26+
srcs = [
27+
"test_vulkan_delegate_header.py",
28+
],
29+
deps = [
30+
"//executorch/backends/vulkan:vulkan_preprocess",
31+
],
32+
)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import unittest
8+
9+
from executorch.backends.vulkan.serialization.vulkan_graph_serialize import (
10+
VulkanDelegateHeader,
11+
)
12+
13+
EXAMPLE_FLATBUFFER_OFFSET: int = 0x11223344
14+
EXAMPLE_FLATBUFFER_SIZE: int = 0x55667788
15+
EXAMPLE_BYTES_OFFSET: int = EXAMPLE_FLATBUFFER_OFFSET + EXAMPLE_FLATBUFFER_SIZE
16+
EXAMPLE_BYTES_SIZE: int = 0x99AABBCC99AABBCC
17+
18+
# If header layout or magic changes, this test must change too.
19+
# The layout of the header is a contract, not an implementation detail
20+
EXAMPLE_HEADER_DATA: bytes = (
21+
# zeros
22+
b"\x00\x00\x00\x00"
23+
# magic
24+
+ b"VH00"
25+
# All Values below are littl Endian
26+
# header length
27+
+ b"\x1E\x00"
28+
# Flatbuffer Offset
29+
+ b"\x44\x33\x22\x11"
30+
# Flatbuffer Size
31+
+ b"\x88\x77\x66\x55"
32+
# Bytes Data Offset
33+
+ b"\xCC\xAA\x88\x66"
34+
# Bytes Data Size
35+
+ b"\xCC\xBB\xAA\x99\xCC\xBB\xAA\x99"
36+
)
37+
38+
39+
class TestVulkanDelegateHeader(unittest.TestCase):
40+
def test_to_bytes(self) -> None:
41+
header = VulkanDelegateHeader(
42+
EXAMPLE_FLATBUFFER_OFFSET,
43+
EXAMPLE_FLATBUFFER_SIZE,
44+
EXAMPLE_BYTES_OFFSET,
45+
EXAMPLE_BYTES_SIZE,
46+
)
47+
self.assertEqual(header.to_bytes(), EXAMPLE_HEADER_DATA)
48+
self.assertTrue(header.is_valid())
49+
50+
def test_from_bytes(self) -> None:
51+
header = VulkanDelegateHeader.from_bytes(EXAMPLE_HEADER_DATA)
52+
self.assertEqual(header.flatbuffer_offset, EXAMPLE_FLATBUFFER_OFFSET)
53+
self.assertEqual(header.flatbuffer_size, EXAMPLE_FLATBUFFER_SIZE)
54+
self.assertEqual(header.bytes_offset, EXAMPLE_BYTES_OFFSET)
55+
self.assertEqual(header.bytes_size, EXAMPLE_BYTES_SIZE)
56+
57+
def test_invalid_metadata(self) -> None:
58+
WRONG_MAGIC_DATA = EXAMPLE_HEADER_DATA[0:4] + b"YT01" + EXAMPLE_HEADER_DATA[8:]
59+
with self.assertRaisesRegex(
60+
ValueError,
61+
"Expected magic bytes to be b'VH00', but got b'YT01'",
62+
):
63+
VulkanDelegateHeader.from_bytes(WRONG_MAGIC_DATA)
64+
65+
WRONG_LENGTH_DATA = (
66+
EXAMPLE_HEADER_DATA[0:8] + b"\x1D\x00" + EXAMPLE_HEADER_DATA[10:]
67+
)
68+
with self.assertRaisesRegex(
69+
ValueError, "Expected header to be 30 bytes, but got 29 bytes."
70+
):
71+
VulkanDelegateHeader.from_bytes(WRONG_LENGTH_DATA)
72+
73+
with self.assertRaisesRegex(
74+
ValueError, "Expected header to be 30 bytes, but got 31 bytes."
75+
):
76+
VulkanDelegateHeader.from_bytes(EXAMPLE_HEADER_DATA + b"\x00")
77+
78+
def test_invalid_flatbuffer_size(self) -> None:
79+
header = VulkanDelegateHeader(
80+
EXAMPLE_FLATBUFFER_OFFSET,
81+
0,
82+
EXAMPLE_BYTES_OFFSET,
83+
EXAMPLE_BYTES_SIZE,
84+
)
85+
86+
with self.assertRaises(ValueError):
87+
header.to_bytes()
88+
89+
def test_invalid_constants_offset(self) -> None:
90+
header = VulkanDelegateHeader(
91+
EXAMPLE_FLATBUFFER_OFFSET,
92+
EXAMPLE_FLATBUFFER_SIZE,
93+
EXAMPLE_FLATBUFFER_OFFSET + EXAMPLE_FLATBUFFER_SIZE - 1,
94+
EXAMPLE_BYTES_SIZE,
95+
)
96+
97+
with self.assertRaises(ValueError):
98+
header.to_bytes()
99+
100+
def test_to_bytes_same_as_from_bytes(self) -> None:
101+
header = VulkanDelegateHeader.from_bytes(EXAMPLE_HEADER_DATA)
102+
103+
to_bytes = header.to_bytes()
104+
self.assertEquals(EXAMPLE_HEADER_DATA, to_bytes)

0 commit comments

Comments
 (0)