Skip to content

test(m1): synthetic decode harness asserting M1's memory acceptance criteria (SKEEP-003 M1, S1.11a) - #1078

Merged
michalharakal merged 1 commit into
developfrom
feature/1032-decode-harness
Aug 23, 2026
Merged

michalharakal merged 1 commit into
developfrom
feature/1032-decode-harness

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

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-decode in SKaiNET-transformers, which owns the model.

Two real gaps the assertions surfaced (both fixed here)

  1. Adopted weights were invisible to plan-vs-actual. Borrowing is not an allocation, so mapped/borrowed weights emitted no event — yet decision BPETokenizer #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 holding mapped weights.
  2. The planner assumed bf16 KV while DefaultKvCacheStore stores FP32, understating a dense ring by 2×. KvCacheMode.FP32 added 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 own Format.

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

…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>
@michalharakal

Copy link
Copy Markdown
Contributor Author

Local gate scripts/pr-gate.sh (JDK 25) on 60ed0c1: all legs passed — jvmTest · apiCheck · JS/Wasm (browser targets included) · linuxX64Test · assemble · Java consumer API tests.

Targeted: sk.ainet.exec.harness.* 5/5 on JVM, JS, wasmJs and linuxX64.

@github-actions

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully for this PR.

Generated Files:

  • Operator documentation: docs/modules/operators/_generated_/
  • JSON schema output: operators.json

Artifacts:

  • Download the documentation-preview-1078 artifact to view the complete documentation locally.

This comment will be updated automatically when the PR is updated.

@michalharakal
michalharakal merged commit d12f9f9 into develop Aug 23, 2026
17 checks passed
@michalharakal
michalharakal deleted the feature/1032-decode-harness branch August 23, 2026 20:26
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.

[S1.11] M1 sample: decide the home of skainet-decode (SKaiNET core vs SKaiNET-transformers), then build the CLI + Android activity

1 participant