Skip to content

fix(serial): #370 ODTS wire format v2 — fixed-width LE fields, checked I/O#379

Merged
LeoBuron merged 1 commit into
developfrom
370-serial-wire-format-v2
Jul 20, 2026
Merged

fix(serial): #370 ODTS wire format v2 — fixed-width LE fields, checked I/O#379
LeoBuron merged 1 commit into
developfrom
370-serial-wire-format-v2

Conversation

@LeoBuron

Copy link
Copy Markdown
Member

Summary

The ODTS model format wrote every count/dimension/kernel field as raw host size_t (8 bytes on the host, 4 on Cortex-M) with host byte order, ignored every fwrite/fread return value, and qConfig.roundingMode even leaked the host's enum width onto the wire. A model serialized on a 64-bit host was structurally unreadable on a 32-bit MCU, and a truncated file deserialized as silent garbage.

This PR pins wire format v2 in one version bump:

  • All counts, dims, orderOfDimensions, kernel geometry, normalizedShape, groups/outputPadding/outputSize, numNormDims/numGroups/numChannels: u32 little-endian.
  • Floats (scale, eps, dropout p): IEEE bit pattern as u32 LE.
  • roundingMode in qConfigs: u8 (was sizeof(roundingMode_t) = host enum width).
  • SERIALIZE_FORMAT_VERSION = 2, existing fail-fast on version mismatch kept. No v1 back-compat shim — v1 files were host-local artifacts.

Design decisions

Tests (TDD: each written first and observed red)

  • Golden-bytes fixtures pin the exact v2 encoding byte-for-byte: FLOAT32 tensor record (shape u32 LE + payload), model header + RELU record with SYM_INT32/ASYM qConfigs (incl. zeroPoint -3FD FF FF FF), MAXPOOL1D kernel geometry.
  • Short-I/O death tests (fork-based ASSERT_EXITS_WITH_FAILURE): truncated header, truncated DATA payload, unwritable stream on serialize.
  • Guard death tests: SYM record into FLOAT32 skeleton (the issue's named direction) and the pre-existing FLOAT32→SYM_INT32 direction, rank mismatch, LayerNorm numNormDims mismatch, dimension > UINT32_MAX on serialize, out-of-int16-range zeroPoint on deserialize; plus an ASYM zeroPoint round-trip through the i32 wire.
  • Mutation-checked: re-widening a shape field to raw size_t, removing the fread length check, and re-narrowing zeroPoint to i16 each turn at least one test red; implementation restored and re-verified after each.

Verification

100% tests passed, 0 tests failed out of 81        (ctest --preset unit_test_debug)
[397/397] Linking C executable examples/mixed_width_mlp/train_c_mixed_width_mlp   (examples preset, all targets)

Docs updated: docs/FEATURES.md (serialization section + gaps list), docs/CONTINUAL_LEARNING.md (ODTR v2, portability note).

Closes #370

🤖 Generated with Claude Code

Pin the ODTS model format to fixed-width little-endian: all counts, dims,
kernel geometry, and normalizedShape fields are u32 LE (was raw host size_t),
qConfig roundingMode is u8 (was host enum width), floats travel as IEEE bit
patterns in u32 LE. ASYM zeroPoint is pinned as i32 LE on the wire now, so the
in-memory int16->int32 widening (#246) needs no second format break; until
then deserialize range-guards the narrowing.

New checked SerialWire primitives (shared serial lib) fail fast on short
fwrite/fread, u32 overflow on serialize, and back the same v2 encoding in the
ODTR PPCA checkpoint (peek mirror updated, ODTR version also bumped to 2 since
its embedded tensor records changed shape).

Deserialize additionally guards: file rank vs skeleton rank (OOB dim write),
LayerNorm numNormDims mismatch (OOB normalizedShape write), file dtype vs
skeleton dtype (pre-existing #316 guard, now pinned by tests in both
directions). SERIALIZE_FORMAT_VERSION = 2; no v1 back-compat shim (v1 files
were host-local artifacts).

Tests: golden-bytes fixtures pin the exact v2 encoding (tensor record, model
header + RELU with SYM_INT32/ASYM qConfigs, MAXPOOL1D kernel geometry);
death tests for truncated header/payload, unwritable stream, dim > u32,
rank/numNormDims/dtype mismatches, oversize zeroPoint; ASYM zeroPoint
round-trip. All mutation-checked (size_t re-widen, unchecked fread, i16
zeroPoint each turn a test red).

Co-Authored-By: Claude Fable 5 <[email protected]>
@LeoBuron
LeoBuron merged commit f9e7f78 into develop Jul 20, 2026
12 checks passed
@LeoBuron
LeoBuron deleted the 370-serial-wire-format-v2 branch July 20, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant