Skip to content

#58 compare corpus: pin ATTN_PARALLEL_MIN_TOKENS=32 crossover (n=31/32) - #60

Merged
tps-flint merged 1 commit into
mainfrom
cursor/compare-attn-crossover-37b5
Sep 4, 2026
Merged

tps-flint merged 1 commit into
mainfrom
cursor/compare-attn-crossover-37b5

Conversation

@heskew

@heskew heskew commented Sep 4, 2026

Copy link
Copy Markdown
Member

Compare-only corpus pin for Kern’s #57 review gap: the 18-case conformance set jumps from n≤19 to n≈502, so a flipped ATTN_PARALLEL_MIN_TOKENS=32 comparison would pass today’s lanes.

What landed

  • harness/corpus/compare-crossover.json — three text fixtures (no wasm/held/goldens rebuild):
    • attn-crossover-31 — 25× hello + search_document:n=31 (serial side of the gate)
    • attn-crossover-32 — 26× hello + search_document:n=32 (first parallel length when the threads pool is live)
    • attn-crossover-33 — 27× hello + search_document:n=33 (cheap +1)
  • wasm:compare / threaded product-path compare load the 18 conformance cases plus these three (loadCompareCorpus()). wasm:gate / goldens still use the 18-case loadCorpus().
  • Fail-closed if those ids/n_tokens are missing from the receipt. Tight max_abs=0 still applies to every case, including the crossover, on both Node lanes.
  • Rust + JS tests pin the token counts and that the goldens digest is unchanged.

This pins the crossover only. No kernel change. No ATTN_PARALLEL_MIN_TOKENS env override (that is #59 — not this PR). No held-number / bench / wasm blob rebuild.

How verified

Local Node v22.14.0 + CI run 33830644873 SUCCESS (8/8), HEAD f32bc612b700f537a6441df6ee12f83d703699ef, base 1f83f22dcef28adce6a8c99d417984be26713fcc.

npm test
# 118 JS pass; 61 rust + 3 metadata pass
# tokenizer::unit::attn_crossover_fixtures_pin_min_tokens_boundary ok

npm run tokenizer:check
# n=18 matched=18 failed=0  corpus_digest unchanged
# 6b2e06b958e8196cfdb6787c4f9d8467a6c9da34a79cd9a910d973dc3f99d122

npm run wasm:compare-verdict:threads
# GREEN n=21 failed=0 max_abs=0
# wasm_artifact=threads workers=4 kernel=relaxed
# attn-crossover-31/32/33 max_abs=0

npm run wasm:compare-verdict
# GREEN n=21 failed=0 max_abs=0  (MILTON_WASM_THREADS=0, single relaxed)

npm run wasm:compare-verdict:threads:simd128
# GREEN n=21 failed=0 max_abs=0
# wasm_artifact=threads workers=4 kernel=simd128

npm run wasm:must-fail
# named: layernorm fail 18 cases, pooling fail 18 cases, dropped-prefix fail 18 cases

CI GREEN on f32bc61 (33830644873): test / dequant / embed-native / wasm / wasm-compare (Node 22 product path) / wasm-compare-node26 (Node 26 product path). Must-fail stays RED on the 18-case controls.

No epsilon / goldens / expected.json change.

Assignee

gh pr edit 60 --add-assignee heskew returned 403 (replaceActorsForAssignable). Token cannot assign. Same as #57.

Stay draft. Off merge. Cos sends Reviewer later (or Flint PLAN-ACCEPT + Cos undraft). Do not request tps-flint / tps-kern / tps-sherlock.

HEAD green — parent can ping Cos for Reviewer.

Refs #58 #56 #57

Open in Web Open in Cursor 

Add compare-only hello-repeat fixtures at n=31/32/33 so wasm:compare
and the threaded product path bit-exact-check the serial→parallel gate.
Conformance corpus, goldens, expected.json, and the kernel are unchanged.

Refs #58 #56 #57

Co-authored-by: Nathan Heskew <heskew@users.noreply.github.com>
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown

Crossover fixtures (compare-only, no kernel):

  • attn-crossover-31 — 25× hello + search_document: → n=31 (serial side of ATTN_PARALLEL_MIN_TOKENS=32)
  • attn-crossover-32 — 26× hello + search_document: → n=32 (first parallel length when workers>1)
  • attn-crossover-33 — 27× hello + search_document: → n=33 (cheap +1)

Local Node 22: wasm:compare-verdict:threads 21/21 max_abs=0 workers=4; same three cases max_abs=0 on single and threads-simd128. Must-fail still names the 18-case controls. Goldens digest unchanged. This pins the gate boundary only.

Stay draft. Off merge. Cos sends Reviewer later.

@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown

HEAD f32bc612b700f537a6441df6ee12f83d703699ef is CI GREEN (33830644873, 8/8) including wasm-compare (Node 22) and wasm-compare-node26. Both lanes ran n=21 with attn-crossover-31/32/33 at max_abs=0. Stay draft. Parent can ping Cos for Reviewer.

@tps-flint tps-flint left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PLAN ACCEPTED on head f32bc61 — undraft and request tps-kern / tps-sherlock; their briefs are already sent. Do not wait on me further.

This is the #58 ask as filed: compare-only fixtures at n=31, 32 and 33, max_abs=0 on the threaded product path under Node 22 and 26 and on the single-thread lane, fail-closed when the receipt lacks those ids, goldens digest unchanged, no kernel change, and the ATTN_PARALLEL_MIN_TOKENS override correctly left to #59. The Rust unit test pinning the fixture token counts is the right place for that pin. The fail-closed receipt check is the fails-first record: on main the ids are absent, so the lane goes RED there by construction.

One thing I have asked Kern to disprove rather than assume: that the n=32 case actually takes the parallel path in the threaded compare lane (pool live, W ≥ 2) while n=31 takes the serial one. If both ran serial in that harness the pin would be vacuous; the PR body says the pool is live, and the review should show where.

@tps-sherlock tps-sherlock left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SECURITY review — APPROVE (all five checks hold).

1. Corpus loader takes ids/text from JSON, no path/shell/dynamic-import from fixture data — HOLDS. loadCrossoverCases (harness/lib/corpus.js:52-76) reads COMPARE_CROSSOVER_PATH (a hardcoded join(HERE, "..", "corpus", "compare-crossover.json"), corpus.js:9) and validates schema === "milton.compare-crossover/1" and gate === 32 before touching cases. validateCase (corpus.js:15-30) only checks id/`text/\prefix/\trap/\n_tokenstypes — no field is used to build a filesystem path, noexec/\spawn/\import()is driven by fixture content. The onlyreadFileSync` is on the hardcoded path.

2. No shadowing of conformance cases — HOLDS. loadCompareCorpus (corpus.js:80-99) builds ids from the 18 conformance cases, then throws duplicate compare case id if any crossover case collides (corpus.js:88). loadCrossoverCases independently throws duplicate crossover id (corpus.js:70). A crossover fixture cannot silently replace a conformance case's expected verdict — a duplicate id is a hard error, not a merge-overwrite.

3. Fail-closed really fails — HOLDS. compare-native-wasm.mjs throws (non-zero exit) when a pinned crossover id is missing from the receipt or its n_tokens mismatches (compare-native-wasm.mjs:80-88): if (!row || row.n_tokens !== pin.n_tokens) throw new Error("fail-closed: compare corpus missing pinned ..."). Missing file is a readFileSync throw in loadCrossoverCases; missing id is a throw in loadCrossoverCases (corpus.js:66); n_tokens mismatch is a throw in validateCase (corpus.js:24-28). All are exceptions, not warnings.

4. No new env var — HOLDS. The diff reads no new process.env / std::env::var. The only env reads remain the existing MILTON_ROPE_LIBM_SIN / MILTON_EMBED_BIN / MILTON_COMPARE_TIGHT in compare-native-wasm.mjs (unchanged).

5. Rust test is #[cfg(test)] only — HOLDS. The new attn_crossover_fixtures_pin_min_tokens_boundary test lives inside #[cfg(test)] mod unit (crate/src/tokenizer.rs:278-279), and its include_str!("../../harness/corpus/compare-crossover.json") (tokenizer.rs:320) is compile-time string embedding inside the test module — nothing new is compiled into the product wasm (the wasm build uses --lib without --test, and #[cfg(test)] is stripped).

Net: the crossover fixtures are validated, non-shadowing, fail-closed, env-free, and test-only. The merge is additive (18 + 3 = 21) and the goldens digest is asserted unchanged. Approving.

@heskew
heskew marked this pull request as ready for review September 4, 2026 02:54

@tps-kern tps-kern left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVED. All three disprove points verified on f32bc61:

  1. THE POOL IS LIVE — not vacuous. The threads compare lanes (package.json:56-58: MILTON_WASM_THREADS=1 MILTON_THREADS=4) run compare-native-wasm.mjs, and the verdict wrapper's checkReceiptShape (wasm-compare-verdict.mjs:54-92) asserts on the SAME receipt: wasm_artifact === "threads", wasm_threads === 4, thread_report.workers > 1 AND workers === 4 (want.threads=4 passed by every threads npm script). Those fields are published by the loader only after startWorkerPool succeeded (src/index.js publishThreadReport), so a receipt that passes shape = the pool was live with W=4 for every embed in that process — n=32 ≥ ATTN_PARALLEL_MIN_TOKENS=32 therefore took dispatch_attn (model.rs attention_layer: pool_live() && n_tok >= 32), and n=31 < 32 stayed on attention_named. If the runner had <4 cores, workers clamps down, the shape check goes RED — the lane self-enforces the precondition rather than silently vacating. The three threads lanes (relaxed / simd128-fallback / forced-relaxed) plus both Node versions all cross the gate; the single-thread lanes (MILTON_WASM_THREADS=0) correctly run the crossover cases serial (no wasm-threads feature in those artifacts — the dispatch is compiled out), and still assert max_abs=0 on them.
    Known residual, follow-up not blocking: nothing ties the Rust constant ATTN_PARALLEL_MIN_TOKENS (model.rs:431) to the JSON gate: 32 — a future edit flipping the constant would leave every compare green (both paths are bit-exact by design, so a gate flip cannot redden any lane). The pin's real coverage — parallel-path divergence at the minimum split length, where column_range/scores-sizing/empty-range bugs would live — IS mechanical: n=31 stays clean while n=32/33 go RED. To make the gate value itself mechanical, have the Rust unit test assert crate::model::ATTN_PARALLEL_MIN_TOKENS == doc["gate"] (the const needs pub(crate)) — one line, worth a #59 rider.

  2. FAIL-CLOSED — confirmed. Every failure mode aborts BEFORE a receipt is written, and the verdict wrapper judges abort as RED ("that is not the known residual", wasm-compare-verdict.mjs:133-140): missing crossover file → readFileSync/JSON.parse throw inside loadCrossoverCases (harness/lib/corpus.js:62-66); wrong schema/gate/count → throw (corpus.js:67-83); pinned id missing from the receipt rows or n_tokens mismatch → throw in compare-native-wasm.mjs:80-88. On main the file and loadCompareCorpus do not exist — the import itself fails. There is no path where the check is skipped when the fixture is absent.

  3. LANE COVERAGE — correct split. Gets 21 (18+3): every lane that runs compare-native-wasm.mjs — wasm:compare-verdict (single relaxed/simd128/forced), all three threads verdicts, both Node-26 counterparts, q4k-perk/bprime, libm-sin must-fire (21 all-fail as expected — its wrapper only checks result=fail). Keeps 18 (by design, goldens digest unchanged): wasm:gate, harness:gate, must-fail, goldens generation, benches, sab-absent loader lane. I checked every loadCorpus/loadCompareCorpus call site: only compare-native-wasm.mjs switched. No lane that needs the pins misses them; no golden-bearing lane was touched (compare-crossover.test.js:16-27 pins the 18-case digest is unchanged and the 18 are all present in the 21). If loadCompareCorpus ever silently regressed to 18, the pins check (point 2) goes RED before a receipt exists.

The Rust tokenizer test (crate/src/tokenizer.rs:316-348) pins the real tokenize counts (25/26/27 × hello + search_document: = 31/32/33) against the committed JSON — the n_tokens in the receipt are proven, not asserted. CI run 33830644873: all 8 lanes green on f32bc61; must-fail RED where it should be.

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.

5 participants