Repository navigation
fix(v4): close the audit's last open finding-4 item; the long-prompt death is not a reference divergence - #97
Merged
Merged
Conversation
…ength PR #27's two CRITICAL and four HIGH findings are all already fixed on master (#28 findings 3+4, #30 findings 1+2, #35 findings 5+6). Re-read against the current code, one sub-item of finding 4 (§1(e)) was never actioned and is closed here, and the length question the audit raised now has coverage. **`SdpaParams::sliding_window` was inverted.** `sinks_attn` forwards it to the fused flash-sinks kernels as `window_size`, a bottom-right-aligned relative-distance window over the key axis those kernels are handed. That axis is the raw sliding window on Standard layers and `[raw ++ compressed]` on CSA/HCA — so Standard is the one ratio that may declare a window, and it was the one declaring `None`. On CSA/HCA a window over the union axis deletes precisely the distant-context keys the compressed branch exists to supply: compressed entry `j` stands for absolute position `j * ratio`, not `t_k - j`. Inert today only because `flash_sinks_ok` rejects `head_dim = 512`, the head dim every V4 layer uses — and PR #94 is adding 512-wide kernels, which arms it, silently, because the fused path takes no mask that could disagree. The decision now lives in `dsv4_attention::sdpa_sliding_window`, next to the code that builds the axis it describes. **Length coverage.** `v4_long_prompt_prefill_and_decode_sweep` prefills and then decodes through the real V4 dispatch at 127/129/511/513/1400 tokens — bracketing the 128-token window, the CSA/HCA block boundaries and `CACHE_GROW_SIZE` (512), the three thresholds a length-dependent defect would sit on. It is a CPU unit test, not a GPU claim; the fixture's position ceiling is now a parameter so the sweep measures the model rather than the fixture. Both tests mutation-checked: inverting `sdpa_sliding_window` fails the first; pinning `XsRollingCache`'s row ceiling to its initial 64 fails the second at T=511 and only there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code Metrics Report━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Language Files Lines Code Comments Blanks ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ C Header 5 305 210 52 43 CSS 2 1181 1036 34 111 CUDA 72 24328 17592 4018 2718 Dockerfile 1 39 22 8 9 JavaScript 16 3546 2676 482 388 Jinja2 7 694 656 5 33 JSON 74 4600 4597 0 3 Makefile 1 6 5 0 1 Metal Shading Lan| 33 12224 9431 1142 1651 PowerShell 1 300 227 30 43 Python 140 14489 11930 782 1777 Shell 21 4531 3182 1056 293 Plain Text 4 3801 0 2479 1322 TOML 33 1485 1292 43 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 182 35380 0 27061 8319 |- BASH 65 1567 1163 294 110 |- C 1 10 10 0 0 |- CUDA 2 84 56 16 12 |- JSON 18 708 708 0 0 |- PowerShell 1 1 1 0 0 |- Python 23 1008 787 113 108 |- Rust 64 2043 1710 75 258 |- TOML 6 207 164 0 43 |- YAML 4 38 33 5 0 (Total) 41046 4632 27564 8850 ───────────────────────────────────────────────────────────────────────────────── Rust 660 305484 264599 13728 27157 |- Markdown 475 22019 471 18941 2607 (Total) 327503 265070 32669 29764 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Total 1263 444870 327105 70504 47261 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
…fiable PR #97 fitted `a + b·T²` to two points. Two points determine a two-parameter curve exactly, so that fit is consistent-with the mechanism, not evidence for it — and six workstreams have since been re-ranked on it. This script turns it into evidence or kills it, unattended, in ~80 minutes. Three arms, all criteria pre-registered in the header before any number exists: * **Arm B (memory law, runs first)** — peak transient VRAM at T ∈ {512, 1024, 2048, 4096}. Cheap because the score matrix is allocated in layer 0: fire the request, sample `nvidia-smi` at 100 ms, abort after 90 s. Spans 8× for ~6 minutes and can refute the mechanism before Arm A spends an hour on it. * **Arm A (timing law)** — prefill isolated, not assumed: `max_tokens` 1 and 5 at each T give `decode_step = (t5-t1)/4` and `prefill = t1 - decode_step`, so the constant and quadratic terms separate instead of confounding. Ascending ladder under a wall-clock budget; a timeout is recorded as a lower bound. * **Arm C (optional discriminator)** — `ARC_V4_WINDOW_ONLY=1` takes `n_keys` from 1.25T to T. Score-matrix-bound prefill falls to ~0.64; MoE-bound prefill barely moves. This separates "attention" from "everything else" directly. The predicted peak is **one layer's** working set (`320·T² + 163840·T` bytes), not 43 layers': the layers are sequential and each frees before the next allocates. A 43× criterion would reject a correct mechanism, so the falsifiable claim is the curve's shape — adjacent ratios ~3.0/3.3/3.6 against ~2.0 for any linear mechanism. Verdict logic self-tested on synthetic data: quadratic in → `supported`, linear in → `refuted`, quadratic timing with linear memory → `mixed`, and **two points in → `insufficient`**, which is the script scoring the evidence that prompted it as not yet evidence. Exit 2 (environment could not answer) is never conflated with a refutation. Also fixes a silent-gate hole in the caller convention: `source gpu_box_preflight.sh || handler` never fires, because when sourced the script ends on `[ "$_arc_pf_sourced" = "1" ] || exit 1` — the `[` succeeds, short-circuits, and `source` returns 0 on a FAILED box. This checks `_ARC_PF_FAILED` directly and refuses when it is unset, i.e. when the checks never ran. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…odel it where it is not The prefill isolation ran a second full prefill at every T purely to measure that T's decode step. At T=1024 that is ~75 minutes spent on a quantity worth ~2.6 s. Decode is linear in context and prefill is the thing under test, so decode's share of t1 falls as 1/T: measure it at the cheap end of the ladder (ARC_PC_SUBTRACT_TS, default "128 256"), fit `d = d0 + d1·T`, evaluate it at the expensive end. Full ladder drops from ~3.3 h to ~1.8 h. Every point records `decode_source` (measured | extrapolated) and `decode_share_of_t1`, so the modelled contribution is auditable rather than assumed — at T=1024 it is ~0.06% of t1, which is the argument for doing it. Also documents the run's own cost as a prediction: ~1.8 h if prefill really is quadratic, ~15 min if it is not. The cheap outcome is the one that refutes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Admin-merged without review (this fork has no second reviewer); all 16 checks green at merge time — verified against head |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the reference-audit follow-up (PR #27, task #16) and answers the
long-prompt serving failure (task #22).
The six findings were already fixed
All of PR #27's two CRITICAL and four HIGH findings are on
master. Verified byreading the current code, not by trusting the PR titles:
embed(T0), not the target's hidden statepropose_chainis seeded fromlast_hidden;v4_mtp_draft_depends_on_the_target_hidden_state_not_only_the_tokenfails on the old seedingnew_draft_cacheis persistent per sequence and extended over accepted tokens (extend_draft_kv_row), mirroringforward_draft_extend; pinned byv4_mtp_draft_kv_holds_the_accepted_contextb_sz > 1prefill routed to mask-freesinks_attn_varlenis_varlennow requiresmask.is_none(), anddsv4_attentionalways passesSome(&mask), so V4 can never reach itattention_maskdiscardedcompose_caller_maskfolds it in and errors on a width it cannot place. One sub-item was left open and is fixed in this PR (below)swiglu_clamp; the shared expert carries.with_swiglu_limit(cfg.swiglu_limit)rope_scaling_for_compress_ratioreturnsNoneat ratio 0; the old behaviour is behindARC_YARN_ON_STANDARD_LAYERS=1What this PR changes
SdpaParams::sliding_windowwas inverted (audit §1(e), the one open piece offinding 4).
sinks_attnforwards it to the fused flash-sinks kernels aswindow_size— a bottom-right-aligned relative-distance window over whateverkey axis those kernels are handed.
dsv4_attentionhands them the raw slidingwindow on Standard layers and the concatenation
[raw ++ compressed]onCSA/HCA. So Standard is the only ratio that may declare a window, and it was the
one declaring
None. On CSA/HCA a window over the union axis deletes exactly thedistant-context keys the compressed branch exists to supply — compressed entry
jstands for absolute positionj * ratio, nott_k - j.Inert today only because
flash_sinks_okrejectshead_dim = 512, the head dimevery V4 layer uses. PR #94 adds 512-wide kernels, which arms it — silently,
because the fused path takes no mask that could disagree with it.
Length coverage.
v4_long_prompt_prefill_and_decode_sweepprefills and thendecodes through the real V4 dispatch at 127/129/511/513/1400 tokens, bracketing
the 128-token window, the CSA/HCA block boundaries, and
CACHE_GROW_SIZE(512).CPU unit test, labelled as such — not a GPU claim.
Both new tests are mutation-checked: inverting
sdpa_sliding_windowfails thefirst; pinning
XsRollingCache's row ceiling to its initial 64 fails the secondat T=511 and only there, which is what makes the sweep length-selective rather
than a smoke test.
Does any finding explain the long-prompt death?
No. All six are eliminated, and the mechanism is throughput, not correctness.
The measurement (FACTS, session 8): 9 words → 100.15 s for 8 tokens; 198 words →
386.15 s for 8 tokens; ~1,055 words → connection refused.
Elimination:
clamp and the RoPE table are identical at 12 tokens and at 1,400.
b_sz > 1; a single canary request isb = 1, and theroute is closed regardless.
direct test: prefill + decode at 1,400 tokens through the real dispatch is
clean and finite. The shape/mask/position/compressed-KV class — a mask built
wrong, a position index that mis-bases, a compressed-KV path that only holds
short — is ruled out on CPU.
step_catching_panicsturns an engine panic intoa failed batch, and
handle_pipeline_forward_error!turns anErrinto afailed request. Neither closes the socket. "Connection refused" means the
process left.
What is left is cost. V4 has no fused attention kernel at
head_dim = 512(
flash_sinks_ok={64,80,96,112,128,192,256}), so every prefill layer runsunfused through
sinks_attn_cpu, whichrepeat_kv-expands K and V to all 64heads with a real
Tensor::cat(attention/mod.rs:98) and materialises the fullscore matrix. At the real geometry, per layer, bf16, CSA (
n_keys = 1.25·T):repeat_kvK+V:2 · 64 · 1.25T · 512 · 2 B=163,840·Tbytes2 · 64 · 1.25T² · 2 B=320·T²bytes⇒ 71 MB/layer at T = 280, 856 MB/layer at T = 1400, 5.6 GB/layer at T = 4096,
against 43 layers of arithmetic that is
7.05e6·T²FLOP for the stack.Fitting the two OK cells to
t = a + b·T²(≈1.33 tok/word) givesa ≈ 96 s,b ≈ 4.3e-3 s/token²— and predicts ≈ 8,500 s (2.4 h) for the 1,055-wordprompt. A request that never returns is indistinguishable from a dead server to
a canary, and the transient working set is an order of magnitude larger at the
same time.
Stated honestly: two points fit a two-parameter curve exactly, so this is
consistent with quadratic prefill, not a measurement of it. The
a ≈ 96 slength-independent term is separately corroborated — it is 8 decode steps of the
unfused decode path plus the CPU sampler fallback already logged in FACTS
(
tensor_device_ptr: unsupported dtype I32, ~10/sec), which another agent owns.That fallback is additive to
aand does not touchb, so fixing it will notmove the long-prompt cell.
⇒ Task #22 needs a separate owner, and the owner is the kernel workstream, not
the audit. The two things that would settle it on a box, in order:
quadratic rather than assuming it, and separate
afromb;nvidia-smipeak reserved during the 1,055-word attempt, to say whether itdied of the clock or of memory.
Both fit inside any rental already up.
Also surfaced, not shipped
deepseek4.rs,has_graph_mode_positions())still passes
raw_prefix: 0while reading a fixedcap = sliding_window-widerolling window, so
q0is pinned atcap - 1and the compressed-blockthreshold freezes at
floor(128/ratio)— the distant-context branch stopsgrowing past token 128. This is the second half of audit §1(e) note 2. Not
fixed here: the correct
raw_prefixis a host value and the position isdeliberately device-side, so it needs a design decision (plumb the host
position, or refuse compressed layers on the graph arm), not a patch. It is
behind a double opt-in (
ARC_V4_CAPTURE_PROBE+ARC_CANDLE_ALLOC_CACHE) andis not on the serving path.
MtpSpeculativePipeline::extend_draft_kvis dead (extend_draft_kv_rowis thelive one). Pre-existing.
🤖 Generated with Claude Code