feat(memory): block order belongs to the layout — BlockOrder, a visible prepack adapter, the normative doc (#973.1) - #1099
Merged
Conversation
…le prepack adapter, the normative doc Closes #1094, the foundational slice of #973. #973's census found seven mutually contradicting statements of one contract inside this repository, two of which had already shipped wrong numbers (#968, #971). The contract is: a packed weight's blocks are in one of two orders, and which one is a property of the value. - `BlockOrder { ROW_MAJOR, INPUT_BLOCK_MAJOR }` on `Layout`, defaulting to ROW_MAJOR — what a file holds. The order is expressed **in the strides** (input-block-major is `[1, out]` over the block grid, not `[blocksPerRow, 1]`), so narrow, transpose, get and toFloatArray keep working unchanged on a view in either order rather than growing a branch each. - `LayoutClass.BLOCKED` splits into `BLOCKED_ROW_MAJOR` and `BLOCKED_INPUT_MAJOR`, so a kernel *declares* the order it reads in its `KernelKey` and the dispatcher can relayout instead of the caller having to know. This is what #1029 was waiting for. - `TensorView.prepack(order, scope, sink)`: the conversion as a visible adapter — allocates in the caller's scope, emits `AdapterInserted` with the byte count, returns `this` when nothing has to move. Named as the conversion it is, not as a transpose. - `RelayoutedBlockDecoder` keeps rule 4 true across a relayout: the M1 `PackedBlockDecoder` decodes from the `TensorData` it wraps rather than from the storage it is handed, so a prepacked view maps the block index back instead of silently decoding the old bytes. - `docs/design/memory/packed-weight-layout.md`: the normative contract, with the five rules and the semver policy that `packedData` byte semantics are public API. The stale kdocs the census names are corrected to point at it — `Q5_0/Q5_1TensorData` claimed input-block-major bytes and a shape-swap transpose, neither of which has been true since 0.40.1, and two kernel SPI kdocs claimed byte-identity with a storage type that holds the other order. `BlockOrderTest` uses a three-block-wide weight throughout, because the two orders coincide at one block per row — the case that hides the bug. It pins that prepacking moves the bytes to `b * rows + o`, that the decoded matrix is unchanged, that going there and back is the identity (the double-transpose hazard, which nothing detected before), that the order survives narrow / transpose / unsqueeze, and that a same-order prepack copies nothing and reports nothing. Gate: scripts/pr-gate.sh — all legs passed; --golden passed on JVM and linuxX64 (no decoder or kernel moved a bit). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
📖 Documentation Preview The documentation has been built successfully for this PR. Generated Files:
Artifacts:
This comment will be updated automatically when the PR is updated. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1094 · the foundational slice of #973 · Proposal §5.1
The contract, finally written in the type system
#973's census found seven mutually contradicting statements of one contract inside this repository — two of which had already shipped wrong numbers (#968, #971). The contract is simple to state and was nowhere expressed: a packed weight's blocks are in one of two orders, and which one is a property of the value.
ROW_MAJOR(canonical)o * blocksPerRow + bTernaryCodectoFloatArray(),get(), the reference matmulINPUT_BLOCK_MAJOR(kernel feed order)b * out + oprepackThey coincide only when a row is a single block, which is exactly why mixing them produced plausible, finite, wrong numbers instead of a crash.
What landed
Layout.blockOrder, defaulting toROW_MAJOR. The order is expressed in the strides — input-block-major is[1, out]over the block grid rather than[blocksPerRow, 1]— sonarrow,transpose,getandtoFloatArraykeep working unchanged on a view in either order instead of each growing a branch.LayoutClass.BLOCKEDsplits intoBLOCKED_ROW_MAJOR/BLOCKED_INPUT_MAJOR, so a kernel declares the order it reads in itsKernelKeyand the dispatcher can relayout. This is precisely what [S1.7c] P3:DefaultCpuOpsJvmmatmul arms → registered kernels; provider packs (reference always present, capabilities in the key) #1029 deferred, and it unblocks [973.2] Bridge the packed SPI kernels through the ordered key (unblocks #1029) #1095.TensorView.prepack(order, scope, sink)— the conversion as a visible adapter: allocates in the caller's scope, emitsAdapterInsertedwith its byte count, and returnsthiswhen nothing has to move. Named as the conversion it is, not as a transpose.RelayoutedBlockDecoderkeeps rule 4 true across a relayout. The M1PackedBlockDecoderdecodes from theTensorDatait wraps rather than from the storage it is handed, so without this a prepacked view would silently decode the old bytes — found by a test, not by reading.docs/design/memory/packed-weight-layout.md— the normative contract: the two orders, where the order lives, how to convert, five rules, and the semver policy thatpackedDatabyte semantics are public API (a change is minor/major, never a patch — that is how a byte-layout change shipped as a green-CI hotfix once already).Q5_0/Q5_1TensorDataclaimed input-block-major bytes and a shape-swap transpose, neither true since 0.40.1; two kernel SPI kdocs claimed byte-identity with a storage type that holds the other order.Acceptance
BlockOrderTestuses a three-block-wide weight throughout, because at one block per row the two orders coincide and every such test passes vacuously. It pins:ROW_MAJOR, and says so intoString();(o, b)to flat indexb * rows + o— asserted on the bytes;narrow/transpose/unsqueeze;Gate
scripts/pr-gate.sh— all legs passed.--goldenpassed on JVM and Kotlin/Native: no decoder or kernel moved a bit.Scope
This is slice 1 of 5. The rest of #973 is tracked as #1095 (bridge the packed SPI kernels through the ordered key), #1096 (a weight-transposing matmul primitive so
ops.transposestops being a per-forward O(bytes) copy of a lie), #1097 (engine-owned prepacking + cross-repo contract fixtures) and #1098 (normalize weight orientation at the load boundary).Keeps develop green by
Everything is additive except the
LayoutClassenum split, which is@ExperimentalMemoryApi, introduced in M1 by this same work, and has six in-repo usages — all updated. No packed byte layout changes: the golden gate proves it.🤖 Generated with Claude Code