Skip to content

apr prefills at decode rate on CPU: no batched prefill outside src/cuda/, 6.5x slower than llama.cpp on the same box #2787

Closed

Description

@noahgift

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.

Activity

  1. added
    pp-067PP-066 spec §6: 0.67 lane (carried row)
    on Sep 5, 2026
  2. noahgift commented on Sep 5, 2026

    @noahgift
    MemberAuthor

    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.

  3. modified the milestones: 0.68.0, 0.70.0, 0.71.0 on Sep 10, 2026
  4. noahgift commented on Sep 24, 2026

    @noahgift
    MemberAuthor

    Closed by paiml-implement — folded into epic #3994 (triage evidence/triage-1717.md)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pp-067PP-066 spec §6: 0.67 lane (carried row)

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions