opus audit v4: verification of the v3 fixes (audits/opus_audit_phase0_2_m5_v3.md) #
scope: read-only. the JSON is a fresh run (generated_at_utc 2026-10-15T13:00:39Z, file mtime 16:00:39 local), so every strata number changed again since v3. i re-parsed all 22 output_tails with my own regexes (STRATA_RESULT, STRATA_KLD, STRATA_KLD_REF2, STRATA_PPL, expert stream, window T1, expert arena, locked, prompt scratch … unmapped, llama common_perf_print / [ Prompt: … ]). from that i recomputed every perf dict, every summary_comparison field, and every number on PLAN L274–282 (#29, #30) and L571–586 (§7.1 #5).
what i ran:
vendor/strata/build-metal/route_parity: 3× default math, 3×STRATA_METAL_MATH=fastpython3 -B scripts/run_phase0_phase2_m5_validation.py --self-test- a mutation check of the new self-test:
_madvise_filewith advice 0 / 3 / 4 onF_NOCACHE-written files (256 KiB and 64 MiB), measured with the script's ownfile_cache_residency - a GGUF-vocab detokenizer, plus a byte-level BPE using the GGUF merges, on the three
eval_tf_*files and the unpatched llama output. the BPE round-trips the chat_json ids exactly. - SHA-256 of the 7 prompt files and 5 scripts/binaries against
provenance
no repo files were edited except this report.
verdict: FALSIFIER HIT (PLAN provenance only).
- all 4 requested fix items pass.
summary_comparison: 0 mismatches (80 scalar fields + 3 KLD blocks).- both tests pass.
- but PLAN still carries 8 numbers attributed to
v3that appear in no file on disk, plus 4 older #29/#30 numbers with no archived source (§1c).
1. PLAN / summary_comparison vs raw #
1a. perf + summary_comparison: PASS (0 mismatches) #
-
all 14 strata
perfdicts equal my parse field for field, includingref2=STRATA_KLD_REF2andpure_gpu = T1 GPU − host. -
strata_metal_iq3s_moce0_43tok/strata_metal_q2_0_moce0_43tok: 8 + 8 fields exact. -
strata_metal_optimized_k256_q2_0_moce085: all 50 fields exact.- miss reductions recompute:
1 − 40.9/84.3 = 51.48%→ 51.5 ✓, and1 − 40.9/108.9 = 62.44%→ 62.4 ✓ - footprint / RSS before→after recompute from the 1024 run:
4.73→4.68,7.41→7.36✓ swapouts_mib= the max over all 22 runs = 0.0 ✓
- miss reductions recompute:
-
quality block: all per-domain KLD / PPL / top-1 / perf values exact. macros:
block macro mean KLD macro top-1 q2_0_moce0 vs iq3s 0.203236 90.63 (90.627) moce085 vs q2_0_moce0 0.058768 96.88 (96.877) moce085 vs iq3s 0.238592 89.58 (89.583) all ✓.
-
llama blocks: the 6 + 8 fields equal the archived tails:
- unpatched
[ Prompt: 14.3 | Generation: 1.4 ],21.7at ub2048, disk0105,851.73/85,725.73 - patched
38.11/63.27/3.22/0.28, disk033,428.34/46,808.58/28,272.94
- unpatched
-
no
65.0/65tokstring anywhere in the JSON or the runner. the per-token divisor is gone ✓. -
nit: the quality block's per-domain
eval_tokens: 64sits next toeval_ms/eval_tok_per_s, which are over the 63 decode steps (10746.76 / 63 = 170.58).perf.eval_tokensis overwritten by theSTRATA_KLDcount. label clash only, nothing is wrong numerically.
1b. PLAN numbers that recompute (✓) #
- #29:
1.40(=1.4),14.30/21.70(=14.3/21.7; the trailing zeros are false precision, cosmetic),105,851.73,85,725.73,3.22,22-token,33,428.34,63decode (the patched run's "63 runs"),5,600-byte,1,264-token,38.11,63.27,0decode tokens (eval time = 0.00 ms / 1 runs+[end of text]),46,808.58,28,272.94,0.28. - #30:
- route_parity:
200windows,2.16e-07/2.15e-07,3.06e-07/4.00e-07 - memory:
2.90/0.34/1.28/4.53;3.24= 2.90 + 0.34 (derived) - prompt scratch:
1.36,4.73→4.68,7.41→7.36,0.052(labelled hypothesis ✓) - expert files:
23.42/15.82GiB (.experts= 23.419 / 15.820 GiB ✓).85pages: 15.82 GiB / 12288 = 84.4 × 16 KiB → 85 ✓ - arena:
1,550/1,440/2,441/3.40 - misses:
51.5%,84.3→40.9,304.5→127.2,82.44→91.48,62.4%,108.9 - soaked speeds:
229.99/268.23,8.04–9.33(the soaked MoCE decodes are 8.04 / 9.33 / 8.37) - KLD:
0.0588/96.88/0.2032/90.63/0.2386, per-domain top-1,192 = 3 × 64,244 / 159 / 456 - cool:
320.58/353.49are in v3 ✓
- route_parity:
- §7.1 #5:
- run count and watchdog:
22 = 5 + 9 + 4 + 4; watchdog0.25 s/2.2 GiB/64.0 MiB=watchdog_policy; 0.0 MiB swap ✓ - init:
424.45–504.58(MoCE speed: 469.70 / 424.45 / 504.58) ✓,424.45–506.90(all 14) ✓ - fill:
0.6–1.3 s✓;2.69–5.92 GB/sover the 10 Q2_0 runs ✓;3.98–5.04over the 4 IQ3_S runs ✓ - prefill:
4,452.38/7,635.20✓ - every soaked tok/s, ms/tok, wait, GPU, hit split, misses/tok and MB/tok on L579–582 ✓. the splits now sum correctly:
52.62 + 24.70 = 77.32,63.86 + 18.58 = 82.44,82.34 + 9.14 = 91.48,84.38 + 8.25 = 92.63,86.70 + 7.14 = 93.84. - KLD per domain is rounded to 4 dp, all ✓.
248,320= GGUFtokenizer.ggml.tokenslength ✓. - the v3-cited cool values that are in v3:
339.50–356.06,3,194.19,5,793.65,9.51,13.52,16.11 + 56.29✓.
- run count and watchdog:
- the 7 v3 §1c offenders (69.43/7.89, 71.09/11.35, 3,194.23, 5,793.62, 6.39–6.44, 6.82–7.07, 0.5 s) are all gone ✓.
1c. unsourced PLAN numbers (falsifier) #
| PLAN claim | line | status |
|---|---|---|
IQ3_S cool 7.27 tok/s / 137.56 ms/tok "cool in v3" |
L579 | not in v3 audit, not in any file. 7.27 occurs nowhere in v3, and 137.56 only in an unrelated upstream bench JSON. |
Q2_0 m0 cool 105.12 ms/tok |
L580 | not in v3 (v3 has only 9.51) |
MoCE 43-tok cool 73.94 ms/tok |
L581 | not in v3 (v3 has 13.52, 16.11, 56.29) |
1024 / 2048 cool 13.63 / 73.37, 13.22 / 75.62 |
L582, L282 (13.22–13.63) |
not in v3 or on disk |
17.0 GiB repack buffer at src/llama-model.cpp L1914 |
L275 | not in any archived output. every unpatched run uses -nr, so no tail can show it. in the patched tree, L1914 is the mmap-vs-alloc branch condition, which is plausible but not the allocation itself. |
| "96 graph splits/token" | L276 | not in any tail. v1 already flagged this as unverified. |
IQ3_S size classes "93..163 pages" |
L281 | not in any tail/artifact. my recompute from the GGUF tensor types gives 121/133/143/163 pages for the 39 layers whose types i decode. the max of 163 matches; the min of 93 depends on the 9 layers with ffn_down type 42, which i didn't decode. |
| unpatched "generating 63 tokens" / "63 generated tokens" | L276, L573 | llama-cli prints no count. the visible thinking text tokenizes to ≈64–65 tokens with -n 64 (the smoke run gives 32 at -n 32, moecache gives 64). that's 64 generated tokens = 63 decode steps, so the wording is off by one and not printed. minor. |
where the 8 cool numbers came from:
- the parent's transcript row 176426 holds the v3-era
summary_comparison(decode_ms_per_tok137.56 / 105.12 / 73.94 / 73.37 / 75.62). - they were real values of the 05:03Z run that v3 recomputed, and each pair is internally consistent (
1000/137.56 = 7.27etc.). - but that JSON was overwritten by this run: the runner writes the same
--outputpath, and the file isn't in git. - the cited audit file only quotes 6 of the cool values, so the citation can't be checked from the repo.
fix, either of:
- re-archive the v3-era summary as a separate file (raw tails are lost), or
- drop the 8 values / quote only what v3 contains, or
- re-run cool and soaked and archive both to timestamped outputs.
1d. "thermal soak" attribution: unmeasured, and the runner contradicts it #
- PLAN labels all soaked numbers "after sustained fanless thermal soak".
- but the runner itself says (L506): "Phase A: Run all 5 speed benchmark runs first while the fanless M5 Air is thermally unthrottled". in this run the speed runs came first. in the v3-era run they were interleaved with the TF refs.
- no temperature,
pmset -g thermorpowermetricsdata is archived. - both sides of the decode time grew, and pure GPU grew too, so this isn't just I/O:
- host wait doubled at near-identical misses/tok and MB/tok (43-tok MoCE:
16.11 → 34.15 msat 126.2 vs 127.2 MB/tok). the implied SSD rate fell from ~7.8 to ~3.7 GB/s. - pure GPU rose
56.29 → 88.35.
- host wait doubled at near-identical misses/tok and MB/tok (43-tok MoCE:
- that is consistent with thermal throttling, but also with the binary rebuild in between (window.cpp / route_parity / CLI rebuilt 15:56–15:57) or with background load. label it a hypothesis or log thermals.
2. llama prompt archive + #29 / §7.1 caveats: PASS #
-
data/m5_val_prompts/llama_prompt_1264tok.txt: 5,600 bytes, sha2562549439506dda855058b64b48167d9b87aeb0fe5ab4fdfb1e191782218a9ac79. that equalsprovenance.prompt_files_sha256and is byte-identical to/tmp/m5_prompt_1600tok.txt, which still exists. -
the patched echo tail "…You are solving a Termina [end of text]" equals the file's last bytes ✓. the unpatched echo begins with the file's first task ✓.
-
#29 (L276) explicitly states:
- unpatched =
llama-clichat mode - patched =
llama-completion -no-cnv -t 8raw - the patched ub1024/2048 runs hit EOS after prefill with
0decode tokens, so their disk0 is load + prefill only - "the two runs also differ in
llama-clichat vsllama-completion -t 8raw mode"
✓
- unpatched =
-
§7.1 #5 (L573–574): names both modes and says "hitting EOS after prefill with
0decode tokens" / "prefill only" ✓. it has no explicit "not like-for-like" sentence the way #29 does, but the modes and the prefill-only status are on the lines. acceptable. -
summary_comparisonkeywhole_run_disk0_mb_1264tok_ub*_prefill_onlycarries the caveat in the name. no/ 65.0✓. -
data caveats (not falsifiers):
- the 5,600-byte file is cut mid-word ("Termina"). that's the likely reason the raw-mode run emits EOS immediately.
- the unpatched keys say
1264tok, but llama-cli wraps the text in the chat template, so its prompt is 1,264 + template tokens (never printed).
3. eval_tf_{ops_tools,chat_json,ja}.txt: PASS (with content caveats) #
| file | ids | --n-prompt / --n-gen |
starts with | <think> |
scored span (last 64) |
|---|---|---|---|---|---|
| ops_tools | 308 | 244 / 64 | `< | im_start | >user\n` (248045 846 198) |
| chat_json | 223 | 159 / 64 | `< | im_start | >user\n` |
| ja | 520 | 456 / 64 | `< | im_start | >user\n` |
- all three contain exactly 2
<|im_start|>: one complete user turn (ending in<|im_end|>\n) plus the assistant turn. 244 + 64, 159 + 64 and 456 + 64 match the cmds, and all 9 runs reporteval_tokens=64. no<think>(248068) in any scored span ✓. chat_jsonis now a genuine schema-extraction task: title / date / start / alphabetical attendees. the scored JSON is correct ✓.- in all 9 runs the per-run
GEN_IDS(argmax) align position-for-position with the forced tokens (ops 49/64, chat 61/64, ja 54/64 on the ref). so teacher forcing is aligned and off-by-one is ruled out ✓. - caveats:
- ops_tools' scored span has no reasoning and no
</think>. the</think>is in the prompt, and the 64 tokens are visible JSON only. so PLAN L583's "scoring final<think>reasoning +</think>+ complete visible JSON/Japanese output" holds for chat_json and ja, not ops_tools. - ops_tools still has
<think>with no following\n(v3 caveat 4), and</think>\n\n\n{has a triple newline. this doesn't match the chat template. - ops_tools' user turn is complete but is a mid-trajectory
New Terminal Outputobservation. the task statement from earlier turns is absent. - ja's reasoning is a keyword-extraction / question-writing trace (「質問文を作成する」), a data-synthesis artifact that doesn't match the user's QA request. its tail is in the scored span.
- still 1 turn × 64 tokens per domain, no CI. v3 fix 6's "n ≥ 3 docs/domain" is not done.
- ops_tools' scored span has no reasoning and no
4. code fixes: PASS #
prompt_gemm_need_bytes()(window.cpp L2148–2195):- declares
PromptGemm p_tm;and usesp_tm.TMfor bothmr = C*TOPK + NEXP*p_tm.TM(L2161) and the tiles buffer8*(mr/p_tm.TM + 1)(L2180). no hard-coded16remains ✓.
- declares
prompt_gemm_init()L2137:if (need != prompt_gemm_need_bytes()) { err = "metal window: prompt_gemm_need_bytes() != need"; return false; }, aftermake()in measure mode.- this is a hard runtime check, stronger than
assert(whichNDEBUGwould compile out). - the string is in the
strata_window_clibinary (sha matches provenance, built 15:57:05 after window.cpp 15:56:59). all 14 runs completed, so the check passed atC = 1024, PLE on, flash. - nit: on mismatch it returns after
st_->region(need)has already mapped the region, and leaves it mapped.
evict_file_cacheremoved: no occurrence inscripts/,results/or PLAN ✓.file_cache_residency: ctypesmmap(PROT_READ, MAP_SHARED)+mincore, countingvec[i] & 1(L62–90).- self-test (L419–434): writes 16 × 16 KiB with
F_NOCACHE, asserts residency== 0.0, callswarm_file_cache, asserts== 1.0✓.- the mutation check shows it discriminates:
- advice 0 (no-op) →
0.0 → 0.0, which the test would fail - advice 3 (
WILLNEED) →0.0 → 1.0 - advice 4 (
DONTNEED) →0.0 → 0.0 - a 64 MiB file also warms
0.0 → 1.0
- advice 0 (no-op) →
- remaining caveat:
warm_file_cachestill targets the denseK256-*-metal.gguf, not the.expertsfile the arena fill reads. the first Q2_0 fill of each phase is still the slow one:q2_0_moce0speed 1.1 s / 2.94 GB/s,tf_ops_tools_q2_0_moce01.3 s / 2.69 GB/s.
- the mutation check shows it discriminates:
- unchanged from v3: the cap check is still log-only when
STRATA_METAL_ARENA_GBis set (L1208). out of scope for this round.
5. tests / provenance #
route_parity(6 runs, all exit 0, deterministic):- default:
K=512 … mismatches 0, worst 2.16e-07/K=256 + MoCE=0.85 … mismatches 0, 3.06e-07 PASS - fast:
2.15e-07/4.00e-07 PASS - matches PLAN #30 exactly.
- default:
--self-test: exit 0, outputscripts/run_phase0_phase2_m5_validation.py self-test: PASS. the v3 garbled prefix is gone.- provenance: all 7 prompt hashes and all 5 script/binary hashes match the disk.
falsifier summary #
| falsifier | result |
|---|---|
summary_comparison vs raw |
0 mismatches |
| tests | route_parity 6/6 PASS, --self-test PASS |
| v3 fix 1 (splits / prefill ms / fill ranges) | ✓ all recompute |
| v3 fix 2 (llama caveats) | ✓ |
| v3 fix 3 (5,600-B prompt archived) | ✓ sha 2549439506dd… |
| v3 fix 4 (evict dropped, mincore test) | ✓, and the test discriminates |
v3 fix 5 (need check, p_tm.TM) |
✓ |
| v3 fix 6 (TF turns) | ✓ complete turns, real JSON task, <think> unscored. n = 1/domain is still open. |
| unsourced PLAN numbers | 8 "cool in v3" values not in v3 or any file (L579–582, L282). plus 17.0 GiB / L1914, "96 graph splits", "93..163 pages", and "63 generated tokens" (unpatched; ≈64 visible, not printed) |
| unsourced claim | "sustained fanless thermal soak": no thermal data, and the runner comment says the speed runs were thermally unthrottled |
what to fix #
- archive the v3-era cool numbers (e.g.
results/phase0_phase2_m5_validation_v3_cool_summary.jsonfrom parent transcript row 176426) or drop the 8 values. better: make the runner write timestamped outputs so a re-run can't destroy the previous raw. - #29: mark
17.0 GiBand "96 graph splits/token" as [unverified], or archive a--repackload log and aGGML_SCHEDsplit count. fix "63 generated tokens" → "64 generated tokens (63 decode steps)" for llama-cli. - #30: source "93..163 pages" (print the class page sizes in the strata log) or drop it.
- relabel "thermal soak" as a hypothesis, or log
pmset -g therm/powermetricsaround the runs. fix the runner's Phase A comment. - PLAN L583: say that ops_tools' 64 scored tokens are the visible JSON only, with no reasoning and no
</think>. optionally fix the ops_tools<think>/\ntemplate drift.