test(m1): synthetic decode harness asserting M1's memory acceptance criteria (SKEEP-003 M1, S1.11a) - #1078
Merged
Conversation
…riteria; count adopted weights; KvCacheMode.FP32 (SKEEP-003 M1) Milestone M1 (#1002), decision on #1032: the acceptance evidence is split by what each half is for. This is half (1) — a synthetic decode loop over the *real* memory machinery, in the repository where that machinery lives, so the criteria that are about memory behaviour are checked on every commit. Half (2), the real GGUF model with tok/s, TTFT and the release-notes table, belongs to skainet-decode in SKaiNET-transformers. - DecodeHarness (backend-cpu commonTest): a Llama-shaped stack of packed Q8_0 matmuls whose weights live in a ModelScope, activations come from a recycled ForwardScope, KV ring is preallocated in the model scope (#1076) and dispatch goes through KernelDispatch (#1070/#1071), with every event captured in a RecordingTraceSink. No tokenizer, no sampling, no checkpoint — it is a memory-behaviour fixture, not a model. - DecodeAcceptanceTest: M1-A1 (memory flat across 200 steps: the forward scope is empty between steps and every post-warm-up reset reports the same live-bytes-before), M1-A3 (zero forward-scope allocations in steps 5..20), M1-A8 (plan matches the run within 10 %, no adapters), M1-A7 (the Perfetto trace has one track per scope, kernel spans labelled by TensorId and a live-bytes counter returning to zero). Two real gaps surfaced by writing the assertions, both fixed here: - **Adopted weights were invisible to plan-vs-actual.** Borrowing is not an allocation, so mapped/borrowed weights emitted no event — yet decision #11 counts weights as resident because decode touches every weight every token. ModelScope.adopt now emits the allocation (site "adopted") and close() balances it with a Free, so a plan can be checked against a run that holds mapped weights. - **The planner assumed bf16 KV while DefaultKvCacheStore stores FP32**, understating a dense ring by 2x. KvCacheMode.FP32 added and documented as what the dense store actually does; bf16/TurboQuant remain the compressed stores. Exactly the drift #1074 exists to catch. Closes #1032 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
|
Local gate Targeted: |
|
📖 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.
Summary
The last M1 slice (#1032), built to the decision recorded on that issue — option (c), both homes. This is half (1): a synthetic decode loop over the real memory machinery, in the repository where that machinery lives, so the criteria that are about memory behaviour are asserted on every commit. Half (2) — the real GGUF model with tok/s, TTFT and the release-notes table — belongs to
skainet-decodein SKaiNET-transformers, which owns the model.DecodeHarness(backend-cpucommonTest): a Llama-shaped stack of packed Q8_0 matmuls whose weights live in aModelScope, activations come from a recycledForwardScope, the KV ring is preallocated in the model scope (feat(memory): KV cache preallocates its ring in Scope.Model (SKEEP-003 P2, S1.10) #1076), dispatch goes throughKernelDispatch(feat(backend-api): KernelKey dispatch — declared formats/layouts, rank normalised once, visible adapters, reference matmul (SKEEP-003 P3, S1.7a) #1070/feat(backend-cpu): route the generic matmul path through the kernel registry (SKEEP-003 P3, S1.7b) #1071) and every event lands in aRecordingTraceSink. No tokenizer, no sampling, no checkpoint — it is a memory-behaviour fixture, not a model. Deliberately tiny (2 layers × 32 hidden, 12 steps) because the reference kernel decodes every element and these tests also run in a browser under Karma's 2 s per-test budget.DecodeAcceptanceTestasserts:TensorId, and a live-bytes counter that returns to zero.Two real gaps the assertions surfaced (both fixed here)
ModelScope.adoptnow emits the allocation (site"adopted") andclose()balances it with aFree, so a plan can be checked against a run holding mapped weights.DefaultKvCacheStorestores FP32, understating a dense ring by 2×.KvCacheMode.FP32added and documented as what the dense store actually does. This is exactly the drift feat(memory): plan-vs-actual — reconstruct a run's memory from the event stream and fail on drift (SKEEP-003 P2, S1.9) #1074 exists to catch — and it motivated KvCacheStore should declare its Format (dtype + encoding), not just an encoding #1077, which replaces the guess with the store declaring its ownFormat.Test plan
Full local gate (
scripts/pr-gate.sh, JDK 25) — all legs passed (JVM, apiCheck, JS/Wasm incl. the browser targets, linuxX64, assemble, Java consumer API tests). The first run failed only on Karma's per-test timeout, which is why the harness shapes are small; the assertions are unchanged in substance.Closes #1032
🤖 Generated with Claude Code