feat(backend): one engine-owned block relayout, and contract fixtures a downstream repo can run (#973.4) - #1102
Merged
Conversation
… a downstream repo can run Closes #1097 (#973, proposal items 3 and 6). The census in #973 found the same permutation reimplemented in several places and drifting — an inlined Apertus relayout that had diverged from the shared packer it was copied from, three conventions applied to the same GGUF K-quant tensor depending on the converter, and layout knowledge owned by the wrong repository. - `PackedWeights`: `prepackForMatmul` / `toCanonical` for views and `toKernelOrder` / `toCanonicalOrder` for a converter that holds bytes — the only sanctioned implementation of `out[b * rows + o] = in[o * blocksPerRow + b]`. Idempotent by construction: prepacking an already-prepacked weight returns it unchanged, unlike the transpose it replaces, whose double application silently produced a different matrix. `blocksPerRow(encoding, inputDim)` reads the geometry from the encoding's own descriptor rather than a caller's constant. - `PackedLayoutFixtures`: canonical and kernel-order fixtures per format, in `main` rather than a test source set, so the artifact a downstream repository already depends on carries them. `disagreement(bytes, encoding, kernelOrder)` returns null when the bytes agree or names the first block in the wrong place. Every fixture is three blocks wide, because at one block per row the two orders coincide — the shape that hid #968. - The normative doc gains the downstream section, the "prepack once, at load" rule with the M1-A3 regression that chose the dispatcher's default, and an updated status. Tests: the byte relayout puts each block where the contract says and is its own inverse; every covered format's fixture agrees with its own descriptor; a disagreement names the block that moved; `prepackForMatmul` is idempotent and preserves the decoded matrix; and it refuses what it cannot relayout. Gate: scripts/pr-gate.sh — all legs passed. 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 #1097 · #973 proposal items 3 and 6
The problem this removes
The census in #973 found the same permutation reimplemented in several places, drifting apart: an inlined Apertus relayout that had diverged from the shared packer it was copied from, three different conventions applied to the same GGUF K-quant tensor depending on which converter loaded it, and — the part that matters most — layout knowledge owned by the downstream repository, with the engine's own code naming
GemmaMemSegConverteras responsible for honouring a kernel's layout.One implementation, published
PackedWeights—prepackForMatmul(view)/toCanonical(view)for views, andtoKernelOrder(bytes, …)/toCanonicalOrder(bytes, …)for a converter that holds bytes. This is the only sanctioned implementation ofout[b * rows + o] = in[o * blocksPerRow + b].It is idempotent by construction: prepacking an already-prepacked weight returns it unchanged. That is the direct answer to the double-transpose hazard #973 lists, where applying the old packed "transpose" twice silently produced a different matrix for a non-square block grid and nothing detected it.
blocksPerRow(encoding, inputDim)reads the geometry from the encoding's own descriptor instead of a caller's constant — one fewer place for a 32 to be written where a 256 belongs.Fixtures that cross the repository boundary
PackedLayoutFixturespublishes canonical and kernel-order fixtures per format — inmain, not a test source set, so the artifact a downstream repository already depends on carries them.disagreement(bytes, encoding, kernelOrder)returnsnullwhen the bytes agree, or names the first block that is in the wrong place.This is the guardrail #973 asks for. A byte-layout change once shipped as a green-CI hotfix precisely because each repository's suite proved only its own convention: the engine built canonical fixtures, the downstream converter built kernel-native ones, and neither crossed the boundary. A downstream test asserting
disagreement(myConverterOutput, Q4_K, kernelOrder = true) == nullnow runs against the same bytes the engine's own tests run against.Every fixture is three blocks wide. At one block per row the two orders coincide, and a test built that way passes whichever convention the code happens to hold — that is the shape that hid #968.
Documentation
docs/design/memory/packed-weight-layout.mdgains a section for downstream repositories, and the "prepack once, at load" rule with the reason its default is what it is: wiring the dispatcher's relayout on by default broke M1-A3 during #1095 by copying the whole weight per token. That regression is recorded, not smoothed over.Gate
scripts/pr-gate.sh— all legs passed.Keeps develop green by
Entirely additive: two new objects in an already-published module, one documentation file changed. No existing call site is touched — adopting
PackedWeightsdownstream is a follow-up in that repository, which is exactly the point of publishing it.🤖 Generated with Claude Code