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,
)
|