You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
apr prefills at decode rate on CPU: no batched prefill outside src/cuda/, 6.5x slower than llama.cpp on the same box #2787
Found by PERF-060 while calibrating intel and mini for the measurement campaign.
apr prefills at decode rate on CPU. Measured prefill-to-decode ratio: 1.07× on intel, 1.02× on mini. A GEMM-based prefill should be many times faster than token-by-token decode; these are indistinguishable.
crates/.../generate_quantized.rs:568 loops forward_single_with_cache over every prompt token. Every batched prefill_* in the tree lives under crates/aprender-serve/src/cuda/. So there is no batched prefill on the CPU path at all — it is decode, run once per prompt token.
The comparator gap, on the same box and the same file
prefill tok/s
apr, intel
8.14 – 8.97
llama.cpp, intel, same GGUF
53.8 – 57.5
Roughly 6.5× slower. Recorded here as a host-capability reference, not as an Arm B number — that llama.cpp build is the wrong pin (version: 1 (35bee03)) and cannot be an oracle.
What it costs the campaign
Every replicate measured, W1 shape (513-token prompt, 128 generated, c=1):
host
prefill
decode
t_req
intel (Xeon W-3245, load 19–24)
8.14 – 8.97
6.57 – 7.66
73.9 – 81.9 s
mini (M4)
2.371 – 2.372
2.318
270.9 – 271.1 s
evidence/perf-054/campaign-scope.md assumed 40/6 and 60/8 tok/s. Prefill is 4.8× optimistic on intel and 25.3× on mini; t_req is 2.3× and 11.1× off. intel's apr-lane cell goes from an estimated 8.9 h to 19.2 – 21.3 h.
Note the decode estimate for intel (6 assumed vs 6.57–7.66 measured) was the one figure that held, and was slightly pessimistic. The bandwidth model of the hardware was sound. The error was assuming apr prefills with a GEMM.
Recommendation
Land batched CPU prefill before scheduling either measurement slot. It takes intel's cell to roughly 7 h — below the original estimate — and it moves mini from unmeasurable to measurable (see the timeout blocker filed separately: mini's 270 s t_req exceeds the protocol's hard 120 s per-request timeout, so every request aborts).
Scheduling the campaign first would spend ~19–21 h on intel and produce a guaranteed-empty receipt on mini, then have to be redone — and per I-9 a band may not be re-run to green, so those runs would be spent.
This is also a product defect independent of the campaign: CPU serving is 6.5× off the comparator on prefill for every user, not only for the benchmark.
No batched prefill exists outside crates/aprender-serve/src/cuda/ (generate_quantized.rs:568 loops forward_single_with_cache). Kernel/scheduler work is 0.67 by D-1; §6 has no CPU-prefill row — nearest is W-G's batched-prefill clause; flagged as a §6 gap. It is also why the CPU-lane W1 cells on intel/mini time out (#2855) and why LEDGER rows 10–11 refused their parity blocks.
Instance of class #2880 — the shipped CPU path runs a narrower ISA or algorithm than the host supports.
Activity
fix(perf): the repro example shipped one developer's model path as it…
fix(guards): three widenings, each opened by a measured escape, plus …
noahgift commentedon Sep 5, 2026
0.67 lane — attaches to PP-066 spec §6 row W-G.
No batched prefill exists outside crates/aprender-serve/src/cuda/ (generate_quantized.rs:568 loops forward_single_with_cache). Kernel/scheduler work is 0.67 by D-1; §6 has no CPU-prefill row — nearest is W-G's batched-prefill clause; flagged as a §6 gap. It is also why the CPU-lane W1 cells on intel/mini time out (#2855) and why LEDGER rows 10–11 refused their parity blocks.
Instance of class #2880 — the shipped CPU path runs a narrower ISA or algorithm than the host supports.
PP-066 triage 2026-09-05 (issue #2873); ledger
docs/audits/pp-066-triage-2026-09-05.md.noahgift commentedon Sep 24, 2026
Closed by paiml-implement — folded into epic #3994 (triage evidence/triage-1717.md)