File size: 6,400 Bytes
afef12f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d31e060
 
 
afef12f
d31e060
 
 
 
afef12f
d31e060
 
 
afef12f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d31e060
 
 
 
 
 
 
afef12f
 
 
 
 
d31e060
afef12f
 
 
 
 
 
 
 
 
 
 
 
 
 
d31e060
afef12f
 
 
 
d31e060
afef12f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d31e060
afef12f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
"""Write the fixed-topology YOLO26n model container consumed by the runtime."""

from __future__ import annotations

import struct
from dataclasses import dataclass

from .model_contract import (
    BOX_COORDINATE_CHANNELS,
    RUNTIME_WEIGHT_NAMES,
    RUNTIME_WEIGHT_SLOT_COUNT,
    SUPPORTED_RESOLUTIONS,
)
from .weight_contract import LoweredWeight

# This module owns the writer's wire-format constants; model_contract.h mirrors
# them in the C++ reader. Native validation exercises the complete writer/reader
# boundary for every supported resolution.
#
# The magic and version let the runtime reject unrelated or incompatible model
# files before interpreting offsets. Both are checked by ModelStore::Load.
_MAGIC = b"Y26MOD01"
_VERSION = 1

# XNNPACK uses 64-byte-aligned static buffers. The header occupies one aligned
# block. Directory position identifies the semantic slot, so each entry needs
# only the payload offset and byte count.
_ALIGNMENT = 64
_HEADER_BYTES = _ALIGNMENT
_ENTRY_BYTES = 16
_HEADER = struct.Struct("<8sIIIIQ")
_ENTRY = struct.Struct("<QQ")
_UINT32_MAX = (1 << 32) - 1
_UINT64_MAX = (1 << 64) - 1
_FP32_BYTES = 4
_CLASSIFICATION_CHANNEL_FLOOR = 64


@dataclass(frozen=True)
class RuntimeModelHeader:
    """Validated header fields from the runtime's serialized ``model.bin``."""

    resolution: int
    class_count: int
    slot_count: int
    payload_offset: int


@dataclass(frozen=True)
class _ModelWeight:
    slot: int
    name: str
    payload: bytes

    def __post_init__(self) -> None:
        if (
            type(self.slot) is not int
            or self.slot < 0
            or not self.name
            or not isinstance(self.payload, bytes)
            or not self.payload
        ):
            raise ValueError("invalid model weight")


def _build_model_weight_table(
    derived_weights: tuple[LoweredWeight, ...],
) -> tuple[_ModelWeight, ...]:
    """Build the runtime's stable table from its closed weight inventory."""
    named = []
    for weight in derived_weights:
        named.append((weight.binding.name, weight.payload))
    named.sort(key=lambda row: row[0])
    names = [name for name, _ in named]
    if len(names) != len(set(names)):
        raise ValueError("semantic model weight slots are not unique")
    if tuple(names) != RUNTIME_WEIGHT_NAMES:
        missing = sorted(set(RUNTIME_WEIGHT_NAMES) - set(names))
        unexpected = sorted(set(names) - set(RUNTIME_WEIGHT_NAMES))
        raise ValueError(
            "lowered weights do not match the fixed runtime inventory: "
            f"missing={missing!r}, unexpected={unexpected!r}"
        )
    weights = tuple(_ModelWeight(slot, name, payload) for slot, (name, payload) in enumerate(named))
    return weights


def _round_up_to_alignment(value: int) -> int:
    """Round a byte offset up to the next 64-byte model-file boundary."""
    return ((value + _ALIGNMENT - 1) // _ALIGNMENT) * _ALIGNMENT


def _class_dependent_weight_bytes_fit(class_count: int) -> bool:
    """Return whether the target runtime can size its largest learned tensor."""
    width = max(_CLASSIFICATION_CHANNEL_FLOOR, class_count)
    return (
        0 < class_count <= _UINT32_MAX - BOX_COORDINATE_CHANNELS
        and class_count <= _UINT64_MAX // _FP32_BYTES // width
    )


def read_runtime_model_header(payload: bytes) -> RuntimeModelHeader:
    """Read and validate the fixed header of an exported runtime ``model.bin``."""
    if len(payload) < _HEADER_BYTES:
        raise ValueError("model file is smaller than its required 64-byte header")
    (
        magic,
        version,
        resolution,
        class_count,
        slot_count,
        payload_offset,
    ) = _HEADER.unpack_from(payload)
    maximum_directory_entries = (len(payload) - _HEADER_BYTES) // _ENTRY_BYTES
    expected_payload_offset = _round_up_to_alignment(_HEADER_BYTES + _ENTRY_BYTES * slot_count)
    if (
        magic != _MAGIC
        or version != _VERSION
        or resolution not in SUPPORTED_RESOLUTIONS
        or not _class_dependent_weight_bytes_fit(class_count)
        or slot_count == 0
        or slot_count != RUNTIME_WEIGHT_SLOT_COUNT
        or slot_count > maximum_directory_entries
        or payload_offset != expected_payload_offset
        or payload[_HEADER.size : _HEADER_BYTES] != bytes(_HEADER_BYTES - _HEADER.size)
    ):
        raise ValueError("model header does not match the fixed runtime format")
    return RuntimeModelHeader(
        resolution=resolution,
        class_count=class_count,
        slot_count=slot_count,
        payload_offset=payload_offset,
    )


def _pack_model_weights(
    weights: tuple[_ModelWeight, ...],
    resolution: int,
    class_count: int,
) -> bytes:
    """Pack validated slot records into the binary model container."""
    if len(weights) != RUNTIME_WEIGHT_SLOT_COUNT or [weight.slot for weight in weights] != list(
        range(RUNTIME_WEIGHT_SLOT_COUNT)
    ):
        raise ValueError("model weight slots must be complete and contiguous")
    if len({weight.name for weight in weights}) != len(weights):
        raise ValueError("model weight names must be unique")
    if (
        resolution not in SUPPORTED_RESOLUTIONS
        or type(class_count) is not int
        or not _class_dependent_weight_bytes_fit(class_count)
    ):
        raise ValueError("invalid model dimensions")

    directory_offset = _HEADER_BYTES
    payload_offset = _round_up_to_alignment(directory_offset + _ENTRY_BYTES * len(weights))
    packed = bytearray(payload_offset)
    for weight in weights:
        while len(packed) % _ALIGNMENT:
            packed.append(0)
        offset = len(packed)
        packed.extend(weight.payload)
        _ENTRY.pack_into(
            packed,
            directory_offset + weight.slot * _ENTRY_BYTES,
            offset,
            len(weight.payload),
        )

    _HEADER.pack_into(
        packed,
        0,
        _MAGIC,
        _VERSION,
        resolution,
        class_count,
        len(weights),
        payload_offset,
    )
    return bytes(packed)


def pack_model(
    lowered_weights: tuple[LoweredWeight, ...],
    resolution: int,
    class_count: int,
) -> bytes:
    """Validate stable weight identities and serialize the runtime model file."""
    return _pack_model_weights(
        _build_model_weight_table(lowered_weights),
        resolution,
        class_count,
    )