Skip to content

docs(ArcQuant/ArcInfer): V4 could not serve past ~22 tokens — TCFRAG held 63 GB (wave66-CS) - #214

Merged
heydryft merged 10 commits into
masterfrom
docs/session9-the-22-token-wall
Aug 21, 2026
Merged

heydryft merged 10 commits into
masterfrom
docs/session9-the-22-token-wall

Conversation

@heydryft

@heydryft heydryft commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

The durable record of session 9. Issues are disabled on this repo, so this is the issue.

Rebased onto fdf0b6c19. Every citation re-verified there — see "the citations rotted twice" below, which is the most reusable thing in this PR.

What lands

🔴 The blocker, and it is CLOSED — but not by the PR everyone expected

V4 died with CUDA_ERROR_OUT_OF_MEMORY after 22–30 generated tokens, 3/3 runs, card full at 143,151 of 143,771 MiB. It looked like an allocator leak. The leak was never what changed — the headroom was:

probe frees
ARC_QTIP_TCFRAG=0 64,262 MiB
TCFRAG on (the shipped default at cc5487ad3) 262 MiB

tcfrag_words is a per-weight OnceLock caching a repacked copy of every weight for the process lifetime — ~63 GB against a ~79.5 GB model. That left decode 844 MiB free, so a ~12 MiB/token leak that PR #182's leg survived for 2,600 tokens killed this one in ~20. #182's leg predates TCFRAG entirely.

Two independent single-variable rescues, each sufficient alone — which is what makes this an attribution rather than a story: ARC_QTIP_TCFRAG=0 → 256/256 tokens, five runs, finish=length (cache still on); ARC_CANDLE_ALLOC_CACHE=0 → 1,000 tokens (TCFRAG still on).

🔑 PR #209 (merged) is the fix that restored serving. PR #213 is defence in depth on the leak rate and must not be recorded as "the OOM fix".

#203 costed instructions and never costed memory. The .cu's performance section still only costs instructions.

The tok/s consequence — two claims, and only one is universal

The brief said "every single-user number was measured inside a crashing run". The TCFRAG result forces that apart, and I did not keep the half that isn't supported:

✅ A real steady-state single-user number is now obtainable for the first time — 256/256 runs clean. Nobody has taken it.

🔴 The citations in this file rotted twice while it was being written, and the gate caught neither

doc_citations stayed green through both, and that is its contract, not a bug: it checks a line is in range, not that it is the right line, and a citation with no backticked symbol gets no symbol check at all. A green gate is not a verified citation. Everything was re-derived by grep against fdf0b6c19.

Master carries the same rot in code: the site marker inside mark_unreachable("cuda_graph.autonomous_decode.capture", …) still reads "normal.rs:2619", which #205 moved to 2905. It is a string literal, so the gate cannot see it by design.

The other six

§ finding state
2 TCFRAG was default-ON while its own header reads "UNVERIFIED ON HARDWARE — NEVER RUN" ✅ #209, merged
3 Two host round-trips per decode step, not one. Retracts "op count is retired as a lever" — the captured arm kept both, so the CPU never left the loop open; zero-sync arm unmeasured
4 The dedicated decode tier cannot accept V4 architecturally — seven non-optional projection slots, no MLA slot, no expert slot open, design change
5 The b=1 FP8 GEMV is instruction-bound. Retracts the "4% memory-controller ⇒ latency-bound" story #210 merged
6 Four instrument failures, incl. ci_cuda.yaml gating nothing and 21 presence-tested env flags where =0 turns a feature ON #212 open
7 #182 is a precondition, not a fix — and its hazard is now UN-masked, because 256-token runs finally live long enough for the probe to mean something #211 merged (registers it; the pin is unwritten)

Corrections to the brief, kept as corrections

normal.rs:2515 → :2619 → now :2905. tcfrag2b.rs:151-157 → :151-158. has_cached_kv_info is 3 prose occurrences, 0 definitions, and the misleading comment is already fixed in-tree. ba026e9d1 is a root/import commit (1,430 files, 0 deletions) and introduced nothing — the analysis is 6e58c5dbf. The OnceLock caches None permanently rather than panicking — same blast radius, different mechanism. "Declined 4 of 4" is removed as unsourced, and the 63 GB measurement points the other way: a repack that declined every time would retain nothing. The 2.97 TB/s ceiling used the FP32 warp rate; the INT32 pipe gives 1.49, so the honest form is the range 1.5–3.0 TB/s. "45 inst / 4 B" is derived from #210's own prose, not measured — probe is cuobjdump -sass. ARC_NO_DEFERRED_FREE was overstated. mark_unreachable being dark was already on the record from session 8; only the consequence is new.

One correction goes the other way: the 08-19 row narrowing "the default config silently disables prefix caching" is confirmed, not contradicted.

Gates

doc_citations 12/12, run standalone (std-only) against fdf0b6c19; zero drift from the new content. The green is not vacuous — the gate failed on an earlier revision of this file first, on a device.rs citation inside the very sentence explaining why candle must not be cited that way. typos clean (two SHAs reworded rather than widening .typos.toml, per #211's precedent). cargo check -p mistralrs-quant green; cargo fmt -p mistralrs-quant --check shows no diff in cuda_ops.rs.

Related: #209, #213, #211, #210, #212, #205, #206, #182, #203.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7

@github-actions

github-actions Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Code Metrics Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Language              Files        Lines         Code     Comments       Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 C Header                  5          305          210           52           43
 CSS                       2         1181         1036           34          111
 CUDA                     81        29464        20161         6264         3039
 Dockerfile                1           39           22            8            9
 JavaScript               16         3546         2676          482          388
 Jinja2                    7          694          656            5           33
 JSON                     75         4896         4893            0            3
 Makefile                  1            6            5            0            1
 Metal Shading Lan|       33        12224         9431         1142         1651
 PowerShell                1          300          227           30           43
 Python                  147        15371        12677          824         1870
 Shell                    42        10274         6849         2750          675
 Plain Text                4         3801            0         2479         1322
 TOML                     33         1498         1294           54          150
 YAML                      3           25           23            2            0
─────────────────────────────────────────────────────────────────────────────────
 HTML                      4         2687         2604           43           40
 |- CSS                    2          543          479           37           27
 |- JavaScript             1         1233         1215           12            6
 (Total)                             4463         4298           92           73
─────────────────────────────────────────────────────────────────────────────────
 Jupyter Notebooks         4          122           83           23           16
 |- Markdown               1           60           30           22            8
 |- Python                 1          122          113            1            8
 (Total)                              304          226           46           32
─────────────────────────────────────────────────────────────────────────────────
 Markdown                212        47877            0        37224        10653
 |- BASH                  72         1655         1203          331          121
 |- C                      3           17           17            0            0
 |- CUDA                   2           84           56           16           12
 |- JSON                  19          779          779            0            0
 |- PowerShell             1            1            1            0            0
 |- Python                23         1008          787          113          108
 |- Rust                  67         2058         1723           77          258
 |- TOML                   6          207          164            0           43
 |- YAML                   5           41           36            5            0
 (Total)                            53727         4766        37766        11195
─────────────────────────────────────────────────────────────────────────────────
 Rust                    680       340521       292882        18037        29602
 |- Markdown             503        32080          471        27656         3953
 (Total)                           372601       293353        45693        33555
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Total                  1351       514719       362803        97723        54193
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

heydryft and others added 8 commits August 21, 2026 15:02
…e-user number is void (wave66-CS)

The durable record of session 9. Issues are disabled on this repo, so this is
the issue.

THE BLOCKER. At cc5487a on an H200, batch=1 decode dies with
CUDA_ERROR_OUT_OF_MEMORY after 22-30 generated tokens, 3/3 runs, two
independent arms. Growth is ~11 MiB/token and is never released across
requests: one run ended at 142,959 MiB used and the next started there. The
card is genuinely full at failure, 143,151 of 143,771 MiB.

Consequence, stated as strongly as the evidence supports: every single-user V4
throughput number this project has recorded was measured over <=30 tokens
before a crash. The three-way b=1 agreement (15.11 / 15.39 / 15.51 tok/s) has
"24 tokens" written in its own FACTS row. The best clean fragment tonight was
37.35 tok/s over 20 tokens - a fragment, not a rate.

Six more findings, each with its ref: TCFRAG was default-ON with "UNVERIFIED ON
HARDWARE - NEVER RUN" in its own header (fixed, #209); two host round-trips per
decode step, not one; the dedicated decode tier cannot accept V4
architecturally; the b=1 FP8 GEMV is instruction-bound; four instrument
failures; and #182 is a precondition whose hazard is masked by the OOM.

Retractions added to FACTS: the 5,300-cast / 1,571-MHC premise (stale, profiled
at 05af600), DecodeKvGeometry (never existed in Rust), the 27.2% TAIL (sole
source is a docs file not on master), the latency-bound b=1 story, and "op
count is retired as a lever".

Corrections made while verifying, all against 9c127a2 rather than against the
report: normal.rs:2515 -> :2619; has_cached_kv_info is 3 prose references and 0
definitions, not zero occurrences; ba026e9 is a root/import commit and
introduced nothing; the TCFRAG OnceLock does not panic, it caches None
permanently; tcfrag2b.rs:151-157 -> :151-158; the 2.97 TB/s issue ceiling used
the FP32 warp rate, and the INT32 pipe gives 1.49, so the defensible claim is a
1.5-3.0 TB/s range; ARC_NO_DEFERRED_FREE was overstated; and mark_unreachable
being dark was already on the record from session 8.

Also surfaced, unresolved: PR #182's H200 leg ran 2,600 tokens with no OOM,
which needs >=15.7 GB free at decode start against tonight's 844 MiB. Nobody
has explained the ~60 GB gap, and if it is the serve config then this is a
concurrency-budget defect as much as a per-token leak.

Gates: doc_citations 12/12 green, run standalone against this tree, and the
gate reproduced a real failure on an earlier revision of this file first, so
the green is not vacuous. typos clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
…26-08-21

The blocker block at the top is no use if the read order still sends people to
the session-8 FACTS section first. Adds wave66-CS as item 2 (before the census,
because a GPU-spend decision made on void single-user numbers is worse than one
made late) and renumbers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
… round-trips

The first wording said the 1,137x launch cut "left two host syncs in both arms",
which is wrong: the eager arm never replays a graph, so it never reaches
graph.rs:362. The correct and stronger statement is about the CAPTURED arm — it
still pays the replay sync and the sampler's 4-byte D2H, so the CPU never left
the loop and the ~8% cannot be read as the value of removing it.

Also adds the guard against over-claiming in the other direction: this does not
show the two syncs ARE the limiter, only that the experiment could not see them.
The number that would settle it has not been taken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
…arity cuts both ways

The env-flag consequence was stated as "both arms ran with the feature on".
That holds for the 13 .is_some() flags; for the 5 .is_none() ones, =0 counts as
set and therefore SUPPRESSES, so the legs are inverted rather than identical.
Either way the control leg was not a control, but the two failure shapes are
different and a reader debugging an old A/B needs to know which one they have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
It was cross-referenced loosely as "the same provenance problem as the 27.2%".
Both numbers live on the same line of docs/engineering/OPENROUTER_READY.md,
which is not on master — it exists only on agent/tail-sinkhorn-warp-v2 and
agent/decode-share-probe. One of the two is already being multiplied into a
projected win, so the pointer should be explicit rather than a cross-reference.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
#209's merged doc comment argues TCFRAG "owns the whole of the b=1 decode path"
and cites the .cu header for it. The header says that about
qtip2b_gemv_tuned_kernel — the shipped kernel TCFRAG was written to replace —
at qtip2b_tcfrag.cu:19, not about TCFRAG.

The conclusion is still right, and the two dispatch sites in bitshift.rs
establish it without the quote, so this changes nothing about #209 being the
correct fix. But the quoted sentence does not say what it was cited as saying,
and it is now on master saying it. Recorded rather than left for someone to
re-derive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
The box measured the missing ~60 GB after this file was drafted:
ARC_QTIP_TCFRAG=0 frees 64,262 MiB against 262 MiB with it on. tcfrag_words is
a per-weight OnceLock caching a repacked copy of every weight for the process
lifetime — ~63 GB against a ~79.5 GB model. #182's leg predates TCFRAG, so it
had ~15.7 GB of headroom and survived 2,600 tokens at the same per-token rate.
Same leak, two orders of magnitude less room.

The concurrency-budget hypothesis is WITHDRAWN. It was a fair reading of the
evidence available; the measurement removed the need for it.

Attribution corrected: two independent single-variable rescues each work alone
(ARC_QTIP_TCFRAG=0 -> 256/256 tokens x5; ARC_CANDLE_ALLOC_CACHE=0 -> 1,000
tokens), so #209 restored the headroom and #213 is defence in depth on the rate.

Also splits an over-claim of my own. "Every single-user number was measured
inside a crashing run" is two claims and only one is universal:
  A: every V4 single-user tok/s figure is a <=30-token short window. Stands.
  B: "inside a crashing run" holds only at or after cc5487a (#203, today).
     The b=1 trio is from 08-17, before TCFRAG existed - its 24 was a harness
     choice, not a truncation. Claiming otherwise would be a fabrication.
And the 33.4 -> 34.2 pair is a third case: its source CAPTURE_LANE.md exists
nowhere in this repo, on any branch. Unsourced, like the 27.2% and the 16.0%.

Drops the unsourced "declined 4 of 4 loads" claim, records that #210 merged
with a projection multiplied out of the unsourced 16.0% share, and notes #211
merged (registers the hazard; the pin is still unwritten) - and that #209
un-masks it, since 256-token runs now live long enough for the probe to mean
something.

cuda_ops.rs: fix the mis-quote #209 shipped. The header phrase "the whole of
the b=1 decode path" describes qtip2b_gemv_tuned_kernel, not TCFRAG. The
conclusion rests on the dispatch sites instead. Adds the 63 GB retention to the
gate's doc comment, where the next reader of #203 will look.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
…f them

Master moved twice more while this was being written (#210 -> f709872, #205
-> fdf0b6c). #205 added +292 lines to normal.rs, +275 to layers.rs and +144
to deepseek4.rs, moving fifteen citations in this file including every
mark_unreachable site and the return Ok(None) that section 4 is built on.

doc_citations stayed GREEN through both. That is not a gate failure, it is the
gate's contract: it checks a line is in RANGE, not that it is the RIGHT line,
and a citation with no backticked symbol gets no symbol check at all. Recorded
in the provenance block, because it is the most useful thing on the page for
anyone maintaining these docs. Everything re-derived by grep, twice.

Corrections: normal.rs 1432->1465, 2379->2665, 2370->2656, 2575->2861,
2604->2890, 2619->2905, 2977->3263, and the six mark_unreachable sites
1556/1635/1921/2404/2474/2602 -> 1589/1668/1954/2690/2760/2888; deepseek4.rs
2968->3030, 3149->3190, 3165->3206, 3283->3324, 3334->3375, 2219->2281;
cuda_ops.rs 1601->1614, 1612->1627; graph.rs 214->212; blockwise_fp8/ops.rs
1139->1179, 1140->1180; and three self-citations into 00_RESUME_HERE.md that my
own edits had shifted.

Also notes that master's own code carries the same rot: the site marker inside
mark_unreachable("cuda_graph.autonomous_decode.capture") still reads
"normal.rs:2619". It is a string literal, so the gate cannot see it by design.

Title no longer says "taken inside a crashing run" — that was the half of the
claim the TCFRAG measurement showed is not universal.

Verified capture is still default-off after #205 (ARC_V4_CAPTURE_PROBE, unset,
normal.rs:1940), so section 7's sequencing statement stands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
@heydryft
heydryft force-pushed the docs/session9-the-22-token-wall branch from 16018ae to df0e29f Compare August 21, 2026 14:06
@heydryft heydryft changed the title docs(ArcInfer/ArcKV): V4 cannot serve past ~22 tokens, so every single-user number is void (wave66-CS) docs(ArcQuant/ArcInfer): V4 could not serve past ~22 tokens — TCFRAG held 63 GB (wave66-CS) Aug 21, 2026
heydryft and others added 2 commits August 21, 2026 15:08
The "Owner: PR #213" block still sat above the TCFRAG resolution and read as if
#213 owned the blocker. Moved below it and retitled "the rate half", with the
part of #213's diagnosis that survives stated plainly: the 1 GiB constant really
was unreachable at 844 MiB free, and that stays true at any headroom small
enough to matter. It is worth landing; it is not why serving was broken.

Adds the sequencing point that follows: #213's falsification criterion wants a
256-token run, which only exists because #209 landed. Testing it against a
22-token crash would have measured nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
Three corrections to my own text:

- The FACTS session-9 header still asserted "every single-user V4 tok/s figure
  was measured inside a crashing run", which the Claim A/B split immediately
  below it contradicts. Header now states the universal half (<=30-token
  fragment) and points at section 2 for what it does and does not void.
- Provenance SHA f709872 -> fdf0b6c (#205 landed after the rebase).
- TCFRAG (#203, cc5487a) landed 2026-08-21, not 08-20. git log -1
  --date=short is the check; the date is load-bearing because it is what
  establishes that #182's leg predates TCFRAG.

Also 11 -> ~11.8 MiB/token where the retained figure is contrasted with
wave65-CQ's ~22 MB/token moved, so the two are not confusable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
@heydryft
heydryft merged commit 9fc15ba into master Aug 21, 2026
18 checks passed
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.

1 participant