Repository navigation
The 794 ms decode step, itemised: lm_head's 244x, the duplicate K/V, and the 91 ms named - #202
Merged
Merged
Conversation
Code Metrics Report━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Language Files Lines Code Comments Blanks ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ C Header 5 305 210 52 43 CSS 2 1181 1036 34 111 CUDA 78 27867 19381 5568 2918 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 147 15371 12677 824 1870 Shell 42 10258 6849 2734 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 204 45330 0 35183 10147 |- BASH 72 1655 1203 331 121 |- C 3 17 17 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 66 2051 1716 77 258 |- TOML 6 207 164 0 43 |- YAML 5 41 36 5 0 (Total) 51102 4688 35725 10689 ───────────────────────────────────────────────────────────────────────────────── Rust 678 336264 289507 17400 29357 |- Markdown 500 30663 471 26449 3743 (Total) 366927 289978 43849 33100 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Total 1337 504511 358274 93126 53111 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
…ations ungated
`doc_citations.rs` says, in its own header, that it verifies the file-and-line
evidence a reader is expected to follow. It scanned `SCAN_ROOTS = ["memory",
"arc-tools"]` and filtered to `.md`. So for its whole life it checked the
mission record and never once opened a source file — while ~310 citations sat
in Rust doc comments, pointing into the fastest-moving code in the repo,
completely ungated. Its own doc named the fix: "Adding a root here is a
one-line change plus whatever waivers it brings."
This is that change, plus the two things it turned out to need.
* `SCAN_ROOTS` gains the first-party crates; `SCANNED_DOC_EXTS` gains `.rs`.
Census 972 -> 1,244 citations. `CITATION_FLOOR` 700 -> 1,100, above the
`.md`-only total on purpose: a scope guard has to notice scope *shrinking*,
which is exactly how the Rust corpus stayed invisible.
* **A `.rs` line cannot be scanned whole.** Citation-shaped text lives in
string literals — this file's own BASELINE is a table of them, and its
fixtures build citations that are deliberately wrong. Scanning those
manufactures `SymbolRot`, which has no waiver path, so the gate would have
been unlandable against its own source. `split_rust` lexes comments out:
nested `/* */`, `\`-continued and multi-line raw strings, `'x'` char
literals kept distinct from `'a` lifetimes. A per-line scanner is not
enough and this is not theoretical — the first fixture written here was a
multi-line raw string, and a per-line scanner reported every deliberately
broken citation in it as real.
* **Widening silently disarmed the one unwaivable verdict.** Once `.rs` files
were documents, `symbol_exists_anywhere` started finding a cited symbol in
the very comment that cited it. Every `SymbolRot` degraded to the waivable
`SymbolMoved`, for the entire new corpus, and nothing would have said so.
The symbol search now reads comment-free code — the same rule `.md` already
gets from `CODE_EXTS`: a symbol surviving only in prose is the rot.
Two new tests, because a capability nobody proves is a capability nobody has:
`rust_comments_are_scanned_and_code_is_not` pins the exact citations extracted
from a fixture holding an escaped quote, a raw string containing `//`, and a
lifetime; `rust_comment_citations_are_gated` proves a dead symbol cited from a
Rust comment still hard-fails.
Result: 0 live findings. The 130 that surfaced are all UNRESOLVED-PATH into
upstream trees Arc reads but does not vendor — sglang, vLLM, TensorRT-LLM,
DeepSeek's `inference/`, the QTIP reference, candle, cudarc — pinned as 79
BASELINE rows (one per doc/path, generated from the run, not hand-guessed).
**No SymbolRot accompanied the widening**: all ~310 Rust-comment citations name
symbols that still exist. Two self-inflicted findings were fixed rather than
waived, since this file's prose about citations is now itself scanned.
cargo test -p mistralrs-core --test doc_citations: 12 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
…ten rank-3 in UnquantLinear V4's `lm_head` is 67 ms/step at B=256, 8.41% of a 794 ms decode step, in a single launch. Its bound is `max(271 GFLOP / 989 TFLOP/s, 1.06 GB / 4.8 TB/s) = 0.27 ms`. That is **244x over, and 244 ~= B = 256**, which is the entire explanation: back-solve the traffic and it is 256 x 1.06 GB = 271 GB per step at 4.05 TB/s — the kernel is running at 84% of peak HBM bandwidth while doing 256x the necessary reads. It is not a slow kernel. It is a correct kernel being asked for the wrong work. Every activation in this engine is rank-3 `[B, T, hidden]`. `UnquantLinear:: forward` matched on that and took `w.broadcast_left(B)` — a stride-0 view — after which both dispatch arms treat `B` as a GEMM batch dimension with `stride_b = 0`. At decode `T = 1`, so it is B independent `m = 1` GEMVs over one weight: a batched GEMV wearing a GEMM's name. The 1.06 GB weight does not fit in L2, so there is no reuse across rows to save it. Flattening `[B, T, hidden]` to `[B*T, hidden]` makes it one `[256, 4096] x [4096, 129280]` GEMM: weight streamed once, `m = 256` instead of `m = 1`. It also drops the trailing `.t()` the cuBLASLt arm returned, so the result is contiguous for whatever reads it next. Why here and not at the call site: 445b920 fixed exactly this by flattening inside `BlockwiseFP8Linear` before delegating. That covers activations routed through blockwise FP8, and `lm_head` is not one — DeepSeek ships `lm_head.weight` in BF16 with no `weight_scale_inv`, so `blockwise_fp8::linear_no_bias` short-circuits straight to `UnquantLinear`. The defect is in `UnquantLinear`; fixing it there is the general case and subsumes the caller-side workaround. Every other `bias: None` rank-3 linear that reaches this method is fixed by the same three lines. One latent arm had to be closed on the way. The bias-on-CUDA branch called `cublaslt.batch_matmul` unconditionally, and `batch_matmul` calls `dims3()` on both operands — so a 2-D activation errored instead of falling back. That was unreachable-by-accident only because every caller passed rank 3. It now carries the same `rank() >= 3` guard the no-bias arm already had. The GEMV pre-check deliberately stays ahead of the flatten. `should_use_gemv` reads `product(dims[..rank-1])`, which flattening does not change, so the ordering is belt-and-braces rather than load-bearing — but it makes that a fact instead of a coincidence. **UNVERIFIED ON HARDWARE.** The 67 ms is measured (CUDA-event `device_span`); the bound, the 244x and the 271 GB are derived from shapes and H200 peaks. No speedup is claimed here — only that the weight is read once instead of 256 times. Three CPU tests pin what the change owes: a rank-3 forward must agree with its own flattened 2-D result element-for-element across four `[B, T]` splittings, with and without bias; rank 4 must keep its leading dims; and a non-contiguous rank-3 input must still agree, since `reshape` is the only thing between a transposed view and `matmul`. cargo test -p mistralrs-quant --lib: 331 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
… that loaded on CPU
`arc validate --target-hbm` measures residency as
`used_bytes = free_before.saturating_sub(free_after)`. That expression is `0`
whenever the model did not become resident — loaded on the CPU with no device
map, snapshots straddling a CUDA driver state reset, or free memory simply not
going down. The verdict was `pass = total_gb <= target`, so zero gave
`0.0 <= 60.0` — **true**. The tool printed `result: PASS`, wrote `"pass": true`
into the JSON the rental step reads, and exited `0`.
The zero case was already detected. It pushed a free-form `note`. A note
changes no exit code and nothing in CI reads one.
A gate that cannot tell "it fits" from "we did not look" is worse than no gate:
it manufactures the exact number a buying decision rests on. Three changes:
* `hbm_verdict` returns `(pass, unmeasured)`, and `pass` is `false` whenever
the delta is zero.
* `HbmReport` carries `unmeasured` (`#[serde(default)]`, so reports written
before this field still parse). The printed verdict gains a third line.
* `exit_code` is three-valued: **0 fits, 1 measured-and-does-not-fit, 2 we do
not know**. `2` is reserved for the environment and never `1`, matching
what `main.rs` already exits with for a bad `--compression-stack` or a load
error — so callers need no new rule: non-zero-and-not-1 means look at the
box, not the model. Collapsing "unmeasured" into `1` would read as "V4 does
not fit", the opposite error to the one this code used to make and just as
wrong.
Both new functions live **outside** `#[cfg(feature = "cuda")]`, and that is the
structural half of the fix. The defect sat in CUDA-gated code that no CPU CI
job compiles, let alone runs, so a test next to the bug would have been exactly
as unreachable as the bug. Moving the decision into always-compiled code is
what lets `zero_hbm_delta_is_unmeasured_not_a_pass` go red on a laptop.
Four tests: the zero case is not a pass; a real 40 GiB still passes and a real
80 GiB still fails, so the check did not swallow the measurement it guards; the
exit mapping stays three-valued with `unmeasured` dominating `pass`; and a
legacy JSON report without the new field still parses.
cargo test -p arc-cli --bins validate: 15 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
…y else could read
Rust comments across `mistralrs-core`, `mistralrs-quant` and
`mistralrs-core/src/paged_attention` cite seven mission documents as the
evidence behind load-bearing decisions. None of them was in the repository.
They were sitting untracked in one laptop's `~/.claude/projects/...` directory
— not committed, not gitignored, just absent. `memory/mission/` already tracks
47 files, so nothing was stopping them; they were never added.
`CEILINGS.json` is the file this project's own rules order an agent to read
before quoting any speed number or saying something is not achievable. Anyone
cloning this repo has been unable to read it.
Committed at the cited paths, so no citation had to be rewritten to reach them:
* `CEILINGS.json` — physics vs implementation bounds, cited from
`mtp_pipeline.rs` and `qtip_grouped_curve.rs`
* `BUDGET_V4_PREFILL.md` — cited from `inputs_processor.rs`, `gather_policy.rs`
* `BUDGET_V4_B1.md` — cited from `00_RESUME_HERE.md`, `COMPETITIVE_TEARDOWN.md`
* `EXTERNAL_FINDINGS.md` — cited twice from `mtp_pipeline.rs`
* `wave13-AH-config.md` — cited from `deepseek4.rs:7517`
* `wave16-AF-beam-perf.md` — cited from `qtip/search_bench.rs:743`
* `wave45-BW-mtp-batched.md` — see below
The seventh citation was rot, not absence. `segmented_allocator.rs:15` cited
`memory/mission/wave59-CJ-mtp-batch.md` §3, and no such file exists anywhere —
not in the repo, not on the laptop, not in any worktree. The claim it carries
is real: it is in `wave45-BW-mtp-batched.md` **§2**, which states the shared
cache rolls back to `C + min_i(u_i + accepted_i)`, "the one length every
sequence can prove". So the pointer was wrong twice over — wrong file, wrong
section — and pointed at a claim that does exist. Both fixed, and the quoted
formula now matches the document verbatim.
Worth naming, because this session's other commit widened the citation gate and
it did **not** catch this one: the gate matches `path.ext:N`, and all seven of
these are cited by bare filename or with a `§` section. A path citation with no
line number is invisible to it. That is a real hole in the widening and a
follow-up, not something to fix by loosening a rule that currently has zero
false positives.
The seven docs bring 17 citations of their own into scope, all into sglang,
CUDA headers and DeepSeek configs that Arc reads and does not vendor; pinned in
`BASELINE`. Census 1,244 -> 1,323, still 0 live findings.
cargo test -p mistralrs-core --test doc_citations: 12 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
The doc records, as evidence for "the GPU sampler is referenced nowhere", that searching for `sampl` returns zero hits. The search string is the evidence, so it cannot be spelled "sample". Waived with the backticks included, so this covers the quoted pattern and nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
…a time `make_completion_chunk` built one `Tensor::new(.., device)` per sequence and `Tensor::cat`-ed them. On CUDA that is B separate **1-element** H2D transfers, each a pageable copy on the legacy default stream and therefore a full host/device round trip, plus a B-argument `cat`, which candle services with one `copy2d` per argument. At B=256: **256 synchronous transfers and 256 copy kernels to assemble a `[256, 1]` tensor holding one kilobyte.** The call site already carried the note — "CLAUDE.md pitfall #5, still live" — and a `sync_span` named `input_prep.h2d_per_seq` measuring it. It was named, instrumented, and left running. Every decode row is exactly one token, so the cohort is a rectangle. Rows now stay on the host and `stack_uniform_rows` sends the whole thing in one transfer: **256 round trips and 256 launches become 1 and 0.** Ragged input is **refused, not reshaped**. The dangerous case is the one that still divides evenly — rows of 1, 2 and 3 tokens hold 6 elements, which reshapes cleanly to `[3, 2]` and hands sequence 0 a token belonging to sequence 1. The shape would be right and only the contents wrong, which no downstream assertion can see. It errors and names the offending row. Scoped deliberately to decode. The prefill path has the same shape at `make_prompt_chunk`, but its rows are padded by branching logic with cache-hit and chunking cases, its payload is large enough that the per-row round trip is a smaller share, and a hard error on a ragged batch there would turn working behaviour into a failure. Named, not touched. **UNVERIFIED ON HARDWARE.** The launch and transfer counts are structural — one per sequence, B sequences. No wall-clock claim. Three tests, on a seam extracted so they need no `Sequence` fixture: the single-transfer form is byte-identical to the per-row `cat` it replaces, on the `[B, 1]` decode shape and on a wider rectangle; a ragged cohort whose element count divides evenly is refused and the error names the row; an empty batch is an error rather than a `[0, 0]` tensor. cargo test -p mistralrs-core --lib: 652 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
…le, and it moved
CI caught what local `--lib` runs could not: the flatten broke
`v4_xs_history_two_seq_batch_matches_single_sequence`
(`synthetic_load_smoke.rs`) on ubuntu, and only on ubuntu.
It is not a correctness bug and it is worth being precise about why, because
the test's own message says "the xs-history / KV state of one sequence leaked
into the other". It did not. Measured: max abs diff 0.0625 against a tolerance
of 0.0399 — **1.6x over**, where that test documents a real leak as O(1),
roughly **100x** over. This is floating-point associativity.
The mechanism, established by probe rather than guessed. `MatMul::matmul` casts
to F16 on CPU. Before the flatten, `broadcast_left` made the batched case B
separate `m = 1` GEMMs — the identical shape, and therefore the identical
accumulation order, as the solo run it is compared against. So batched-vs-solo
was **bit identical**: instrumenting `v4c_assert_close` showed `max_diff`
exactly 0 on every comparison. The 1e-2 tolerance had never been exercised in
its life. Flattening makes the batched case `m = B`, and x86's F16 microkernel
blocks `m = 2` differently from `m = 1`; aarch64 happens not to, which is why
macOS stayed green and ubuntu did not.
The fix is not to widen the tolerance. Widening a leak detector to admit a
change made in the same PR is how a gate stops being a gate, and this one is
load-bearing — the whole V4 batched-vs-solo suite is calibrated against CPU bit
identity.
So the flatten is now device-gated, and the boundary is the same one the
derivation always had: **the defect is HBM traffic** — a stride-0 batched GEMM
streaming a 1.06 GB weight once per batch row. There is no HBM on the CPU path,
`MatMul::matmul` throws away precision there anyway, and CPU exists in this
workspace to be a reference oracle, not a fast path. The fix lands where the
271 GB/step is; the oracle keeps its arithmetic.
Tests restructured so none of them is now vacuous — the point of the gate is
that CPU takes the old path, so a test asserting the new one on CPU would prove
nothing:
* `two_d_and_rank3_agree_within_the_cpu_f16_budget` — the mathematical claim,
across four `[B, T]` splittings with and without bias, inside an F16 budget
rather than at bit equality. The gap it tolerates is the gap that motivates
the device gate; a wrong broadcast or a transposed weight is O(1) and still
fails it.
* `cpu_forward_still_takes_the_broadcast_path_bit_for_bit` — the oracle's
arithmetic is pinned against the pre-change formulation spelled out
literally, at zero tolerance.
* `flatten_batch_dims_*` — the shape arithmetic the device path depends on,
extracted into a named function so it is checkable on a host without a GPU.
Rank-3 and rank-4 collapse; a non-contiguous activation round-trips through
the strided copy `reshape` performs. Shape logic that only executes on
hardware nobody has is shape logic nobody checks.
cargo test -p mistralrs-quant --lib: 332 passed.
cargo test -p mistralrs-core (all targets): 652 + 11 + 12 + 7 + 17 + 2 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
…est helper `clippy::needless_question_mark` on `max_abs_diff`. My local run of the scoped lane predated this helper, so it went out unchecked — the lane itself was right, I ran it too early. Also noting drift for whoever maintains CLAUDE.md: the scoped lane documented there omits `-p arc-profiler`, which the real `ci.yml` Clippy job includes. Running the documented command is therefore not the same as running the gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMmjFy8TvsgypxVWNVhhC7
heydryft
force-pushed
the
agent/decode-remainder-200ms
branch
from
August 21, 2026 05:54
3194890 to
5e12594
Compare
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.
The 794 ms decode step, itemised — and the 91 ms nobody had opened
Two peers own the two biggest kernels (
fp8_matmul_tiled524 ms,qtip2b_grouped_gemm69 ms). This PR takes apart everything else: the 67 mslm_head, the 43 ms copy storm, and the 91 ms that had never been examined. Plus four correctness defects, three of which are gates that could not fail.Nothing here is measured on hardware. Every number below is derived from V4-Flash's shipped
config.jsonagainst H200 peaks (4.8 TB/s, 989 TFLOP/s dense BF16, 67 TFLOP/s FP32 scalar). No speedup is claimed anywhere. Where a derivation cannot close, it is reported as not closing.The budget
Ground truth: 43 layers (2 Standard, 21 CSA/ratio-4, 20 HCA/ratio-128), hidden 4096, 64 heads × head_dim 512, 1 KV head, 256 routed experts / top-6, vocab 129,280,
first_k_dense_replace=0so every layer is MoE. Context-dependent rows are given at C=2048.fp8_matmul_tiledqtip2b_grouped_gemmlm_headucopy_bf16+copy2d_bf16)lm_head— 67 ms, and244 ≈ B = 256is the whole answerBound:
max(2·256·4096·129280 / 989 TFLOP/s, 4096·129280·2 B / 4.8 TB/s) = max(0.274, 0.221) = 0.274 ms. Measured 67 ms is 244× over. Back-solving the traffic:256 × 1.06 GB = 271 GBper step,271 GB / 67 ms = 4.05 TB/s= 84% of peak HBM bandwidth. The kernel is not slow. It is streaming the weight once per batch row.Cause: every activation is rank-3
[B, T, hidden].UnquantLinear::forwardtookw.broadcast_left(B)— a stride-0 view — and both dispatch arms then treatedBas a GEMM batch dim withstride_b = 0. At decodeT = 1that is B independentm = 1GEMVs over one 1.06 GB weight that does not fit in L2. Fixed by flattening to[B*T, hidden].The same defect was fixed one level up in
BlockwiseFP8Linearby 445b920. That did not coverlm_head, because DeepSeek shipslm_head.weightin BF16 with noweight_scale_inv, soblockwise_fp8::linear_no_biasshort-circuits straight toUnquantLinear. The defect is inUnquantLinear; fixing it there is the general case and covers everybias: Nonerank-3 linear.CPU is deliberately excluded, and the reason is worth reading. The first push broke
v4_xs_history_two_seq_batch_matches_single_sequenceon ubuntu and nowhere else. Not a leak: max diff 0.0625 against a 0.0399 tolerance — 1.6× over, where that test documents a real leak as O(1), ~100× over. Probing it:MatMul::matmulcasts to F16 on CPU, and before the flattenbroadcast_leftmade the batched case B separatem = 1GEMMs — the same shape, so the same accumulation order, as the solo run it is compared against. Batched-vs-solo was bit identical,max_diffexactly 0; that 1e-2 tolerance had never been exercised in its life. Flattening makes itm = B, and x86's F16 microkernel blocksm = 2differently fromm = 1(aarch64 does not, which is why macOS stayed green).The fix was not to widen the tolerance — widening a leak detector to admit a change made in the same PR is how a gate stops being a gate. The flatten is device-gated instead, on the boundary the derivation always had: the defect is HBM traffic, there is no HBM on the CPU path, and CPU exists here to be a reference oracle the whole V4 batched-vs-solo suite is calibrated against. The fix lands where the 271 GB/step is; the oracle keeps its arithmetic.
The copy storm — 22,794 factorises exactly
84 = 43 KV + 41 xs: V4 appends oneXsRollingcompressor-history slot per CSA/HCA layer after the KV entries, andNormalCacheManagerderives its loop bound from the cache vector's length. So 94.3% of the strided copies are the per-sequence cache scatter/gather — one stridednarrow().contiguous()per sequence per cache slot per step. A second exact factorisation,256 × (2×43 + 3) + 10, lands on the same family.That is a layout problem, not a kernel problem, and the fix is architectural (a persistent batched cache with per-row indices — the design
paged_attention/already has and V4 opted out of viaKvCacheLayout::Standard). Scoped, not attempted here. What is fixed here is a pure duplication in the same bucket: V4 is MQA anddeepseek4.rscallsdsv4_attention(&q, &k_cached, &k_cached, …), so the raw-window narrow and the compressedcatwere each done twice over identical bytes — 24.2 GB/step for the concat pair at C=2048, half of it duplicate, plus 5.8 GB/step for the narrow pair.The 91 ms — named, with the residual stated honestly
Bytes cannot explain it. 91 ms at 4.8 TB/s is 437 GB; every un-owned kernel in the step touches 36 GB in total. Bandwidth/FLOP work in un-owned kernels is 7.5 ms at C=2048 (11.7 ms at C=8192). So the 91 ms is launch count, and the arithmetic is
47,120 un-owned launches × floor:I can name 57–91 of the 91 ms, and at the repo's own on-stream floor the arithmetic overshoots. That is not forced to fit. What does not depend on the floor:
~114,200 kernel launches per step, and 110,080 of them — 96.4% — come from two
forloops over the 256 sequences, each running once per layer per step:layers.rsDeepSeekV2RotaryEmbedding::forward— 66,048 launches/steplayers.rsforward_inverse_tail— 44,032 launches/stepThey move 0.8 GB total (0.17 ms of bandwidth). They are ~100% overhead by construction. The trigger is one condition:
seqlen_offsets.len()is 256 at B=256, not 1, so three sites take their per-sequence branch — includingdeepseek4.rs's fusedqk_norm_ropekernel, which declines outright withdecline!("per-sequence position offsets")and hands the work to a 1,548-launch-per-layer eager path.66,048 of those launches are copies and therefore sit inside the already-named 43 ms bucket, which independently implies an effective per-copy-launch cost of ~0.4–0.6 µs once the ~15.7 ms of genuine copy bandwidth is subtracted. Either the effective floor is ~1.8 µs, or the 43 ms copy bucket is understated. Both readings point at the same two loops, and the remedy is the same either way, so the ambiguity does not need resolving to act.
PR #198 is the fix for these two loops, and it is sitting unmerged. It replaces
seqlen_offsets.len() == 1— a test of length — withuniform_offset(), a test of values, in bothforwardandforward_inverse_tail. That collapses 99,072 launches/step to 301.But note the boundary the 91 ms analysis exposes:
uniform_offsetreturnsNonethe moment two sequences sit at different positions ([7, 7, 8]→None). A uniform cohort is what the default scheduler admits, so #198 is the right fix for that case — but in a serving mix with staggered arrivals the offsets are not uniform and the 110,080-launch loop fires again. The general fix is oneindex_selectof the cos/sin tables by a[B]position tensor followed by a single batchedrope_i, which works for any offsets; the machinery already exists a few functions down inforward_at_positions. That is a follow-up on top of #198, not a criticism of it.Not double-counted: #198 removes
eager_qk's data movement, which is disjoint from the cache-manager copies that make up 94.3% of the strided copies.Also un-owned, and not GPU time: 172 pageable H2D transfers per step from
Tensor::arange/Tensor::fullinside thedsv4_attentionmask build, plus 256 one-element H2D round trips ininputs_processor.rs. These serialise the host and make the decode forward uncapturable.The second of those is fixed here.
make_completion_chunkbuilt oneTensor::new(.., device)per sequence andcat-ed them — B pageable 1-element H2D transfers on the legacy default stream, each a full round trip, plus onecopy2dpercatargument. At B=256 that is 256 synchronous transfers and 256 copy kernels to assemble a[256, 1]tensor holding one kilobyte. It was already named in-file as "CLAUDE.md pitfall #5, still live" and already instrumented with its ownsync_span— named, measured, and left running. Rows now stay on the host and cross once: 256 round trips and 256 launches become 1 and 0. Ragged input is refused rather than reshaped, because the dangerous case divides evenly — rows of 1, 2 and 3 tokens reshape cleanly to[3, 2]and hand sequence 0 a token belonging to sequence 1, with a correct shape and somebody else's contents.Ruled out by reading: the C4 indexer is built and never called — zero use sites in
forward; it costs HBM, not time. Allv4_*_dbghooks areenv::var_osearly-returns. The MoE per-expert loop emitsindex_select/index_add, notucopy_bf16—43 × 256 = 11,008is a red herring.Top remaining un-owned items, all scoped and none attempted here: (1) the two per-sequence RoPE loops beyond #198's uniform case; (2) re-enabling the fused
qk_norm_ropekernel above batch 1 by taking a per-row[B]position tensor instead of a scalar; (3) the MoE router GEMM runs in FP32 on CUDA cores — 23.1 GFLOP/step at 67 TFLOP/s, ~15× the cost of the same GEMM on tensor cores, plus a 0.27 GB activation cast to feed it (a peer has a branch on this); (4)V4MHCHead::forwardis the one mHC site never fused — 15 launches where the structurally identicalhc_prenext door is 1.Correctness
The citation gate had never opened a
.rsfile.doc_citations.rsclaims in its own header to verify that sentences describing the code are true. It scanned["memory", "arc-tools"]filtered to.md— so it checked the mission record and never once opened a source file, while ~310 citations sat in Rust doc comments pointing into the fastest-moving code in the repo. Widening it needed two things the one-line change did not anticipate:.rsline cannot be scanned whole. Citation-shaped text lives in string literals — this file's ownBASELINEis a table of them and its fixtures build citations that are deliberately wrong. Scanning those manufacturesSymbolRot, which has no waiver path, so the gate would have been unlandable against its own source.split_rustlexes comments out: nested/* */,\-continued and multi-line raw strings,'x'char literals kept distinct from'alifetimes. A per-line scanner is not enough — the first fixture written here was a multi-line raw string, and a per-line scanner reported every deliberately-broken citation in it as real..rsfiles were documents,symbol_exists_anywherestarted finding a cited symbol in the comment that cited it. EverySymbolRotdegraded to the waivableSymbolMoved, for the whole new corpus, and nothing would have said so. The symbol search now reads comment-free code — the same rule.mdalready gets.Census 972 → 1,323 citations, 0 live findings.
CITATION_FLOOR700 → 1,100, deliberately above the.md-only total so a scope shrink fails. The 130 findings that surfaced are all unresolved paths into upstream trees Arc reads and does not vendor, pinned as generatedBASELINErows. NoSymbolRotaccompanied the widening — all ~310 Rust-comment citations name symbols that still exist.The rental gate certified "V4 fits in 60 GB" from a run that loaded on CPU.
used_bytes = free_before.saturating_sub(free_after)is0when nothing became resident; the verdict wastotal_gb <= target, i.e.0.0 <= 60.0, i.e. true. It printedPASS, wrote"pass": trueinto the JSON the rental step reads, and exited 0. The zero case was detected — it pushed a free-form note, which changes no exit code and which nothing reads. Now three-valued: 0 fits, 1 measured-and-does-not-fit, 2 we do not know. Both new functions live outside#[cfg(feature = "cuda")]on purpose — the defect sat in CUDA-gated code no CPU CI job compiles, so a test beside the bug would have been as unreachable as the bug.Seven cited mission docs existed only on one laptop. Untracked, not gitignored, just never added — including
CEILINGS.json, the file this project's rules order an agent to read before quoting any speed number. Committed at their cited paths. The seventh citation was rot, not absence:segmented_allocator.rscitedwave59-CJ-mtp-batch.md§3, which exists nowhere; the claim is real and is inwave45-BW-mtp-batched.md§2. Both fixed, and the quoted formula now matches the document verbatim.Worth naming: the widened gate did not catch these seven. It matches
path.ext:N, and all of them are cited by bare filename or§. That is a real hole in the widening and a follow-up — not a reason to loosen a rule that currently has zero false positives.PR #199 (the attention mask fix) is shepherded, not merged here. Verified against master: merges cleanly, 0 conflicts, 2 files, all 18 checks green.
mergeStateStatus: BLOCKEDisREVIEW_REQUIREDonly. It needs a human approval — nothing else.This PR targets
master, not #194. It was opened againstrelease/openrouter-readyand CI'sBase branchlane rejected it by design: "This PR targets '…', not 'master'. Its lanes compile the head merged into another PR's branch, which is not the tree that will land." Retargeted. Worth flagging that #195 and #198 are red for exactly this reason — their only failing lanes areBase branchand theCI completeroll-up that depends on it. The two loops #198 fixes are 96.4% of the step's kernel launches, and the PR is failing on its base ref, not its content.What has a real test, and what does not
UnquantLinearflatten[B,T]splittings ± bias inside an F16 budget (the gap that motivates the device gate); the CPU oracle is pinned bit-for-bit against the pre-change formulation; the extracted shape arithmetic round-trips rank-3, rank-4 and a non-contiguous inputis_same_tensoris identity not equalitycat, on[B,1]and a wider rectangle; a ragged cohort that divides evenly is refused and the error names the row; empty batch errors//, and a lifetime; a dead symbol in a Rust comment still hard-failscargo test -p mistralrs-core(all targets) 652 + 11 + 12 + 7 + 17 + 2 passed ·-p mistralrs-quant --lib332 passed ·-p arc-cli --bins validate15 passed · scoped clippy lane green ·cargo check --workspace --testsclean · formatting scoped to touched files only.