diff --git a/.agents/claims/CLAIM-MODEL-MM-QWEN4-EXP.md b/.agents/claims/CLAIM-MODEL-MM-QWEN4-EXP.md new file mode 100644 index 000000000..e750d8ea1 --- /dev/null +++ b/.agents/claims/CLAIM-MODEL-MM-QWEN4-EXP.md @@ -0,0 +1,5 @@ +# CLAIM-MODEL-MM-QWEN4-EXP + +| Claim | Row IDs | Agent | Worktree / remote dir | Branch | Owned scope | State | Last update | +|---|---|---|---|---|---|---|---| +| `CLAIM-MODEL-MM-QWEN4-EXP` | `MODEL-MM-qwen4-exp-qwen4-exp-for-conditional-generation` (`ACTIVE`) | Campaign label `MODEL-MM-QWEN4-EXP` (branch and issue naming only, not a matrix row). Claude Code (opus-5), coordinating session; every wave is written by a fresh implementer and reviewed by a different agent, and the coordinator repairs no finding itself | one linked worktree per wave under `/home/mudler/_git/vllm.cpp-q4w*`; CPU only, no GPU lease taken | `row/MODEL-MM-QWEN4-EXP-W*`, row issue [#1978](https://github.com/mudler/vllm.cpp/issues/1978) | Owns ONLY: `.agents/specs/qwen4-exp-flash-next.md`; this matrix row and the rollup counts it moves; the `qwen4_exp` model TUs (`qwen4_exp{,_ple,_hc,_qsa,_registry,_gguf_weights}.*`) and their tests; the `qwen4exp` GGUF arch row in `kGgufArchArms`; and the IQ4_NL / Q5_0 reader, dtype, dequant, traits and gather surfaces W6a added. EXCLUDES the shared residency POLICY beyond the `kEmbeddingTable` narrowing W6a landed, EXCLUDES every other model's loader except the two `NoKeepQuant` call sites the same wave was obliged to repair, and EXCLUDES any speed claim until G4 opens | `ACTIVE` | 2026-08-26 — W6a (#1989) is the first wave to reach `main`: IQ4_NL (20) and Q5_0 (6) in the reader with geometry from the pinned llama.cpp oracle at `b10451` and decoders gated bit-exactly against its compiled output on real file bytes, a dequantizing gather, and the `qwen4exp` config builder, so `unsloth/Qwen3.8-Flash-Next-GGUF` UD-IQ1_S (67.56 GiB, 3 shards) now OPENS. Its first fresh review returned FAIL on a BLOCKER — the shared keep-quant change reached one of three consumers and broke DeepSeek-V4 and Laguna GGUF loads on CPU, with the guarding assertion deleted in the same commit — repaired by a different implementer and PASSED on re-review. W1/W2/W3/W4 are implemented, reviewed, and repaired or repairing on their branches. NOTHING IS REACHABLE: no `ModelRegistry` entry resolves `Qwen4ExpForConditionalGeneration`, so every landed slice is unreached by design and named under the spec's `## Owed`. W5 (the assembled forward) is unfiled and owns the wiring; no token and no speed number exists | diff --git a/.agents/engine-matrix.md b/.agents/engine-matrix.md index 7d5e725ad..fe647bee0 100644 --- a/.agents/engine-matrix.md +++ b/.agents/engine-matrix.md @@ -171,8 +171,8 @@ lifecycle are unchanged. |---|---|---|---|---|---|---|---|---| | `SPEC-MTP` | Qwen3.6 MTP heads, k=1 first. **M-mtp-0 CLOSED 2026-07-24: the standalone draft head is oracle-parity-proven on BOTH checkpoints** (27B dense + 35B MoE, k=1, vLLM 0.25.0 executable @ pin `e24d1b24`) - argmax exact on 26/26 unambiguous rows each; the one remaining row per checkpoint is an EXACT oracle top1==top2 tie where vLLM's own `argmax` and `topk` disagree and our pick is a tied maximum; logits within the whole-model bound (atol 0.05 + rtol 0.05), 0/216 out-of-tol on both; shared lm_head isolated is bit-exact on the 35B NVFP4 head. **I2 scheduler-half LANDED (2026-07-24)**: host-side spec plumbing + the FROZEN spec-metadata ABI (spec §2.7) - `SpeculativeConfig`, `DraftTokenIds`, `Request::spec_token_ids`/`NumTokensWithSpec`, populated `scheduled_spec_decode_tokens`, `Scheduler::update_draft_token_ids`, `take_draft_token_ids` seam, `EngineCore::post_step`, `InputBatch::num_accepted_tokens`/`update_req_spec_token_ids`; DEFAULT-OFF and INERT (no `SpeculativeConfig` => `num_lookahead_tokens == 0`). **I3 verify-half LANDED (2026-07-24)**: greedy rejection sampler + per-request logits expansion (see `SPEC-REJECTION`, now `ACTIVE`). **I4 GDN-half LANDED (2026-07-24)**: the GDN speculative slot path + bit-exact state rollback, the piece BOTH GDN-hybrid gate checkpoints need (see `SPEC-GDN-SEGMENTS`, now `ACTIVE`). **I5a GDN LAYER ROUTING + runner spec-metadata upload LANDED (2026-07-24, `CLAIM-SPEC-MTP-I5A`)**: `GdnBlockPaged` now routes a pure-spec batch through `vt::GdnSpecDecode`/`vt::CausalConv1dSpecUpdate` and the runner uploads I4's six spec device tensors — first sub-increment of the scoped M-mtp-1 (I5a GDN wiring → I5b prepare_prefill → I5c MTP paged propose → I5d config+runner-loop+the 27B token gate, spec §5). DEFAULT-OFF INERT, bit-exact vs the I4 ops, no e2e loop yet. **I5b `prepare_prefill_inputs` LANDED (2026-07-24, `CLAIM-SPEC-MTP-I5B`, recorded under `SPEC-REJECTION`)**: the drafter prefill input-prep host routine (shift-splice + `query_len -= num_rejected` + last-token index / metadata) — second scoped M-mtp-1 sub-increment, DEFAULT-OFF INERT, unit-gated RED-first, additive. **I5d CONFIG + RUNNER LOOP LANDED, PARTIAL (2026-07-25, `CLAIM-SPEC-MTP-I5D`)**: `--speculative-config` JSON parse -> `EngineParams::speculative_config`; `LoadedEngine` resolution (`ResolveSpecConfig`/`ResolveMtp`, widened KV `MakeQwen3_5KVCacheSpec(num_spec>0)`, `BuildMtpDraft`, forced sync scheduling, `MakeScheduler(spec)`, `EngineCore(check_for_draft=true)`); the full runner verify/propose loop (draft splice, hidden-tap capture, GDN builder spec-overload feed, k+1 GDN state-slot remap + widened conv cache + draft-KV alloc, `MtpProposePrefill` post-sampling, `take_draft_token_ids`, acceptance telemetry). CUDA `-Werror` 0 warnings, cutlass-ON banner. SPEC-OFF BYTE-IDENTICAL (all gated on `spec_on()`): SACRED 27B 235/235, 35B 315/315, Coder 138/138 + unit test_runner 257 / test_mtp_speculator 169 / test_gdn_metadata_builder 483 / test_ops_gdn 3630 ALL PASS. **The three-way 27B token gate is NOT yet passing** (`tests/parity/test_qwen27_spec_decode.cpp` RUNS the loop + MEASURES the blocker): the spec-ON engine throws on the FIRST prefill step at `gdn_state_gather: working/cache row shapes must match` (`src/vt/ops.cpp:1773`) — I4's spec conv rollback needs the conv row widened to `(K-1)+num_spec` but the non-spec GDN conv ops assume `(K-1)`. Closing needs widened-cache-aware non-spec GDN conv ops + the MIXED `GdnBlockPaged` split/merge. Row LEFT `GATING` at I5e. **I5e LANDED 2026-07-25 (`CLAIM-SPEC-MTP-I5E`) — `SPEC-MTP` LEAVES `GATING`.** Made the non-spec GDN conv ops widened-cache-aware (mirror vLLM `state_len=KERNEL_WIDTH-1` + physical `stride_conv_state_tok`; leading `(K-1)` sub-window; byte-identical at `num_spec==0`, contiguous fast path kept) AND RCA'd the resulting 0-acceptance dead-drafter to the async input-combine overwriting the verify batch's draft position with the committed token (forced off under spec, nullopt-guarded). **THREE-WAY 27B GATE PASSES** (single-request greedy): our-ON == vLLM `--speculative-config mtp` greedy == our-OFF token-for-token; **acceptance 16/16 drafts accepted**, ~16 target steps saved. Spec-OFF SACRED byte-identical (27B 235/235, 35B 315/315, Coder 138/138), `test_ops_gdn` 3678, compute-sanitizer 0 on the spec step. NOT `DONE`: MIXED `GdnBlockPaged` split/merge (concurrency) + throughput A/B are I6. **I6 LANDED 2026-07-25 (`CLAIM-SPEC-MTP-I6`), `benchmark_binding=true` — the §5 c1 THROUGHPUT GATE, first spec-decode speed number:** OURS spec-ON (`examples/vllm-bench` + an additive `--speculative-config` flag, production config) vs pinned vLLM 0.25.0 spec-ON (graphed `vllm serve --speculative-config mtp` + `vllm bench serve`, `enforce_eager=False`/`FULL_AND_PIECEWISE`/inductor; MTP confirmed `Resolved architecture: Qwen3_5MTP`), SAME `{"method":"mtp","num_speculative_tokens":1}`, 27B `~/bench/q36-27b-nvfp4-vllm`, c1, greedy, 8 real prompts x 256 out, prose + code, idle box one-engine-at-a-time under one `flock`, 3 reps (cold TTFT discarded), token-identity re-confirmed FIRST (`test_qwen27_spec_decode` PASS 16/16). RESULT — **ours AT/ABOVE vLLM on EVERY measured axis** (prose / code): TPOT 66.2/62.95 vs 69.1/65.3 ms (ours ~1.04x faster), output tput 15.10/15.72 vs 14.43/15.13 tok/s (+4.6%/+3.9%), ITL 121.6/121.1 vs 123.2 ms, TTFT(warm) 131/131 vs 151.5/181 ms, acceptance ours 0.85/0.92 vs vLLM 0.838 overall (within noise, live drafter both), peak RSS 28.4 GB ON / 24.8 GB OFF (both inside the 119 GiB pool). Spec helps both (ours 1.52x/1.59x, vLLM 1.51x/1.60x TPOT); ours already ~4% faster spec-OFF. STAYS `ACTIVE`: the c>1 mixed spec+non-spec `GdnBlockPaged` split/merge is still refused (needs a row `IndexSelect`/`IndexCopy` vt op) + owes a c>1 A/B, and no user-facing supported `--speculative-config` on the OpenAI server yet (bench flag example-only/additive). Raw logs dgx `~/work/mtp-bench-i6/{results,vresults}`. **I7 LANDED 2026-07-25 (`CLAIM-SPEC-MTP-I7`, `benchmark_binding=true`) — the MIXED spec+non-spec GDN batch (concurrency), the server/CLI `--speculative-config`, and the c>1 A/B — implementation COMPLETE + at vLLM parity; STAYS `ACTIVE` for one honest reason (below), NOT a lag.** New row op `vt::IndexSelect`/`vt::IndexCopy` (CUDA==CPU bit-exact at GDN widths, RED-first); `GdnBlockPagedMixedSpec` split/merge (mirror `qwen_gdn_linear_attn.py:1329-1576`) proven MODEL-INDEPENDENTLY bit-exact (mixed == pure spec + pure prefill, 27B/35B, `test_qwen3_5_gdn_spec_routing`, RED-first by a broken merge); compute-sanitizer 0 on the mixed step + op; server (I5d) + CLI (ABI v6) `--speculative-config`. **c>1 A/B (both spec-ON, same config):** ours ON-PAR-OR-ABOVE vLLM at c2/c4/c8 (output tput within ~+/-2%, ours +1.6%/+2.5% c2, +0.9%/+1.7% c4, +0.9%/-1.1% c8 within noise, prose/code; both ~1.5x spec speedup — does NOT go neutral; acceptance 0.84-0.92 vs vLLM 0.835). **Why STAYS `ACTIVE` (honest, not a lag):** the DONE criterion's strict `token-exact at c>1` clause is a proven MODEL impossibility — the 27B greedy is bf16-batch-nondeterministic (spec-OFF max_seqs 4-vs-1 differs 2/3 short prompts, NO spec involved), affecting vLLM identically, so exact c>1 token identity cannot be met by any correct implementation; c>1 correctness is instead established by the model-independent bit-exact split/merge proof + acceptance parity (near-tie-distributional-gate), with token-exact strict at c1 (I6). No missing work, no lever — the DONE final call is deferred to the user given this criterion ambiguity. SACRED spec-OFF byte-identical 27B 235/235, 35B 315/315, Coder 138/138; CUDA `-Werror` 0 warnings. Raw logs dgx `~/work/mixed-batch/{cN_results,cN_vresults}`. **I8 — `SPEC-MTP` → `DONE` 2026-07-26 (`CLAIM-SPEC-MTP-DONE`, records-only, ZERO code):** the user RATIFIED the deferred c>1 criterion — at concurrency > 1 the DONE bar is the near-tie-distributional form (ours ∈ vLLM's batch-nondeterministic set) + the SPEED delta, NOT strict token-exact (a proven bf16-batch-nondeterminism MODEL impossibility that affects vLLM identically). Both I6-owed DONE items are therefore CLOSED: (1) the MIXED spec+non-spec `GdnBlockPaged` split/merge (I7, model-independently bit-exact + compute-sanitizer 0) with the c2-c8 A/B on-par-or-above vLLM, and (2) the server + CLI + C-ABI(v6) `--speculative-config` flag (I5d/I7, `examples/server/main.cpp`+`examples/cli/main.cpp`+`src/capi/vllm_c.cpp`). MTP k=1 spec-decode is COMPLETE and gated: 27B three-way token-exact at c1 (I5e), c1 above vLLM on every axis (I6), c2-c8 on-par-or-above (I7), spec-OFF byte-identical SACRED (27B 235/235, 35B 315/315, Coder 138/138). This transition is byte-identical BY CONSTRUCTION (`git diff --stat` = records only; ZERO `src/`/`include/`/`examples/` touched, so the I5d/I6/I7 GPU gates stand on this exact code). Tracked follow-ons: the 35B `Qwen3_5MoeMTP` full e2e token gate (M-mtp-2) is now **CLOSED — `DONE` 2026-07-26 (`CLAIM-SPEC-MTP-M-MTP-2`)**: three-way token-exact 16/16 vs the live vLLM 0.25.0 oracle (spec-ON AND spec-OFF), acceptance 16/16 both sides, c1 spec-ON 1.19x TPOT / +16.3% output-tput vs spec-OFF (0.908) — `MODEL-SPEC-qwen3-5-mtp-qwen3-5-moe-mtp` `GATING`→`DONE`, so MTP is `DONE` on BOTH gate models. Remaining spec-decode follow-on: `SPEC-DFLASH` (oracle-BLOCKED, vllm#40898) | T1 | `vllm/v1/worker/gpu/spec_decode/mtp/speculator.py:12`; `vllm/model_executor/models/qwen3_5_mtp.py:63,129-165,192-301`; **I5d** `vllm/engine/arg_utils.py` (`--speculative-config`); `vllm/v1/worker/gpu/model_runner.py:1455-1489` | `include/vllm/config/speculative.h`; `include/vllm/v1/core/sched/scheduler.h`; `src/vllm/v1/core/sched/scheduler.cpp`; `include/vllm/v1/worker/gpu/input_batch.h`; `include/vllm/model_executor/models/qwen3_5_mtp.h:23,58`; `src/vllm/model_executor/models/qwen3_5_mtp.cpp:271`; `src/vllm/model_executor/models/qwen3_5.cpp:3336,3359`; **I5d** `src/vllm/config/speculative.cpp`; `src/vllm/entrypoints/model_loader.cpp` (`ResolveSpecConfig`/`MakeKVCacheMaybeSpec`/ctor wiring); `src/vllm/v1/worker/gpu/runner.cpp` (splice/tap/GDN spec feed/`propose_drafts`/`take_draft_token_ids`/spec-slot remap/draft-KV alloc); `examples/server/main.cpp` | `tests/vllm/v1/test_scheduler.cpp:1135,1238,1272,1316`; `tests/vllm/v1/worker/test_input_batch.cpp`; `tests/vllm/v1/spec_decode/test_mtp_speculator.cpp:201,225,263,299,331` (7/7 cases, 141 assertions); oracle runner `tests/parity/test_op_parity.cpp:1373` + focused case `:1914` (20/20 assertions, both checkpoints, `VLLM_MTP_REQUIRE_CHECKPOINTS=1`); goldens `tests/parity/goldens/qwen3_5_mtp_head_{27b,35b}/`; dump `tools/parity/dump_qwen3_5_mtp.py:144`; **I5d** `tests/parity/test_qwen27_spec_decode.cpp` (three-way gate, RUNS + measures the RCA blocker); **I6** `examples/bench/{main.cpp,bench_core.h}` (additive `--speculative-config` bench flag + acceptance telemetry); **I7** `tests/vllm/models/test_qwen3_5_gdn_spec_routing.cpp` (mixed == pure spec + prefill bit-exact), `tests/parity/test_qwen27_spec_decode_concurrent.cpp`, `tests/vt/test_ops_gdn.cpp` (IndexSelect/IndexCopy); DONE closure [ledger](parity-ledger.md#L714) | [mtp-spec-decode.md](specs/mtp-spec-decode.md) | `DONE` | `72f9fb1` | | `SPEC-MTP-K-GT-1` | **MTP speculation DEPTH (`num_speculative_tokens` > 1).** Ports the autoregressive multi-step propose the k=1 early exit sits in front of, so a configured depth is SERVED instead of silently degraded. Before it, `--num-speculative-tokens 3` reserved KV for 3, captured the verify shape at T=4 and stashed ONE draft per request, with no error and no log; a refusal by name landed first and this row removed it in the same flow. `MtpProposeDrafts` runs the prefill, the k=1 early exit, then `prepare_decode_inputs` and the k-1 single-token draft decode steps over the draft's own paged KV, with `update_draft_inputs` recording each step and feeding it forward. Greedy plus accept-if-equal makes the emitted sequence INDEPENDENT of k, so a token-identity gate cannot see a clamped drafter and every depth assertion needs a positive witness beside the identity. The per-depth counters were the FIRST witness and a fresh review proved them BLIND: they report the LENGTH of the emitted draft list, so a propose that runs one forward and pads all k columns satisfies them, and acceptance is zero at every depth on the CPU model, so no acceptance figure separates the arms either. TWO witnesses survive, because one does not cover both failures. `spec_mtp_draft_decode_forwards() == spec_mtp_propose_calls() * (k - 1)`, counted after each draft decode forward RETURNS and guarded by a non-zero call count, catches a propose that SHORT-CIRCUITS or CLAMPS. A third fresh review then proved it does NOT catch PADDING, since a loop that runs every forward and then discards what it sampled increments it honestly. `spec_mtp_proposals_with_varied_drafts()`, read at the CONSUMER on the array the propose delivered, catches exactly that. NEITHER shows per-column provenance, and neither does a non-zero acceptance count AT DEPTH, which a padded row earns whenever the target repeats a token. The owed DGX gate closes it with a per-depth acceptance RATE against a PADDED CONTROL. The CPU tier therefore proves k drafts are PROPOSED and VERIFIED, never ACCEPTED at depth. DEFAULT unchanged at k=1 (both checkpoints' `n_predict`). **NO speed number at any k>1**: the GPU was held by another session for the whole flow, so the DGX three-way at k=2..4 on the 27B and 35B and the matched-k throughput A/B are OWED, as is the bf16 GDN-state arm (the CPU gate runs the f32 arm because `vt::CausalConv1dSpecUpdate` rejects bf16 off CUDA). Also owed and filed: [#1020](https://github.com/mudler/vllm.cpp/issues/1020), a step whose ACTUAL draft count differs from the configured k leaves the captured verify graph silently. | T1 | `vllm/v1/worker/gpu/spec_decode/autoregressive/speculator.py:129-274,335-371,374-419,426-471,597-671,674-771` @ `555967922`; `vllm/config/speculative.py:967-991` | [`src/vllm/v1/worker/gpu/spec_decode/mtp/speculator.cpp`](../src/vllm/v1/worker/gpu/spec_decode/mtp/speculator.cpp) (`MtpProposeDrafts`); [`prepare_decode_inputs.cpp`](../src/vllm/v1/worker/gpu/spec_decode/autoregressive/prepare_decode_inputs.cpp); `Qwen3_5MTPModel::GatherHiddenRows` ([qwen3_5.cpp](../src/vllm/model_executor/models/qwen3_5.cpp)); `GPUModelRunner::propose_drafts` + the per-depth counters ([runner.cpp](../src/vllm/v1/worker/gpu/runner.cpp), [runner.h](../include/vllm/v1/worker/gpu/runner.h)); the in-memory `mtp_weights` seam ([model_loader.h](../include/vllm/entrypoints/model_loader.h)) | [`test_mtp_depth`](../tests/vllm/v1/spec_decode/test_mtp_depth.cpp) 5/5, 63 assertions (k=1,2,3,4 through `LoadedEngine`, greedy tokens identical to spec-OFF, each arm witnessed BOTH by the draft decode forwards the propose RAN and by whether the DELIVERED draft row varied with depth; neither witness shows per-column provenance, which is owed to the DGX gate); [`test_prepare_decode_inputs`](../tests/vllm/v1/spec_decode/test_prepare_decode_inputs.cpp) 8/8, 33 (both kernel ports + both `max_model_len` clamps, 5 mutations caught); [`test_speculative_mtp_depth`](../tests/vllm/config/test_speculative_mtp_depth.cpp) 4/4, 20; full CPU suite ctest 493 passed / 0 failed / 2 skipped of 495 (the two skips checkpoint-gated and unrelated) | [mtp-k-gt-1.md](specs/mtp-k-gt-1.md) | `ACTIVE` | `CLAIM-SPEC-MTP-K-GT-1` ([#81](https://github.com/mudler/vllm.cpp/issues/81)) | -| `SPEC-MTP-GGUF` | MTP speculative decoding from a GGUF TARGET. Today `FromModelDir` refuses `mtp`+GGUF outright (`src/vllm/entrypoints/model_loader.cpp:717-723`) on the original spike's assumption that GGUF exports carry no `mtp.*` ([mtp-spec-decode.md](specs/mtp-spec-decode.md):979-980, "until we re-export GGUFs with the head"). That is stale: llama.cpp's Qwen3.5 converter DOES emit the head, under layer-indexed `nextn` naming, and our own `HfConfigFromGguf` ALREADY reads `nextn_predict_layers` (it just discards the value into the trunk layer count). Gap is a `TensorResolver` over `GgufFile` mapping `mtp.*` onto `blk.{L+i}.nextn.*` with dequant-to-bf16, one config field, and narrowing the rejection to `dflash`. `ngram`+GGUF already works and is untouched. Qwen3.5/3.6 only (the widened spec KV path serves no other arch). NO ABI change | T2 | llama.cpp (the producer contract; vLLM has no GGUF MTP path) `conversion/qwen.py:535-604` `_Qwen35MtpMixin` (the authoritative `mtp.*`->`nextn` remapper + `add_nextn_predict_layers`); `gguf-py/gguf/constants.py:129,910-917,1494-1501`; `gguf-py/gguf/tensor_mapping.py` `NEXTN_*` | **G1-G3 LANDED 2026-07-28.** `HfConfigFromGguf` republishes the head depth `src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp:598` (`c.raw["mtp_num_hidden_layers"] = nextn`, previously read then discarded); the head loader `LoadQwen3_5MTPFromGguf` `src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp:971` (+ decl `include/vllm/model_executor/models/qwen3_5_gguf_weights.h:143`) reusing the TRUNK helpers `OwnNormMinus1`/`OwnMatmulWeight`/`OwnBf16`/`LoadAttnGguf`/`LoadMoeGguf` so the head inherits the GGUF (w+1) norm storage, quantization/residency routing and torch [N,K] shapes; `NumMtpLayers`/`UsesDedicatedEmbeddings` exported out of the anon namespace `include/vllm/model_executor/models/qwen3_5_mtp.h`; rejection narrowed to dflash + a head-less-GGUF check `src/vllm/entrypoints/model_loader.cpp` and the head attached in the GGUF branch; **G4 GREEN + `CPU-SPEC-DIVERGENCE` FIXED 2026-07-28**: root cause `src/vllm/model_executor/models/qwen3_5.cpp:3616` sized the GDN state gather/scatter row by `(Kw-1)` while the speculative persistent row is `(Kw-1)+num_spec`, so `GatherRows`/`ScatterRows` mis-strode the slot AND every channel past the first, corrupting post-prefill recurrent state. Fix = `CopyStateRowsStrided` (same TU) used by `GatherStateF32`/`ScatterStateF32` when `cache.shape[2] != work.shape[2]`; the contiguous helpers are kept when the widths agree, so every non-spec path is byte-identical by construction. CPU-only in effect (the fp16/bf16 arm routes through the `GdnStateGather`/`Scatter` ops, so CUDA was never exposed; no GPU result affected) | `tests/vllm/models/test_qwen3_5_gguf_mtp.cpp:109,146,156,184` **4 cases, and the split is the 2026-08-21 repair** ([#1454](https://github.com/mudler/vllm.cpp/issues/1454)): the file used to be the env-gated pair ALONE, each opening on a bare `return`, so with `VLLM_MTP_GGUF_MODEL` unset it reported `test cases: 2 \| 2 passed`, **`assertions: 0`**, `Status: SUCCESS!`, exit 0 - which is every CI run of this repository, the variable being set nowhere in `.github/workflows/`. The `18 assertions` this cell used to record was the LIVE count and was never once reached in CI. Now `:109` and `:146` are **HERMETIC** (KV-only synthetic GGUFs, no weight bytes, 18 assertions on any machine) and pin the arithmetic the old file only NAMED in a comment above `CHECK(c.num_hidden_layers > 0)`: `num_hidden_layers + mtp_num_hidden_layers == block_count` over 65/1, 25/1 and 28/3 - the third arm separating `- nextn` from `- 1` - plus the head-less arm, where the key is NOT published and `NumMtpLayers` answering 1 for an absent key is exactly why the invariant cannot be written with that helper alone. `:156` and `:184` stay env-gated on `VLLM_MTP_GGUF_MODEL` (so CI stays asset-free) and now SKIP LOUDLY with a `MESSAGE` naming the variable, as `tests/vllm/entrypoints/test_gguf_mmproj_reach.cpp` does; `:156` re-derives the same invariant from the file's OWN `block_count` kv. Unset: **4 cases / 18 assertions / `Status: SUCCESS!` / rc 0**. Live on `Qwen3.8-27B-Q4_K_M.gguf` (`block_count` 65, `nextn_predict_layers` 1): **4 cases / 38 assertions / `Status: SUCCESS!` / rc 0**. Mutation-proved on the production line `src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp:889`, both compiling clean and both restored against a pre-taken sha256: `= block_count` (drop the subtraction) 3/4 cases, 9/18 red, exit 1; `= block_count - 1` (the wrong constant) 2/4 cases, 5/18 red, exit 1. The SAME mutations left the PREVIOUS file at 2/2 cases, 0 assertions, `SUCCESS!`, exit 0. Correctness of the production line is unchanged and was never in question (`1a4db5c3c`, `493327b4e`); this was a test defect. Live-arm content unchanged: depth reaches config.raw; fc is [H,2H] verbatim; 3 norms [H]; head block is full-attention. **RED-first BEHAVIOURAL** (reverting only the G1 line fails both cases 2/2). Trunk inertness: `test_gguf` 103, `test_gguf_qwen36_loader` 99, `test_gguf_keep_quant` 5958, `test_gguf_dequant` 215, `test_capi` 33/232 all unchanged; `tests/parity/test_qwen35_gguf_spec_decode.cpp:74,139` - spec-ON == spec-OFF token-exact with 13 proposed/11 accepted, plus an `ngram` regression guard (widens the cache, never runs the spec conv update) that was token-exact throughout and pinned the widening as innocent. Regression sweep all unchanged: ops_gdn 1825, gdn_metadata_builder 483, gdn_prefill_conv 28, gdn_spec_routing 12, gguf 103, gguf_qwen36_loader 99, gguf_keep_quant 5958, gguf_dequant 215, llm_engine 196, input_batch 163, runner 257, capi 232 **GPU CLOSE-OUT + DEVICE-DELTA ATTRIBUTION 2026-07-28 (`G5`-`G7`), ledger [parity-ledger.md#L800](parity-ledger.md#L800).** The GPU end-to-end gate re-run on a from-scratch RELEASE-TARGET build (`-DVLLM_CPP_CUDA_ARCHITECTURES=121a`, build dir DELETED first; arch VERIFIED by `build-cuda/CMakeFiles/vllm.dir/flags.make` `--generate-code=arch=compute_121a,code=[compute_121a,sm_121a]` and by `cuobjdump -lelf` 20 cubins ALL `sm_121a` zero sm_75, NOT by `CMakeCache.txt`, whose `CMAKE_CUDA_ARCHITECTURES:STRING=75` is the `enable_language(CUDA)` compiler-probe default shadowed by the normal variable at `CMakeLists.txt:186` - the prior wrong-arch conclusion was that decoy): dgx.casa GB10 under `flock $HOME/gpu.lock`, 35B A3B NVFP4 GGUF, **2/2 cases, 10/10 assertions, exit 0**, spec-ON token-identical to spec-OFF, 13 proposed / 11 accepted, 90.2 GiB peak RSS, 8m01s; re-run on the EXACT committed source **3/3 cases, 10/10 assertions, exit 0**, 7m25s, the new probe case SKIPping and adding zero assertions. **The CPU-vs-GPU token delta is a MEASURED near-tie, not a defect** (it was never this row's bar - spec-ON == spec-OFF WITHIN a device is): NEW double-gated spec-OFF-only probe `tests/parity/test_qwen35_gguf_spec_decode.cpp:217` (asset + `VLLM_MTP_GGUF_PROBE=1`, 20 alternatives per position, 484/484 assertions per arm, GPU then `CUDA_VISIBLE_DEVICES=` in one `flock` series) shows both arms picking `11751` at position 0 and forking at position 1 on a BIT-IDENTICAL prefix: GPU rank1 `13` -0.773180 over rank2 `11` -0.847055 (margin 0.0739 nats), CPU rank1 `11` -0.765499 over rank2 `13` -0.830374 (margin 0.0649 nats). Each device's pick is the other's rank 2, both ~7x inside the ratified 0.5-nat band, and the cross-device disagreement on the SAME token (0.057 and 0.082 nats) EXCEEDS the margin being decided, so rounding settles it; the 24 texts look unrelated only because positions 2+ cascade off that one coin flip. Margin sweep over all 24 positions: **GGUF GPU and GGUF CPU carry ZERO exact ties**, minimum margins 0.0482 and 0.0649 nats, and both arms reproduced their sequence across every run. **Gate 4 MET on the safetensors sibling of the same quantization run** (`FromModelDir` takes it unchanged): acceptance 12 proposed / 11 accepted vs the GGUF's 13 / 11. That arm, however, FAILS spec-ON == spec-OFF at concurrency 1 and does not reproduce its own spec-OFF sequence run to run, and the probe attributes both to THREE EXACT ties (positions 7, 10, 16, bit-identical logprobs) produced by its 1/16-grid quantized-GEMM logits - which EXONERATES the GGUF arm and opens a recorded, not-root-caused `SPEC-MTP` item on the safetensors NVFP4 path, not on this row. Gate 3 is NOT APPLICABLE twice over: no F16/F32 head-carrying export exists, and the only same-weights sibling is not token-stable against itself. **EVIDENCE RE-ANCHORED 2026-07-29 to a PRODUCTION-CONFIGURED build, because every GPU number above came from a build configured WITHOUT `-DVLLM_CPP_CUTLASS_DIR` and WITHOUT `-DVLLM_CPP_TRITON=ON`** (the defect `CLAIM-27B-GATE-RCA` proved, which runs the emulation fp4 GEMM + hand GDN kernels). Re-run from a clean `git archive` tree of `main` `3f34534d`, build proven correct three ways (configure log has ZERO `CUTLASS not found` and prints `CUTLASS found ... sm120a NVFP4 cutlass GEMM` + `FlashAttention-2 ... ENABLED for arch(es) [121a]` + the vendored `sm_121a` Triton-AOT lines with `MANIFEST hashes OK`; `cuobjdump -lelf` 40 cubins ALL `sm_121a`, zero `sm_75`; SACRED `test_qwen27_paged_engine` **235/235 exit 0**, and the build precondition proven to FIRE by recompiling only that TU without the two defines against the same `libvllm.a`, which throws and exits 1 with 0 assertions). **The row PASSES UNCHANGED:** `tests/parity/test_qwen35_gguf_spec_decode` **3/3 cases, 10/10 assertions, exit 0**, spec-ON token-identical to spec-OFF, **13 proposed / 11 accepted (identical to the recorded number)**, 90.26 GiB, 7m13.59s; loader gate 19 assertions on the Qwen3.5-2B and 18 on the 35B A3B, unchanged. **ONE recorded finding is RETRACTED by the re-measurement: the CPU-vs-GPU token delta was a BUILD artifact, not a device near-tie cascade.** On the production build both devices emit the SAME 24 tokens; the probe shows GPU rank1 `11` -0.763897 over rank2 `13` -0.824083 where the defective build had rank1 `13` -0.773180 over rank2 `11` -0.847055, while the CPU arm is bit-identical to the earlier measurement (CUTLASS and Triton are CUDA-only). Zero exact ties in either arm, min margins 0.060186 GPU / 0.064875 CPU, 484/484 assertions per arm. Evidence: [docs/BENCHMARKS.md](../docs/BENCHMARKS.md) top section, [parity-ledger.md](parity-ledger.md) | [specs/gguf-mtp-spec-decode.md](specs/gguf-mtp-spec-decode.md) | `DONE` | `edf91449` | -| `SPEC-DFLASH-GGUF` | DFlash speculative decoding from GGUF, two axes: (A) GGUF DRAFT + safetensors target, (B) GGUF target too. llama.cpp master carries a full `dflash` GGUF contract (arch string `dflash`, tensors `fc`/`enc.output_norm`/`output_norm`/`blk.N.*`, KVs `dflash.target_layers` + `dflash.target_hidden_size`); the arch is ABSENT from checkouts older than ~2026-07, so a stale tree reads as "no contract exists". The GGUF tensor set omits `token_embd`/`output` because the draft SHARES the target's embed+lm_head, which is exactly what `LoadDflashDraft` already does. Blockers are in the loader, not the model: `MakeDflashDraftConfig` reads `draft_dir/config.json` (a GGUF has none), `ResolveDflashDraftDir` probes for `config.json` so it cannot see a `.gguf`, and `LoadDflashDraft` is typed on `std::vector` for the shared bf16 head (the axis-B blocker). Axis A independently shippable. NO ABI change | T2 | llama.cpp `origin/master` @ 2026-07-28 (tag era `b10158`): `gguf-py/gguf/constants.py:547,1151,4350`; `gguf-py/gguf/tensor_mapping.py:1297-1305` (`ENC_OUTPUT_NORM`<-`model.hidden_norm`, `FC`<-`model.fc`); `conversion/qwen.py:351` (mask token via the standard tokenizer KV); `convert_hf_to_gguf.py --target-model-dir` | **GD1-GD7 LANDED 2026-07-28 (BOTH AXES COMPLETE and PROVEN end to end on GB10)**: `MakeDflashGgufConfig` + `LoadQwen3DFlashFromGguf` `src/vllm/model_executor/models/qwen3_dflash_gguf.cpp:88,227` (+ header), `IsDflashGgufDraft` + the `.gguf` branch in `ResolveDflashDraftDir`/`LoadDflashDraft` `src/vllm/entrypoints/model_loader.cpp:121,222`. Goes through the `TensorResolver` seam (unlike `SPEC-MTP-GGUF`) because dflash norms are RAW, so the existing `LoadQwen3DFlash` qkv/gate_up concatenation is reused unchanged. **`GD4` defect FIXED** (`model_loader.cpp:238-249`): the GGUF branch left `config.vocab_size` 0 - correct for `MakeDflashGgufConfig` (the DFLASH arch has no vocab KV and no `token_embd`) but fatal for the forward, which sizes the shared embedding view as `{config.vocab_size, H}`, so the first propose threw `cuda embedding: empty table (vocab 0)`. Now back-filled from the target's `embed_tokens` rows (the condition is on the VALUE, not the draft source, so it generalizes to a GGUF target). Load-level green had hidden it; only GENERATING found it. **GD5-GD7 = axis B**: `SharedHeadSource` `src/vllm/entrypoints/model_loader.cpp` re-expresses the shared bf16 `embed_tokens`+`lm_head` seam as a SOURCE and re-types `LoadDflashDraft`'s second parameter - THAT TYPE was the whole axis-B blocker - with the GGUF arm `LoadGgufSharedEmbedAndHeadBf16` `src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp:773` reusing the trunk loader's tied-embedding rule and sidecar-aware dequant instead of restating them; the shared-head load moved into ONE common tail so all four (draft format x target container) combinations run identical code; the `dflash` half of the GGUF-branch rejection `model_loader.cpp` is deleted (the `mtp` half untouched) and the draft load is wired into the GGUF branch | `tests/vllm/models/test_qwen3_dflash_gguf.cpp:36,84` 2 cases / 47 assertions against the REAL published Qwen3.6-27B DFlash draft (env-gated `VLLM_DFLASH_GGUF_MODEL`, CI asset-free): the +1 target-layer offset undone against the KV read back from the same file, block_size/mask_token present, vocab_size left 0, layer_types cover every block, fc `[H, H*num_taps]` with `nk` SET, qkv/gate_up row-concat shapes, embed/lm_head left EMPTY for the target. **RED-first BEHAVIOURAL** (dropping the `-1` fails the offset checks). **`GD4` e2e gate** `tests/parity/test_qwen27_dflash_spec_decode.cpp:343` (second case, draft source env-driven via `VLLM_DFLASH_DRAFT`/`_B`; asset-gated, CI-inert): on dgx GB10 sm_121a against the Qwen3.6-27B NVFP4 safetensors target, the Q4_K_M GGUF draft and the bf16 z-lab safetensors draft produce **token-for-token IDENTICAL** DFlash-ON continuations with **IDENTICAL** accepted/proposed (20/80 on a 24-token prompt, 42/96 on a 48-token prompt), spec-OFF self-reproducible 3/3 and 0 exact ties (min margin 0.197/0.400 nats). Regression: gguf_mtp 19, qwen35_gguf_spec_decode 10, gguf 103, gguf_qwen36_loader 99, gguf_keep_quant 5958, ops_gdn 1825, llm_engine 196, capi 232, runner 257 all unchanged. **`GD5` unit gate** `tests/vllm/test_gguf_qwen36_loader.cpp` 3 new synthetic-GGUF cases (6 cases / 286 assertions total, CPU and the dgx CUDA build): the untied head really comes from `output.weight` and not the embedding (distinct fill values), the tied fallback aliases it onto `token_embd`, the `nk` flags separate the gather table from the MatmulBT weight, a file with no `token_embd` is refused. 3-mutant battery, 3 caught (`nk` flipped, head forced to the embedding, tied forced false). **`GD7` e2e gate** `tests/parity/test_qwen27_dflash_spec_decode.cpp` third case (targets env-driven via `VLLM_DFLASH_TARGET_B`; asset-gated, CI-inert): on dgx GB10 sm_121a the Qwen3.6-27B NVFP4 **GGUF** target + `Q4_K_M` GGUF draft loads, takes the shared head from the GGUF, generates, and its DFlash-ON continuation is **token-for-token IDENTICAL to that same target's spec-OFF** (24/24, the STRICT form) with acceptance ALIVE at 14/160; 1 case / 15 assertions, exit 0. **The spike's highest risk is EMPTY on this asset, proven not assumed**: the 27B NVFP4 GGUF stores `token_embd`/`output` as ggml BF16, byte-identical to the safetensors sibling (2,542,796,800 bytes each, ZERO differing), so B1's shared-head read is verbatim, not a dequant. Acceptance IS lower than the safetensors-target arm and is NOT chargeable to the head: the two containers diverge at index 4 with NO speculation, because `QUANT-GGUF-NVFP4` is dequant-only so the GGUF target computes in bf16 while the safetensors target runs the true W4A4 kernels. **RE-MEASURED 2026-07-29 on a PRODUCTION-CONFIGURED build (`CLAIM-GGUF-SPEC-REVERIFY`), because every GD4/GD7 GPU number above came from a build configured WITHOUT `-DVLLM_CPP_CUTLASS_DIR` and WITHOUT `-DVLLM_CPP_TRITON=ON`.** Build proven correct three ways (see the `SPEC-MTP-GGUF` row; SACRED 27B **235/235**, `cuobjdump` 40 cubins all `sm_121a`). **AXIS B HOLDS EXACTLY**: `test_qwen27_dflash_spec_decode -tc="dflash axis-B*"` **15/15 assertions, exit 0**, GGUF-target DFlash-ON token-identical to that target's own spec-OFF 24/24, acceptance **14/160 unchanged**, cross-target spec-OFF divergence still at index 4, 81.01 GiB peak RSS, 6m53.08s. **AXIS A WAS RED ON THE 48-TOKEN PROMPT (reproducibly, 3 of 3 runs) AND IS NOW CLOSED.** The RED was real: cross-format TOKEN identity held on both prompts, but the exact accept-count half of bar (a) failed (`arm_a.proposed == arm_b.proposed` / `arm_a.accepted == arm_b.accepted`) because the Q4_K_M draft measured **46/112** against the bf16 z-lab draft's **47/96** (one extra 16-wide propose block, one fewer acceptance, zero token difference), 15/17, exit 1; the 24-token prompt stayed green at 17/17 with both drafts at 15/144. **`GD9` 2026-07-29 root-caused it IN WEIGHT SPACE as ordinary `Q4_K_M` cost, category (a), not a defect in our GGUF draft path - and the bar's own premise ("Same weights, two containers") was false for the asset it was pointed at.** The publishing repo also carries an UNQUANTIZED `BF16` GGUF (3,471,497,440 B) beside `Q8_0`/`Q6_K`/`Q5_K`/`Q4_K_M`, which the spec had recorded as nonexistent; that retired the `NOT APPLICABLE` on gate 2. CPU gate `tests/vllm/models/test_qwen3_dflash_gguf.cpp` third case (asset-gated `VLLM_DFLASH_GGUF_BF16_MODEL` + `VLLM_DFLASH_ST_DIR`): `LoadQwen3DFlashFromGguf(BF16)` is **BYTE-IDENTICAL to `LoadQwen3DFlash(z-lab shards)` on all 58 tensors, 302/302 assertions, exit 0**, and FUNCTIONALLY RED against the `Q4_K_M` file (21/302 red, exactly the 21 quantized matmul tensors), so not a vacuous pass. Supporting: our `DequantGgufRowToBf16` is bit-equal to `gguf-py`'s `gguf.quants.dequantize` on the real `fc.weight` (Q4_K), `blk.0.attn_q.weight` (Q4_K) and `blk.2.ffn_down.weight` (Q6_K), zero differing bf16 values; the ladder's mean relative weight error is monotone and uniform with NO outlier tensor (BF16 0, Q8_0 5.6e-3, Q6_K 1.85e-2, Q5_K 3.85e-2, Q4_K_M 7.6e-2); the only numeric config delta is `rms_norm_eps` at 2.5e-9 relative. Also landed: an off-by-default `VT_SPEC_TRACE=1` per-block propose/accept trace in `GPUModelRunner::sample_tokens_with_rejection` (`src/vllm/v1/worker/gpu/runner.cpp`). **`GD10` 2026-07-29 CONFIRMED IT END TO END ON GB10 and closed gates 3 and 5.** Build proven production-configured three ways (configure log 0 `CUTLASS not found`; `cuobjdump -lelf` 40 cubins ALL `sm_121a` zero `sm_75` on both binaries; SACRED `test_qwen27_paged_engine` **235/235, exit 0**, 31.34s, 23.67 GiB). The **`BF16` GGUF draft reads EXACTLY 47/96**, the safetensors draft's own number, at 48 tokens on the discriminating prompt - reproduced 2 of 2 - plus 27/64 = 27/64 at 24 tokens and 15/144 = 15/144 on the second prompt, tokens IDENTICAL throughout, 17/17 exit 0 each time; the `Q4_K_M` arm reads 46/112 on the SAME binary in the SAME `flock` series. Restoring only the draft's numeric precision restores the count, so quantization is the whole cause and nothing structural survives. Bar (a) is consequently SPLIT rather than relaxed (`tests/parity/test_qwen27_dflash_spec_decode.cpp`): tokens stay EXACT unconditionally; accept counts are EXACT on a cross-FORMAT arm and BANDED (`abs(d_accepted) <= 2`, `abs(d_proposed) <= k*2`) on a cross-QUANTIZATION one, with the arm chosen by `IsQuantizedGgufDraft` reading the draft file's ggml types (`GgmlTraits().block_elems > 1`) rather than by a flag. The band is derived, not picked: measured `d_accepted` is 0, 0, -1, so the bound is that maximum plus one quantum; and `d_proposed = -k * d_accepted` EXACTLY once the token streams match (confirmed at -1 / +16), so the proposed bound follows. **Mutation-proved non-vacuous**: rebuilt at band 0 the `Q4_K_M` arm is 15/17 exit 1 while the `BF16` arm stays 17/17 exit 0 on the exact branch. **AXIS B BROADENED from ONE prompt to THREE**, strict form green on all: "The capital of France is" IDENTICAL 14/160 (15/15), "Write a Python function that reverses a string:" IDENTICAL 24/64 (15/15), "Photosynthesis is the process by which" IDENTICAL 15/128 (9/9), all exit 0, ~6m30-6m52 and ~81 GiB peak RSS each. The second prompt REFINES the recorded acceptance claim: the safetensors-target arm is ALSO 24/64 there with the two containers' DFlash-ON streams IDENTICAL, so the GGUF target's lower acceptance is prompt-dependent (their spec-OFF streams diverge at index 4 on the first prompt, index 16 on the second) and not a standing penalty; the cause remains `QUANT-GGUF-NVFP4` being dequant-only, with the shared head excluded by a byte comparison. Gates 1-5 and 7 MET; gate 6 (speed) `PENDING` BY DESIGN and not owed - a DFlash-ON throughput A/B between the two target containers is not a fair comparison until a native NVFP4 GGUF GEMM exists. Evidence: [docs/BENCHMARKS.md](../docs/BENCHMARKS.md) top section, [parity-ledger.md](parity-ledger.md#L845) | [specs/gguf-dflash-draft.md](specs/gguf-dflash-draft.md) | `DONE` | `c62f2fa3` | +| `SPEC-MTP-GGUF` | MTP speculative decoding from a GGUF TARGET. Today `FromModelDir` refuses `mtp`+GGUF outright (`src/vllm/entrypoints/model_loader.cpp:717-723`) on the original spike's assumption that GGUF exports carry no `mtp.*` ([mtp-spec-decode.md](specs/mtp-spec-decode.md):979-980, "until we re-export GGUFs with the head"). That is stale: llama.cpp's Qwen3.5 converter DOES emit the head, under layer-indexed `nextn` naming, and our own `HfConfigFromGguf` ALREADY reads `nextn_predict_layers` (it just discards the value into the trunk layer count). Gap is a `TensorResolver` over `GgufFile` mapping `mtp.*` onto `blk.{L+i}.nextn.*` with dequant-to-bf16, one config field, and narrowing the rejection to `dflash`. `ngram`+GGUF already works and is untouched. Qwen3.5/3.6 only (the widened spec KV path serves no other arch). NO ABI change | T2 | llama.cpp (the producer contract; vLLM has no GGUF MTP path) `conversion/qwen.py:535-604` `_Qwen35MtpMixin` (the authoritative `mtp.*`->`nextn` remapper + `add_nextn_predict_layers`); `gguf-py/gguf/constants.py:129,910-917,1494-1501`; `gguf-py/gguf/tensor_mapping.py` `NEXTN_*` | **G1-G3 LANDED 2026-07-28.** `HfConfigFromGguf` republishes the head depth `src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp:598` (`c.raw["mtp_num_hidden_layers"] = nextn`, previously read then discarded); the head loader `LoadQwen3_5MTPFromGguf` `src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp:1425` (+ decl `include/vllm/model_executor/models/qwen3_5_gguf_weights.h:143`) reusing the TRUNK helpers `OwnNormMinus1`/`OwnMatmulWeight`/`OwnBf16`/`LoadAttnGguf`/`LoadMoeGguf` so the head inherits the GGUF (w+1) norm storage, quantization/residency routing and torch [N,K] shapes; `NumMtpLayers`/`UsesDedicatedEmbeddings` exported out of the anon namespace `include/vllm/model_executor/models/qwen3_5_mtp.h`; rejection narrowed to dflash + a head-less-GGUF check `src/vllm/entrypoints/model_loader.cpp` and the head attached in the GGUF branch; **G4 GREEN + `CPU-SPEC-DIVERGENCE` FIXED 2026-07-28**: root cause `src/vllm/model_executor/models/qwen3_5.cpp:3616` sized the GDN state gather/scatter row by `(Kw-1)` while the speculative persistent row is `(Kw-1)+num_spec`, so `GatherRows`/`ScatterRows` mis-strode the slot AND every channel past the first, corrupting post-prefill recurrent state. Fix = `CopyStateRowsStrided` (same TU) used by `GatherStateF32`/`ScatterStateF32` when `cache.shape[2] != work.shape[2]`; the contiguous helpers are kept when the widths agree, so every non-spec path is byte-identical by construction. CPU-only in effect (the fp16/bf16 arm routes through the `GdnStateGather`/`Scatter` ops, so CUDA was never exposed; no GPU result affected) | `tests/vllm/models/test_qwen3_5_gguf_mtp.cpp:109,146,156,184` **4 cases, and the split is the 2026-08-21 repair** ([#1454](https://github.com/mudler/vllm.cpp/issues/1454)): the file used to be the env-gated pair ALONE, each opening on a bare `return`, so with `VLLM_MTP_GGUF_MODEL` unset it reported `test cases: 2 \| 2 passed`, **`assertions: 0`**, `Status: SUCCESS!`, exit 0 - which is every CI run of this repository, the variable being set nowhere in `.github/workflows/`. The `18 assertions` this cell used to record was the LIVE count and was never once reached in CI. Now `:109` and `:146` are **HERMETIC** (KV-only synthetic GGUFs, no weight bytes, 18 assertions on any machine) and pin the arithmetic the old file only NAMED in a comment above `CHECK(c.num_hidden_layers > 0)`: `num_hidden_layers + mtp_num_hidden_layers == block_count` over 65/1, 25/1 and 28/3 - the third arm separating `- nextn` from `- 1` - plus the head-less arm, where the key is NOT published and `NumMtpLayers` answering 1 for an absent key is exactly why the invariant cannot be written with that helper alone. `:156` and `:184` stay env-gated on `VLLM_MTP_GGUF_MODEL` (so CI stays asset-free) and now SKIP LOUDLY with a `MESSAGE` naming the variable, as `tests/vllm/entrypoints/test_gguf_mmproj_reach.cpp` does; `:156` re-derives the same invariant from the file's OWN `block_count` kv. Unset: **4 cases / 18 assertions / `Status: SUCCESS!` / rc 0**. Live on `Qwen3.8-27B-Q4_K_M.gguf` (`block_count` 65, `nextn_predict_layers` 1): **4 cases / 38 assertions / `Status: SUCCESS!` / rc 0**. Mutation-proved on the production line `src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp:889`, both compiling clean and both restored against a pre-taken sha256: `= block_count` (drop the subtraction) 3/4 cases, 9/18 red, exit 1; `= block_count - 1` (the wrong constant) 2/4 cases, 5/18 red, exit 1. The SAME mutations left the PREVIOUS file at 2/2 cases, 0 assertions, `SUCCESS!`, exit 0. Correctness of the production line is unchanged and was never in question (`1a4db5c3c`, `493327b4e`); this was a test defect. Live-arm content unchanged: depth reaches config.raw; fc is [H,2H] verbatim; 3 norms [H]; head block is full-attention. **RED-first BEHAVIOURAL** (reverting only the G1 line fails both cases 2/2). Trunk inertness: `test_gguf` 103, `test_gguf_qwen36_loader` 99, `test_gguf_keep_quant` 5958, `test_gguf_dequant` 215, `test_capi` 33/232 all unchanged; `tests/parity/test_qwen35_gguf_spec_decode.cpp:74,139` - spec-ON == spec-OFF token-exact with 13 proposed/11 accepted, plus an `ngram` regression guard (widens the cache, never runs the spec conv update) that was token-exact throughout and pinned the widening as innocent. Regression sweep all unchanged: ops_gdn 1825, gdn_metadata_builder 483, gdn_prefill_conv 28, gdn_spec_routing 12, gguf 103, gguf_qwen36_loader 99, gguf_keep_quant 5958, gguf_dequant 215, llm_engine 196, input_batch 163, runner 257, capi 232 **GPU CLOSE-OUT + DEVICE-DELTA ATTRIBUTION 2026-07-28 (`G5`-`G7`), ledger [parity-ledger.md#L800](parity-ledger.md#L800).** The GPU end-to-end gate re-run on a from-scratch RELEASE-TARGET build (`-DVLLM_CPP_CUDA_ARCHITECTURES=121a`, build dir DELETED first; arch VERIFIED by `build-cuda/CMakeFiles/vllm.dir/flags.make` `--generate-code=arch=compute_121a,code=[compute_121a,sm_121a]` and by `cuobjdump -lelf` 20 cubins ALL `sm_121a` zero sm_75, NOT by `CMakeCache.txt`, whose `CMAKE_CUDA_ARCHITECTURES:STRING=75` is the `enable_language(CUDA)` compiler-probe default shadowed by the normal variable at `CMakeLists.txt:186` - the prior wrong-arch conclusion was that decoy): dgx.casa GB10 under `flock $HOME/gpu.lock`, 35B A3B NVFP4 GGUF, **2/2 cases, 10/10 assertions, exit 0**, spec-ON token-identical to spec-OFF, 13 proposed / 11 accepted, 90.2 GiB peak RSS, 8m01s; re-run on the EXACT committed source **3/3 cases, 10/10 assertions, exit 0**, 7m25s, the new probe case SKIPping and adding zero assertions. **The CPU-vs-GPU token delta is a MEASURED near-tie, not a defect** (it was never this row's bar - spec-ON == spec-OFF WITHIN a device is): NEW double-gated spec-OFF-only probe `tests/parity/test_qwen35_gguf_spec_decode.cpp:217` (asset + `VLLM_MTP_GGUF_PROBE=1`, 20 alternatives per position, 484/484 assertions per arm, GPU then `CUDA_VISIBLE_DEVICES=` in one `flock` series) shows both arms picking `11751` at position 0 and forking at position 1 on a BIT-IDENTICAL prefix: GPU rank1 `13` -0.773180 over rank2 `11` -0.847055 (margin 0.0739 nats), CPU rank1 `11` -0.765499 over rank2 `13` -0.830374 (margin 0.0649 nats). Each device's pick is the other's rank 2, both ~7x inside the ratified 0.5-nat band, and the cross-device disagreement on the SAME token (0.057 and 0.082 nats) EXCEEDS the margin being decided, so rounding settles it; the 24 texts look unrelated only because positions 2+ cascade off that one coin flip. Margin sweep over all 24 positions: **GGUF GPU and GGUF CPU carry ZERO exact ties**, minimum margins 0.0482 and 0.0649 nats, and both arms reproduced their sequence across every run. **Gate 4 MET on the safetensors sibling of the same quantization run** (`FromModelDir` takes it unchanged): acceptance 12 proposed / 11 accepted vs the GGUF's 13 / 11. That arm, however, FAILS spec-ON == spec-OFF at concurrency 1 and does not reproduce its own spec-OFF sequence run to run, and the probe attributes both to THREE EXACT ties (positions 7, 10, 16, bit-identical logprobs) produced by its 1/16-grid quantized-GEMM logits - which EXONERATES the GGUF arm and opens a recorded, not-root-caused `SPEC-MTP` item on the safetensors NVFP4 path, not on this row. Gate 3 is NOT APPLICABLE twice over: no F16/F32 head-carrying export exists, and the only same-weights sibling is not token-stable against itself. **EVIDENCE RE-ANCHORED 2026-07-29 to a PRODUCTION-CONFIGURED build, because every GPU number above came from a build configured WITHOUT `-DVLLM_CPP_CUTLASS_DIR` and WITHOUT `-DVLLM_CPP_TRITON=ON`** (the defect `CLAIM-27B-GATE-RCA` proved, which runs the emulation fp4 GEMM + hand GDN kernels). Re-run from a clean `git archive` tree of `main` `3f34534d`, build proven correct three ways (configure log has ZERO `CUTLASS not found` and prints `CUTLASS found ... sm120a NVFP4 cutlass GEMM` + `FlashAttention-2 ... ENABLED for arch(es) [121a]` + the vendored `sm_121a` Triton-AOT lines with `MANIFEST hashes OK`; `cuobjdump -lelf` 40 cubins ALL `sm_121a`, zero `sm_75`; SACRED `test_qwen27_paged_engine` **235/235 exit 0**, and the build precondition proven to FIRE by recompiling only that TU without the two defines against the same `libvllm.a`, which throws and exits 1 with 0 assertions). **The row PASSES UNCHANGED:** `tests/parity/test_qwen35_gguf_spec_decode` **3/3 cases, 10/10 assertions, exit 0**, spec-ON token-identical to spec-OFF, **13 proposed / 11 accepted (identical to the recorded number)**, 90.26 GiB, 7m13.59s; loader gate 19 assertions on the Qwen3.5-2B and 18 on the 35B A3B, unchanged. **ONE recorded finding is RETRACTED by the re-measurement: the CPU-vs-GPU token delta was a BUILD artifact, not a device near-tie cascade.** On the production build both devices emit the SAME 24 tokens; the probe shows GPU rank1 `11` -0.763897 over rank2 `13` -0.824083 where the defective build had rank1 `13` -0.773180 over rank2 `11` -0.847055, while the CPU arm is bit-identical to the earlier measurement (CUTLASS and Triton are CUDA-only). Zero exact ties in either arm, min margins 0.060186 GPU / 0.064875 CPU, 484/484 assertions per arm. Evidence: [docs/BENCHMARKS.md](../docs/BENCHMARKS.md) top section, [parity-ledger.md](parity-ledger.md) | [specs/gguf-mtp-spec-decode.md](specs/gguf-mtp-spec-decode.md) | `DONE` | `edf91449` | +| `SPEC-DFLASH-GGUF` | DFlash speculative decoding from GGUF, two axes: (A) GGUF DRAFT + safetensors target, (B) GGUF target too. llama.cpp master carries a full `dflash` GGUF contract (arch string `dflash`, tensors `fc`/`enc.output_norm`/`output_norm`/`blk.N.*`, KVs `dflash.target_layers` + `dflash.target_hidden_size`); the arch is ABSENT from checkouts older than ~2026-07, so a stale tree reads as "no contract exists". The GGUF tensor set omits `token_embd`/`output` because the draft SHARES the target's embed+lm_head, which is exactly what `LoadDflashDraft` already does. Blockers are in the loader, not the model: `MakeDflashDraftConfig` reads `draft_dir/config.json` (a GGUF has none), `ResolveDflashDraftDir` probes for `config.json` so it cannot see a `.gguf`, and `LoadDflashDraft` is typed on `std::vector` for the shared bf16 head (the axis-B blocker). Axis A independently shippable. NO ABI change | T2 | llama.cpp `origin/master` @ 2026-07-28 (tag era `b10158`): `gguf-py/gguf/constants.py:547,1151,4350`; `gguf-py/gguf/tensor_mapping.py:1297-1305` (`ENC_OUTPUT_NORM`<-`model.hidden_norm`, `FC`<-`model.fc`); `conversion/qwen.py:351` (mask token via the standard tokenizer KV); `convert_hf_to_gguf.py --target-model-dir` | **GD1-GD7 LANDED 2026-07-28 (BOTH AXES COMPLETE and PROVEN end to end on GB10)**: `MakeDflashGgufConfig` + `LoadQwen3DFlashFromGguf` `src/vllm/model_executor/models/qwen3_dflash_gguf.cpp:88,227` (+ header), `IsDflashGgufDraft` + the `.gguf` branch in `ResolveDflashDraftDir`/`LoadDflashDraft` `src/vllm/entrypoints/model_loader.cpp:121,222`. Goes through the `TensorResolver` seam (unlike `SPEC-MTP-GGUF`) because dflash norms are RAW, so the existing `LoadQwen3DFlash` qkv/gate_up concatenation is reused unchanged. **`GD4` defect FIXED** (`model_loader.cpp:238-249`): the GGUF branch left `config.vocab_size` 0 - correct for `MakeDflashGgufConfig` (the DFLASH arch has no vocab KV and no `token_embd`) but fatal for the forward, which sizes the shared embedding view as `{config.vocab_size, H}`, so the first propose threw `cuda embedding: empty table (vocab 0)`. Now back-filled from the target's `embed_tokens` rows (the condition is on the VALUE, not the draft source, so it generalizes to a GGUF target). Load-level green had hidden it; only GENERATING found it. **GD5-GD7 = axis B**: `SharedHeadSource` `src/vllm/entrypoints/model_loader.cpp` re-expresses the shared bf16 `embed_tokens`+`lm_head` seam as a SOURCE and re-types `LoadDflashDraft`'s second parameter - THAT TYPE was the whole axis-B blocker - with the GGUF arm `LoadGgufSharedEmbedAndHeadBf16` `src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp:1015` reusing the trunk loader's tied-embedding rule and sidecar-aware dequant instead of restating them; the shared-head load moved into ONE common tail so all four (draft format x target container) combinations run identical code; the `dflash` half of the GGUF-branch rejection `model_loader.cpp` is deleted (the `mtp` half untouched) and the draft load is wired into the GGUF branch | `tests/vllm/models/test_qwen3_dflash_gguf.cpp:36,84` 2 cases / 47 assertions against the REAL published Qwen3.6-27B DFlash draft (env-gated `VLLM_DFLASH_GGUF_MODEL`, CI asset-free): the +1 target-layer offset undone against the KV read back from the same file, block_size/mask_token present, vocab_size left 0, layer_types cover every block, fc `[H, H*num_taps]` with `nk` SET, qkv/gate_up row-concat shapes, embed/lm_head left EMPTY for the target. **RED-first BEHAVIOURAL** (dropping the `-1` fails the offset checks). **`GD4` e2e gate** `tests/parity/test_qwen27_dflash_spec_decode.cpp:343` (second case, draft source env-driven via `VLLM_DFLASH_DRAFT`/`_B`; asset-gated, CI-inert): on dgx GB10 sm_121a against the Qwen3.6-27B NVFP4 safetensors target, the Q4_K_M GGUF draft and the bf16 z-lab safetensors draft produce **token-for-token IDENTICAL** DFlash-ON continuations with **IDENTICAL** accepted/proposed (20/80 on a 24-token prompt, 42/96 on a 48-token prompt), spec-OFF self-reproducible 3/3 and 0 exact ties (min margin 0.197/0.400 nats). Regression: gguf_mtp 19, qwen35_gguf_spec_decode 10, gguf 103, gguf_qwen36_loader 99, gguf_keep_quant 5958, ops_gdn 1825, llm_engine 196, capi 232, runner 257 all unchanged. **`GD5` unit gate** `tests/vllm/test_gguf_qwen36_loader.cpp` 3 new synthetic-GGUF cases (6 cases / 286 assertions total, CPU and the dgx CUDA build): the untied head really comes from `output.weight` and not the embedding (distinct fill values), the tied fallback aliases it onto `token_embd`, the `nk` flags separate the gather table from the MatmulBT weight, a file with no `token_embd` is refused. 3-mutant battery, 3 caught (`nk` flipped, head forced to the embedding, tied forced false). **`GD7` e2e gate** `tests/parity/test_qwen27_dflash_spec_decode.cpp` third case (targets env-driven via `VLLM_DFLASH_TARGET_B`; asset-gated, CI-inert): on dgx GB10 sm_121a the Qwen3.6-27B NVFP4 **GGUF** target + `Q4_K_M` GGUF draft loads, takes the shared head from the GGUF, generates, and its DFlash-ON continuation is **token-for-token IDENTICAL to that same target's spec-OFF** (24/24, the STRICT form) with acceptance ALIVE at 14/160; 1 case / 15 assertions, exit 0. **The spike's highest risk is EMPTY on this asset, proven not assumed**: the 27B NVFP4 GGUF stores `token_embd`/`output` as ggml BF16, byte-identical to the safetensors sibling (2,542,796,800 bytes each, ZERO differing), so B1's shared-head read is verbatim, not a dequant. Acceptance IS lower than the safetensors-target arm and is NOT chargeable to the head: the two containers diverge at index 4 with NO speculation, because `QUANT-GGUF-NVFP4` is dequant-only so the GGUF target computes in bf16 while the safetensors target runs the true W4A4 kernels. **RE-MEASURED 2026-07-29 on a PRODUCTION-CONFIGURED build (`CLAIM-GGUF-SPEC-REVERIFY`), because every GD4/GD7 GPU number above came from a build configured WITHOUT `-DVLLM_CPP_CUTLASS_DIR` and WITHOUT `-DVLLM_CPP_TRITON=ON`.** Build proven correct three ways (see the `SPEC-MTP-GGUF` row; SACRED 27B **235/235**, `cuobjdump` 40 cubins all `sm_121a`). **AXIS B HOLDS EXACTLY**: `test_qwen27_dflash_spec_decode -tc="dflash axis-B*"` **15/15 assertions, exit 0**, GGUF-target DFlash-ON token-identical to that target's own spec-OFF 24/24, acceptance **14/160 unchanged**, cross-target spec-OFF divergence still at index 4, 81.01 GiB peak RSS, 6m53.08s. **AXIS A WAS RED ON THE 48-TOKEN PROMPT (reproducibly, 3 of 3 runs) AND IS NOW CLOSED.** The RED was real: cross-format TOKEN identity held on both prompts, but the exact accept-count half of bar (a) failed (`arm_a.proposed == arm_b.proposed` / `arm_a.accepted == arm_b.accepted`) because the Q4_K_M draft measured **46/112** against the bf16 z-lab draft's **47/96** (one extra 16-wide propose block, one fewer acceptance, zero token difference), 15/17, exit 1; the 24-token prompt stayed green at 17/17 with both drafts at 15/144. **`GD9` 2026-07-29 root-caused it IN WEIGHT SPACE as ordinary `Q4_K_M` cost, category (a), not a defect in our GGUF draft path - and the bar's own premise ("Same weights, two containers") was false for the asset it was pointed at.** The publishing repo also carries an UNQUANTIZED `BF16` GGUF (3,471,497,440 B) beside `Q8_0`/`Q6_K`/`Q5_K`/`Q4_K_M`, which the spec had recorded as nonexistent; that retired the `NOT APPLICABLE` on gate 2. CPU gate `tests/vllm/models/test_qwen3_dflash_gguf.cpp` third case (asset-gated `VLLM_DFLASH_GGUF_BF16_MODEL` + `VLLM_DFLASH_ST_DIR`): `LoadQwen3DFlashFromGguf(BF16)` is **BYTE-IDENTICAL to `LoadQwen3DFlash(z-lab shards)` on all 58 tensors, 302/302 assertions, exit 0**, and FUNCTIONALLY RED against the `Q4_K_M` file (21/302 red, exactly the 21 quantized matmul tensors), so not a vacuous pass. Supporting: our `DequantGgufRowToBf16` is bit-equal to `gguf-py`'s `gguf.quants.dequantize` on the real `fc.weight` (Q4_K), `blk.0.attn_q.weight` (Q4_K) and `blk.2.ffn_down.weight` (Q6_K), zero differing bf16 values; the ladder's mean relative weight error is monotone and uniform with NO outlier tensor (BF16 0, Q8_0 5.6e-3, Q6_K 1.85e-2, Q5_K 3.85e-2, Q4_K_M 7.6e-2); the only numeric config delta is `rms_norm_eps` at 2.5e-9 relative. Also landed: an off-by-default `VT_SPEC_TRACE=1` per-block propose/accept trace in `GPUModelRunner::sample_tokens_with_rejection` (`src/vllm/v1/worker/gpu/runner.cpp`). **`GD10` 2026-07-29 CONFIRMED IT END TO END ON GB10 and closed gates 3 and 5.** Build proven production-configured three ways (configure log 0 `CUTLASS not found`; `cuobjdump -lelf` 40 cubins ALL `sm_121a` zero `sm_75` on both binaries; SACRED `test_qwen27_paged_engine` **235/235, exit 0**, 31.34s, 23.67 GiB). The **`BF16` GGUF draft reads EXACTLY 47/96**, the safetensors draft's own number, at 48 tokens on the discriminating prompt - reproduced 2 of 2 - plus 27/64 = 27/64 at 24 tokens and 15/144 = 15/144 on the second prompt, tokens IDENTICAL throughout, 17/17 exit 0 each time; the `Q4_K_M` arm reads 46/112 on the SAME binary in the SAME `flock` series. Restoring only the draft's numeric precision restores the count, so quantization is the whole cause and nothing structural survives. Bar (a) is consequently SPLIT rather than relaxed (`tests/parity/test_qwen27_dflash_spec_decode.cpp`): tokens stay EXACT unconditionally; accept counts are EXACT on a cross-FORMAT arm and BANDED (`abs(d_accepted) <= 2`, `abs(d_proposed) <= k*2`) on a cross-QUANTIZATION one, with the arm chosen by `IsQuantizedGgufDraft` reading the draft file's ggml types (`GgmlTraits().block_elems > 1`) rather than by a flag. The band is derived, not picked: measured `d_accepted` is 0, 0, -1, so the bound is that maximum plus one quantum; and `d_proposed = -k * d_accepted` EXACTLY once the token streams match (confirmed at -1 / +16), so the proposed bound follows. **Mutation-proved non-vacuous**: rebuilt at band 0 the `Q4_K_M` arm is 15/17 exit 1 while the `BF16` arm stays 17/17 exit 0 on the exact branch. **AXIS B BROADENED from ONE prompt to THREE**, strict form green on all: "The capital of France is" IDENTICAL 14/160 (15/15), "Write a Python function that reverses a string:" IDENTICAL 24/64 (15/15), "Photosynthesis is the process by which" IDENTICAL 15/128 (9/9), all exit 0, ~6m30-6m52 and ~81 GiB peak RSS each. The second prompt REFINES the recorded acceptance claim: the safetensors-target arm is ALSO 24/64 there with the two containers' DFlash-ON streams IDENTICAL, so the GGUF target's lower acceptance is prompt-dependent (their spec-OFF streams diverge at index 4 on the first prompt, index 16 on the second) and not a standing penalty; the cause remains `QUANT-GGUF-NVFP4` being dequant-only, with the shared head excluded by a byte comparison. Gates 1-5 and 7 MET; gate 6 (speed) `PENDING` BY DESIGN and not owed - a DFlash-ON throughput A/B between the two target containers is not a fair comparison until a native NVFP4 GGUF GEMM exists. Evidence: [docs/BENCHMARKS.md](../docs/BENCHMARKS.md) top section, [parity-ledger.md](parity-ledger.md#L845) | [specs/gguf-dflash-draft.md](specs/gguf-dflash-draft.md) | `DONE` | `c62f2fa3` | | `SPEC-REJECTION` | Rejection sampler. **I3 verify half LANDED (2026-07-24)**: per-request logits EXPANSION to `1 + k_i` rows (`StepInputs::cu_num_logits` / `num_draft_tokens_per_req` / expanded `logits_indices`) plus the GREEDY rejection sampler — accept a draft iff it equals the target argmax at its own position, emit the target argmax on the FIRST mismatch and stop, emit the bonus argmax when all `k_i` accept, `num_sampled = accepted + 1`, `num_rejected = k_i - accepted` (feeds I2's `num_computed_tokens` rollback and `InputBatch::num_accepted_tokens`). One additive vt op (`kGreedyRejectionSample`) with a CPU reference and a CUDA two-phase mirror of upstream's row-argmax + one-thread-per-request accept walk. DEFAULT-OFF and INERT: with no `SpeculativeConfig` no drafts are ever scheduled, `cu_num_logits` is `arange(num_reqs+1)`, `logits_indices` is the pre-change array and the runner never enters the rejection branch. STOCHASTIC/Gumbel, block verification, `apply_sampling_params` over the expanded batch, and the spec grammar bitmask stay DEFERRED (M-mtp-3). **I5b DRAFTER PREFILL INPUT-PREP LANDED (2026-07-24, `CLAIM-SPEC-MTP-I5B`)**: the draft-token input splice this row's I3 note deferred to I5 — `vllm::v1::prepare_prefill_inputs` + its `SpecPrefillInputs` output struct shift each request's `input_ids` left one within its query span, splice the just-sampled next token (`num_sampled>0 ? last_sampled[idx_mapping[r]] : next_prefill_tokens[...]`) into the freed slot, `query_len -= num_rejected`, and emit last-token index / query_start_loc / seq_lens + CG padding (mirror `speculator.py:469-588`, k=1 early-exit :236-238). A HOST routine in a NEW spec_decode-tree TU (no new CUDA kernel; mirrors the DEVICE-NEUTRAL `prepare_inputs`/`combine_sampled_and_draft_tokens` family — the DGX runner leaf ports the loop to the Triton kernel at I5d), unit-gated `test_prepare_prefill_inputs` 7 cases / 27 assertions RED-first, DEFAULT-OFF INERT (nothing calls it until I5d), additive by construction. Row stays `ACTIVE` — the e2e greedy token gate (M-mtp-1) is owed before `DONE` | T1 | `vllm/v1/worker/gpu/spec_decode/rejection_sampler.py:43,101-160`; `rejection_sampler_utils.py:524,564-585,628,828-841,846-849,863-1125`; `vllm/v1/worker/gpu/model_runner.py:866-898,1065-1077`; `vllm/v1/worker/gpu/input_batch.py:303-397,408-453`; **I5b** `vllm/v1/worker/gpu/spec_decode/autoregressive/speculator.py:469-588,236-238` | `include/vllm/v1/spec_decode/rejection_sampler.h`; `src/vllm/v1/spec_decode/rejection_sampler.cpp`; `include/vt/ops.h` (`kGreedyRejectionSample`, `vt::GreedyRejectionSample`); `src/vt/cpu/cpu_sample.cpp` (CPU reference); `src/vt/cuda/cuda_sample.cu` (`RejectionRowArgmaxKernel` + `GreedyRejectAcceptKernel`); `src/vt/ops.cpp`; `include/vllm/v1/worker/gpu/prepare_inputs.h` + `src/vllm/v1/worker/gpu/prepare_inputs.cpp` (the expansion); `include/vllm/v1/worker/gpu/runner.h` + `src/vllm/v1/worker/gpu/runner.cpp` (`step_num_logits`, `sample_tokens_with_rejection`); **I5b** `include/vllm/v1/worker/gpu/spec_decode/autoregressive/prepare_prefill_inputs.h` + `src/vllm/v1/worker/gpu/spec_decode/autoregressive/prepare_prefill_inputs.cpp` — anchor `include/vllm/v1/spec_decode/rejection_sampler.h:96` | `tests/vllm/v1/spec_decode/test_rejection_sampler.cpp`; `tests/vllm/v1/worker/test_prepare_inputs.cpp` (expansion + no-draft byte-identity); `tests/vt/test_cuda_ops.cpp` (CUDA==CPU bit-exact at vocab 248320); **I5b** `tests/vllm/v1/spec_decode/test_prepare_prefill_inputs.cpp` (7 cases / 27 assertions, RED-first) — anchor `tests/vllm/v1/spec_decode/test_rejection_sampler.cpp:128` | [mtp-spec-decode.md §2.4,§5](specs/mtp-spec-decode.md) | `ACTIVE` | `CLAIM-SPEC-REJECTION-I3`, `CLAIM-SPEC-MTP-I5B` | | `SPEC-GDN-SEGMENTS` | GDN speculative metadata and slot-snapshot rollback. **I4 LANDED (2026-07-24):** the spec/non-spec metadata split with decode→prefill reclassification (the #34845 case), the `T>1`/`IS_SPEC` GDN recurrence with per-timestep state snapshots, the conv sliding window advancing by the ACCEPTED count, and the k+1 state-slot allocation. DEFAULT-OFF and INERT (`num_spec==0` ⇒ `num_spec_decodes==0`, no shipped kernel branched — both spec kernels are NEW op ids). ROLLBACK PROVEN bit-exact: for every rejection point j the surviving SSM state and conv window are memcmp-identical to running only the accepted prefix through the shipped `vt::GdnDecode`/`CausalConv1dUpdate`, at the real 27B (Hv=48) and 35B (Hv=32) GDN dims on CPU and CUDA. MEASURED state cost: one f32 SSM slot = Hv·Dv·Dk·4B ⇒ 144 MiB/req (27B, 48 layers) / 60 MiB/req (35B, 30 layers) per extra slot; k=1 doubles the GDN SSM state. **I5a GDN LAYER ROUTING WIRED (2026-07-24, `CLAIM-SPEC-MTP-I5A`):** `GdnBlockPaged`'s `num_spec_decodes>0` branch now routes a PURE-spec batch through `vt::CausalConv1dSpecUpdate` + `vt::GdnSpecDecode` (mirror `qwen_gdn_linear_attn.py:1344-1357,1455-1475`), and the runner per-step upload (`StepDevInputs`/`BuildStepDevInputs` + the two decode-graph `Refresh` copies) now carries I4's six spec device tensors, gated by the extended `ValidateGdnAttentionMetadata` spec contract. DEFAULT-OFF INERT (`num_spec_decodes==0` ⇒ stub uploads + the identical non-spec branch). BIT-EXACT vs the I4 ops applied as a token-sequential decode chain, at the real 27B/35B GDN dims, via `GdnBlockPagedForTest` (`tests/vllm/models/test_qwen3_5_gdn_spec_routing.cpp`, CPU bit-exact + CUDA on-device); RED-first by a reverted stub (spec recurrence zeroed ⇒ 4/8 fail, maxΔ 1.3-1.6). MIXED spec+non-spec batch refused loudly — lands with I5d's runner loop. Row advances to `ACTIVE`: the M-mtp-1 e2e greedy token gate (verify/propose runner wiring) is owed before `DONE`, and `SPEC-MTP` STAYS `GATING` | T1 | `vllm/v1/attention/backends/gdn_attn.py:189-326,413-462`; `fla/ops/fused_sigmoid_gating.py:66-72,103-116,156-166`; `mamba/ops/causal_conv1d.py:818-1067,1181-1184`; `qwen_gdn_linear_attn.py:1329-1576`; `mamba_utils.py:213-234`; `mamba/abstract.py:55-59` | `include/vllm/v1/attention/backends/gdn_attn.h`; `src/vllm/v1/attention/backends/gdn_attn.cpp`; `include/vt/ops.h` (`kGdnSpecDecode`, `kCausalConv1dSpecUpdate`); `src/vt/ops.cpp`; `src/vt/cpu/cpu_ops.cpp`; `src/vt/cuda/cuda_gdn.cu`; `src/vllm/model_executor/models/qwen3_5_common.{h,cpp}` (`MakeQwen3_5KVCacheSpec`); **I5a:** `src/vllm/model_executor/models/qwen3_5.cpp` (`GdnBlockPaged` spec branch, `StepDevInputs`/`BuildStepDevInputs`, `ValidateGdnAttentionMetadata`), `src/vllm/model_executor/models/qwen3_5_internal.h` (`GdnBlockPagedForTest`) | `tests/vllm/v1/attention/test_gdn_metadata_builder.cpp` (20 cases / 483 assertions incl. the full upstream `GDN_BUILD_TEST_CASES` + default-off byte-identity); `tests/vt/test_ops_gdn.cpp` (reject-at-every-j rollback, CPU + CUDA, real dims); `tests/vllm/models/test_model_registry.cpp` (k+1 slot / widened-conv sizing + `num_spec==0` identity); **I5a** `tests/vllm/models/test_qwen3_5_gdn_spec_routing.cpp` (spec-routing bit-exact, RED-first) — anchor `tests/vllm/v1/attention/test_gdn_metadata_builder.cpp:83` | [mtp-spec-decode.md §3,§5](specs/mtp-spec-decode.md) | `ACTIVE` | `CLAIM-SPEC-GDN-I4`, `CLAIM-SPEC-MTP-I5A` | | `SPEC-DFLASH` | Block-diffusion drafter. **READINESS RE-ASSESSED 2026-07-25 (`CLAIM-SPEC-DFLASH-READINESS`, design-only, DONE) against the LANDED MTP machinery (`SPEC-MTP` I1..I7).** Verdict **GREEN, dispatch-ready, NO hardware/oracle/download blocker** (spec [§0](specs/dflash-spec-decode.md)). Refreshed reuse-vs-new map: DFlash gets FREE from landed MTP — the frozen spec-metadata ABI, the greedy rejection sampler (k-general, I3 tested k∈{1,3}), the GDN spec slot path + rollback + mixed spec/non-spec batch (`GdnBlockPagedMixedSpec`/`IndexSelect`/`IndexCopy`, general `num_spec`), the widened-cache-aware conv ops (I5e), the draft-KV layer pattern (`fa_draft`), the I5d/I7 runner verify/propose loop, and **`num_lookahead_tokens=k+1` ALREADY coded** (`speculative.h:91-108` `use_dflash()`); EXTENDS the single I5d-pre `hidden_tap` seam to multi-tap `[T,H×taps]`; builds NEW the `qwen3_dflash` drafter, the project's FIRST non-causal in-block attention primitive, context-KV precompute, `prepare_dflash_inputs`, and the uniform-1+k FULL CG. **k>1 verdict:** the landed rejection + GDN machinery is MECHANICALLY k-general (no `k==1` hardwiring) — DFlash's k=15 blocks need NO mechanism extension, only exercise/validation at scale (D4) + the k+1-slot memory measurement (~2.3 GiB/req 27B GDN state at block-16, the #1 risk, §5). **Checkpoint-fit:** both z-lab drafts EXIST on HF (27B 1.73 GB / 35B 368 MB bf16, DFlashDraftModel) and FIT the 119 GiB pool trivially (drafts NOT yet on dgx — D0 downloads ≤1.73 GB); the active dgx oracle `vllm-oracle-v0.25.0-stage` CONSTRUCTS DFlash (registry `DFlashDraftModel→qwen3_dflash`, speculator dir present) — soft D0 risk = confirm it SERVES DFlash+NVFP4 on sm_121 (non-causal backend; community `AEON-7/vllm-dflash` container proves the combination runs on GB10). W-plan D0-D6 in the spec. **D0+D1 LANDED 2026-07-26 (`CLAIM-DFLASH-D0D1`) on the ADVANCED pin `555967922`/vLLM 0.26.0.dev0 — `SPEC-DFLASH` → `ACTIVE`.** D0 UNBLOCKED (vllm#40898 resolved under `VLLM_USE_V2_MODEL_RUNNER=1`): the mixed-attn z-lab 27B draft CONSTRUCTS + the drafter is ALIVE (acceptance 2.21/8.80/4.75/4.57 > 1, `num_spec=16`, flashinfer-native fp8-KV, goldens committed); gate FORM measured STRICT MODE-MATCHED (vLLM-ON run-deterministic K>=3 but != vLLM-OFF — the k=16 block verify diverges at bf16 near-ties, so NOT the MTP three-way identity). D1 `DF-AUX-TAPS` DONE: `Qwen3_5AuxTaps` + `ModelForwardInput::aux_tap` route to `Qwen3_5{,Dense}Model::ForwardDeviceMultiTap` capturing `(hidden+res)` at `target_layer_ids` into `[T,H×taps]` (eagle3 `_maybe_add_hidden_state`, aux key L+1); config-gated byte-identical off. Unit gate 598 assertions (independent truncated-model reference, RED-first reversed-concat 384 fail); CUDA 697/697 + compute-sanitizer 0; INERTNESS PROVEN — 27B MTP e2e 9/9 + 27B text SACRED 235/235 byte-identical on the new oracle. **D2 `DF-DRAFT-MODEL` CODE LANDED + CPU-GATED 2026-07-26 (`CLAIM-DFLASH-D2`, kernel row `KERNEL-ATTN-DFLASH-BLOCK`):** the `qwen3_dflash` draft model (plain 5-layer Qwen3-dense reusing `dense_attn_block.h` ops), the project's FIRST non-causal / bidirectional attention primitive `vt::DFlashBlockAttention` (a SEPARATE op — causal `kAttention`/`kPagedAttention` byte-identical), the fc aux-combine, mask-embed, per-layer SWA/full resolution, and the z-lab loader. CPU gate GREEN (op 12/12 incl. RED non-causal; model forward 95/95 incl. RED full-layer-causal-flip + block isolation + fc RED); existing causal `test_ops_attention` 9/9 + `test_qwen3_forward` 1028 UNCHANGED. **D2 GPU PROMOTION GREEN on dgx (`CLAIM-DFLASH-D2`):** CUDA `-Werror` clean, CUDA==CPU 198412/198412 + compute-sanitizer 0, draft-forward parity vs the REAL vLLM draft (fc rel-L2 0.46%, hidden ≤1.3%, 11 STRICT + 5 near-tie ids), 27B SACRED 235/235 + MTP 9/9 byte-identical — **D2 DONE.** **D3 `DF-DRAFT-KV-PREP` DONE 2026-07-26 (`CLAIM-DFLASH-D3`):** `PrecomputeContextKV` + `PrepareDflashInputs` + `ForwardBlockLogitsWithContext` (reuse the UNCHANGED D2 kernel via [context;block]); GPU numeric-parity `test_qwen3_dflash_kvprep_parity` 61/61 (prepare INTEGER bit-exact vs vLLM's Triton kernel, context-KV K/V rel-L2 0.31%/0.26%, 13 STRICT + 3 near-tie = 16/16), CPU 114/114 RED-proven, inertness 235/235 + 9/9 + D2 37/37 byte-identical. **D4 `DF-ENGINE-INTEGRATION` propose brick + `dflash` config-select CODE LANDED + CPU-GATED 2026-07-26 (`CLAIM-DFLASH-D4D5`):** `DflashProposeBlock`/`SampleDflashBlockDrafts` (the non-autoregressive whole-block propose composing D3 `ForwardBlockLogitsWithContext` + greedy per-mask argmax, anchor not sampled, `dflash/speculator.py:300-413`) + `ParseSpeculativeConfigJson`/`ResolveDflash` accept `method:"dflash"`. CPU gate `test_dflash_propose` 5/19 GREEN (RED-first anchor-read fails 4/5; brick composes forward+sampler; empty-ctx degenerates to D2; config lookahead k+1). Additive + config-gated ⇒ MTP + non-spec byte-identical BY CONSTRUCTION (`git diff --stat` = new speculator TU + config accept-list + CMake + test, NO runner/model/loader/scheduler edit). **D5 `DF-ENGINE-INTEGRATION` runner-loop LANDED + e2e RUNS on dgx 2026-07-26 (`CLAIM-DFLASH-D5`):** full verify/propose loop wired — loader loads the SEPARATE z-lab draft (`LoadDflashDraft`, host bf16 + target-SHARED bf16 embed/lm_head) via a `--speculative-config` `model` key + `ResolveSpecConfig` dflash branch + `runner.set_dflash_draft`; the verify forward captures the D1 multi-tap (`aux_tap`→`ForwardDeviceMultiTap`) instead of the MTP single tap; `propose_drafts_dflash` ACCUMULATES the per-request combined-feature context (`CombineAuxFeatures(aux_tap)`) across steps and honors the `num_rejected` rollback by appending only the `(T_req−num_rejected)` accepted-prefix features, then runs `DflashProposeBlock` (k=16 GDN-spec exercised first time). **e2e (`test_qwen27_dflash_spec_decode`, 4 prompts×32 tok, our-DFlash-ON vs the committed vLLM-DFlash-ON golden): 2/4 STRICT token-exact (fibonacci, three-laws) + acceptance ~ vLLM on ALL 4 (accepted 19/39/29/25 vs golden 17/39/30/25, deltas +2/0/−1/0 — the MANDATORY dead-drafter-trap condition MET).** The 2 divergences (France tok11 `2972`↔`11751`, 17*23 tok12 `567`↔`488`) are SINGLE bf16 near-tie flips (17*23 RE-CONVERGES after one token = proven near-tie; France cascades from one flip) — the ratified near-tie ROOT the D0 gate-form anticipated, rooted in the D3-documented inline bf16 context-KV recompute envelope (~0.3-1.3% rel-L2), NOT a wiring bug (proven by the 2 exact prompts + near-exact acceptance + a non-trivial shared prefix). Inertness GREEN on this build: SACRED `test_qwen27_paged_engine` 235/235 + MTP `test_qwen27_spec_decode` 9/9 byte-identical; CUDA `-Werror` clean; NO new CUDA kernel (host orchestration reusing D1/D2/D3-sanitized ops). **NOT a clean strict-4/4 pass; STRICT 4/4 token-identity + the speed A/B = D6 (the persistent paged draft-KV bit-matching vLLM's fused context-KV projections + the uniform-1+k FULL CG).** Row STAYS `ACTIVE` (correctness at the ratified near-tie envelope; D6 remains) **D6 2026-07-27 (`CLAIM-DFLASH-D6`) — c1 SPEED A/B DONE + STRICT-irreducibility RCA + CG feasibility (records-only, NO source code):** (1) **c1 speed A/B** (`examples/vllm-bench` at `361189a7`, 8 prose+code prompts×256 tok greedy c1, 2 reps): our DFlash-ON = **2.50x TPOT (40.4 vs 101.2 ms) / 2.48x output-tput (24.4 vs 9.86 tok/s)** over our OFF, acceptance 0.22 (3.56/16), rep-stable <1.5%; `benchmark_binding=true`. vs vLLM-DFlash-ON graphed (same workload): vLLM-DFlash-ON graphed = 28.5 tok/s / 35.1 ms TPOT / acceptance_len 4.30 (same 8 prompts, `VLLM_USE_V2_MODEL_RUNNER=1`, mm-off, gpu_util 0.30), so OURS IS ~14% BELOW vLLM-DFlash-ON on output throughput (24.4 vs 28.5 tok/s) - both ~on-par at spec-OFF (9.86 vs 9.83 tok/s), but vLLM extracts a larger DFlash speedup (2.90x vs our 2.47x) because its draft step is fully device-resident + CUDA-graphed (ours host-orchestrates 13 downloads/step) + slightly higher acceptance (~4.3 vs ~3.6 draft tokens/step). The DONE speed bar (ours >= vLLM) is NOT met; closing it = the device-resident draft rewrite + FULL CG (D6 part 2). (2) **STRICT-4/4 proven bf16-IRREDUCIBLE** — the draft KV cache is bf16 not fp8 (`torch_utils.py:398` `auto`→model dtype; the D0 "fp8-KV" was the backend name, not the KV storage dtype), the D3 golden already compares pre-storage bf16 (residual K 0.31%/V 0.26% = sub-ULP kernel noise), and a fused multi-layer KV GEMM is per-element invariant to our per-layer GEMMs ⇒ bit-exact needs vLLM's exact kernels ⇒ the ratified near-tie gate is the FINAL correctness form (no fused-KV code landed). (3) **FULL CG BLOCKED** on a device-resident draft-path rewrite (the D5 path does 13 device→host downloads/step + host `[context;block]` interleaving) — the remaining throughput-parity increment (the perf form of persistent-paged-KV + the graph). Inertness by construction (the gated binary is the D5 binary; SACRED 235/235 + MTP 9/9 stand). Evidence tool `scripts/spec/vllm_dflash_timing.py`. **D7 2026-07-27 (`CLAIM-DFLASH-D7`) — within-step draft forward made DEVICE-RESIDENT (source-owning): `PrecomputeContextKVDevice` keeps per-layer K/V on device; `ForwardBlockLogitsWithContext` builds [context;block] with `vt::IndexCopy`/`IndexSelect` (removes ~30 D→H `Download`s/step). BIT-IDENTICAL (identity bf16↔f32 round-trips replaced) — e2e `test_qwen27_dflash_spec_decode` 27/27 SAME tokens (2/4 STRICT + 2/4 near-tie, acceptance 19/39/29/25), SACRED 235/235 + MTP 9/9, CUDA `-Werror` clean, compute-sanitizer 0 (198412). But the direct old-vs-new A/B = +2.0% output-tput (IN-NOISE) ⇒ D6's "downloads = the ~14% gap" REFUTED by measurement; ours 19.68 tok/s STILL ~33% BELOW vLLM-DFlash-ON 29.2 tok/s (reconstructed 8-prompt set, more prose-heavy); OFF parity our 9.97 ≥ vLLM 9.66. Residual re-attributed: acceptance (ours 2.49 vs vLLM ~3.13 accepted draft-tok/step, bf16-irreducible) + per-step context-KV RECOMPUTE (O(context²), needs the cross-step persistent paged draft-KV store) + eager-vs-graphed. SPEED BAR NOT met; SPEC-DFLASH stays `ACTIVE`; next = persistent paged draft-KV store → then FULL CG. **D9 2026-07-27 (`CLAIM-DFLASH-D9`) — PERSISTENT PAGED DRAFT-KV LANDED (bit-identical, +22.7% throughput, 0.69×→0.917×); D8 acceptance-ceiling REFUTED; residual = FULL CG ONLY:** `qwen3_dflash.cpp` `AppendContextKVHost` (project ONLY newly-accepted rows → per-layer bf16 K/V, append to `PrecomputedContextKV`) + `ForwardBlockLogitsWithPrecomputedKV` (upload the persistent store, NO re-projection) share the core `ForwardWithCtxKVDev` with the old recompute; `runner.cpp::propose_drafts_dflash` swaps the O(context²) per-step recompute (`dflash_ctx_feats_`) for an append-only per-request `dflash_kv_store_` (rollback=don't-append). NO new CUDA kernel; config-gated. BIT-IDENTICAL: CPU `test_dflash_propose` two new D9 cases = exact float equality vs full recompute; GPU e2e `test_qwen27_dflash_spec_decode` **27/27 SAME tokens** (acceptance 19/39/29/25, same divergences France@11/17×23@12); SACRED 235/235 + MTP 9/9 byte-identical; CUDA `-Werror` clean. **A/B (c1, 8 prose+code×256 tok input-len 512, 2 reps <0.1%, `benchmark_binding=true`):** ours-ON **25.75 tok/s** (was D8 20.99, +22.7%) / 38.40 ms TPOT / acc **3.68/step** vs vLLM-ON graphed **28.09** / 35.60 / acc 3.31 = **0.917×** (~8% below, was 0.69×). **Part 1 same-trajectory:** on the 2 token-identical-trajectory prompts ours per-step acceptance == vLLM's EXACTLY (fibonacci 7.80/7.80, three-laws 3.571/3.571, ratio 1.00) AND on the A/B ours acceptance (3.68) is HIGHER than vLLM's (3.31) ⇒ D8's 0.80–0.85× "bf16 acceptance ceiling" is a trajectory-divergence CONFOUND, REFUTED. Residual (~8%) = eager-vs-graphed ONLY (ours ON/OFF 2.60× vs vLLM 2.91×, OFF at parity, recompute eliminated, acceptance higher) — NOT an irreducible ceiling; the FULL uniform-(1+k) CG (device paged-KV store + paged attn, new-CUDA multi-file) is the SOLE un-landed increment. SPEC-DFLASH stays `ACTIVE` (speed not yet ≥ vLLM; residual isolated to FULL CG). **D12 2026-07-27 (`CLAIM-DFLASH-D12`) — A-wire + Part B LANDED + GPU-gated; Part C (capture) remaining; 0.917×:** A-wire makes the D11 Part-A device store the PRODUCTION path (`runner.{h,cpp}` `dflash_kv_store_`→`shared_ptr`, `MakeDeviceKVStore`/`AppendContextKVDevice`/`ForwardBlockLogitsWithDeviceKV`; GPU-gated e2e `test_qwen27_dflash_spec_decode` 27/27 all-exact acceptance 19/39/29/25 + SACRED 235/235 + MTP 9/9 byte-identical, `-Werror` clean). Part B adds `vt::DFlashPagedBlockAttention` (`OpId::kDFlashPagedBlockAttention`), the capture-safe paged kernel with EVERY metadata input a persistent DEVICE tensor and NO function-local host `cu_seqlens` upload (fixes the `cuda_ops.cu:1277-1280` capture-UAF class), gated CPU==CUDA + cross-check vs materialized `DFlashBlockAttention` `test_ops_dflash_paged_block_attn` 795648/795648 + compute-sanitizer 0. Speed 0.917× (A-wire eager + Part B not yet wired into the forward); `benchmark_binding=false`. Part C (static-shape capture + device mask-scatter + `BeginCapture`/replay + the ≥vLLM c1 A/B) is the SOLE remaining piece; if ours-ON-graphed ≥ vLLM-ON → SPEC-DFLASH DONE. Stays `ACTIVE`. **D13 2026-07-27 (`CLAIM-DFLASH-D13`) — Part C LANDED + GPU-GATED; capture-correctness PROVEN; c1 throughput NEAR-PARITY (ours 0.978x, ~2% below vLLM); gap CLOSED 0.917x→0.978x; STAYS `ACTIVE` (≥vLLM bar not yet met):** single-file additive change (`qwen3_dflash.cpp` +368/-58). (C.1) `DflashDeviceKVStore` → fixed-capacity PAGED cache (per-layer pool `[max_pages,16,Hkv,Dh]` + identity `block_table` + `seq_lens`; append = `vt::IndexCopy` scatter at slot==abs-pos, bit-identical to the D9/D11 store). (C.2) `ForwardPagedBody` runs the (1+k) block through the D12 `vt::DFlashPagedBlockAttention` reading the paged store (no `[context;block]` materialization, no function-local host uploads); runner P==1 propose routes through it, P>1 bit-identical materialized fallback. (C.3) per-request CUDA GRAPH over the paged draft step (warm-in-step repopulates the shared pool free-list right before `BeginCapture` — the fix for a `cudaMalloc`-in-capture `Get` miss from the intervening 27B target forward — then `BeginCapture → ForwardPagedBody → EndCaptureGraph`, replay with growing context entering only via in-place `seq_lens`). **Capture-correctness (MANDATORY): `test_qwen27_dflash_spec_decode` 27/27 with the graph (VT_DFLASH_GRAPH=1) BIT-IDENTICAL to eager (=0)** — same divergence tokens (France@11 got[…2972…], 17×23@12 got[…567…]), same acceptance 19/39/29/25 as D5/D7/D9/D12; graph ENGAGED (5 captures C=2048/5/4/15/6, 32+ replays); the token-diff is the capture-safety proof ([[cudagraph-capture-bakes-stack-addresses]]). **c1 A/B (one flock series, cold rep discarded, 8 prompts×256 tok):** our OFF 10.24 / our ON eager-paged 28.65 (28.69,28.61) / **our ON GRAPHED 28.70 (28.70,28.70), TPOT 34.40** / vLLM-ON graphed steady-state 29.35 (tight 3-rep 29.33/29.37/29.33, TPOT 34.07, acc_len 4.44); D9's 28.09 was a colder cross-session outlier — **NEAR-PARITY: ours 0.978× (~2% below) on the rigorous same-session band** (across sessions ours 28.70 falls inside vLLM's observed 28.09–29.37 range). ON/OFF 2.80× (vLLM ~2.98×), our OFF ≥ vLLM OFF. Per the acceptance rule ("below on any axis = an open gap; near-parity is NOT met"), the ≥vLLM bar is NOT met; STAYS `ACTIVE`. Residual (data-grounded): NOT acceptance (ours realized ~3.68 accepted draft-tok/step > vLLM's 3.44) and NOT launch/graph (both graphed, CG neutral) — per-step COMPUTE (~2% slower target-step); next lever = nsys both draft steps (`--cuda-graph-trace=node`), no premature ceiling. **ATTRIBUTION (supersedes D9):** the CUDA graph is perf-NEUTRAL (+0.3%); the ACTUAL lever was the paged context read (C.1/C.2) removing the D9/D12 per-layer `[context;block]` `IndexCopy` materialization of the whole growing context (25.75 D9 → 28.65 eager-paged, +11%) — the roadmap's "the full CG closes the gap" premise is corrected by measurement. Inertness VERIFIED on the capture binary: SACRED 235/235 + MTP 9/9 byte-identical, CUDA `-Werror` clean, no new kernel (D12 paged kernel already memcheck-0 795648), `check-device-leakage` not increased (paged path REMOVES the materialized-buffer allocs + host uploads). `benchmark_binding=true`. Correctness-complete (ratified near-tie); throughput NEAR-PARITY (0.978×, ~2% residual) ⇒ STAYS `ACTIVE` (the capture-correctness gate is MET; the ≥vLLM speed bar is the sole remaining item, a ~2% per-step-compute residual for an nsys). Anchors: `src/vllm/model_executor/models/qwen3_dflash.cpp` (`DflashDeviceKVStore` paged store, `ForwardPagedBody`, the per-request graph in `ForwardBlockLogitsWithDeviceKV`). **D14 2026-07-27 (`CLAIM-DFLASH-D14`) — SPEED GATE MET → SPEC-DFLASH `DONE`:** an nsys (`--cuda-graph-trace=node`) of the graphed spec-on step attributed the D13 ~2% residual to the from-scratch `DFlashPagedBlockAttentionKernel` draft attention (242.9 ms = 1.8% of GPU time, median ~460 us/call over context C~500-640, vs vLLM's fused flash draft-attn ~0.15%; BOTH engines run identical `cutlass_80_wmma` for the draft bf16 GEMMs, so the GEMMs were NOT the gap). Ported it to a WARP-scoped online-softmax variant `DFlashPagedBlockAttentionWarpKernel` (mirrors the shipped `AttentionWarpKernel`: one warp per (block-query,head), `__shfl_xor` butterfly reduction, register accumulator, NO `__syncthreads` storm; SAME paged/block combined-index read + causal/SWA mask + GQA; default ON, `VT_DFLASH_ATTN_BLOCK=1` keeps the bit-identical D12/D13 block kernel for A/B). Draft attn 242.9 → 77.9 ms (3.1x); our-ON c1 28.60 → 29.32 tok/s (+2.5%). **FINAL same-session 3-rep A/B (8 prompts×256 tok, cold leg discarded): our-ON graphed 29.42/29.27/29.32 (med 29.32) vs vLLM-ON graphed 29.240/29.247/29.233 (med 29.240) — our WORST rep (29.27) > vLLM's BEST (29.247), NON-OVERLAPPING bands, 1.003× ⇒ the ≥vLLM speed gate is MET.** Correctness UNCHANGED (output is exact by spec-decode construction — the target verify is untouched, only which draft proposals are accepted can shift): e2e `test_qwen27_dflash_spec_decode` 27/27 with graph==eager BIT-IDENTICAL, acceptance 19/39/29/25 unchanged (draft accepted 1629 identical warp-vs-block across the whole A/B set), 2/4 STRICT (France@11, 17×23@12 unchanged); CUDA==CPU `test_ops_dflash_paged_block_attn` 795648/795648 (warp within the f32 1e-4 / bf16 3e-2 envelope) + compute-sanitizer 0. Inertness SACRED 235/235 + MTP 9/9 byte-identical; CUDA `-Werror` clean; `check-device-leakage` not increased. `benchmark_binding=true`. Block-diffusion drafting is now correctness-complete (ratified near-tie) AND at/above vLLM throughput — this was the roadmap's FINAL open speed item. Anchors: `src/vt/cuda/cuda_ops.cu` (`DFlashPagedBlockAttentionWarpKernel` + `UseDflashAttnBlockKernel`; the D12 block kernel retained as the `VT_DFLASH_ATTN_BLOCK=1` reference). | T1 | `vllm/v1/worker/gpu/spec_decode/dflash/speculator.py`; `vllm/model_executor/models/qwen3_dflash.py`; `vllm/model_executor/models/interfaces.py:1382` (aux value); `eagle3_utils.py:41-56` (+1 shift) | `include/vllm/model_executor/models/qwen3_5.h` (`Qwen3_5AuxTaps`, `ForwardDeviceMultiTap`); `qwen3_5_dense.h`; `model_registry.h` (`aux_tap`); `src/vllm/model_executor/models/qwen3_5.cpp` (`MaybeCaptureAuxTap`/`ValidateAuxTapLayerIds`/`ForwardDeviceMultiTap`); `qwen3_5_moe.cpp`+`qwen3_5_dense.cpp` (routing); D2/D3 `include/vllm/model_executor/models/qwen3_dflash.h` + `src/vllm/model_executor/models/qwen3_dflash{,_weights}.cpp`; D4 `include/vllm/v1/worker/gpu/spec_decode/dflash/speculator.h` + `src/vllm/v1/worker/gpu/spec_decode/dflash/speculator.cpp` (`DflashProposeBlock`/`SampleDflashBlockDrafts`); D5 `src/vllm/entrypoints/model_loader.cpp` (`LoadDflashDraft`/`DflashDraft`) + `include/vllm/entrypoints/model_loader.h`; D5 `src/vllm/v1/worker/gpu/runner.cpp` (`set_dflash_draft`/`propose_drafts_dflash`/aux-tap capture) + `include/vllm/v1/worker/gpu/runner.h`; `src/vllm/config/speculative.cpp` + `include/vllm/config/speculative.h` (`ResolveDflash` + `dflash`/`model` parse); D14 warp kernel [cuda_ops.cu](../src/vt/cuda/cuda_ops.cu#L1433) | `tests/vllm/models/test_qwen27_paged_forward.cpp` (multi-tap 598); `tests/vt/test_ops_dflash_block_attn.cpp`; `tests/vllm/models/test_qwen3_dflash_forward.cpp`; `tests/vllm/v1/spec_decode/test_dflash_kvprep.cpp`; `tests/parity/test_qwen3_dflash_{draft,kvprep}_parity.cpp`; D4 `tests/vllm/v1/spec_decode/test_dflash_propose.cpp` (5/19, RED-first); D5 `tests/parity/test_qwen27_dflash_spec_decode.cpp` (e2e 27/27, 2/4 strict + acceptance~vLLM); `scripts/spec/d{0,2,3}_dflash_*.py`; `tests/parity/goldens/dflash_27b{,_draft,_kvprep}/`; D6 `scripts/spec/vllm_dflash_timing.py` (vLLM-DFlash c1 timing); D7 device-resident `src/vllm/model_executor/models/qwen3_dflash.cpp` (`PrecomputeContextKVDevice` + `ForwardBlockLogitsWithContext` via `vt::IndexCopy`/`IndexSelect`); D9 persistent paged draft-KV `qwen3_dflash.{h,cpp}` (`AppendContextKVHost`/`ForwardBlockLogitsWithPrecomputedKV`/`ForwardWithCtxKVDev`/`PrecomputedContextKV`) + `runner.{h,cpp}` (`dflash_kv_store_`/`propose_drafts_dflash`) + `tests/vllm/v1/spec_decode/test_dflash_propose.cpp` (2 D9 bit-identity cases); D12 A-wire `runner.{h,cpp}` (device store as production path) + D12 Part B `include/vt/ops.h`/`src/vt/ops.cpp`/`src/vt/cpu/cpu_ops.cpp`/`src/vt/cuda/cuda_ops.cu` (`kDFlashPagedBlockAttention`) + `tests/vt/test_ops_dflash_paged_block_attn.cpp` (CPU==CUDA + cross-check, 795648/795648 + sanitizer-0); D13 `src/vllm/model_executor/models/qwen3_dflash.cpp` (fixed-capacity paged `DflashDeviceKVStore` + `ForwardPagedBody` + the per-request draft-step CUDA graph in `ForwardBlockLogitsWithDeviceKV`); D14 [test_ops_dflash_paged_block_attn](../tests/vt/test_ops_dflash_paged_block_attn.cpp#L79) + [ledger](parity-ledger.md#L738) | [dflash-spec-decode.md](specs/dflash-spec-decode.md) | `DONE` | `489a7544` | @@ -227,7 +227,7 @@ claims it. | `ENG-RELEASE-WINDOWS` | Native Windows x86_64 pre-alpha release extension: one adaptive MSVC/UCRT CPU bundle with AVX2 executed in CI and one Vulkan preview bundle, both deterministic ZIPs and authenticated by the existing release handoff | T0 | vLLM has no Windows release path; runtime behavior remains pinned to vLLM `555967922`. Platform substrate reference: llama.cpp `src/llama-mmap.cpp:520-590` @ `237ad9b961f009ae19ac29dbce4cd0c1251f94b3`; Win32 API is the OS authority | W14 Win32 portability/MSVC CPU, W15 deterministic ZIP/PE packaging + Vulkan, and W16 ten-tuple prerelease workflow/version/docs implemented for one PR | Linux portability/release mutation gates are local evidence only. Native `windows-2022` MSVC `/W4 /WX`, extracted runtime/ISA smokes, merged-SHA ten-tuple dry run, `v0.0.3-pre.1` publication, attestations, and exact 32-asset audit remain pending; no Windows ZIP exists yet | [windows-binary-release.md](specs/windows-binary-release.md); [#117](https://github.com/mudler/vllm.cpp/issues/117) | `ACTIVE` | `CLAIM-ENG-RELEASE-WINDOWS` | | `ENG-RELEASE-CONTAINERS` | Published OCI container images on GHCR, built by GitHub Actions: the same staged server bundle as `ENG-RELEASE-BINARIES`, shipped from one package `ghcr.io/mudler/vllm.cpp` with the lane in the tag — `:-cuda` / `-vulkan` / `-cpu`, the moving `:latest-cuda` / `:latest-vulkan` / `:latest-cpu`, and a bare `:latest` aliasing the cpu lane, with `ENTRYPOINT vllm-server`. Lanes `cuda` (one fat image covering every supported SM), `vulkan`, `cpu` (adaptive baseline); `rocm` blocked-preview, tracking its binary channel. Version tags are immutable; every `latest-` moves. Each lane is a `linux/amd64` + `linux/arm64` multi-arch manifest built on native runners — aarch64 is first-class here because GB10 (sm_121a), Thor (sm_110) and Orin (sm_87) are all arm64. The image contains the bundle and nothing else: no weights, no Python, no PyTorch, no compiler, no build tree. BOUNDARY: the GPU driver and container runtime stay on the host and are never bundled; Metal and MLX are NOT-CONTAINERIZABLE (no macOS container runtime and no Metal passthrough exists) and remain static-binary-only lanes, recorded as a permanent boundary rather than pending work. No image, workflow, registry package or pull is claimed to exist. | T0 | release image lanes `.buildkite/release-pipeline.yaml:34-170` and the published-image dependency boundary `docker/Dockerfile.cpu:262-290` @ `555967922` | `docker/Dockerfile` (cpu/vulkan/cuda targets calling the release scripts); `docker/healthcheck.sh`; `release/container-matrix.json`; `scripts/check-container-matrix.py`; `scripts/check-container-workflow.py`; `scripts/validate-container-image.py`; `scripts/container_tags.py`; `.github/workflows/containers.yml`; SIGTERM handler `src/vllm/entrypoints/openai/server_main.cpp` (`SignalShutdown`, all three `listen()` sites); the pre-existing `docker/Dockerfile.arm64` is an unrelated CPU bench cross-check | issues `#170`, `#312`, `#394`; `tests/scripts/test_check_container_matrix.py` 31/31; `test_check_container_workflow.py` 29/29; `test_check_cuda_fat_gencode.py` 7+4 subtests. **GB10 2026-08-11 (`promaxgb10-4ad8`, `sm_121a`, CUDA 13.3): arm64 cuda image 1.71 GB, 673/673 objects, ten-SM gencode audit PASS, and a REAL GPU boot -- `/health` 200, `/version` 200, in-container healthcheck, clean SIGTERM, `--gpus all`, host driver 580.159.03 injected.** cpu amd64 783 MB gated locally; cpu+vulkan amd64 green on hosted CI **arm64 cuda lane RUNTIME-VERIFIED on GB10 2026-08-11** -- the first accelerator-hardware evidence for any lane. Four defects were removed to get there, each found by building rather than reading: the CUDA 12.9 base could not compile `sm_110`, the BuildKit cache mount outlived its toolchain (both #366), Marlin gencode had drifted from the feature table and failed the audit on 14 correctly-compiled TUs (#394, blocking BOTH cuda tuples project-wide), and the validator could only ever produce build evidence because its boot smoke never passed `--gpus`. **NOT established: nothing is published to GHCR; amd64 cuda is unbuilt; the published arm64 image is SBSA (`targets/sbsa-linux`), so Tegra -- Thor `sm_110`, Orin `sm_87` -- is untested and NOT covered** **ORIN (Tegra) 2026-08-11: the SBSA image RUNS on Jetson AGX Orin `sm_87` (L4T R36.4.3, Docker 27.5.1) -- Qwen3-0.6B (rev `c1899de2`) loads and GENERATES via `/v1/completions`, tegrastats GR3D 95-97% during decode vs 14-15% idle.** Tegra needs `--runtime nvidia --gpus all`: `--gpus` alone is refused by the hook and `--runtime` alone mounts no driver | [container-images.md](specs/container-images.md); issues [#170](https://github.com/mudler/vllm.cpp/issues/170), [#312](https://github.com/mudler/vllm.cpp/issues/312), [#394](https://github.com/mudler/vllm.cpp/issues/394) | `ACTIVE` | `CLAIM-ENG-RELEASE-CONTAINERS-W1-W7` | | `ENG-DOCS-SITE` | Publish the 11 `docs/*.md` as a browsable GitHub Pages site at `https://mudler.github.io/vllm.cpp/` WITHOUT a second copy of the prose. A Hugo site at `website/` mounts `../docs` READ-ONLY and derives everything else from what is already in the files: each page title from the file's first `# H1`, the sidebar order from `website/data/nav.yaml`, and links through a Goldmark render hook (internal `.md` → site URL; the 139 `../.agents/**` and `../AGENTS.md` escapes → GitHub blob URLs, since the protocol tree is deliberately NOT published). **No file under `docs/` is modified, moved, renamed, or given front matter**, so `check-doc-checkpoint.py` and every protocol path reference keep working and there is no second surface that can drift — the whole point of the row. Custom lean layouts, NO theme and NO submodule: off-the-shelf docs themes read titles, weights and menus out of front matter this design deliberately does not have, so each would need its title partial, menu and link hook overridden anyway, and hugo-book additionally floors at Hugo 0.158 against the 0.146.3 pin CI and the local toolchain share. Hard prerequisite inside the repo: `classify_path` in `scripts/check-pr-size.py` FAILS CLOSED on `website/**` (verified: raises `ValueError: unclassified repository path`), so the classifier must learn the path or the PR cannot pass the project's own size gate. Hard prerequisite outside it: GitHub Pages must be enabled with the source set to GitHub Actions — the workflow is inert otherwise. A marketing landing page is explicitly OUT of scope (`README.md` stays the front door), as is any restructuring of `docs/`; the custom domain is parked behind the pending vLLM trademark question | T1 | NO vLLM analogue — upstream's docs are a separate mkdocs site and nothing in this row mirrors upstream *behavior*, so it carries no parity obligation. The STRUCTURAL reference is LocalAI's `.github/workflows/gh-pages.yml` (two Hugo sites merged into one Pages artifact), reduced to the docs half | read-only mount `website/hugo.toml:29`; title-from-H1 `website/layouts/partials/title.html:10`; link rewriting `website/layouts/_default/_markup/render-link.html:27`; guard `scripts/check-site.py:70`; deploy `.github/workflows/gh-pages.yml` | `tests/scripts/test_check_site.py:51,56,66,80,89,97` (6 mutation cases: clean tree, H1 stripped, doc absent from nav, nav entry with no file, duplicated entry, missing nav file); build evidence 14 pages with `docs/bench-evidence` + `docs/superpowers` absent from `public/` and no `href` ending in `.md`; 48 protocol links rewritten in `docs/status/`. NO published page is claimed: GitHub Pages is not yet enabled on the repository, which is the recorded stop condition holding this row at `GATING` | [gh-pages-docs-site.md](specs/gh-pages-docs-site.md); issue [#224](https://github.com/mudler/vllm.cpp/issues/224) | `READY` | `CLAIM-ENG-DOCS-SITE` | -| `ENG-RECORD-ANCHOR-RATCHET` | **The record's `path:line` citations were range-checked and never reported.** `check-agent-record.py` parsed BOTH forms: markdown links, and bare `` `file.cpp:123` `` through `RAW_LOCAL_ANCHOR_RE` since `ee511ca8a`. On a missing file or an out-of-range line `local_line_anchors` runs `continue`, so the bad anchor never reaches the caller, and `is_code_anchor` then answers with **any**, so one good sibling covers the rest. There was no symbol test and no report, and **32 of the 38** offenders are IN RANGE, so range-checking could not have found them. Measured at `8daa67b39`: **832 of 867** in-scope citations (**96.0%**) were already parsed and range-checked, and the **35** new to parsing sit under `.agents/`, `docs/` and `website/`; `EVIDENCED_STATES` omits `ACTIVE`/`READY` entirely and is deliberately NOT widened, because requiring an anchor there raises 85 errors across 53 rows. Even the fraction it saw was only range-checked, never checked to CONTAIN the symbol named beside it — every stale anchor found in the 2026-08-13/14 campaign was in range. LANDED as a device-leakage-shaped ratchet over a recorded baseline, never a bulk cleanup: the backlog is fixed by whoever next touches each row | T1 | none — this is our own record surface; the discipline mirrors AGENTS.md §Records ("cite the `file:line` you ported from") | parser + classifier + ratchet in `check-agent-record.py`: `scripts/check-agent-record.py::BARE_CITATION_RE` (the bare form), `scripts/check-agent-record.py::cell_citations` (both forms, with the adjacent-symbol rule), `scripts/check-agent-record.py::classify_citation` (OK / STALE / BROKEN), `scripts/check-agent-record.py::RECORD_ANCHOR_STATES` (gap 3: `ACTIVE` and `READY` join the count), `scripts/check-agent-record.py::check_record_anchors` (the two-way gate). SYMBOL-anchored rather than line-anchored as of `SPEC-DFLASH2` W2, which added a justification paragraph to this file's `KERNEL` count and shifted all five ranges by 14 lines at once -- the rot this row exists to measure, produced by an edit to the very file the row cites; budget in `scripts/record-anchor-baseline.json` | `RecordAnchorRatchet` `tests/scripts/test_agent_record.py:1494` — 10 cases, RED-first, including `test_one_good_link_does_not_cover_a_rotted_bare_citation` `tests/scripts/test_agent_record.py:1562`, the `any()` shape the rot hid in. Five mutants red it: report-only, `EVIDENCED_STATES` restored, links-only, first-citation-only, range-only. Measured baseline **38** (32 STALE + 6 BROKEN); gate wired in `scripts/agent-preflight.sh` and the `agent-record` CI job (`--report`) | [record-anchor-ratchet.md](specs/record-anchor-ratchet.md) | `ACTIVE` | `CLAIM-ENG-RECORD-ANCHOR-RATCHET` | +| `ENG-RECORD-ANCHOR-RATCHET` | **The record's `path:line` citations were range-checked and never reported.** `check-agent-record.py` parsed BOTH forms: markdown links, and bare `` `file.cpp:123` `` through `RAW_LOCAL_ANCHOR_RE` since `ee511ca8a`. On a missing file or an out-of-range line `local_line_anchors` runs `continue`, so the bad anchor never reaches the caller, and `is_code_anchor` then answers with **any**, so one good sibling covers the rest. There was no symbol test and no report, and **32 of the 38** offenders are IN RANGE, so range-checking could not have found them. Measured at `8daa67b39`: **832 of 867** in-scope citations (**96.0%**) were already parsed and range-checked, and the **35** new to parsing sit under `.agents/`, `docs/` and `website/`; `EVIDENCED_STATES` omits `ACTIVE`/`READY` entirely and is deliberately NOT widened, because requiring an anchor there raises 85 errors across 53 rows. Even the fraction it saw was only range-checked, never checked to CONTAIN the symbol named beside it — every stale anchor found in the 2026-08-13/14 campaign was in range. LANDED as a device-leakage-shaped ratchet over a recorded baseline, never a bulk cleanup: the backlog is fixed by whoever next touches each row | T1 | none — this is our own record surface; the discipline mirrors AGENTS.md §Records ("cite the `file:line` you ported from") | parser + classifier + ratchet in `check-agent-record.py`: `scripts/check-agent-record.py::BARE_CITATION_RE` (the bare form), `scripts/check-agent-record.py::cell_citations` (both forms, with the adjacent-symbol rule), `scripts/check-agent-record.py::classify_citation` (OK / STALE / BROKEN), `scripts/check-agent-record.py::RECORD_ANCHOR_STATES` (gap 3: `ACTIVE` and `READY` join the count), `scripts/check-agent-record.py::check_record_anchors` (the two-way gate). SYMBOL-anchored rather than line-anchored as of `SPEC-DFLASH2` W2, which added a justification paragraph to this file's `KERNEL` count and shifted all five ranges by 14 lines at once -- the rot this row exists to measure, produced by an edit to the very file the row cites; budget in `scripts/record-anchor-baseline.json` | `RecordAnchorRatchet` `tests/scripts/test_agent_record.py:1502` — 10 cases, RED-first, including `test_one_good_link_does_not_cover_a_rotted_bare_citation` `tests/scripts/test_agent_record.py:1570`, the `any()` shape the rot hid in. Five mutants red it: report-only, `EVIDENCED_STATES` restored, links-only, first-citation-only, range-only. Measured baseline **38** (32 STALE + 6 BROKEN); gate wired in `scripts/agent-preflight.sh` and the `agent-record` CI job (`--report`) | [record-anchor-ratchet.md](specs/record-anchor-ratchet.md) | `ACTIVE` | `CLAIM-ENG-RECORD-ANCHOR-RATCHET` | | `ENG-RECORD-CONFLICT-SURFACES` | Retire the shared record surfaces that make concurrent PRs conflict by construction. MEASURED at `origin/main` `d928e2c3` with `git merge-tree --write-tree` over every open PR: **16 of 29 conflict (55%), and 13 of the 16 conflict in bookkeeping files ONLY**, with no product code involved — `.agents/coordination.md` in 8, `.agents/NOW.md` in 5, `.agents/roadmap_v1.md` in 4, `scripts/check-public-doc-tables.py` in 4, `docs/STATUS.md` in 4, and any `src/`/`tests/` path in just 3. Three defects, each of which GUARANTEES rather than risks a collision. (1) `.agents/NOW.md` is a fixed-size shared buffer at EXACTLY 6000/6000 chars (`check-now-current.py:31`), so adding a row requires evicting another and every PR is a read-modify-write of one global — and the conflict is the LUCKY outcome, since a clean three-way merge would apply both evictions and both additions, silently dropping live rows and blowing the very budget the checker defends. (2) `STATUS_RATCHET = {"chars": 243245}` (`check-public-doc-tables.py:557`) is a hardcoded byte count of a DIFFERENT file that may only fall, so a PR owing `docs/STATUS.md` one lifecycle line must delete unrelated prose from another row to pay for it and edit the checker too; the checker's own comment at `:331` already records the failure (*"a ratchet pinned to the byte turns every concurrently merged row's one-line status edit into a spurious failure"*) and answered it with slack instead of removing the coupling. (3) `.agents/coordination.md`'s active-claims table is insert-at-one-anchor: the six ROCm GDN PRs (#334 #336 #341 #343 #345 #348) are ONE author's sequential stack that conflicts on nothing else, each appending a ~1,500-char row — the PR description, transcribed into a file every other claim also writes. It also contradicts the protocol it serves: `AGENTS.md` holds that *"History is git"* and *"There is no state log"*, yet both claims tables ARE state logs duplicating `gh pr list`, `row/` branch names and issue state; the argument that refuses a waiver registry applies unchanged to a claims registry. Precedent twice over — `policy.csv` retired in `0f3e44ee`, per-class line budgets retired 2026-08-10 because the gate fired on ordinary work. The exonerated surfaces share ONE property, one writer per file: `.agents/specs/.md` (one file per row, **zero conflicts** in the sample), the `*-matrix.md` inventories, and the append-only `.agents/benchmark-record.md`. SCOPE: remove `STATUS_RATCHET` and the doc-gating global counters while KEEPING the per-cell/per-paragraph caps (local, so they couple nothing); remove the active-claims table and derive claims from open PRs and branch names; drop `NOW.md`'s byte budget; order the roadmap's keyed tables by ID so distinct keys stop colliding at one anchor; and record the invariant — **no surface that every PR must write** — in `AGENTS.md`. No product source, kernel or gate semantic moves | T0 | NO vLLM analogue — this is local protocol machinery, so the mirror rule does not apply and no upstream `file:line` exists to port from. Governed instead by `AGENTS.md` §"Changing the rules or a checker", which requires a spec, a red-before test or mutation, and green-after evidence | - | - (spec-before-code: the red-before suites are named in the spec's Tests section — `tests/scripts/test_check_public_doc_tables.py`, `tests/scripts/test_check_now_current.py`, a mutation case per removed rule proving the obligation survives in the retained caps and `check-doc-checkpoint.py`, and a `git merge-tree` merge-shape regression that must be RED before the `NOW.md`/roadmap work and GREEN after) | [retire-shared-record-surfaces.md](specs/retire-shared-record-surfaces.md); issue [#364](https://github.com/mudler/vllm.cpp/issues/364) | `READY` | `CLAIM-ENG-RECORD-CONFLICT-SURFACES` | | `ENG-TRAILER-MERGE-ARTIFACTS` | The trailer gate rejects CORRECT commits because of paragraph placement, and that is why `main` is red on `agent-record`. `check-commit-trailers.py` reads trailers via `git interpret-trailers --parse`, which treats ONLY the final paragraph as the block; GitHub appends `Co-authored-by:` as a SEPARATE trailing paragraph on a squash merge, so a complete correct block becomes invisible and the gate reports it missing. MEASURED: piping `git show -s --format=%B dbd0d51c` into `git interpret-trailers --parse` prints nothing but the co-author line, and 13 of the last 30 commits on `main` fail the check -- unnoticed only because those runs were cancelled (#274), which HID the defect rather than causing it. FIX: fuse consecutive trailing TRAILER-SHAPED paragraphs before parsing. Nothing is relaxed -- the block must still exist, the marker must still sit above it, each declaration must still appear exactly once, and an AI co-author is still forbidden; the block is merely FOUND where the merge tool left it. A prose paragraph still terminates it. REJECTED IN FLIGHT and recorded because it is the more instructive half: a first attempt also collapsed identical duplicate trailers to fix the multi-commit-squash shape, which relaxes the uniqueness rule an existing test already pins. Rewriting that assertion to suit the change is what AGENTS.md forbids, and the distinction is real -- a doubled block is genuinely malformed and fixable at source, whereas the co-author case is a correct commit defeated by the parser. Reverted in full. SCOPE LIMIT, stated rather than implied: this fixes ONE of five observed shapes. `f64f2b71` (bot co-author) is a REAL violation the parse had been hiding and now correctly fails; `87308dea` (GitHub's `---------` separator), `b8293c88` (squash doubled the block) and `b580452d` (merge button, no trailers) stay red by design. Closing those is a merge-method change, not a checker change | T0 | NO vLLM analogue -- local protocol machinery, so the mirror rule does not apply and there is no upstream `file:line` to port from. Governed by `AGENTS.md` §"Changing the rules or a checker" | `scripts/check-commit-trailers.py:60` (`join_trailing_trailer_paragraphs`, `_is_trailer_paragraph`, and the fused `parsed_trailers`) | `tests/scripts/test_check_commit_trailers.py:1` 21 cases -- the RED-BEFORE appended-co-author case plus four GUARDS that keep the fusion bounded (doubled block still fails, contradictory declarations still fail, a no-trailer merge message still fails, prose after the block still fails), all four green before and after; closure [parity-ledger.md#L941](parity-ledger.md#L941) | [trailer-merge-artifacts.md](specs/trailer-merge-artifacts.md); issue [#406](https://github.com/mudler/vllm.cpp/issues/406) | `DONE` | `157080c8` | | `ENG-FORGE-COAUTHOR` | The forbidden-AI-trailer rule was catching ATTRIBUTION rather than an authorship claim, which is why bot-opened PRs red `main` on merge. GitHub composes the squash message itself and appends the account that opened the PR — `Co-authored-by: localai-org-maint-bot <...@users.noreply.github.com>` — and most PRs here are opened by a bot, so nearly every squash trips the AI-identity check. Real instance `f64f2b71`, invisible until #406 repaired the parse, which is why it reads as a new failure and is not one. The rule exists so an AI cannot claim it WROTE the code, and that stays; GitHub is recording who pressed the button, and the AI-involvement claim is already carried separately by `AI-Assisted` and `Assisted-by` in the same block. FIX: accept a `Co-authored-by` at a GitHub account noreply address even when the name matches an AI identity token, keyed on the FORGE'S OWN DOMAIN rather than the name so the exemption cannot be borrowed. A hand-written `Co-authored-by: Claude ` still fails; `Signed-off-by` is excluded from the exemption entirely, because a sign-off is a legal assertion about provenance rather than attribution. `AGENTS.md` records the same distinction in the same change so prose and checker cannot drift | T0 | NO vLLM analogue -- local protocol machinery, so the mirror rule does not apply and there is no upstream `file:line` to port from. Governed by `AGENTS.md` §"Changing the rules or a checker" | `scripts/check-commit-trailers.py:38` (`FORGE_ACCOUNT_EMAIL` and the forbidden-trailer skip) | `tests/scripts/test_check_commit_trailers.py:1` 25 cases -- the RED-BEFORE forge-bot case plus THREE guards that matter more than the relaxation because this LOOSENS a rule: a hand-written AI co-author still fails, `Signed-off-by` at the same noreply address still fails, and a human co-author still passes; all three green before and after. Real commit `f64f2b71` re-verified per commit | [forge-coauthor-attribution.md](specs/forge-coauthor-attribution.md); issue [#418](https://github.com/mudler/vllm.cpp/issues/418) | `ACTIVE` | `CLAIM-ENG-FORGE-COAUTHOR` | diff --git a/.agents/issue-index.md b/.agents/issue-index.md index e26686fa4..e61bf525f 100644 --- a/.agents/issue-index.md +++ b/.agents/issue-index.md @@ -748,6 +748,7 @@ rather than merged. `scripts/check-agent-record.py` gates both. | [#526](https://github.com/mudler/vllm.cpp/issues/526) | `SERVE-TOOL-HISTORY-ARGS` | OpenAI multi-turn tool history reaches chat templates with string-valued arguments | bug | | [#1934](https://github.com/mudler/vllm.cpp/issues/1934) | `BACKEND-ROCM` | `RocmPlatform::needs_weight_staging()` is stale-false (a W0-era placeholder never revisited despite #523/#509/#506/ROCM_ATTN/hipGraph landing since), so `CheckDeviceWeightFit` — the #1123/#1870 load-time refusal, including the `policy_forces_full_expand` fix — never runs on ROCm: measured directly, `VT_DEVICE_WEIGHT_BUDGET_BYTES=1` produced no refusal on a real load. The actual device allocation the refusal guards is not gated on this flag, so #1870's crash stays reachable until this closes; owed, not fixed in flow, because flipping the flag also moves `DirectDeviceLoadEligible` and several GDN kernel-dispatch defaults that each need their own correctness check | bug | | [#1978](https://github.com/mudler/vllm.cpp/issues/1978) | `MODEL-MM-QWEN4-EXP` | **`Qwen/Qwen3.8-Flash-Next` declares `Qwen4ExpForConditionalGeneration` / `qwen4_exp`, a new architecture vLLM does not implement, so the port runs on a split oracle: transformers for the ALGORITHM, vLLM ops for the OPTIMIZED PATH.** Released 2026-08-24, 180B total / 6B activated, image-text-to-text. The `Qwen3.8` in the name is marketing continuity: `.agents/specs/qwen38-27b-bf16-gate.md`'s "one config key differs" precedent does NOT extend here. Read live 2026-08-26 at vLLM `origin/main` = `6a5e8f5979`: no `qwen4*` path, no registry entry, and a repository-wide GitHub search for `qwen4` returns ZERO results; `vllm-omni` likewise. That is absence from vLLM `main` rather than staleness in our pin `555967922`, so a pin advance does not reach it. What exists is transformers [#48337](https://github.com/huggingface/transformers/pull/48337) "Add Qwen4Exp model", MERGED 2026-08-26, 5211 lines, and SGLang [#36497](https://github.com/sgl-project/sglang/pull/36497), still OPEN and therefore inadmissible. **Developer direction 2026-08-26, recorded verbatim: "use transformers as oracle for algorithmic side. but use ops from vllm so we account for optimized path."** Justified rather than convenient: `Qwen4ExpTextQSAIndexer.forward` loops in Python over `(batch_idx, query_idx)` and says "we only allow eager and sdpa", so porting it as written yields a correct model at an indefensible speed, while AGENTS.md's mirror-vLLM polarity still binds every primitive vLLM implements. `Qwen4ExpTextModel` inherits from `Qwen3_5MoeTextModel` and leaves rotary, MLP, experts, TopK router and the ENTIRE vision tower unchanged (`class Qwen4ExpVisionModel(Qwen3_5MoeVisionModel): pass`), all of which this tree has; GDN is an exact match for our AOT gate (`K=V=128, Hg=16, Hv=48` against `src/vt/cuda/cuda_gdn.cu`'s `H in {48,32}`). The delta is four things, and **exactly two have no vLLM op at all**: the PLE dilated depthwise conv (kernel 4, dilation 3; `git grep dilation` over vLLM `layers/mamba/` = 0 hits) and the n-gram hashed embedding. **The survey's load-bearing finding, and it REVERSES this row's first reading: QSA's structural twin is DeepSeek-V4's C4 indexer lane, NOT MiniMax-M3.** The original call was that QSA, being plain GQA rather than MLA, had to map onto vLLM's non-MLA block-sparse case; that reasoning rested on treating `MLAAttentionSpec` as an MLA claim, and **it is not one** — M3's own indexer cache uses it while M3 is a plain-GQA model, with the comment "Key-only: MLAAttentionSpec budgets one vector/token (not 2x for K+V)". It is a budget shape. Remove that prop and the GQA-vs-MLA argument collapses. Verified line by line at `6a5e8f5979`: **nine independent structural matches with DSv4**, `compress_ratio == 4` literally the same number — MQA index with 1 key head at dim 128; `relu(q.k)` summed over index heads vs `(score.relu() * weights).sum(dim=0)`; `1/sqrt(head_dim)`; one score set per query token with no head axis vs `topk_indices_buffer[num_tokens, topk]`; pooling boundary `(position+1) % COMPRESS_RATIO == 0`; RMSNorm on the pooled key; **RoPE at the block-start position** vs `compressed_pos = (position // CR) * CR`; candidate count `visible // compress_ratio`; and one stored state per 4 tokens via `MLAAttentionSpec(tokens_per_state=compress_ratio)`, a first-class KV field documented as "Ints > 1 compress multiple tokens into one state (DSv4 sparse MLA)" that has no M3 equivalent. **M3 is a DIFFERENT ALGORITHM**, not a worse fit: its score is `tl.max(qk, axis=1)` over 128 RAW token dots with no pooling, no relu and no head reduction, it asserts `num_idx_heads == num_kv_heads` ("no topk index reduce") so it emits one block set PER KV HEAD, and its `SPARSE_BLOCK_SIZE = 128` is welded to the KV page size ("One sparse block == one KV page") on both the score and the attend side — moving it to 4 forces a page size of 4 and breaks `tl.dot`, whose tile needs >= 16. M3 contributes exactly ONE thing and it is a wiring precedent, not an algorithm: that a plain-GQA model can own a key-only side cache through `MLAAttentionSpec` and a private indexer backend. **The genuinely new work is the CONSUMER and nothing upstream supplies it** — every DSv4 sparse consumer attends to COMPRESSED MLA KV (one state per 4 tokens) and M3's attend to raw tokens only at page granularity, while QSA attends to RAW tokens selected at ratio-4 granularity. Two silent-failure traps follow: wiring QSA's top-k into a DSv4 sparse-MLA consumer attends a POOLED key/value and still emits plausible tokens, and **a short-prompt token gate cannot catch it because at context <= `indexer_budget` 2048 every candidate is selected** — so any QSA gate must run past 2048 tokens of context, which is now a stated `## Gates` requirement; and `SparseAttnCompressNormRopeStoreC4Kernel` does NOT mean-pool despite its name — it is a learned softmax pool over an OVERLAPPING window of 8 using a score channel this checkpoint does not have, and the CuteDSL variant refuses `overlap=False` at compile, so the **Triton** `head_dim=128` variant is the correct starting point. Two structural consequences beyond the module list: the residual stream is `hc_count * hidden_size` = **4 x 2560 = 10240 wide through the whole stack** with a low-rank read gate and per-branch scalar write gate around both attention and MLP, which is a change to the per-layer loop and every residual buffer rather than a drop-in module; and `number_of_conv_states = 3` on a PLE layer (GDN conv, PLE conv, n-gram token history) plus the indexer side cache, adjacent to [#1963](https://github.com/mudler/vllm.cpp/issues/1963) and [#1966](https://github.com/mudler/vllm.cpp/issues/1966). **NOTHING PUBLISHED FITS**, read live from the HF API against ~119 GB usable on GB10: BF16 ~360 GB, official FP8 ~180 GB, `RadixArk/...-NVFP4` ~128 GB (NVFP4 backbone with the n-gram table left at FP8, 51.2 GB) and `unsloth/...-GGUF` is a README with ZERO weight files. No GGUF exists and no tool can make one, because llama.cpp has no `qwen4_exp` either, so the standing k-quant requirement means authoring the arch on our side AND states that the quantized arms have NO llama.cpp oracle. **The chosen arm does NOT load today, and the blocker is neither the offload nor the budget: this tree cannot keep a gather table quantized at all.** `KeepQuantKDim` returns `-1` for `GgufTensorRole::kEmbeddingTable` (`src/vllm/model_executor/model_loader/gguf_keep_quant.cpp`), and `qwen3_5_gguf_weights.cpp` asserts it by name — "the embedding table cannot keep quant blocks" — so a Q4_K or Q8_0 n-gram table EXPANDS to bf16 and 51.2B params become **102.4 GB of anonymous memory**; the arm dies before the first forward. The reason was already sitting in a header comment ("a gather, not a GEMM ... A quantized-gather op is a follow-up row") and **no such row exists**. The only non-expanding gather residency is `kKeepF16`, requiring ggml type 1 exactly (102.4 GB on disk) and CPU-ONLY, because `EmbeddingKernelCuda` refuses anything but f32/bf16. **Second blocker:** `moe_intermediate_size = 640` makes `ffn_down_exps` Q4_K-illegal on its reduction dim (640 % 256 = 128), as does `hc_lowrank = 320`; llama.cpp's substitution is believed to be Q5_0 (**UNVERIFIED, owed against the pinned llama.cpp oracle**) and the dependent fact IS verified in-tree — our reader knows ggml ids `0,1,2,8,10..14,16,18,19,22..28,30,39,40,41,66` and has **no entry for 3, 6, 7 or 20**, so a stock `llama-quantize -Q4_K_M` file fails at header parse. We author the converter, so the fix is Q4_0 (block 32, same 4.5 bpw). **`ENG-WEIGHT-OFFLOAD` will not help** — it moves zero bytes today (`ConsiderWeight` has no production callers, pinned by a test) and is documented inert on GB10; the tier that DOES work already ships and is proven by the 2.4T model serving 369.97 GiB from a 119.631 GiB box at ~62 GiB resident: mmap `MAP_PRIVATE`, borrow in place, alias the host pointer, `prefault: false`. Corrected sizing: backbone ~67.7 GiB, whole process ~73.5 GiB of 119.631 at 32K single-stream, ~46 GiB of headroom for the page cache, so the ~76 GB estimate was right within 10%. The design works because per-token demand is **<= 64 KiB of reads** (16 lookups x 160 dims over at most 16 pages) against the 2.4T expert lane's 6.95 GB/token. The architecture supplies its own lever: the per-token n-gram cost is `(ngram_size-1)*heads_per_ngram` = 16 lookups of 160 dims, so **51 GB of the 180 GB, 28% of the model, is a table touched 16 times per token** and making it non-resident is the intended design point (RadixArk reached the same split independently). Sizing arithmetic, NOT measurement: Q8_0 throughout ~191 GB (no), Q4_K_M throughout ~109 GB (yes, ~10 GB left for KV), Q4_K_M backbone with the table non-resident ~76 GB. GB10 is UNIFIED memory so "offload to host" is not a move there; non-resident means disk-backed, and its cost is unmeasured. **Two decisions were put to the developer as explicit accept-or-reject and BOTH are settled 2026-08-26, recorded in place rather than left open.** (1) `.agents/oracles/transformers.md` pins transformers to 5.14.1, deliberately tied to what the pinned vLLM environment resolves so the environment cannot hold two `transformers` at once, and **5.14.1 does not contain `Qwen4Exp`**; the lane-scoped second pin is **ACCEPTED**, on the argument that the invariant guards a vLLM environment against drifting from its transformers and here there is no vLLM implementation to drift from, and it expires the moment vLLM registers `qwen4_exp`. **The lane pin is a real release, not a branch SHA**, which was not the expected outcome: `Qwen4Exp` merged to `main` at 12:03:40Z on 2026-08-26 and `v5.16.0` published at 12:35:15Z, and this was BOUNDED rather than assumed by fetching `models/qwen4_exp/modeling_qwen4_exp.py` at each tag — `v5.16.0` HTTP **200**, `v5.15.0` HTTP **404** — making 5.16.0 the FIRST release carrying the architecture and therefore the tightest available pin. The version string is UNMEASURED (it is the release proven to contain the model, not a `transformers.__version__` read off a running oracle) and `gateable` stays `no`. (2) The first runnable arm is the **Q4_K_M backbone with the n-gram table NON-RESIDENT** (~76 GB). Q8_0 was raised and does not fit at ~191 GB, and no partial-Q8 split reaches 119 GB with the backbone at 8 bits; Q4_K_M-throughout fits on paper at ~109 GB but leaves ~10 GB for KV and activations on a 262144-native-context model, which is not a margin. This promotes the non-resident table from a note to a first-class W6 deliverable, and it is NOT free: GB10 is UNIFIED memory, so the existing host-pinned offload seam (`ENG-WEIGHT-OFFLOAD`, mirroring vLLM's `cpu_offload_gb`) does not by itself solve it there and the mechanism must be disk-backed or genuinely unloaded — established before it is designed around. Spec: [`specs/qwen4-exp-flash-next.md`](specs/qwen4-exp-flash-next.md). No product code lands under the spec pull request | feature | +| [#1989](https://github.com/mudler/vllm.cpp/issues/1989) | `MODEL-MM-QWEN4-EXP` | **W6a: the GGUF reader had no `case 20`, so `GgufFile::OpenOne` died at header parse on shard 2 of the ONLY published Qwen3.8-Flash-Next artifact that fits any device this project owns.** `unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S` is 67.56 GiB in three shards against ~119.6 GiB usable on GB10, where bf16 is ~360 GB, the official FP8 ~180 GB and NVFP4 ~128 GB; the GGUF arm is therefore the path to a running model, not a follow-up to a safetensors one. Read live 2026-08-26 by HTTP range request over the shard headers: `general.architecture = "qwen4exp"`, `split.tensors.count = 1224` (shard 1 is 67 keys and ZERO tensors, shards 2 and 3 carry 595 and 629), `per_layer_token_embd.weight` IQ4_NL `[160, 320001536]`, `ffn_down_exps` IQ4_NL `[640, 2560, 512]`, `ffn_{gate,up}_exps` IQ1_S or IQ2_XXS, `indexer.{q,k}_proj` left BF16. IQ4_NL (id 20) appears 49 times and is unavoidable rather than a recipe preference: `moe_intermediate_size` 640 and the table row 160 are neither a multiple of 256, so no K-quant can encode them, and upstream's own `tensor_type_fallback` drops `IQ4_XS -> IQ4_NL` and `Q4_K -> Q5_0` — VERIFIED at the pin, `src/llama-quant.cpp:374-405 @ b10451`, discharging the spec's UNVERIFIED item; the same table maps `Q5_K -> Q5_1` (id 7), which we still lack, so a `-Q5_K_M` build of this model remains refused. **Second blocker, independent of the first:** a gather table could not be kept quantized AT ALL. `KeepQuantKDim` returned `-1` for `GgufTensorRole::kEmbeddingTable` and `qwen3_5_gguf_weights.cpp` asserted it by name, so a quantized 51.2 G-parameter n-gram table expanded to **102.4 GB of anonymous memory** against 28.8 GB of IQ4_NL blocks — the end of the box before the first forward. The `-1` was CORRECT until this row: without a dequantizing gather a kept table is bytes nothing can read. Both are closed. `vt::Embedding` now takes a block-quantized table and decodes ONE ROW per gathered id, a port of `ggml_compute_forward_get_rows_q` (`ggml/src/ggml-cpu/ops.cpp:4850 @ b10451`), and the table's residency follows the ordinary policy behind a gather-specific admission rule (`KeepQuantGatherDType`: a row DECODER, not the GEMM arm's `vec_dot`) and a device gate (`DeviceQuantGatherSupported`). Both new decoders are gated BIT-EXACTLY against the pinned llama.cpp decoding REAL bytes of the shipped tensor, read by range request at absolute offset 364622656 of shard 2 — ten IQ4_NL blocks, two whole gather rows — with the oracle built from a clean `git archive b10451` rather than from a working checkout. **The CUDA gather arm is OWED and it is the expensive half:** `EmbeddingKernelCuda` still refuses a block table, so on CUDA the table keeps expand-bf16, and a device-resident quantized table gathered on device is exactly the shape llama.cpp's #27742 does NOT have (it pins the n-gram table to the CPU by tensor class), which is where this model's high-concurrency advantage lives. Also landed: the `qwen4exp` config builder in its OWN translation unit with its own dispatch row, deliberately NOT reusing `HfConfigFromGguf`, which asserts its own three architectures by name and would refuse a fourth family as "qwen3_5 gguf:" — the #809 defect. Its key names follow llama.cpp #27742, which is what the shipped file uses, and it carries the architecture-specific numbers under the RELEASED `config.json`'s own spellings; `ple.layers` is the one exception, kept under its GGUF name because the file says `[1]` where config.json says `ple_layer_ids: [2]` and nothing in either resolves the offset. **Landed unreached, named per "Nothing lands dead":** the config builder IS reached through `kGgufArchArms`, but `ModelRegistry` does not resolve `Qwen4ExpForConditionalGeneration` — the model wiring is owed to [#1978](https://github.com/mudler/vllm.cpp/issues/1978) and listed under `## Owed` in `specs/qwen4-exp-flash-next.md`. No forward, no token claim and no speed claim from this wave | feature | | [#1988](https://github.com/mudler/vllm.cpp/issues/1988) | `MODEL-MM-QWEN4-EXP` | **W3 of the Qwen4-Exp port: the 4-branch GATED-RESIDUAL hyper-connection stream and the grouped RMSNorm it stands on.** The residual stream is `hc_count * hidden_size` = 4 x 2560 = 10240 wide through the whole 48-layer stack, read and written twice per layer, and collapsed at the end by the same class with its injection branch switched off — a change to every residual buffer, not a drop-in module. Landed here as a HOST reference (`src/vllm/model_executor/models/qwen4_exp_hc.{h,cpp}`) gated against goldens dumped by EXECUTING the lane-pinned oracle source: transformers `v5.16.0` `models/qwen4_exp/modeling_qwen4_exp.py` (sha256 `77fec77d…`), `Qwen4ExpTextRMSNorm` (:158-181) and `Qwen4ExpTextGatedResidual` (:941-969) lifted verbatim by line range, plus an independent double-precision reference. The grouped norm mirrors vLLM's op form — `RMSNormGated` (`layers/layernorm.py:172`, `group_size` at `:187`, grouped branch `:258-264`) with the gate disabled, NOT the plain `RMSNorm` (`:37`), whose only related knob is `var_hidden_size`, a prefix reduction that cannot express per-group norms. **Three findings the gate now pins.** (1) The `1 + w` parameterization: transformers applies `out * (1.0 + weight)` on a ZERO-init weight while vLLM applies `out * weight` on a ONES-init one, they coincide only under a load-time `w = 1.0 + w_hf`, and the published GGUF has that fold applied at CONVERT time — so it lives in exactly one named function, `HcNormWeightFromHf`, and skipping it scales every `hc_norm` by ~0 (reads as a checkpoint bug) while applying it twice scales by ~2x. (2) The two divisions by `hc_count` are different: one is INSIDE the SiLU on the `[320]` low-rank intermediate BEFORE the activation (`silu(down(x)/4)`, not `silu(down(x))/4`; SiLU is not homogeneous), the other is inside the injection sigmoid with the whole sigmoid scaled by 2 (`2*sigmoid(inject(x)/4)`, range (0,2), exactly 1.0 at a zero logit), and there is NO division on the up-projection sigmoid. (3) The elementwise multiply uses the NORMED stream, the reduce over hc is a MEAN and not a sum, and `hyper_input` is written back RAW. **The spec's `MhcPost`-with-identity-comb reuse claim is VERIFIED rather than trusted**, by a bit-equality case against our DeepSeek-V4 kernel; it holds on finite inputs and is not an identity for a negative-zero or non-finite residual, neither of which is reachable here. **Not reached at its merge commit** — W1 config registration (#1986) is still in review, so nothing loads a `qwen4_exp`; the wiring is owed by W5 (assembly) under #1978 and is listed in the spec's `## Owed`. No token claim and no speed claim: no arm of this model runs on any fleet device. | feature | | [#1991](https://github.com/mudler/vllm.cpp/issues/1991) | `MODEL-MM-QWEN4-EXP` | **W4, Qwen Sparse Attention: the indexer, its side cache, and a GATHER consumer rather than a mask.** New TU `src/vllm/model_executor/models/qwen4_exp_qsa.{h,cpp}` (`indexer_n_heads` 4, `indexer_kv_heads` 1, `indexer_head_dim` 128, `indexer_budget` 2048 tokens, `indexer_compress_ratio` 4, so `block_topk` 512 and an index buffer 2051 wide). Ported onto DeepSeek-V4's C4 indexer lane per the spec's reversal, with the two things it must NOT inherit reconciled in code: DSv4's `weights_proj` + `head_scale` fold (QSA's weight is the constant `1/sqrt(128)`, applied after the sum over heads) and DSv4's GPT-J RoPE over a TRAILING span (QSA is NeoX `rotate_half` over the LEADING 64 dims with the NoPE dims trailing — the halves are swapped end for end AND the pairing convention differs). `SparseAttnCompressNormRopeStoreC4Kernel` supplies scaffolding only: its `tl.softmax(score, dim=0)` pool over an overlapping window is replaced by an unweighted mean over a non-overlapping window of 4. **The consumer is a GATHER, and that is the wave's point.** llama.cpp [#27739](https://github.com/ggml-org/llama.cpp/pull/27739) records that a sparse mask over a dense cache costs the same as dense attention under CUDA flash attention, because `flash_attn_mask_to_KV_max` only scans back to the first tile that is not all `-inf`; a mask-only QSA is CORRECT and forfeits the lever silently. Proved rather than asserted: mutating `QsaGatherAttention` to delegate to the mask reference leaves **11 of 12 cases green** and fails only `qsa-consumer: the GATHER touches only the selected rows`, so the `keys_visited` counter is the only instrument in the suite that can tell the two apart. Goldens come from RUNNING the unmodified `Qwen4ExpTextQSAIndexer.forward` at the transformers 5.16.0 lane pin (`tests/vllm/models/fixtures/gen_qwen4_exp_qsa_goldens.py`), which resolves the spec's UNMEASURED version string: `transformers.__version__` reads **5.16.0** on a live import, and the oracle runs on CPU with random weights, no checkpoint and no GPU. Both free oracles are used: sub-budget bit-identity with dense attention at kv_len = `budget + compress_ratio - 1`, and index-set equality including the ragged tail above the budget. Side cache is `MLAAttentionSpec(num_kv_heads=1, head_size=128, tokens_per_state=4)` = **64 B/token/layer at bf16**, asserted rather than defaulted, with `StatesForTokens` flooring because a ragged block writes no state. LANDS UNREACHED: `Qwen4ExpTextModel` does not exist (W2 #1987, W3 #1988, W6a #1989 are sibling waves; the registry and runner wiring is W5), row `MODEL-MM-QWEN4-EXP` owns it, [#1978](https://github.com/mudler/vllm.cpp/issues/1978) tracks it, and the spec lists it under `## Owed`. No CUDA arm and no speed claim — the speed axis opens at G4, after W6a | feature | | [#1987](https://github.com/mudler/vllm.cpp/issues/1987) | `MODEL-MM-QWEN4-EXP` | **W2 of [#1978](https://github.com/mudler/vllm.cpp/issues/1978): the hashed n-gram embedding and the PLE dilated depthwise conv, the ONLY two components of `Qwen4ExpForConditionalGeneration` with no vLLM op at all.** The negative is confirmed rather than unfound — at vLLM `origin/main` = `6a5e8f5979`, `git grep -in dilat` returns ZERO lines in `layers/mamba/`, zero in `csrc/` and zero in `tests/`, and `layers/conv.py` defines only `Conv2dLayer`/`Conv3dLayer` — so transformers **v5.16.0**, this row's accepted lane pin, is the sole oracle. Three silent divergence sites, all verified: `_splitmix64` must be `uint64_t` throughout (its `>> 30/27/31` are LOGICAL shifts on a non-negative Python int; on `int64_t` they go arithmetic and the multiplicand is wrong, with the top bit set about half the time), `_splitmix64(value) % half_bound` must be an UNSIGNED modulo (the dividend routinely exceeds 2^63), and shard reassembly is NUMERIC not lexicographic (`shard_0, shard_1, shard_10, ...` silently permutes a 95 GiB table). The forward is int64-exact and needs no bignum because `multiplier_max * vocab_size <= 2^63 - 1` — but **that bound holds only while every token id is below `vocab_size`**, so an out-of-range id overflows and diverges in silence, and this wave refuses one by name. Goldens confirmed a FOURTH way beyond #1987's three: `vocab_size = 248320`, read from the released `config.json`, is the UNIQUE preimage below 2e6 of the published `layer_multipliers = [23703573157769, 20109073645365, 8052911324071]` at the absent-`seed` default 1234. PLE specifics: the conv state is **9 columns** — `(kernel-1) * dilation`, NOT `kernel-1` — and holds the **normed** conv input while the skip term is the **un-normed** copy; the signed-sqrt gate clamps BEFORE the sqrt so the output magnitude floor is `sqrt(1e-6) = 1e-3` and tiny scores are AMPLIFIED, with exactly zero mapping to zero, making the function genuinely discontinuous at an origin that a fully masked row reaches; and the n-gram history must be padded with EOS because upstream's `update_conv_state` pads with 0, a valid token id. **Lands UNREACHED** per AGENTS.md "Nothing lands dead": no production entry point calls it until W5 assembles the model, the wiring is owned by `MODEL-MM-QWEN4-EXP` and tracked by #1978, and the spec lists it under `## Owed`. Host reference only; the batched device arm is owed, and the per-sequence signatures exist so it drops in — the gather is 16 uncoalesced random rows per token PER SEQUENCE, so at batch B it is 16*B independent gathers with no coalescing. No token claim, no speed claim: `## Gates` admits none from this row until an arm runs, and nothing published fits any fleet device | feature | @@ -764,6 +765,7 @@ rather than merged. `scripts/check-agent-record.py` gates both. | [#2032](https://github.com/mudler/vllm.cpp/issues/2032) | `ENG-HYBRID-PLACEMENT` | W3a: `MoePlacementPlan`, resolving a name-keyed `DevicePlacement` to the per-layer decision the forward can actually read, against llama.cpp's own GGUF tensor spelling — and REFUSING a partial placement, where a layer's gate, up and down do not share a device, because the MoE block runs one grouped GEMM over the three | feature | | [#2026](https://github.com/mudler/vllm.cpp/issues/2026) | `ENG-HYBRID-PLACEMENT` | W3b: route a placed layer's routed-expert compute to the placement device, with the activation round trip at the boundary — and refuse the fp4-resident arm, whose device residents are built eagerly at load so placing it would upload every expert and then compute across the bus, a defect a token gate cannot see | feature | | [#2040](https://github.com/mudler/vllm.cpp/issues/2040) | `ENG-HYBRID-PLACEMENT` | W3c: replace W3b's hand-written per-model round trip with ONE shared seam every architecture routes through, exploiting that every MoE block already has the same `(Dev, weights, params, [T,H] dh, T)` shape — and correct `docs/ENVIRONMENT.md` and `docs/FEATURES.md`, which W3b made false by leaving them saying the knobs place nothing | bug | +| [#2049](https://github.com/mudler/vllm.cpp/issues/2049) | `MODEL-MM-QWEN4-EXP` | **The row spec's settled-decisions list stated the REFUTED QSA mapping**, telling a fresh W4/W5 implementer to build QSA on MiniMax-M3 and calling the DeepSeek-V4 lane "the wrong port" — the exact reverse of the correction recorded in the same file's Port map and Design section, in the matrix row, and in [#1978](https://github.com/mudler/vllm.cpp/issues/1978). Pre-existing on `main`; found while reviewing the W6a merge ([#2019](https://github.com/mudler/vllm.cpp/pull/2019)) and fixed in that same flow per AGENTS.md "Every change starts from an issue". Load-bearing rather than cosmetic: item 2 sits in the section written so an implementer does NOT re-derive it, and a top-down reader hits the stale instruction before the corrected Design section. | doc | | [#2046](https://github.com/mudler/vllm.cpp/issues/2046) | `ENG-HYBRID-PLACEMENT` | `qwen3_5.cpp` kept private `Dev`/`DBuf`/`MakeTensor`/`Reshape` copies instead of the shared `dense_device_glue.h` set — the off-framework divergence its own `ResidentWeight` comment records, where a repair reached 25 model files and not this one. The private types also had INTERNAL LINKAGE, which is what forced the MoE placement seam to carry a glue-templated second spelling; migrating collapses it back to one | bug | | [#2050](https://github.com/mudler/vllm.cpp/issues/2050) | `ENG-HYBRID-PLACEMENT` | Laguna's FFN is host-orchestrated token-at-a-time — per-token host rows, the router on the host through `MatmulNK`, and a host scalar combine loop — so a device-shaped MoE entry wrapping those loops would put it in the placement seam's wired list while moving nothing and adding a round trip: supported to read, a regression to measure. The real repair is a device-resident batched FFN, which is a model rework with a performance gate | gap | | [#1984](https://github.com/mudler/vllm.cpp/issues/1984) | `SAMPLE-CORE` | `RandomSampleKernel` is launched `<<>>` and scans a 248,320-wide vocab on one thread per row, computing two `SplitMix64` rounds and an f64 `log` per element. Eleven lines above it the same file records that a single-block single-thread scan of a ~151k vocab cost ~7.5 ms/token, which is why greedy argmax was rewritten into `ArgmaxPartialKernel`/`ArgmaxFinalKernel`; the Gumbel draw never got that treatment. Upstream is whole-tensor (`vllm/v1/sample/ops/topk_topp_sampler.py::sample_with_exponential_noise`), so this is a mirror obligation. Reached by every non-greedy row through `ModelRunner::execute_model` -> `Sampler::forward` -> `vt::RandomSample`. Spec: [sample-gen-config-and-parallel-gumbel.md](specs/sample-gen-config-and-parallel-gumbel.md) | perf | diff --git a/.agents/kernel-matrix.md b/.agents/kernel-matrix.md index 610b9db41..3603c0725 100644 --- a/.agents/kernel-matrix.md +++ b/.agents/kernel-matrix.md @@ -138,7 +138,7 @@ host/sched. Detail: state `KERNEL-FA2-GQA-SWAP-FLIP`. | `KERNEL-ATTN-FLASHINFER-TRTLLM` | FlashInfer paged attention, XQA, and TensorRT-LLM generation kernels | availability/dispatch `vllm/utils/flashinfer.py:206-342,373-511`; SM90 decode and SM100+ prefill/decode gates | - | - | [inventory](specs/kernel-family-inventory.md) | `INVENTORIED` | - | | `KERNEL-ATTN-TRITON-FLEX-HPC` | Triton, FlexAttention, TurboQuant, and HPC attention families | registry `registry.py:34-120`; Triton contract `triton_attn.py:250-383`; HPC gate `hpc_attn.py:235-247` | - | - | [inventory](specs/kernel-family-inventory.md) | `INVENTORIED` | - | | `KERNEL-ATTN-MLA-SPARSE` | MLA and sparse attention: CUTLASS, FlashMLA, FlashInfer, FA, Triton, MSA **W6: the MLA attention BLOCK + WEIGHT ABSORPTION — the layer that COMPOSES W3+W4+W5** — [mla_attention.h](../include/vllm/model_executor/models/mla_attention.h) + [mla_attention.cpp](../src/vllm/model_executor/layers/attention/mla_attention.cpp) <- `mla.py:119-181` (`MultiHeadLatentAttentionWrapper.forward`) over `mla_attention.py:553-620` (the cache-update-BEFORE-attention order), `:624-874` (`forward_impl`: the dispatch + the absorbed decode) and `:2344-2425` (`forward_mha`); `AbsorbKvBProjBf16` <- `:875-962 process_weights_after_loading` (split `:892-900`, permutes `:959-962`); `MakeMlaUpProjectFn` <- `:2141-2170` (the `kv_b_proj` callback W5 left open); `BuildDeepseekRopeCosSinCache` <- `deepseek_scaling_rope.py:76-118` over `rotary_embedding/common.py:34-70`; `MlaAttentionScale` <- `deepseek_v2.py:995,1067-1075` (the mscale^2 correction, kept SEPARATE from the rope's own rotation mscale). **Absorption is a LOAD-TIME weight transform plus TWO batched GEMMs, not a fused kernel**, so it needed only two new general primitives: **`vt::BatchedMatmul`** <- `torch.bmm` at `mla_attention.py:789` (q-side W_UK fold) and `:1034` (`_v_up_proj`), CUDA impl = cuBLASLt STRIDED-BATCHED [cuda_matmul.cu](../src/vt/cuda/cuda_matmul.cu) (the cuBLASLt form of the cuBLAS `gemmStridedBatchedEx` torch.bmm resolves to; the only upstream alternatives are ROCm-only aiter fp8/fp4 bmm branches) + CPU ref [cpu_ops.cpp](../src/vt/cpu/cpu_ops.cpp), stride-driven because BOTH call sites pass `.transpose(0,1)` views; and **`vt::ConcatMlaNopeRope`** <- `ConcatMLAQKernel` (`csrc/libtorch_stable/concat_mla_q.cuh`) + wrapper `cache_kernels.cu:1555-1600`, GENERALIZED to arbitrary nope/rope widths and a head-BROADCAST rope operand so one op also serves `_concat_k_nope_k_pe` (`:2063-2092`) — CUDA [cuda_mla_attn.cu](../src/vt/cuda/cuda_mla_attn.cu), CPU [cpu_ops.cpp](../src/vt/cpu/cpu_ops.cpp). Two ADDITIVE relaxations of existing ops, integer-identical for contiguous tensors: `vt::RopeFromCache` stride-driven on q/k (DeepSeek rotates the TRAILING 64-dim slice and its `k_pe` is a column block of the fused kv_a projection) and `vt::MatmulBT` accepting a row-strided ACTIVATION (`kv_b_proj` applied to a 512-column slice of the 576-wide workspace, `:2160`) | CUDA priority `vllm/platforms/cuda.py:84-176` (`_get_backend_priorities`, both branches); MLA classes `vllm/v1/attention/backends/mla/*.py`; MLA prefill selector `mla/prefill/selector.py:47-76`; capability filter `vllm/v1/attention/backend.py:307-360`; CUTLASS build `CMakeLists.txt:1037-1061` **W6** [test_mla_attention_block.cpp](../tests/vllm/model_executor/layers/attention/test_mla_attention_block.cpp) **10/10 cases / 2,372,644 assertions** and [test_ops_mla_absorb.cpp](../tests/vt/test_ops_mla_absorb.cpp) **9/9 / 1,644,807 assertions** on dgx sm_121 — ports of `tests/kernels/test_concat_mla_q.py` (BOTH arms incl. the NON-CONTIGUOUS transposed-nope case, compared bit-exactly since a concat is a pure copy), the MLA-geometry sweep of `tests/v1/attention/test_mla_backends.py`, and the two-pass-oracle discipline of `tests/kernels/attention/test_mla_decode_cpu.py`. **THE ABSORBED-vs-UNABSORBED EQUIVALENCE IS PROVEN NUMERICALLY, THREE WAYS:** an INDEPENDENT double-precision block oracle computing the attention BOTH ways agrees to **< 1e-11** (the identity itself, at both query branches); our absorbed decode reproduces the UNABSORBED oracle to **< 2e-4** (f32); and the SAME batch driven once through the ABSORBED MQA decode kernel and once through the UNABSORBED materialized-MHA prefill path agrees to **< 3e-4** (CPU f32) / **< 4e-2** (CUDA bf16) — two code paths sharing nothing but the weights. Real geometry throughout (V2-Lite 512/128/64/128/16-head, plus V3's 7168 / 128-head / `q_lora_rank=1536` for the lora branch, which has NO e2e coverage and says so). Decode-only / prefill-only-no-context / chunked-prefill-with-context / MIXED (decode packed FIRST) all gated; NaN-poisoned outputs; run-to-run BIT-exact; CUDA cases proven to EXECUTE (124,941 + 290,835 assertions when run alone). memcheck **0 errors**, racecheck **0 hazards**, synccheck **0 errors** (the last requires `--num-cuda-barriers 65536`: the default table OVERFLOWS on a binary driving this many kernel families and the tool then emits a bogus `unspecified launch failure`). Clean CUDA build 0 warn/0 err; regression set UNCHANGED (27B 235/235, 35B 315/315, Coder 138/138, Qwen3-dense 664/664, OPT 36/36). No speed number — W9 owns tuning | **SELECTION (W2) + the DECODE KERNEL (W4) + the PREFILL PATH and CHUNKED-CONTEXT LOOP (W5). What is still absent is the MLA attention BLOCK and MODEL (W6/W7).** Priority TABLE [cuda_attn_priority.h:49](../include/vllm/platforms/cuda_attn_priority.h#L49) (both branches, one row per upstream arch arm) + lookup [cuda_attn_priority.h:86](../include/vllm/platforms/cuda_attn_priority.h#L86); `is_mla()`/`is_sparse()` filter [registry.cpp:63](../src/vllm/v1/attention/registry.cpp#L63); `TritonMLABackend` NAME + 3-D `get_kv_cache_shape` [backend.h:580](../include/vllm/v1/attention/backend.h#L580), [backend.cpp:83](../src/vllm/v1/attention/backend.cpp#L83), registration [backend.cpp:108](../src/vllm/v1/attention/backend.cpp#L108) — **W4: `vt::MlaDecodeAttention`** — `OpId::kMlaDecodeAttention` + args/validation [ops.h](../include/vt/ops.h), [ops.cpp](../src/vt/ops.cpp); CPU single-pass REFERENCE [cpu_mla_attn.cpp](../src/vt/cpu/cpu_mla_attn.cpp) (numerics from `csrc/cpu/mla_decode.cpp`); CUDA two-stage split-KV [cuda_mla_attn.cu](../src/vt/cuda/cuda_mla_attn.cu) — `MlaDecodeStage1` <- `_fwd_grouped_kernel_stage1` (`triton_decode_attention.py:278-458`, IS_MLA `v = tl.trans(k)` branch `:424-431`), `MlaDecodeStage2` <- `_fwd_kernel_stage2` (`:575-639`), `ComputeNumKvSplits` <- `_compute_num_kv_splits` (`triton_mla.py:40-47`), split workspace via the house grow-only per-stream scratch (upstream's `_reserve_attn_logits_workspace` `:57-78`). Deterministic by construction: fixed ASCENDING split merge, NO atomicAdd. `TritonMLABackend::get_impl_cls()` now returns a real `TritonMLAImpl` [backend.h](../include/vllm/v1/attention/backend.h), [backend.cpp](../src/vllm/v1/attention/backend.cpp); PREFILL remains W5 and `TritonMLAImpl::forward` refuses a prefill-shaped batch by name. **W5: `vt::MlaPrefillAttention` + `vt::GatherMlaCache` + `vt::MergeAttnStates` + the chunked-context driver** — `vt::MlaPrefillAttention` [cuda_mla_prefill.cu](../src/vt/cuda/cuda_mla_prefill.cu) / CPU ref [cpu_mla_prefill.cpp](../src/vt/cpu/cpu_mla_prefill.cpp) <- `mla/prefill/flash_attn.py:153-248` `FlashAttnPrefillBackend` (the ONLY MLA prefill backend reachable on sm_121 per `mla/prefill/selector.py:66-76`, and it HARD-RAISES with no fallback at `:191-194`), running over the vendored FA-2 through the NEW launcher entry `LaunchMlaPrefillFA2Bf16` [cuda_flash_attn_fa2.cu](../src/vt/cuda/cuda_flash_attn_fa2.cu) plus two new explicit instantiations of the UNCHANGED generic template (`flash_fwd_split_hdim192_bf16{,_causal}_sm80.cu`). V is ZERO-PADDED 128->192 and the output sliced back, exactly as upstream's `requires_v_padding` path does (`flash_attn.py:88-99,164-168,196-197`) — which is WHY the asymmetric QK 192 / V 128 pair needs no asymmetric kernel. `vt::GatherMlaCache` <- `csrc/libtorch_stable/cache_kernels.cu:992-1064`; `vt::MergeAttnStates` <- `csrc/libtorch_stable/attention/merge_attn_states.cu:18-192` (BOTH `-inf` edge cases ported verbatim). The workspace-bounded loop is [mla_chunked_context.h](../include/vllm/model_executor/layers/attention/mla_chunked_context.h) <- `mla_attention.py:1422-1451,1667-1745,2094-2199,2344-2425`. **The paged launcher `LaunchPrefillFA2Bf16` that every non-MLA prefill calls is textually UNTOUCHED** (211 insertions / 0 deletions in that TU; 2 new vendored files) | [test_attn_backend_registry.cpp:146](../tests/vllm/v1/attention/test_attn_backend_registry.cpp#L146) (GB10 MLA list), [:203](../tests/vllm/v1/attention/test_attn_backend_registry.cpp#L203) (`use_mla=true` -> `TRITON_MLA`, matching the W0 oracle observation), [:230](../tests/vllm/v1/attention/test_attn_backend_registry.cpp#L230) (the DSA seam, proven both directions with a stand-in sparse backend) — ports of `test_attention_backends_selection.py` (MLA cases), `test_mla_prefill_selector.py`, `test_mla_prefill_registry.py`; **W4** [test_ops_mla_attn.cpp](../tests/vt/test_ops_mla_attn.cpp) — port of `tests/kernels/attention/test_mla_decode_cpu.py` (`ref_mla` as a TWO-PASS oracle, its bs=4/mean_seq_len=256/h_q=16/d=576/dv=512/block=16 parametrization, BOTH varlen arms, and its NaN-padding out-of-bounds detector) plus the `test_mla_backends.py` shape sweep: ragged, multi-block, single-block/single-token, EVERY num_kv_splits in {1,2,3,4,5,8,16,17,64,300,512} (incl. splits > seq_len, the empty-split path both stages must skip), 128-head DeepSeek-V3 geometry, head counts 1/3/17 that do not fill a BLOCK_H tile, a 288/256 block-32 non-V2-Lite geometry, bf16 + f32, and run-to-run BIT-exactness over 5 runs. Gated on dgx/sm_121: 11/11 cases, 2,303,193 assertions; `compute-sanitizer` memcheck **0 errors**, racecheck **0 hazards**, synccheck **0 errors**; clean CUDA build 0 warn/0 err; regression set UNCHANGED (27B 235/235, 35B 315/315, Coder 6/6, Qwen3-dense 16/16, OPT 6/6). NO speed number yet — decode perf is W9. **W5** [test_ops_mla_prefill.cpp](../tests/vt/test_ops_mla_prefill.cpp) **4/4 cases / 2,377,052 assertions** and [test_ops_mla_chunked_context.cpp](../tests/vt/test_ops_mla_chunked_context.cpp) **5/5 / 306,037 assertions** on dgx sm_121 — ports of `tests/v1/attention/test_mla_backends.py` and `tests/v1/attention/test_mla_prefill_quant_output.py` (its fp8 arms NOT ported: they need device-capability family 100, unreachable on sm_121 — recorded, not dropped). REAL V2-Lite prefill geometry (QK 192 / V 128 / latent 576, block 16, mscale^2 scale) against an INDEPENDENT double-precision TWO-PASS oracle, plus — for the chunked loop — a SINGLE-SHOT whole-sequence oracle that never chunks: exact / +1 / -1 chunk boundaries, a request with NO context, a chunk in which a request contributes ZERO keys, ragged multi-chunk, 128-head V3, single-token queries, ADVERSARIAL reverse-interleaved block tables, NaN-poisoned outputs, run-to-run BIT-exact over 5 runs. memcheck **0 errors**, racecheck **0 hazards**, synccheck **0 errors** on both binaries; clean CUDA build 0 warn/0 err; regression set UNCHANGED (27B 235/235, 35B 315/315, Coder 138/138, Qwen3-dense 664/664, OPT 36/36). Prefill perf is W9 | [MLA campaign spike](specs/mla-deepseek-campaign.md) | `PARTIAL` | `CLAIM-MLA-DEEPSEEK` | -| `KERNEL-ATTN-DFLASH-BLOCK` | **DFlash in-block attention — the project's FIRST non-causal / bidirectional attention primitive** (SPEC-DFLASH D2, DF-DRAFT-MODEL). Per-request uniform (1+k) query block attends within its own block: FULL-attention layers BIDIRECTIONAL (`causal=false`, no mask), SWA layers causal-within-window. f32 online softmax, GQA broadcast. A SEPARATE `vt::` op from the causal `kAttention`/`kPagedAttention` so every other model stays byte-identical | `vllm/model_executor/models/qwen3_dflash.py:86-146` (`_resolve_layer_attention`: full layers default non-causal, SWA causal) + `:149-263` (`DFlashQwen3Attention`); flashinfer non-causal path (vllm#48167 Blackwell non-causal attn, in-pin) | `OpId::kDFlashBlockAttention` + `DFlashBlockAttentionArgs` + decl [ops.h:1713](../include/vt/ops.h#L1713) + wrapper/validation [ops.cpp:2069](../src/vt/ops.cpp#L2069); CPU REFERENCE `DFlashBlockAttentionKernel` [cpu_ops.cpp:1843](../src/vt/cpu/cpu_ops.cpp#L1843) (three-pass block-local softmax, the authoritative impl); CUDA `DFlashBlockAttentionKernelCuda` [cuda_ops.cu:1300](../src/vt/cuda/cuda_ops.cu#L1300) mirroring the causal `AttentionKernel` block-reduction recurrence with per-block bounds + the bidirectional/window mask; the draft model that consumes it [qwen3_dflash.cpp:52](../src/vllm/model_executor/models/qwen3_dflash.cpp#L52) | **CPU GATE GREEN** [test_ops_dflash_block_attn.cpp:79](../tests/vt/test_ops_dflash_block_attn.cpp#L79) 5 cases / 12 assertions — hand-checked non-causal (query 0 sees the future key), the RED causal-vs-non-causal separation (the mask is load-bearing), per-request cu_seqlens block isolation, SWA window bound, GQA; model forward [test_qwen3_dflash_forward.cpp:116](../tests/vllm/models/test_qwen3_dflash_forward.cpp#L116) 5 cases / 95 assertions (RED full-layer-causal-flip); existing causal `test_ops_attention` 9/9·23 UNCHANGED. **GPU GATE GREEN on dgx (2026-07-26, GB10 sm_121a):** CUDA `-Werror=all-warnings` build clean (kernel compiles as-written, no change); CUDA==CPU parity [test_ops_dflash_block_attn CUDA case](../tests/vt/test_ops_dflash_block_attn.cpp#L153) 198412/198412 within the 1e-4 f32-softmax envelope over all 5 corners; `compute-sanitizer --tool memcheck` 0 errors; consumed by the draft-forward parity gate ([test_qwen3_dflash_draft_parity](../tests/parity/test_qwen3_dflash_draft_parity.cpp), fc rel-L2 0.46% / hidden ≤1.3% vs the real vLLM draft). **DONE 2026-07-27 with the DFlash block (`CLAIM-DFLASH-D14`):** the D2 non-causal in-block primitive is the CPU/materialized reference the D12+ paged/warp kernels are gated against; closure [ledger](parity-ledger.md#L722). | [DFlash spec](specs/dflash-spec-decode.md) §1.3/§6 D2 | `DONE` | `489a7544` | +| `KERNEL-ATTN-DFLASH-BLOCK` | **DFlash in-block attention — the project's FIRST non-causal / bidirectional attention primitive** (SPEC-DFLASH D2, DF-DRAFT-MODEL). Per-request uniform (1+k) query block attends within its own block: FULL-attention layers BIDIRECTIONAL (`causal=false`, no mask), SWA layers causal-within-window. f32 online softmax, GQA broadcast. A SEPARATE `vt::` op from the causal `kAttention`/`kPagedAttention` so every other model stays byte-identical | `vllm/model_executor/models/qwen3_dflash.py:86-146` (`_resolve_layer_attention`: full layers default non-causal, SWA causal) + `:149-263` (`DFlashQwen3Attention`); flashinfer non-causal path (vllm#48167 Blackwell non-causal attn, in-pin) | `OpId::kDFlashBlockAttention` + `DFlashBlockAttentionArgs` + decl [ops.h:1713](../include/vt/ops.h#L1713) + wrapper/validation [ops.cpp:2069](../src/vt/ops.cpp#L2069); CPU REFERENCE `DFlashBlockAttentionKernel` [cpu_ops.cpp:2923](../src/vt/cpu/cpu_ops.cpp#L2923) (three-pass block-local softmax, the authoritative impl); CUDA `DFlashBlockAttentionKernelCuda` [cuda_ops.cu:1300](../src/vt/cuda/cuda_ops.cu#L1300) mirroring the causal `AttentionKernel` block-reduction recurrence with per-block bounds + the bidirectional/window mask; the draft model that consumes it [qwen3_dflash.cpp:52](../src/vllm/model_executor/models/qwen3_dflash.cpp#L52) | **CPU GATE GREEN** [test_ops_dflash_block_attn.cpp:79](../tests/vt/test_ops_dflash_block_attn.cpp#L79) 5 cases / 12 assertions — hand-checked non-causal (query 0 sees the future key), the RED causal-vs-non-causal separation (the mask is load-bearing), per-request cu_seqlens block isolation, SWA window bound, GQA; model forward [test_qwen3_dflash_forward.cpp:116](../tests/vllm/models/test_qwen3_dflash_forward.cpp#L116) 5 cases / 95 assertions (RED full-layer-causal-flip); existing causal `test_ops_attention` 9/9·23 UNCHANGED. **GPU GATE GREEN on dgx (2026-07-26, GB10 sm_121a):** CUDA `-Werror=all-warnings` build clean (kernel compiles as-written, no change); CUDA==CPU parity [test_ops_dflash_block_attn CUDA case](../tests/vt/test_ops_dflash_block_attn.cpp#L153) 198412/198412 within the 1e-4 f32-softmax envelope over all 5 corners; `compute-sanitizer --tool memcheck` 0 errors; consumed by the draft-forward parity gate ([test_qwen3_dflash_draft_parity](../tests/parity/test_qwen3_dflash_draft_parity.cpp), fc rel-L2 0.46% / hidden ≤1.3% vs the real vLLM draft). **DONE 2026-07-27 with the DFlash block (`CLAIM-DFLASH-D14`):** the D2 non-causal in-block primitive is the CPU/materialized reference the D12+ paged/warp kernels are gated against; closure [ledger](parity-ledger.md#L722). | [DFlash spec](specs/dflash-spec-decode.md) §1.3/§6 D2 | `DONE` | `489a7544` | | `KERNEL-ATTN-DFLASH-PAGED-BLOCK` | **DFlash PAGED in-block attention — the CAPTURE-SAFE form of `KERNEL-ATTN-DFLASH-BLOCK`** (SPEC-DFLASH D12 Part B, the CUDA-graph draft-attention primitive). The (1+k) block queries attend over `[PAGED context ; their own (1+k) block]`: the growing context enters as DATA (paged K/V cache `[pages,block_size,Hkv,D]` + per-request `seq_lens` + `block_table`, mirroring `PagedAttentionKernel`) instead of a variable-size materialized combined buffer, so the launch grid is STATIC over the fixed `Nq=(1+k)*num_reqs` rows and EVERY metadata input is a persistent DEVICE tensor read in place — NO `cudaMallocAsync`/`cudaMemcpyAsync` of a function-local host `cu_seqlens` (the [[cudagraph-capture-bakes-stack-addresses]] UAF class the eager `LaunchDFlashBlockAttention` had). Same f32 online softmax + D2 in-block mask over the COMBINED index; bit-identical to `DFlashBlockAttention` over the materialized `[context;block]` buffer | vLLM full CG `dflash/cudagraph.py` + `speculator.py:411-458` + `precompute_and_store_context_kv` (`qwen3_dflash.py:548-619`) @ `555967922`; paged read mirrors our `PagedAttentionKernel` [cuda_paged_attn.cu:184](../src/vt/cuda/cuda_paged_attn.cu#L184) | `OpId::kDFlashPagedBlockAttention` + `DFlashPagedBlockAttentionArgs` + decl [ops.h](../include/vt/ops.h) + wrapper/validation [ops.cpp](../src/vt/ops.cpp); CPU REFERENCE `DFlashPagedBlockAttentionKernel` [cpu_ops.cpp](../src/vt/cpu/cpu_ops.cpp); CUDA `DFlashPagedBlockAttentionKernelCuda` [cuda_ops.cu](../src/vt/cuda/cuda_ops.cu#L1452) (static grid, persistent device metadata) + D14 WARP variant [DFlashPagedBlockAttentionWarpKernel](../src/vt/cuda/cuda_ops.cu#L1433) | **GPU GATE GREEN on dgx (2026-07-27, GB10 sm_121a):** CUDA `-Werror` clean (0 warnings); [test_ops_dflash_paged_block_attn.cpp](../tests/vt/test_ops_dflash_paged_block_attn.cpp#L79) cross-checks CPU-paged == materialized `DFlashBlockAttention` across 6 corners (non-causal, causal-SWA, block isolation, GQA, multi-page, zero-context) + CUDA==CPU (f32+bf16) = **795648/795648 assertions**; `compute-sanitizer --tool memcheck` **0 errors**. **D13 (2026-07-27, `CLAIM-DFLASH-D13`): WIRED INTO PRODUCTION** — the single-request DFlash draft forward (`ForwardPagedBody`, `qwen3_dflash.cpp`) now runs the (1+k) block through this kernel reading a fixed-capacity paged `DflashDeviceKVStore`, and the whole draft step is captured into a per-request CUDA graph + replayed (the growing context enters only via the in-place `seq_lens`). Capture-correctness PROVEN: `test_qwen27_dflash_spec_decode` 27/27 with the graph BIT-IDENTICAL to eager (same tokens + acceptance 19/39/29/25); c1 throughput NEAR-PARITY with vLLM-DFlash-ON (ours 0.978×, ~2% below the tight 3-rep band; gap closed 0.917×→0.978× via the paged read, the CG is perf-neutral) — the kernel is landed + wired + gated; STAYS `ACTIVE` with the engine feature (the ~2% ≥vLLM residual is per-step compute for an nsys). **D14 (2026-07-27, `CLAIM-DFLASH-D14`): the residual WAS this kernel → WARP-scoped variant added → SPEED GATE MET, `DONE`.** An nsys (`--cuda-graph-trace=node`) attributed the D13 ~2% residual to THIS kernel: `DFlashPagedBlockAttentionKernel` = 242.9 ms = 1.8% of the graphed step's GPU time, median ~460 us/call (grid `(nq=17,hq=32)` × kBlock=256 threads looping SERIALLY over C~500-640 keys with a 256-wide shared-mem tree reduction + 2 `__syncthreads` PER key — the latency/sync storm the ViT tower fixed with `AttentionDenseFast`), vs vLLM's fused flash draft-attn ~0.15%. Added `DFlashPagedBlockAttentionWarpKernel` ([cuda_ops.cu](../src/vt/cuda/cuda_ops.cu)): ONE WARP per (block-query,head), `__shfl_xor` butterfly head_dim reduction, register accumulator, NO `__syncthreads`; SAME paged/block combined-index read + causal/SWA mask + GQA (copied verbatim from the block kernel), mirroring the shipped `AttentionWarpKernel`. Default ON; `VT_DFLASH_ATTN_BLOCK=1` keeps the bit-identical D12/D13 block kernel. Draft attn **242.9 → 77.9 ms (3.1×)**; our-ON c1 **28.60 → 29.32 tok/s**; FINAL 3-rep A/B our-ON 29.32 ≥ vLLM-ON 29.240 (non-overlapping bands, 1.003×) ⇒ **≥vLLM MET**. Not bit-identical to the block kernel but same f32-online-softmax math within envelope; CUDA==CPU `test_ops_dflash_paged_block_attn` **795648/795648** (f32 1e-4/bf16 3e-2) + **compute-sanitizer 0**; e2e 27/27 graph==eager, acceptance 19/39/29/25 unchanged (1629 accepted identical warp-vs-block); SACRED 235/235 + MTP 9/9 inert; `-Werror` clean; closure [ledger](parity-ledger.md#L738) | [DFlash spec](specs/dflash-spec-decode.md) §0 D12/D13/D14 | `DONE` | `489a7544` | | `KERNEL-DFLASH2-GROUPED-CONV` | **DFlash2 grouped dynamic depthwise convolution — the project's FIRST dynamic (input-conditioned) convolution kernel** (SPEC-DFLASH2 W2, #1314). `out[i,c] = sum_t (base[side,t,c] + delta[i,side,t,g(c)]) * x[i-t,c]`, with tap `t` contributing only where `(i mod block_size) >= t`, `g(c) = c / group_size`, and `block_size` the QUERY block `1 + k`. Three things separate it from the shipped `KERNEL-DEPTHWISE-CONV1D`: the kernel is DYNAMIC (a per-position `delta` projected from the sublayer input, added to a static per-channel `base`), it is GROUPED (one delta per group of channels, one base per channel), and its taps are ZEROED ACROSS THE BLOCK BOUNDARY rather than across the sequence — which is what lets a proposal position see the ones before it without another backbone pass. `base_kernel` dim 0 is the prepare/finish SIDE and not a tap; on the published 27B draft both axes are 2, so nothing but the port note and the shape assertion separates a correct load from a transposed one. Every intermediate rounds to the tensor dtype, mirroring upstream's bf16 chain, so the op is elementwise with NO reduction-order freedom and the CUDA arm is specified BIT-IDENTICAL to CPU rather than within an envelope | **BEYOND-PIN** — `vllm/model_executor/models/qwen3_dflash2.py` (`_grouped_conv`, `DFlashGroupedConv`, `DFlash2Qwen3DecoderLayer.forward`) @ [vllm-project/vllm#52816](https://github.com/vllm-project/vllm/pull/52816) head `19c9351904df4c63042671bc67a866ca48dc7d6f`; the parity pin `555967922` does not carry the architecture and this row does NOT advance it | `OpId::kDFlashGroupedConv` + `DFlashGroupedConvArgs` + decl/wrapper `include/vt/ops.h::DFlashGroupedConv` and `src/vt/ops.cpp::DFlashGroupedConv`; CPU REFERENCE `src/vt/cpu/cpu_ops.cpp::DFlashGroupedConvKernel` (the authoritative impl); CUDA mirror `src/vt/cuda/cuda_ops.cu::DFlashGroupedConvKernelCuda` (one thread per (row, channel); `__fadd_rn`/`__fmul_rn` forbid the FMA contraction the CPU build pins off). Consumed by the draft through `src/vllm/model_executor/models/qwen3_dflash.cpp::DflashConvPrepare` and `src/vllm/model_executor/models/qwen3_dflash.cpp::DflashConvFinish`, called from all THREE layer bodies, with the uniform-block precondition in `src/vllm/model_executor/models/qwen3_dflash.cpp::CheckDflashConvBatch`; weights loaded by `src/vllm/model_executor/models/qwen3_dflash_weights.cpp::LoadQwen3DFlash` | **CPU GATE GREEN 2026-08-19** ([test_ops_dflash2_grouped_conv.cpp](../tests/vt/test_ops_dflash2_grouped_conv.cpp)) 8 cases / **9930 assertions**, `Status: SUCCESS!`, exit 0 (was 6 / 9410 on 2026-08-19, before the bf16 rounding cases below) — upstream's own sequential reference loop at block 5 (the `% block` arm), 8 and 16 (the two PUBLISHED checkpoints, `z-lab/Qwen3.8-27B-DFlash2` and `z-lab/Muse-Glimmer-30B-DFlash2`; upstream's parametrize covers 5 and 8 only), both published taps/group shapes on both sides, plus hand-computed corners for the block boundary, the group map and the side. MODEL GATE GREEN ([test_qwen3_dflash2_draft.cpp](../tests/vllm/models/test_qwen3_dflash2_draft.cpp)) 16 cases / 108 assertions, `Status: SUCCESS!`, exit 0 — weights read off a REAL on-disk safetensors shard by the production loader, an IDENTITY conv proven BIT-IDENTICAL to no conv, and each conv driven ALONE through each of the three layer bodies. MUTATION-PROVEN 2026-08-19, each restored byte-for-byte and verified by sha256: deleting the call sites in `ForwardBlockLogits` (5 cases / 9 assertions red), in `ForwardWithCtxKVDev` (1/1) and in `ForwardPagedBody` (1/1); forcing `args.side` to 0 (op 2 cases/4353 assertions red, model 1/1); dropping the block mask (3/449); the wrong group map (3/7436); and dropping the uniform-block guard (1/1). **The PER-STEP ROUNDING was added to that set on 2026-08-20**, after the wave's second fresh review proved it had no executing assertion: replacing the bf16 branch of the `round` lambda in `src/vt/cpu/cpu_ops.cpp::DFlashGroupedConvKernel` with `return v;` compiled clean and left BOTH suites fully green, because every case in the op file ran in f32 where that rounding is the IDENTITY, and the model suite asserts only RELATIONALLY between two runs of the same kernel. Two CPU-only bf16 cases now pin it — one hand-computed against literals that differ from the round-once-at-the-end answer in six of eight outputs, one bit-exact at three shapes against a reference that rounds where UPSTREAM materializes — and under the same mutation the file is 8 cases / 2 failed, 9930 assertions / 225 failed, `Status: FAILURE!`. THREE gate repairs have now come out of this row's mutation passes and are recorded rather than hidden: activating both convs at once could not see one missing call site, the first side probe could not see a forced side, and no case at all could see the rounding policy. **CUDA VERIFIED 2026-08-20** — the kernel and its registration are written and the CUDA==CPU bit-identity case exists over six shapes, and it has now COMPILED AND RUN on a device: an `rc` job on `dgx:gpu0` (GB10, sm_121a, `nvcc` 13.0) at W3 head `b29b6f886`, `BUILD_RC=0`, `COMPILE_ERRORS=0`, zero `no CUDA backend; skipping` lines, this suite among the five of six that passed ([#1489](https://github.com/mudler/vllm.cpp/issues/1489)). Spec `## Owed` O6 is DISCHARGED by that run. The AUTHORING HOST still has no `nvcc`, so the case still reports `no CUDA backend; skipping` there — a property of that box and no longer of the kernel | [DFlash2 spec](specs/dflash2-spec-decode.md) W2, [#1314](https://github.com/mudler/vllm.cpp/issues/1314) | `ACTIVE` | `CLAIM-SPEC-DFLASH2-W2` | | `KERNEL-DFLASH2-SELECTOR-EDGES` | **DFlash2 candidate-selector EDGE LATTICE — the transition scores the path walk chooses among** (SPEC-DFLASH2 W3, #1314). `edge(b,l,p,c) = unary[b,l,c] + `, where `pid` is the request's verified ANCHOR token at step 0 (the same token for every predecessor slot, which is upstream's `anchor_token_ids[:, None, None].expand`) and the PREVIOUS step's candidate at every later step. This is what replaces the DFlash1 independent per-slot argmax: the draft no longer picks each slot alone, it scores adjacent transitions over the target head's top-K and walks the best path. Two bf16 materializations are reproduced at upstream's own two points -- the elementwise `predecessors * hidden` product and the einsum's output -- and the f32 `unary` is added after, which is torch's own promotion. UNLIKE `KERNEL-DFLASH2-GROUPED-CONV` this op is NOT specified bit-identical across backends: the rank contraction is a REDUCTION and the CUDA warp-shuffle tree sums in a different order, so it is gated within an f32 envelope and the difference is stated rather than inherited by analogy | **BEYOND-PIN** — `vllm/model_executor/models/qwen3_dflash2.py:208-276` (`_score_edges`, `CandidateSelector`) @ [vllm-project/vllm#52816](https://github.com/vllm-project/vllm/pull/52816) head `66e5414c6d75a8529473d977f7458c140bbab8a0`, which superseded `19c93519` on 2026-08-19 ([#1404](https://github.com/mudler/vllm.cpp/issues/1404)) and is BYTE-IDENTICAL for this function; the parity pin `555967922` does not carry the architecture and this row does NOT advance it | `OpId::kDflash2SelectorEdges` + `Dflash2SelectorEdgesArgs` + decl/wrapper `include/vt/ops.h::Dflash2SelectorEdges` and `src/vt/ops.cpp::Dflash2SelectorEdges`; CPU REFERENCE `src/vt/cpu/cpu_ops.cpp::Dflash2SelectorEdgesKernel` (the authoritative impl); CUDA mirror `src/vt/cuda/cuda_ops.cu::Dflash2SelectorEdgesKernelCuda` (one WARP per (request, step, predecessor slot); `__shfl_xor_sync` contraction, dynamic shared for the gated product). Consumed by `src/vllm/model_executor/models/qwen3_dflash2.cpp::Qwen3DFlash2Model::SelectorEdgeScores`, called from `src/vllm/v1/worker/gpu/spec_decode/dflash2/speculator.cpp::Dflash2SelectCandidates`, which BOTH propose paths call -- `src/vllm/v1/worker/gpu/runner.cpp::GPUModelRunner::propose_drafts_block` (production) and `src/vllm/v1/worker/gpu/spec_decode/dflash/speculator.cpp::DflashProposeBlock`; weights loaded by `src/vllm/model_executor/models/qwen3_dflash_weights.cpp::LoadQwen3DFlash` | **CPU GATE GREEN 2026-08-20** ([test_ops_dflash2_selector_edges.cpp](../tests/vt/test_ops_dflash2_selector_edges.cpp)) 7 cases / **203 assertions** (199 at W3; W4's review added 4), `Status: SUCCESS!`, exit 0 — upstream's OWN sequential reference loop at upstream's own parameters (batch 2, steps 4, top_k 3, rank 5, vocab 17), bit-exact in f32; the ANCHOR arm (all K predecessor rows of step 0 identical, later steps not, and changing the anchor moves step 0 alone); the PREDECESSOR indexing (swapping two of step l-1's candidate ids swaps exactly those two predecessor rows of step l); the UNARY broadcast (a per-CHILD bias, not per-edge); and the bf16 ROUNDING PLACEMENT with hand-written literals chosen so the three candidate placements answer differently (ours 7.71875, round-once-at-the-end 7.6875, unrounded 7.699830055236816) -- which is the executing assertion W2's second review proved the convolution's evidence lacked. PRODUCTION-REACHED and mutation-proven at the RUNNER: [test_dflash2_runner_reach.cpp](../tests/vllm/v1/spec_decode/test_dflash2_runner_reach.cpp) 3 cases / 14 assertions drives a real `LoadedEngine` over a synthetic Qwen3.5-dense target plus an in-memory DFlash2 draft, and the walk refusal names this op's own output (`scored-transitions=27 requests=1 steps=3 top_k=3`). MUTATION-PROVEN 2026-08-20, each restored byte-for-byte and verified by sha256 — see the row spec's `## Now`. **W4's fresh review found this row's own `[B,L,K,K]` OUTPUT-lattice guard ungated** and repaired it here: the refusal case's two bare `CHECK_THROWS` would each have been answered by a neighbouring guard ONCE THE NAMED ONE WAS DELETED, so deleting the lattice check alone left the suite green. Same [#1518](https://github.com/mudler/vllm.cpp/issues/1518) correction as the sibling row: with the check PRESENT the case throws `scores must be [B,L,K,K]` (`src/vt/ops.cpp:3284`); with it deleted the throw falls through to `contiguous tensors required` (`:3299`). The two trailing axes are now driven by genuinely contiguous wrong-extent output views and every refusal is matched on its message; deleting the check reddens 1 case / 2 assertions. **CUDA VERIFIED 2026-08-20** — the kernel and its registration are written and the CUDA-vs-CPU envelope case exists over two shapes (upstream's small one and the PUBLISHED rank 256 / K 16), and it has now COMPILED AND RUN on a device: `dgx:gpu0` (GB10, sm_121a, `nvcc` 13.0) at W3 head `b29b6f886`, `BUILD_RC=0`, `COMPILE_ERRORS=0`, zero `no CUDA backend; skipping` lines, this suite among the five of six that passed, so the warp-shuffle contraction lands inside the asserted envelope at rank 256 ([#1489](https://github.com/mudler/vllm.cpp/issues/1489)). Spec `## Owed` O10; the AUTHORING HOST still has no `nvcc` and still skips there | [DFlash2 spec](specs/dflash2-spec-decode.md) W3, [#1314](https://github.com/mudler/vllm.cpp/issues/1314) | `ACTIVE` | `CLAIM-SPEC-DFLASH2-W3` | diff --git a/.agents/model-matrix.md b/.agents/model-matrix.md index 6f955c418..f9c72cbc0 100644 --- a/.agents/model-matrix.md +++ b/.agents/model-matrix.md @@ -77,11 +77,11 @@ Rollup by lifecycle state (must equal the detailed per-state row counts): |---|---| | INVENTORIED | 324 | | PARTIAL | 22 | -| ACTIVE | 10 | +| ACTIVE | 11 | | SPIKE | 9 | | BLOCKED | 5 | | DONE | 3 | -| READY | 4 | +| READY | 3 | | GATING | 1 | | **Total** | **378** | @@ -89,7 +89,7 @@ Engaged architectures (the 54 non-`INVENTORIED` rows): | Support | Architecture | Family / example | Status | Row | |---|---|---|---|---| -| 🚧 | `Qwen4ExpForConditionalGeneration` | Qwen3.8-Flash-Next (180B total / 6B activated, image-text-to-text) | **SPEC ONLY.** New architecture vLLM does NOT implement at any revision; split oracle by developer direction 2026-08-26 (transformers for the algorithm, vLLM ops for the optimized path). Nothing published fits ~119 GB on GB10, so `gateable = no` and the quantized arms are load-bearing; no GGUF exists and llama.cpp has no `qwen4_exp` either. NO token, NO speed. [#1978](https://github.com/mudler/vllm.cpp/issues/1978) | `MODEL-MM-qwen4-exp-qwen4-exp-for-conditional-generation` | +| 🚧 | `Qwen4ExpForConditionalGeneration` | Qwen3.8-Flash-Next (180B total / 6B activated, image-text-to-text) | **W6a LANDED; not yet reachable.** New architecture vLLM does NOT implement at any revision; split oracle by developer direction 2026-08-26 (transformers for the algorithm, vLLM ops for the optimized path). Nothing published fits ~119 GB on GB10, so `gateable = no` and the quantized arms are load-bearing; no GGUF exists and llama.cpp has no `qwen4_exp` either. NO token, NO speed. [#1978](https://github.com/mudler/vllm.cpp/issues/1978) | `MODEL-MM-qwen4-exp-qwen4-exp-for-conditional-generation` | | ✅ | `Qwen3ForCausalLM` | Qwen3 dense (0.6B/1.7B/4B/32B) | near-tie-robust token-exact 16/16 on 0.6B+4B vs vLLM 0.25.0; NVFP4A16 (W4A16) dense quant also gated; c1 every-axis speed parity, c8 decode residual; async-serving device token-ids mirror ported (`ROW-SERVE-ASYNC-DENSE-MIRROR`, #31 fix into the shared dense `EmbedInto`) — `test_qwen3_dense_async_serving` RED→GREEN; sibling scope CLOSED (#323): `60e71a0e` fixed the eager path; `DenseDecodeGraphForward` ran first and replayed against stale HOST ids, so it now declines while the mirror is live and falls back to the proven eager path. Async gate 7/7 across Qwen3-0.6B/4B + Llama/Mistral/InternLM2 | `MODEL-TEXT-qwen3-qwen3-for-causal-lm` | | ✅ | `Qwen3MoeForCausalLM` | Qwen3-Coder-30B-A3B (MoE) | STRICT token-exact 6/6 vs vLLM 0.25.0; 11/16 speed-grid cells at/above graphed vLLM, c1/c2 residual | `MODEL-TEXT-qwen3-moe-qwen3-moe-for-causal-lm` | | ✅ | `Qwen3_5ForConditionalGeneration` | Qwen3.6-27B (text path) | text-gen STRICT token-exact 235/235 vs vLLM 0.25.0; mm INPUT pipeline (M0/M1) landed + processor-parity gate PASS; **M3-W0 landed** (vision-inclusive checkpoint `Qwen/Qwen3.6-27B` 51.7 GiB bf16 with 333 `visual.*` FOUND+fits+downloaded; 27B vision config resolved — depth 27/out 5120/**EMPTY deepstack**; MRoPE `[11,11,10]`/rot 64/theta 1e7; the bf16 GDN-hybrid loader ALREADY handles it). **M3-b LANDED 2026-07-25: image→text STRICT token-exact 32/32 vs vLLM 0.25.0** — Qwen3.6-27B image understanding works end-to-end (forked GDN-hybrid VL forward gated on mm input ⇒ text byte-identical; 27B/35B/Coder inertness re-passed 235/315/138). **M3d LANDED 2026-07-25: video→text STRICT token-exact 32/32 vs vLLM 0.25.0** — video works end-to-end too (`Qwen3_5VLGenerateGreedyVideo` reuses the M3c processor/windowed-tower/video-MRoPE on the GDN-hybrid backbone). **Qwen video modalities COMPLETE: image+video both work e2e** (audio N/A for Qwen). **VISION-FORWARD SPEED (2026-07-28, `CLAIM-MM-SPEED-QWEN-IMAGE`, multimodal-speed.md §16): the mm-forward tower BEATS vLLM** — per-image tower forward 142.3 ms (flash `AttentionDenseFlash`, hd-72) vs vLLM 0.25.0 ~250 ms eager encode = 0.57×; attribution-first nsys REFUTED a bigger lever (the t=784 vision attention is serial-latency-bound, flash only 1.04× over warp), STRICT 32/32 image/video HELD + goldens md5 unchanged. Row stays `PARTIAL` — vision-forward speed BEATS vLLM; **umbrella speed pending** on batched c2+/serving. **SECOND CHECKPOINT TOKEN-GATED 2026-08-15 (`Qwen/Qwen3.8-27B`@`1d4bf0f2`, bf16, [#915](https://github.com/mudler/vllm.cpp/issues/915), [spec](specs/qwen38-27b-bf16-gate.md)): 4/7 prompts STRICT 16/16 vs the pinned oracle `555967922`, and all THREE first-divergence positions are EXACT fp32 TIES** — oracle-minus-ours and top-2 gap both **0.000 mnats**, our token at rank **3 / 2 / 2** in the oracle top-20, so `ALL_TIES_OR_IN_BAND` against `kNearTieMnats = 500`. Every one is the [#910](https://github.com/mudler/vllm.cpp/issues/910) tie-break signature and nothing else: vLLM's pick carries the LOWER token id (1814/11/16309) and ours the HIGHER (22960/13/27180) at a bit-identical logprob. Only the first divergence per prompt is adjudicable, so this is three numbers; a raw position count over the grid is NOT a quality score and is not recorded as one. Adjudicated twice on the pinned oracle's fp32 logprobs — a greedy re-decode and an independent TEACHER-FORCED probe that asserts the echoed prefix — because the earlier `transformers` bf16 CPU probe could not resolve below one bf16 ULP (every runner-up gap it printed was a multiple of 0.125) and so could not have reported anything but a tie. **SPEED on the same checkpoint, vs vLLM's PRODUCTION graphed config at the pin, clocks 2184 MHz: 1 of 3 concurrency cells established.** c4 is the only cell where both arms completed every request — **0.963x** output throughput, **1.008x** median ITL. c1 and c8 throughput WERE withheld on 2026-08-15 (superseded, below): our server failed 1/6 in all three reps and 12/11/12 of 48 where vLLM failed none in nine legs ([#931](https://github.com/mudler/vllm.cpp/issues/931)), and `output_throughput` divides tokens by a duration still containing the dead request, so c1 read 0.677x while median TPOT in the SAME file read 1.014x in our favour. **SUPERSEDED 2026-08-19 by the c1/c8 RE-MEASURE ([#915](https://github.com/mudler/vllm.cpp/issues/915), [#979](https://github.com/mudler/vllm.cpp/issues/979), `.agents/benchmark-record.md` `BENCH-QWEN38-27B-BF16 c1/c8 RE-MEASURE`):** #931 landed, and with `VT_SERVER_SSE_PING_S=0` our arm completed **162 of 162** requests, `failed=0` on every leg — c1 **4.4040 tok/s** (CV 0.039%), c8 **22.6402 tok/s** (CV 0.205%). **Our half of the withholding is discharged; NEITHER cell became a ratio and the two halves are blocked differently.** At c1 vLLM also completed everything (**4.2835 tok/s**, CV 0.033%) and `gpu_clock_state compare` returned `PAIRING_VERDICT=DISCARD` on all three pairings — the cross-arm rule PASSED (same boot, both arms 2489 MHz median, 0.0% offset) and the WITHIN-RUN rule failed on both against the 5% ceiling ([#1354](https://github.com/mudler/vllm.cpp/issues/1354): clocks cannot be pinned inside an `rc` lease), so the c1 ratio is OWED, not withheld for being unflattering. At c8 the vLLM denominator is **NOT MEASURABLE on this box at the recorded configuration** — that is the answer, not a gap, and not a claim that vLLM is defective. Read the two output-throughput absolutes with [#1355](https://github.com/mudler/vllm.cpp/issues/1355): our `usage.prompt_tokens` reports 5,942 where vLLM reports 6,144 on identical prompts, which corrupts total-token throughput outright and biases output throughput up by more than its own CV. Cold start **53 s vs 780 s = 14.7x**; host memory after warmup **42.5 vs 110.1 GiB = 2.59x**, caveated because vLLM's is set by `--gpu-memory-utilization 0.85` pre-reserving KV | `MODEL-MM-qwen3-5-qwen3-5-for-conditional-generation` | @@ -510,7 +510,7 @@ Transformers compatibility is capability-driven and excluded from finite counts. | `MODEL-MM-qwen3-vl-moe-qwen3-vlmoe-for-conditional-generation` | `Qwen3VLMoeForConditionalGeneration` | `registry.py:552-555`; `vllm/model_executor/models/qwen3_vl_moe.py::Qwen3VLMoeForConditionalGeneration` | conditional generation / image | MM processor; encoder/merge; vision encoder; video path | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-MM-qwen3-5-qwen3-5-for-conditional-generation` | `Qwen3_5ForConditionalGeneration` | `registry.py:556`; `vllm/model_executor/models/qwen3_5.py::Qwen3_5ForConditionalGeneration` | conditional generation / image | MM processor; encoder/merge; Mamba/SSM state; GDN/linear-attention state; vision encoder; video path | 🚧 [family scoping](specs/mm-tools-scoping-2026-07-10.md); [plain-BF16 loader leaf](specs/qwen35-plain-bf16-direct-load.md); **[multimodal-track W-plan](specs/multimodal-track.md)**; full target spec required | `PARTIAL` (text-only) | text-only: `include/vllm/model_executor/models/qwen3_5_dense.h:40-105,146-171`; plain BF16/F32 + stacked/tied load `src/vllm/model_executor/models/qwen3_5_dense_weights.cpp:52-133,187-246,334-472`; plain execution `src/vllm/model_executor/models/qwen3_5.cpp:1993-2003,4579-4585,5249-5255,5533-5545`; loader route/queue reuse `src/vllm/entrypoints/model_loader.cpp:364-400`; real 4B gate `tests/vllm/models/test_qwen35_plain_weights.cpp:80-196`: CPU topology/load **1656/1656**, AOT CUDA direct OFF/ON full-engine token equivalence **1664/1664**. Existing W3-G immutable `ae9e8ff` default/fallback each pass **235/235 + 16/16** with the frozen 64 plans. Corrected root `/tmp/qwen35-transplant-4b-aot-557ab41d` proves ON=OFF 128/128 and records ON/OFF/vLLM total **6155.10/6064.06/6730.46 tok/s**, peak PSS **2.405/8.571/7.569 GiB**; current ON is 0.9316x historical AOT ON. Current-v0.25 oracle, sanitizer, vision, strict VRAM and external 27B/35B regressions remain unverified, with no new support claim. **MM-completion plan ([multimodal-track.md](specs/multimodal-track.md), `CLAIM-MULTIMODAL-TRACK`, 2026-07-25):** modalities = image + video (NO audio); reuses the landed GDN-hybrid text path — the mm half is the shared `Qwen3_VisionTransformer` (DeepStack, `qwen3_vl.py:519`) stood up on Qwen3-VL-4B first (M2) then attached to this wrapper (M3). Oracle-runnable (0.25.0 ships `qwen3_5.py`+`qwen3_vl.py`); NOT HW/oracle-blocked but **CHECKPOINT-gated** — the cached `unsloth/Qwen3.6-27B-NVFP4` quant is TEXT-ONLY (2111 tensors, ZERO `visual.*`; `vision_config` declared but weights absent), so a vision-inclusive checkpoint download is required (M0). Tower ~0.5-0.7 B params (~1-1.4 GiB bf16) fits GB10 trivially alongside the 27B. Plan owner `CLAIM-MULTIMODAL-TRACK` (row stays PARTIAL/narrative-only; the mm work re-claims it at M3). **M3-b LANDED 2026-07-25 (`CLAIM-MULTIMODAL-M3B`): IMAGE e2e WORKING** — `Qwen3_5VLGenerateGreedy` (`src/vllm/model_executor/models/qwen3_5.cpp`) forks the GDN-hybrid forward on inputs_embeds(scatter tower merger `[196,5120]` into image_token 248056 rows, no deepstack) + 3-section MRoPE `[11,11,10]` interleaved in the 16 full-attn layers (host `BuildMropeCosSinHost` → the `mrope_cos_sin` param on `DenseForwardLayers`, nullptr on text ⇒ byte-identical); vision loader `LoadQwen3VLVisionWeights` (`src/vllm/model_executor/models/qwen3_vl.cpp`, 27B config) + M2a tower + `LoadQwen3_5Dense` bf16 LLM. STRICT gate `tests/vllm/multimodal/test_qwen3_5_vl_e2e.cpp` **32/32 token-exact vs vLLM 0.25.0** (sha256 `ead4b484…`); text-inertness re-run cutlass-ON 27B/35B/Coder **235/315/138**. **M3d LANDED 2026-07-25 (`CLAIM-MULTIMODAL-M3D`): VIDEO e2e WORKING** — `Qwen3_5VLGenerateGreedyVideo` (`src/vllm/model_executor/models/qwen3_5.cpp`) reuses the M3-b image driver via a shared `VLGenerateCoreGdn` (video merge mask on video_token 248057 + `Qwen3VLGetRopeIndexVideo` per-frame temporal MRoPE; M3c processor/windowed-tower reused verbatim; no deepstack). STRICT gate `tests/vllm/multimodal/test_qwen3_5_vl_video_e2e.cpp` **32/32 token-exact vs vLLM 0.25.0** (oracle `scripts/mm/m3d_video_oracle_capture.py`, K=5 deterministic, near-tie gaps 0.0000); image e2e re-run STRICT 32/32 (refactor-safe); text SACRED byte-identical by construction (shared forward untouched). **Qwen video modalities COMPLETE (image+video e2e; audio N/A); speed still pending** (row stays PARTIAL). Speed lever #2 CLOSED 2026-07-27 (`CLAIM-MULTIMODAL-SPEED-DECODE`, multimodal-speed.md §8): on-GPU greedy argmax + decode embed round-trip removed on the shared `VLGenerateCoreGdn`; bit-exact (image+video STRICT 32/32 held, goldens md5-identical); 27B decode TPOT NEUTRAL (223 ms, ~222 ms bandwidth floor, at vLLM parity). **Speed lever #3 FIRST BRICK 2026-07-27 (`CLAIM-MULTIMODAL-SPEED-GRAPH`, multimodal-speed.md §9): the shared `VLGenerateCoreGdn` decode step now routes through the production `Qwen3_5DenseDecodeGraph` (cold→warm→replay captured decode) — mm decode is GRAPH-CAPTURABLE (was eager per-step). S==B==1 bit-identical rebuild; the decode-time 1-D device RoPE at p reproduces the degenerate MRoPE {p,p,p} → token-exact HELD (image+video STRICT 32/32, 30 graph replays confirmed); A/B graphed 232.5 vs eager 233.4 ms/tok = NEUTRAL at the 27B bandwidth floor. Structural gap closed; W-plan = Voxtral decode-graph (audio 1.52× gap-closer) + batched c2+ + serving ingestion. Row stays PARTIAL/speed-pending.** | unassigned | | `MODEL-MM-qwen3-5-qwen3-5-moe-for-conditional-generation` | `Qwen3_5MoeForConditionalGeneration` | `registry.py:557-560`; `vllm/model_executor/models/qwen3_5.py::Qwen3_5MoeForConditionalGeneration` | conditional generation / image | MM processor; encoder/merge; Mamba/SSM state; GDN/linear-attention state; vision encoder; video path | 🚧 [family scoping](specs/mm-tools-scoping-2026-07-10.md); **[multimodal-track W-plan](specs/multimodal-track.md)**; target spec required | `PARTIAL` (text gated, vision NOT gated) | text-only: `include/vllm/model_executor/models/qwen3_5.h:1-17,97`; direct registry `src/vllm/model_executor/models/registry.cpp:10-20`; gate `tests/parity/test_qwen36_paged_engine.cpp:78,140`. W3-G immutable `ae9e8ff` correctness-only ratio-8 inertness passes **2/2 + 315/315**; no 35B performance claim; vision not implemented AT THAT DATE (it landed later, see M2/M3 below, and is still NOT gated). Disk load now DEFERS the routed-expert host copies and streams+frees them per layer during `PrepareMarlinResident` to bound load-phase peak PSS (`ENG-MOE-LOADSTREAM`, engine-matrix; CPU-gated, DGX pending) — device residents byte-identical. **MM-completion plan ([multimodal-track.md](specs/multimodal-track.md), `CLAIM-MULTIMODAL-TRACK`, 2026-07-25):** image + video (NO audio); same shared `Qwen3_VisionTransformer` as the 27B row, attached to the landed MoE GDN-hybrid text path (M3). Oracle-runnable (0.25.0); **CHECKPOINT-gated** — the cached `nvidia/Qwen3.6-35B-A3B-NVFP4` quant is TEXT-ONLY (`vision_config` declared, `visual.*` weights absent); vision-inclusive download required (M0). Tower fits GB10 alongside the 35B MoE per the landed text run. Plan owner `CLAIM-MULTIMODAL-TRACK` (row stays PARTIAL/narrative-only; the mm work re-claims it at M3). **M2/M3 LANDED (#891, `.agents/specs/moe-vision-tower.md`):** the loader no longer drops the checkpoint's 333 `model.visual.*` tensors (`LoadQwen3_5MoeVision` -> the SHARED `LoadQwen3VLVisionWeights` the dense arm is gated on; their ABSENCE is refused by name), and `Qwen3_5MoeVLGenerateGreedy[Video]` forks the forward gated on mm input over a greedy core now TEMPLATED on the weights arm rather than copied. Evidence: CPU suite 479/479 serial; the new `test_qwen3_5_moe_vision` proves the forked forward reduces EXACTLY to the text forward over the tower row (one visual token, 1x1x1 LLM grid) and that MRoPE is applied (8x8 grid must DIFFER from the 1-D run), with 4 mutations driven RED and restored byte-exact; on Thor (sm_110, FALLBACK attention) `test_qwen3_5_moe_vision_hw` loads the real 333 tensors and runs the tower on the fixture image. **OWED: the binding image and video token-exact gates vs the pinned oracle at 35B.** Not runnable on Thor -- vLLM cannot import there (`libcuda.so.1` absent on the host, `torch.cuda.is_available()` False) and the bf16 35B is ~67 GiB against this box's documented 25 GB single-model reboot ceiling; dgx.casa was off-limits mid-run for a sibling row. **TEXT ARM ORACLE-GATED ON THE PUBLISHED BF16 REPO 2026-08-15 ([#740](https://github.com/mudler/vllm.cpp/issues/740) + [#864](https://github.com/mudler/vllm.cpp/issues/864)), and this changes NOTHING about the vision claim:** greedy 7 prompts x 3 repeats x 16 tokens on `Qwen/Qwen3.6-35B-A3B` bf16 @`995ad96eacd98c81ed38be0c5b274b04031597b0` vs the pinned oracle gave **6/7 prompts STRICT 16/16**, the seventh one exact logit tie (`top2_gap_mnats = 0.0`) our on-device argmax breaks toward the higher id ([#910](https://github.com/mudler/vllm.cpp/issues/910)); only the FIRST divergence per prompt is adjudicable, so the raw 108/112 position count is NOT a quality score. SACRED inertness 3/3, goldens byte-identical (27B 235/235, 35B 315/315, Coder 138/138). NO throughput, latency or memory number exists for this checkpoint. The row therefore stays PARTIAL: **the binding image and video token-exact gates at 35B are still OWED**, the vision claim remains "the tower loads and computes" rather than "produces correct tokens", and the sm_110 run that proved it used the FALLBACK attention path, which is not coverage of the shipped GB10 path. [#908](https://github.com/mudler/vllm.cpp/issues/908)'s dense regression check is PARTIAL too: dense TEXT is 235/235 at `2f2bce926`, a true before/after (binary md5 `db889909d4…` vs `49ded1ece8…`, 500 TUs recompiled), while dense image/video stays UNVERIFIED (network-blocked) | unassigned | -| `MODEL-MM-qwen4-exp-qwen4-exp-for-conditional-generation` | `Qwen4ExpForConditionalGeneration` (`model_type: qwen4_exp`; campaign row `MODEL-MM-QWEN4-EXP`) | **NOT IN vLLM AT ANY REVISION** — deliberately written with no pinned module/class target, the convention `MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm` follows for a beyond-pin arm, and stronger here: this is absence from vLLM `main` rather than staleness in `555967922`. Read live 2026-08-26 at `origin/main` = `6a5e8f5979`: no `qwen4*` path, no `registry.py` entry, and a repository-wide GitHub search for `qwen4` returns ZERO results; `vllm-omni` likewise. Algorithm source is [transformers#48337](https://github.com/huggingface/transformers/pull/48337) `models/qwen4_exp/modular_qwen4_exp.py`, MERGED 2026-08-26 | conditional generation / text + image + video | MM processor; vision encoder (UNCHANGED from `Qwen3_5MoeVisionModel`); GDN/linear-attention state; block-sparse attention + indexer side cache; FusedMoE/grouped GEMM; hyper-connection residual streams; hashed n-gram embedding; dilated depthwise conv; MTP | ✅ [Qwen3.8-Flash-Next](specs/qwen4-exp-flash-next.md) | `READY` | **SPEC ONLY, NO PRODUCT CODE, NO TOKEN, NO SPEED.** `Qwen/Qwen3.8-Flash-Next` (2026-08-24, 180B total / 6B activated). Split oracle by developer direction 2026-08-26: **transformers for the ALGORITHM, vLLM ops for the OPTIMIZED PATH**, because `Qwen4ExpTextQSAIndexer.forward` loops in Python over `(batch_idx, query_idx)` and says "we only allow eager and sdpa", so the reference is semantics and not a serving path. `Qwen4ExpTextModel` inherits `Qwen3_5MoeTextModel` and leaves rotary, MLP, experts, TopK router and the ENTIRE vision tower unchanged (`class Qwen4ExpVisionModel(Qwen3_5MoeVisionModel): pass`); GDN matches our AOT gate exactly (`K=V=128, Hg=16, Hv=48` against `src/vt/cuda/cuda_gdn.cu`'s `H in {48,32}`). **Exactly two components have NO vLLM op**: the PLE dilated depthwise conv (`git grep dilation` over vLLM `layers/mamba/` = 0 hits) and the hashed n-gram embedding. **CORRECTED after the component deep-dives: QSA's twin is DeepSeek-V4's C4 indexer lane, NOT MiniMax-M3.** The first reading rested on `MLAAttentionSpec` being an MLA claim; it is a per-state BUDGET shape, and M3 — itself plain GQA — uses it. Nine structural matches with DSv4 including `compress_ratio == 4`, relu-summed MQA scoring, RoPE at the block-start position and `tokens_per_state=compress_ratio`. M3 scores by `max` over 128 RAW dots, emits one set per KV head, and welds `SPARSE_BLOCK_SIZE = 128` to the KV page size. The genuinely NEW work is the consumer: DSv4 attends COMPRESSED KV, M3 attends raw at page granularity, QSA attends RAW at ratio-4 granularity and nothing upstream does that. Any QSA gate must exceed 2048 tokens of context or it cannot distinguish a correct port from one attending pooled keys. Residual stream is `hc_count * hidden_size` = 4 x 2560 = **10240 wide through the whole stack**; `number_of_conv_states = 3` on a PLE layer plus the indexer side cache. **NOTHING PUBLISHED FITS ~119 GB on GB10**: BF16 ~360 GB, official FP8 ~180 GB, `RadixArk/...-NVFP4` ~128 GB, `unsloth/...-GGUF` is a README with ZERO weight files and llama.cpp has no `qwen4_exp` either, so the standing k-quant arms must be authored here and have NO llama.cpp oracle. Sizing ARITHMETIC and not measurement: Q8_0 throughout ~191 GB (no), Q4_K_M throughout ~109 GB, Q4_K_M backbone with the 51 GB n-gram table non-resident ~76 GB — that table is 28% of the model and is touched 16 times per token, which is the offload the card itself argues for. `gateable = no` until an arm runs, and **the chosen arm does not load today**: `KeepQuantKDim` returns `-1` for `kEmbeddingTable`, so a quantized n-gram table expands to bf16 (102.4 GB) and dies at load; `kKeepF16` is the only non-expanding gather residency and is CPU-only. Also `moe_intermediate_size = 640` / `hc_lowrank = 320` are Q4_K-illegal on their reduction dims and our reader cannot open ggml types 3/6/7/20, so the converter must emit Q4_0. `ENG-WEIGHT-OFFLOAD` is inert and does not help; the shipping mmap-borrow tier does. Both blocking decisions SETTLED 2026-08-26: the transformers lane pin is **ACCEPTED at 5.16.0** ([`oracles/transformers.md`](oracles/transformers.md)), because the registry pin 5.14.1 does not contain `Qwen4Exp` — and 5.16.0 is a real release rather than a SHA, bounded by fetching the model file at each tag (`v5.16.0` HTTP 200, `v5.15.0` HTTP 404), with the version string UNMEASURED until an oracle stands up; and the first runnable arm is the **Q4_K_M backbone with a NON-RESIDENT n-gram table** (~76 GB), Q8_0 having been raised and rejected on ~191 GB. [#1978](https://github.com/mudler/vllm.cpp/issues/1978) | unassigned | +| `MODEL-MM-qwen4-exp-qwen4-exp-for-conditional-generation` | `Qwen4ExpForConditionalGeneration` (`model_type: qwen4_exp`; campaign row `MODEL-MM-QWEN4-EXP`) | **NOT IN vLLM AT ANY REVISION** — deliberately written with no pinned module/class target, the convention `MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm` follows for a beyond-pin arm, and stronger here: this is absence from vLLM `main` rather than staleness in `555967922`. Read live 2026-08-26 at `origin/main` = `6a5e8f5979`: no `qwen4*` path, no `registry.py` entry, and a repository-wide GitHub search for `qwen4` returns ZERO results; `vllm-omni` likewise. Algorithm source is [transformers#48337](https://github.com/huggingface/transformers/pull/48337) `models/qwen4_exp/modular_qwen4_exp.py`, MERGED 2026-08-26 | conditional generation / text + image + video | MM processor; vision encoder (UNCHANGED from `Qwen3_5MoeVisionModel`); GDN/linear-attention state; block-sparse attention + indexer side cache; FusedMoE/grouped GEMM; hyper-connection residual streams; hashed n-gram embedding; dilated depthwise conv; MTP | ✅ [Qwen3.8-Flash-Next](specs/qwen4-exp-flash-next.md) | `ACTIVE` | **PRODUCT CODE LANDING, NO TOKEN, NO SPEED.** `Qwen/Qwen3.8-Flash-Next` (2026-08-24, 180B total / 6B activated). W6a ([#1989](https://github.com/mudler/vllm.cpp/issues/1989)) is the first wave to reach `main` and moves this row `READY` -> `ACTIVE`; W1/W2/W3/W4 are implemented, reviewed and repaired on their branches. **Nothing is REACHABLE yet** — no `ModelRegistry` entry resolves `Qwen4ExpForConditionalGeneration`, so every landed slice is unreached by design and named as such under the spec's `## Owed`. Split oracle by developer direction 2026-08-26: **transformers for the ALGORITHM, vLLM ops for the OPTIMIZED PATH**, because `Qwen4ExpTextQSAIndexer.forward` loops in Python over `(batch_idx, query_idx)` and says "we only allow eager and sdpa", so the reference is semantics and not a serving path. `Qwen4ExpTextModel` inherits `Qwen3_5MoeTextModel` and leaves rotary, MLP, experts, TopK router and the ENTIRE vision tower unchanged (`class Qwen4ExpVisionModel(Qwen3_5MoeVisionModel): pass`); GDN matches our AOT gate exactly (`K=V=128, Hg=16, Hv=48` against `src/vt/cuda/cuda_gdn.cu`'s `H in {48,32}`). **Exactly two components have NO vLLM op**: the PLE dilated depthwise conv (`git grep dilation` over vLLM `layers/mamba/` = 0 hits) and the hashed n-gram embedding. **CORRECTED after the component deep-dives: QSA's twin is DeepSeek-V4's C4 indexer lane, NOT MiniMax-M3.** The first reading rested on `MLAAttentionSpec` being an MLA claim; it is a per-state BUDGET shape, and M3 — itself plain GQA — uses it. Nine structural matches with DSv4 including `compress_ratio == 4`, relu-summed MQA scoring, RoPE at the block-start position and `tokens_per_state=compress_ratio`. M3 scores by `max` over 128 RAW dots, emits one set per KV head, and welds `SPARSE_BLOCK_SIZE = 128` to the KV page size. The genuinely NEW work is the consumer: DSv4 attends COMPRESSED KV, M3 attends raw at page granularity, QSA attends RAW at ratio-4 granularity and nothing upstream does that. Any QSA gate must exceed 2048 tokens of context or it cannot distinguish a correct port from one attending pooled keys. Residual stream is `hc_count * hidden_size` = 4 x 2560 = **10240 wide through the whole stack**; `number_of_conv_states = 3` on a PLE layer plus the indexer side cache. **CORRECTED 2026-08-26 — ONE PUBLISHED ARTIFACT FITS.** `unsloth/Qwen3.8-Flash-Next-GGUF` UD-IQ1_S is **67.56 GiB** in 3 shards (`general.architecture = qwen4exp`, 1224 tensors) against ~119.6 GiB usable on GB10, roughly 52 GiB of headroom; the earlier "README with ZERO weight files" reading was true when written and false hours later. Every safetensors artifact still does not fit: BF16 ~360 GB, official FP8 ~180 GB, `RadixArk/...-NVFP4` ~128 GB. llama.cpp has no MERGED `qwen4_exp`, so arms nobody publishes are still ours to author and still have no llama.cpp oracle. Sizing ARITHMETIC and not measurement: Q8_0 throughout ~191 GB (no), Q4_K_M throughout ~109 GB, Q4_K_M backbone with the 51 GB n-gram table non-resident ~76 GB — that table is 28% of the model and is touched 16 times per token, which is the offload the card itself argues for. `gateable = no` until an arm runs. **W6a CLEARED BOTH LOAD BLOCKERS**: IQ4_NL (20) and Q5_0 (6) are in the reader with geometry read from the pinned llama.cpp oracle at `b10451` and decoders gated bit-exactly against its compiled output on real file bytes, so the three shards OPEN; and `kEmbeddingTable` is keep-quant eligible behind a dequantizing gather, so the n-gram table no longer expands to 102.4 GB. `moe_intermediate_size = 640` / `hc_lowrank = 320` remain Q4_K-illegal on their reduction dims — the published file uses IQ4_NL there, and a `-Q4_K_M` build would land on Q5_0, which is why both were added. Types 3 and 7 are still unsupported and recorded as owed. `ENG-WEIGHT-OFFLOAD` is inert and does not help; the shipping mmap-borrow tier does. Both blocking decisions SETTLED 2026-08-26: the transformers lane pin is **ACCEPTED at 5.16.0** ([`oracles/transformers.md`](oracles/transformers.md)), because the registry pin 5.14.1 does not contain `Qwen4Exp` — and 5.16.0 is a real release rather than a SHA, bounded by fetching the model file at each tag (`v5.16.0` HTTP 200, `v5.15.0` HTTP 404), with the version string UNMEASURED until an oracle stands up; and the first runnable arm is the **Q4_K_M backbone with a NON-RESIDENT n-gram table** (~76 GB), Q8_0 having been raised and rejected on ~191 GB. [#1978](https://github.com/mudler/vllm.cpp/issues/1978) | `CLAIM-MODEL-MM-QWEN4-EXP` | | `MODEL-MM-rvl-rfor-conditional-generation` | `RForConditionalGeneration` | `registry.py:561`; `vllm/model_executor/models/rvl.py::RForConditionalGeneration` | conditional generation / video+image | MM processor; encoder/merge; vision encoder | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-MM-skyworkr1v-skywork-r1-vchat-model` | `SkyworkR1VChatModel` | `registry.py:562`; `vllm/model_executor/models/skyworkr1v.py::SkyworkR1VChatModel` | conditional generation / image | MM processor; encoder/merge; vision encoder | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-MM-smolvlm-smol-vlmfor-conditional-generation` | `SmolVLMForConditionalGeneration` | `registry.py:563`; `vllm/model_executor/models/smolvlm.py::SmolVLMForConditionalGeneration` | conditional generation / image | MM processor; encoder/merge; vision encoder | ☐ required | `INVENTORIED` | none | unassigned | diff --git a/.agents/quantization-matrix.md b/.agents/quantization-matrix.md index b4c5972b1..c71830244 100644 --- a/.agents/quantization-matrix.md +++ b/.agents/quantization-matrix.md @@ -63,14 +63,14 @@ forces the full dequant path back. | `QUANT-GGUF-Q5_0` | Q5_0 | 6 / output | - | - | - | - | - | `INVENTORIED` | - | leaf open | - | | `QUANT-GGUF-Q5_1` | Q5_1 | 7 / output | - | - | - | - | - | `INVENTORIED` | - | leaf open | - | | `QUANT-GGUF-Q8_0` | Q8_0 | 8 / output | Y | Y | Y | Y | - | `PARTIAL` | [dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L64), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L65); [unit](../tests/vllm/test_gguf_dequant.cpp#L35); [APEX gate](../tests/parity/test_qwen36_gguf_engine.cpp#L149) **`C` = `Y` since G4 (2026-07-22):** the weight stays in its ggml blocks at load ([keep-quant policy](../src/vllm/model_executor/model_loader/gguf_keep_quant.cpp#L95), DEFAULT ON wherever `kMatmulBTQuant` is registered for the running device) and [`vt::MatmulBT`](../src/vt/ops.cpp#L158) dispatches it to the [tier-0 `vec_dot` GEMM](../src/vt/cpu/cpu_quant_dot.cpp#L1) — no bf16 expansion on the executed path. Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1); residency losslessness + routing gates [test_gguf_keep_quant](../tests/vllm/test_gguf_keep_quant.cpp#L1). Binding CPU A/B on the mixed `Qwen3.5-2B-UD-Q8_K_XL` file (idle dgx aarch64, same binary, 3 reps): decode **3.45x**, prefill **4.16x**, peak RSS **1.16x less**, output tokens **byte-identical** to both the pre-G4 arm and the `VT_CPU_REF=1` oracle. `P` stays `-`: still 3.38x/8.20x behind llama.cpp on the same file, because 60% of ITS weight bytes are `f16` and take the elementwise kernel. | leaf open | - | -| `QUANT-GGUF-Q2_K` | Q2_K | 10 / output | Y | Y | Y | - | - | `ACTIVE` | [dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L246) (`DequantQ2_K`, ported 1:1 from llama.cpp `ggml-quants.c:903`), vt block dtype `kQ2_K` [geometry](../src/vt/dtype.cpp#L46), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L109). **`C` = `Y` since DeepSeek-V4 W8 (2026-07-29):** the keep-quant `vec_dot` [`VecDotQ2_KQ8_K`](../src/vt/cpu/cpu_quant_dot.cpp#L1) (1:1 port of ggml `vec_dot_q2_K_q8_K_generic`, `quants.c:514`) + the [Q8_K traits row](../src/vt/cpu/cpu_quant_traits.cpp#L1) make `HasQuantDotKernel` TRUE, so the loader keeps Q2_K blocks COMPRESSED and dots them directly (never expand-to-bf16) — the `UD-Q2_K_XL` DeepSeek-V4 sibling vehicle. Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1) (added to `kWeightCases`: vec_dot vs f64 dequant-dot ≤1e-5·L1, NMSE ≤5e-4, RED-first proven). `E`/`P` = `-`: the DeepSeek-V4 e2e run is W8-residual (W2b tower materialization pending). **CUDA compute LANDED 2026-07-29 (`KERNEL-QUANT-CIQ-GEMM-CUDA`, `CLAIM-CUDA-KEEPQUANT-GEMM`):** a native kCUDA `kMatmulBTQuant` provider ([cuda_quant_dot.cu](../src/vt/cuda/cuda_quant_dot.cu), MMVQ-style) dots these blocks ON the GPU (Q8_K activation quant + integer dot, dequant-in-kernel, weights stay COMPRESSED in the unified pool), GB10-gated ([test_cuda_quant_dot](../tests/vt/test_cuda_quant_dot.cpp)) 2/2 · 92401 vs the CPU oracle (NMSE ≤1e-6, int core bit-exact) + f64 dequant (≤5e-4), compute-sanitizer 0, RED-first proven — so on a CUDA runner these experts dispatch to the GPU, not the 20 ARM cores. | [CUDA keep-quant GEMM](specs/cuda-keepquant-gemm.md); [iquant DSV4 spike](specs/gguf-iquant-dsv4.md) | `CLAIM-DEEPSEEK-V4-W8` | +| `QUANT-GGUF-Q2_K` | Q2_K | 10 / output | Y | Y | Y | - | - | `ACTIVE` | [dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L286) (`DequantQ2_K`, ported 1:1 from llama.cpp `ggml-quants.c:903`), vt block dtype `kQ2_K` [geometry](../src/vt/dtype.cpp#L54), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L109). **`C` = `Y` since DeepSeek-V4 W8 (2026-07-29):** the keep-quant `vec_dot` [`VecDotQ2_KQ8_K`](../src/vt/cpu/cpu_quant_dot.cpp#L1) (1:1 port of ggml `vec_dot_q2_K_q8_K_generic`, `quants.c:514`) + the [Q8_K traits row](../src/vt/cpu/cpu_quant_traits.cpp#L1) make `HasQuantDotKernel` TRUE, so the loader keeps Q2_K blocks COMPRESSED and dots them directly (never expand-to-bf16) — the `UD-Q2_K_XL` DeepSeek-V4 sibling vehicle. Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1) (added to `kWeightCases`: vec_dot vs f64 dequant-dot ≤1e-5·L1, NMSE ≤5e-4, RED-first proven). `E`/`P` = `-`: the DeepSeek-V4 e2e run is W8-residual (W2b tower materialization pending). **CUDA compute LANDED 2026-07-29 (`KERNEL-QUANT-CIQ-GEMM-CUDA`, `CLAIM-CUDA-KEEPQUANT-GEMM`):** a native kCUDA `kMatmulBTQuant` provider ([cuda_quant_dot.cu](../src/vt/cuda/cuda_quant_dot.cu), MMVQ-style) dots these blocks ON the GPU (Q8_K activation quant + integer dot, dequant-in-kernel, weights stay COMPRESSED in the unified pool), GB10-gated ([test_cuda_quant_dot](../tests/vt/test_cuda_quant_dot.cpp)) 2/2 · 92401 vs the CPU oracle (NMSE ≤1e-6, int core bit-exact) + f64 dequant (≤5e-4), compute-sanitizer 0, RED-first proven — so on a CUDA runner these experts dispatch to the GPU, not the 20 ARM cores. | [CUDA keep-quant GEMM](specs/cuda-keepquant-gemm.md); [iquant DSV4 spike](specs/gguf-iquant-dsv4.md) | `CLAIM-DEEPSEEK-V4-W8` | | `QUANT-GGUF-Q3_K` | Q3_K | 11 / output + S/M/L presets | Y | Y | Y | Y | - | `PARTIAL` | [dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L79), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L75); [unit](../tests/vllm/test_gguf_dequant.cpp#L164); [APEX gate](../tests/parity/test_qwen36_gguf_engine.cpp#L143) **`C` = `Y` since G4 (2026-07-22):** the weight stays in its ggml blocks at load ([keep-quant policy](../src/vllm/model_executor/model_loader/gguf_keep_quant.cpp#L95), DEFAULT ON wherever `kMatmulBTQuant` is registered for the running device) and [`vt::MatmulBT`](../src/vt/ops.cpp#L158) dispatches it to the [tier-0 `vec_dot` GEMM](../src/vt/cpu/cpu_quant_dot.cpp#L1) — no bf16 expansion on the executed path. Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1); residency losslessness + routing gates [test_gguf_keep_quant](../tests/vllm/test_gguf_keep_quant.cpp#L1). Binding CPU A/B on the mixed `Qwen3.5-2B-UD-Q8_K_XL` file (idle dgx aarch64, same binary, 3 reps): decode **3.45x**, prefill **4.16x**, peak RSS **1.16x less**, output tokens **byte-identical** to both the pre-G4 arm and the `VT_CPU_REF=1` oracle. `P` stays `-`: still 3.38x/8.20x behind llama.cpp on the same file, because 60% of ITS weight bytes are `f16` and take the elementwise kernel. | leaf open | - | | `QUANT-GGUF-Q4_K` | Q4_K | 12 / output + S/M presets | Y | Y | Y | Y | - | `PARTIAL` | [dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L125), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L77); [unit](../tests/vllm/test_gguf_dequant.cpp#L77); [APEX gate](../tests/parity/test_qwen36_gguf_engine.cpp#L143) **`C` = `Y` since G4 (2026-07-22):** the weight stays in its ggml blocks at load ([keep-quant policy](../src/vllm/model_executor/model_loader/gguf_keep_quant.cpp#L95), DEFAULT ON wherever `kMatmulBTQuant` is registered for the running device) and [`vt::MatmulBT`](../src/vt/ops.cpp#L158) dispatches it to the [tier-0 `vec_dot` GEMM](../src/vt/cpu/cpu_quant_dot.cpp#L1) — no bf16 expansion on the executed path. Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1); residency losslessness + routing gates [test_gguf_keep_quant](../tests/vllm/test_gguf_keep_quant.cpp#L1). Binding CPU A/B on the mixed `Qwen3.5-2B-UD-Q8_K_XL` file (idle dgx aarch64, same binary, 3 reps): decode **3.45x**, prefill **4.16x**, peak RSS **1.16x less**, output tokens **byte-identical** to both the pre-G4 arm and the `VT_CPU_REF=1` oracle. `P` stays `-`: still 3.38x/8.20x behind llama.cpp on the same file, because 60% of ITS weight bytes are `f16` and take the elementwise kernel. | leaf open | - | | `QUANT-GGUF-Q5_K` | Q5_K | 13 / output + S/M presets | Y | Y | Y | Y | - | `PARTIAL` | [dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L155), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L79); [unit](../tests/vllm/test_gguf_dequant.cpp#L105); [APEX gate](../tests/parity/test_qwen36_gguf_engine.cpp#L149) **`C` = `Y` since G4 (2026-07-22):** the weight stays in its ggml blocks at load ([keep-quant policy](../src/vllm/model_executor/model_loader/gguf_keep_quant.cpp#L95), DEFAULT ON wherever `kMatmulBTQuant` is registered for the running device) and [`vt::MatmulBT`](../src/vt/ops.cpp#L158) dispatches it to the [tier-0 `vec_dot` GEMM](../src/vt/cpu/cpu_quant_dot.cpp#L1) — no bf16 expansion on the executed path. Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1); residency losslessness + routing gates [test_gguf_keep_quant](../tests/vllm/test_gguf_keep_quant.cpp#L1). Binding CPU A/B on the mixed `Qwen3.5-2B-UD-Q8_K_XL` file (idle dgx aarch64, same binary, 3 reps): decode **3.45x**, prefill **4.16x**, peak RSS **1.16x less**, output tokens **byte-identical** to both the pre-G4 arm and the `VT_CPU_REF=1` oracle. `P` stays `-`: still 3.38x/8.20x behind llama.cpp on the same file, because 60% of ITS weight bytes are `f16` and take the elementwise kernel. | leaf open | - | | `QUANT-GGUF-Q6_K` | Q6_K | 14 / output | Y | Y | Y | Y | - | `PARTIAL` | [dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L192), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L80); [unit](../tests/vllm/test_gguf_dequant.cpp#L134); [Compact gate](../tests/parity/test_qwen36_gguf_engine.cpp#L143), [Balanced gate](../tests/parity/test_qwen36_gguf_engine.cpp#L149) **`C` = `Y` since G4 (2026-07-22):** the weight stays in its ggml blocks at load ([keep-quant policy](../src/vllm/model_executor/model_loader/gguf_keep_quant.cpp#L95), DEFAULT ON wherever `kMatmulBTQuant` is registered for the running device) and [`vt::MatmulBT`](../src/vt/ops.cpp#L158) dispatches it to the [tier-0 `vec_dot` GEMM](../src/vt/cpu/cpu_quant_dot.cpp#L1) — no bf16 expansion on the executed path. Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1); residency losslessness + routing gates [test_gguf_keep_quant](../tests/vllm/test_gguf_keep_quant.cpp#L1). Binding CPU A/B on the mixed `Qwen3.5-2B-UD-Q8_K_XL` file (idle dgx aarch64, same binary, 3 reps): decode **3.45x**, prefill **4.16x**, peak RSS **1.16x less**, output tokens **byte-identical** to both the pre-G4 arm and the `VT_CPU_REF=1` oracle. `P` stays `-`: still 3.38x/8.20x behind llama.cpp on the same file, because 60% of ITS weight bytes are `f16` and take the elementwise kernel. | leaf open | - | -| `QUANT-GGUF-IQ2_XXS` | IQ2_XXS | 16 / output | Y | Y | Y | - | - | `ACTIVE` | reader trait [`:230`](../src/vllm/model_executor/model_loader/gguf_reader.cpp#L230) (66 B block); [codebook dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L282) (`DequantIQ2_XXS`, ported 1:1 from llama.cpp `ggml-quants.c:2416`); grids moved to the shared [cpu_quant_iq_tables.h](../src/vt/cpu/cpu_quant_iq_tables.h) (`iq2xxs_grid`/`ksigns_iq2xs`/`kmask_iq2xs`, `ggml-common.h:499,503,550`); vt block dtype `kIQ2_XXS` [geometry](../src/vt/dtype.cpp#L72), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L114). **`C` = `Y` since DeepSeek-V4 W8 (2026-07-29):** the keep-quant `vec_dot` [`VecDotIQ2_XXSQ8_K`](../src/vt/cpu/cpu_quant_dot.cpp#L1) (1:1 port of ggml `vec_dot_iq2_xxs_q8_K_generic`, `quants.c:855`) + the [Q8_K traits row](../src/vt/cpu/cpu_quant_traits.cpp#L1) make `HasQuantDotKernel` TRUE ⇒ the loader keeps IQ2_XXS blocks COMPRESSED and dots them directly — the MEMORY ENABLER for the single-Spark `unsloth/DeepSeek-V4-Flash-GGUF UD-IQ2_XXS` gate/up routed experts (`ffn_gate_exps`/`ffn_up_exps`). Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1); RED-first proven (perturb the `0.125` fold → fail, revert → green). **W2b GGUF→tower materialization LANDED (2026-07-29, `CLAIM-DEEPSEEK-V4-W2B`):** `LoadDeepseekV4FromGguf` wires the `blk.N.*` name-map + these keep-quant blocks into the `DeepseekV4` weight towers (MW/SEW stay COMPRESSED, gated `test_deepseek_v4_gguf_load` 5/5·149 tiny synthetic). `E`/`P` = `-`: the real DeepSeek-V4 91 GB `UD-IQ2_XXS` e2e run stays W8-final (download + DGX). **CUDA compute LANDED 2026-07-29 (`KERNEL-QUANT-CIQ-GEMM-CUDA`, `CLAIM-CUDA-KEEPQUANT-GEMM`):** a native kCUDA `kMatmulBTQuant` provider ([cuda_quant_dot.cu](../src/vt/cuda/cuda_quant_dot.cu), MMVQ-style) dots these blocks ON the GPU (Q8_K activation quant + integer dot, dequant-in-kernel, weights stay COMPRESSED in the unified pool), GB10-gated ([test_cuda_quant_dot](../tests/vt/test_cuda_quant_dot.cpp)) 2/2 · 92401 vs the CPU oracle (NMSE ≤1e-6, int core bit-exact) + f64 dequant (≤5e-4), compute-sanitizer 0, RED-first proven — so on a CUDA runner these experts dispatch to the GPU, not the 20 ARM cores. | [CUDA keep-quant GEMM](specs/cuda-keepquant-gemm.md); [iquant DSV4 spike](specs/gguf-iquant-dsv4.md) | `CLAIM-DEEPSEEK-V4-W8` | +| `QUANT-GGUF-IQ2_XXS` | IQ2_XXS | 16 / output | Y | Y | Y | - | - | `ACTIVE` | reader trait [`:230`](../src/vllm/model_executor/model_loader/gguf_reader.cpp#L230) (66 B block); [codebook dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L322) (`DequantIQ2_XXS`, ported 1:1 from llama.cpp `ggml-quants.c:2416`); grids moved to the shared [cpu_quant_iq_tables.h](../src/vt/cpu/cpu_quant_iq_tables.h) (`iq2xxs_grid`/`ksigns_iq2xs`/`kmask_iq2xs`, `ggml-common.h:499,503,550`); vt block dtype `kIQ2_XXS` [geometry](../src/vt/dtype.cpp#L80), [dispatch](../src/vllm/model_executor/model_loader/gguf_dequant.cpp#L114). **`C` = `Y` since DeepSeek-V4 W8 (2026-07-29):** the keep-quant `vec_dot` [`VecDotIQ2_XXSQ8_K`](../src/vt/cpu/cpu_quant_dot.cpp#L1) (1:1 port of ggml `vec_dot_iq2_xxs_q8_K_generic`, `quants.c:855`) + the [Q8_K traits row](../src/vt/cpu/cpu_quant_traits.cpp#L1) make `HasQuantDotKernel` TRUE ⇒ the loader keeps IQ2_XXS blocks COMPRESSED and dots them directly — the MEMORY ENABLER for the single-Spark `unsloth/DeepSeek-V4-Flash-GGUF UD-IQ2_XXS` gate/up routed experts (`ffn_gate_exps`/`ffn_up_exps`). Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1); RED-first proven (perturb the `0.125` fold → fail, revert → green). **W2b GGUF→tower materialization LANDED (2026-07-29, `CLAIM-DEEPSEEK-V4-W2B`):** `LoadDeepseekV4FromGguf` wires the `blk.N.*` name-map + these keep-quant blocks into the `DeepseekV4` weight towers (MW/SEW stay COMPRESSED, gated `test_deepseek_v4_gguf_load` 5/5·149 tiny synthetic). `E`/`P` = `-`: the real DeepSeek-V4 91 GB `UD-IQ2_XXS` e2e run stays W8-final (download + DGX). **CUDA compute LANDED 2026-07-29 (`KERNEL-QUANT-CIQ-GEMM-CUDA`, `CLAIM-CUDA-KEEPQUANT-GEMM`):** a native kCUDA `kMatmulBTQuant` provider ([cuda_quant_dot.cu](../src/vt/cuda/cuda_quant_dot.cu), MMVQ-style) dots these blocks ON the GPU (Q8_K activation quant + integer dot, dequant-in-kernel, weights stay COMPRESSED in the unified pool), GB10-gated ([test_cuda_quant_dot](../tests/vt/test_cuda_quant_dot.cpp)) 2/2 · 92401 vs the CPU oracle (NMSE ≤1e-6, int core bit-exact) + f64 dequant (≤5e-4), compute-sanitizer 0, RED-first proven — so on a CUDA runner these experts dispatch to the GPU, not the 20 ARM cores. | [CUDA keep-quant GEMM](specs/cuda-keepquant-gemm.md); [iquant DSV4 spike](specs/gguf-iquant-dsv4.md) | `CLAIM-DEEPSEEK-V4-W8` | | `QUANT-GGUF-IQ2_XS` | IQ2_XS | 17 / output; IQ2_S preset storage | - | - | - | - | - | `INVENTORIED` | - | leaf open | - | -| `QUANT-GGUF-IQ3_XXS` | IQ3_XXS | 18 / output | Y | Y | Y | - | - | `READY` | reader trait [`:237`](../src/vllm/model_executor/model_loader/gguf_reader.cpp#L237) (98 B block, ADDED W8); [codebook dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L309) (`DequantIQ3_XXS` + the shared [`iq3xxs_grid`](../src/vt/cpu/cpu_quant_iq_tables.h), ported 1:1 from llama.cpp `ggml-quants.c:2503` + `ggml-common.h:1007`); vt block dtype `kIQ3_XXS` [geometry](../src/vt/dtype.cpp#L78). **`C` = `Y` (DeepSeek-V4 W8, 2026-07-29):** the keep-quant `vec_dot` [`VecDotIQ3_XXSQ8_K`](../src/vt/cpu/cpu_quant_dot.cpp#L1) (1:1 port of ggml `vec_dot_iq3_xxs_q8_K_generic`, `quants.c:999`) + the [Q8_K traits row](../src/vt/cpu/cpu_quant_traits.cpp#L1) keep IQ3_XXS blocks COMPRESSED — the MEMORY ENABLER for the `UD-IQ2_XXS` **down** routed experts (`ffn_down_exps`, which are IQ3_XXS, NOT IQ2_XXS; without keep-quant they alone OOM the box). Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1) (vec_dot vs f64 dequant-dot ≤1e-5·L1, NMSE ≤5e-4). **W2b GGUF→tower materialization LANDED (2026-07-29, `CLAIM-DEEPSEEK-V4-W2B`):** `LoadDeepseekV4FromGguf` wires the name-map + keep-quant blocks into the `DeepseekV4` towers (`test_deepseek_v4_gguf_load` 5/5·149). `E`/`P` = `-`: the real DeepSeek-V4 e2e run stays W8-final. **CUDA compute LANDED 2026-07-29 (`KERNEL-QUANT-CIQ-GEMM-CUDA`, `CLAIM-CUDA-KEEPQUANT-GEMM`):** a native kCUDA `kMatmulBTQuant` provider ([cuda_quant_dot.cu](../src/vt/cuda/cuda_quant_dot.cu), MMVQ-style) dots these blocks ON the GPU (Q8_K activation quant + integer dot, dequant-in-kernel, weights stay COMPRESSED in the unified pool), GB10-gated ([test_cuda_quant_dot](../tests/vt/test_cuda_quant_dot.cpp)) 2/2 · 92401 vs the CPU oracle (NMSE ≤1e-6, int core bit-exact) + f64 dequant (≤5e-4), compute-sanitizer 0, RED-first proven — so on a CUDA runner these experts dispatch to the GPU, not the 20 ARM cores. | [CUDA keep-quant GEMM](specs/cuda-keepquant-gemm.md); [iquant DSV4 spike](specs/gguf-iquant-dsv4.md) | - | +| `QUANT-GGUF-IQ3_XXS` | IQ3_XXS | 18 / output | Y | Y | Y | - | - | `READY` | reader trait [`:237`](../src/vllm/model_executor/model_loader/gguf_reader.cpp#L237) (98 B block, ADDED W8); [codebook dequant kernel](../src/vt/cpu/cpu_quant_dequant.cpp#L349) (`DequantIQ3_XXS` + the shared [`iq3xxs_grid`](../src/vt/cpu/cpu_quant_iq_tables.h), ported 1:1 from llama.cpp `ggml-quants.c:2503` + `ggml-common.h:1007`); vt block dtype `kIQ3_XXS` [geometry](../src/vt/dtype.cpp#L86). **`C` = `Y` (DeepSeek-V4 W8, 2026-07-29):** the keep-quant `vec_dot` [`VecDotIQ3_XXSQ8_K`](../src/vt/cpu/cpu_quant_dot.cpp#L1) (1:1 port of ggml `vec_dot_iq3_xxs_q8_K_generic`, `quants.c:999`) + the [Q8_K traits row](../src/vt/cpu/cpu_quant_traits.cpp#L1) keep IQ3_XXS blocks COMPRESSED — the MEMORY ENABLER for the `UD-IQ2_XXS` **down** routed experts (`ffn_down_exps`, which are IQ3_XXS, NOT IQ2_XXS; without keep-quant they alone OOM the box). Op gate [test_ops_quant_dot](../tests/vt/test_ops_quant_dot.cpp#L1) (vec_dot vs f64 dequant-dot ≤1e-5·L1, NMSE ≤5e-4). **W2b GGUF→tower materialization LANDED (2026-07-29, `CLAIM-DEEPSEEK-V4-W2B`):** `LoadDeepseekV4FromGguf` wires the name-map + keep-quant blocks into the `DeepseekV4` towers (`test_deepseek_v4_gguf_load` 5/5·149). `E`/`P` = `-`: the real DeepSeek-V4 e2e run stays W8-final. **CUDA compute LANDED 2026-07-29 (`KERNEL-QUANT-CIQ-GEMM-CUDA`, `CLAIM-CUDA-KEEPQUANT-GEMM`):** a native kCUDA `kMatmulBTQuant` provider ([cuda_quant_dot.cu](../src/vt/cuda/cuda_quant_dot.cu), MMVQ-style) dots these blocks ON the GPU (Q8_K activation quant + integer dot, dequant-in-kernel, weights stay COMPRESSED in the unified pool), GB10-gated ([test_cuda_quant_dot](../tests/vt/test_cuda_quant_dot.cpp)) 2/2 · 92401 vs the CPU oracle (NMSE ≤1e-6, int core bit-exact) + f64 dequant (≤5e-4), compute-sanitizer 0, RED-first proven — so on a CUDA runner these experts dispatch to the GPU, not the 20 ARM cores. | [CUDA keep-quant GEMM](specs/cuda-keepquant-gemm.md); [iquant DSV4 spike](specs/gguf-iquant-dsv4.md) | - | | `QUANT-GGUF-IQ1_S` | IQ1_S | 19 / output | - | - | - | - | - | `INVENTORIED` | - | leaf open | - | | `QUANT-GGUF-IQ4_NL` | IQ4_NL | 20 / output | - | - | - | - | - | `INVENTORIED` | - | leaf open | - | | `QUANT-GGUF-IQ3_S` | IQ3_S | 21 / IQ3 S/XS/M storage | - | - | - | - | - | `INVENTORIED` | - | leaf open | - | diff --git a/.agents/specs/qwen4-exp-flash-next.md b/.agents/specs/qwen4-exp-flash-next.md index fd5784960..bb2699efb 100644 --- a/.agents/specs/qwen4-exp-flash-next.md +++ b/.agents/specs/qwen4-exp-flash-next.md @@ -27,6 +27,35 @@ Out of scope for the first implementation wave, each named under `## Owed` rathe than dropped: MTP depth > 1, the 1M-token RoPE extension the card advertises above the native 262144, and any throughput claim. +### Merge sequencing for the `ACTIVE` transition and its claim (operator note) + +W1 and W6a BOTH moved this row `READY -> ACTIVE` on their own branches, independently +and correctly — AGENTS.md "Records" requires the matrix row to move with the lifecycle +state, and each wave was the first product code from its own point of view. The result +is a collision that a clean three-way merge will NOT catch, and it is recorded here +because the second merge is where it bites: + +- **The counts happen to be safe.** Both branches make the IDENTICAL edit, `ACTIVE` + 10 -> 11 and `READY` 4 -> 3, so a three-way merge with a base of 10/4 and both sides + at 11/3 resolves to 11/3. That is luck, not design: two branches making DIFFERENT + one-line edits to the same counter merge cleanly and apply BOTH, which is the failure + AGENTS.md names under "Never store a measurement of one file inside another file". + **Verify these two numbers by COUNTING ROWS at every merge, never by trusting the + merge.** +- **The claim owner is NOT safe.** W1 wrote owner `CLAIM-MODEL-MM-QWEN4-EXP-W1` with + `.agents/claims/CLAIM-MODEL-MM-QWEN4-EXP-W1.md`; W6a wrote `CLAIM-MODEL-MM-QWEN4-EXP` + with its own file. Two different owners for one cell, and two claim files for one row. + +**Resolution: the row-level claim `CLAIM-MODEL-MM-QWEN4-EXP` wins**, because the claim +covers the whole campaign rather than one wave, and `check-agent-record.py` binds an +owner to a ROW. Whichever of W1/W6a merges second drops its own transition and its own +claim file, keeping only the survivor. This is a merge-time reconciliation, not a +defect in either branch. + +The same shape will recur for W2, W3 and W4: each is the first product code from its +own vantage, none of them should re-make the transition, and each should drop the edit +if it finds the row already `ACTIVE` on `main`. + ## Why this needs a spec before code Three of this row's decisions are expensive to reverse and cheap to get wrong, and @@ -37,9 +66,18 @@ record. They are settled here so a fresh implementer does not re-derive them. `Qwen/Qwen3.8-27B` as the Qwen3.6-27B shape retrained, differing in exactly one config key. That precedent does not extend here. `qwen4_exp` shares an ancestor with `qwen3_5` and diverges in four load-bearing places. -2. **QSA's twin in vLLM is MiniMax-M3, not DeepSeek-V4.** See `## Design`. Building - it on the DSA/MLA path is the wrong port, and DSA is the path an agent reaches - for first because this tree already has it. +2. **QSA's twin in vLLM is DeepSeek-V4's C4 indexer lane, not MiniMax-M3.** See + `## Design`. This REVERSES the row's first reading, which rested on treating + `MLAAttentionSpec` as an MLA claim; it is a per-state BUDGET shape, and M3 — + itself plain GQA — uses it too. Nine independent structural matches tie QSA to + DeepSeek-V4, `compress_ratio == 4` literally the same number. Building QSA on M3 + is the wrong port and it fails hard rather than subtly: M3 welds + `SPARSE_BLOCK_SIZE = 128` to the KV page size, so ratio 4 forces a page size of 4 + and breaks `tl.dot`, whose tile needs >= 16. What M3 does contribute is a wiring + precedent and not an algorithm: a plain-GQA model owning a key-only side cache + through `MLAAttentionSpec`. The DSA/MLA reflex remains the trap, because this tree + already has that path — the correction is which side of it QSA sits on + ([#2049](https://github.com/mudler/vllm.cpp/issues/2049)). 3. **The oracle split is a direction, not a default.** See below. ## Oracles @@ -755,6 +793,56 @@ checkpoint repo **and revision** plus sha256 for any quantized artifact, the dev and the contention state. `docs/USAGE.md` gains the checkpoint pins in the same change that makes any arm reachable, not later. +## Mutation record — W6a (#1989) + +Committed because the first fresh review could not re-run W6a's claimed +mutations: no table for the wave existed anywhere in the tree, so the reviewer +designed and ran fourteen of their own. This section is the reproducible list. +Every row is one textual change applied to a pristine tree, rebuilt, run, +restored, and rebuilt again with the source `touch`ed after restore — without +that touch ninja skips the rebuild and the mutations ACCUMULATE, which fails +toward RED and makes a weak gate read strong. + +Reviewer battery (14, at `beedfdf31`; R8b and R11-R13 are what the review's +findings F2 and F7 are made of): + +| # | mutation | target(s) | result | +|---|---|---|---| +| R1 | `kValuesIq4nl[8]` `1` -> `0` | dequant, embedding | RED, RED | +| R2 | `DequantQ5_0` upper-half `qh` shift `j+12` -> `j+16` | dequant, embedding | RED, RED | +| R3 | `DequantIQ4_NL` swap the two nibble halves | dequant, embedding | RED, RED | +| R4 | reader `GgmlTypeTraits` IQ4_NL `block_bytes` 18 -> 17 | load_plan, traits | RED, RED | +| R5 | `vt` `BlockGeometry` Q5_0 `block_bytes` 22 -> 21 | traits | RED | +| R6 | delete the block arm of `EmbeddingKernel` | embedding, qwen36_loader | RED, RED | +| R7 | `KeepQuantKDim(kEmbeddingTable)` back to `-1` | keep_quant, qwen36_loader, load_plan | RED x3 | +| R8a | `DeviceQuantGatherSupported` INVERTED | keep_quant | RED | +| R8b | `DeviceQuantGatherSupported` widened to every device but ROCm | keep_quant | SURVIVED — only the CPU branch is reachable on a CPU host, the same limitation `DeviceKeepQuantSupported` already has | +| R9 | remove the NVFP4 `role != kEmbeddingTable` exclusion | keep_quant | RED | +| R10 | delete the `kGgufArchArms` `qwen4exp` row | model_loader_gguf | RED | +| R11 | neuter `vt::Embedding`'s whole-block precondition | embedding | SURVIVED at `beedfdf31` -> **RED after the F7 repair** | +| R12 | `VecDotIQ4_NLQ8_0`: swap the two nibble halves | all 8 suites | SURVIVED x8 at `beedfdf31` -> **RED after the F2 repair** | +| R13 | `VecDotQ5_0Q8_0`: upper-half `qh` shift `j+12` -> `j+16` | all 8 suites | SURVIVED x8 at `beedfdf31` -> **RED after the F2 repair** | + +Repair battery (this change; each restored byte-identically and re-verified +green afterwards): + +| # | mutation | target(s) | result | +|---|---|---|---| +| R11 | neuter `vt::Embedding`'s whole-block precondition (`% BlockElems` -> `% 1`) | `test_ops_embedding_quant` | RED | +| R12 | `VecDotIQ4_NLQ8_0`: swap the two nibble halves | `test_ops_quant_dot` | RED | +| R13 | `VecDotQ5_0Q8_0`: `>> (j + 12)` -> `>> (j + 16)` | `test_ops_quant_dot` | RED | +| R14 | `NoKeepQuant` made a no-op (the F1 defect, restored) | `test_deepseek_v4_gguf_load`, `test_laguna_gguf_load` | RED, RED | +| R15 | delete the block arm's per-id bounds check (`id % v`) | `test_ops_embedding_quant` | RED | +| R16 | `ResidentWeight`'s CPU alias offset by one byte | `test_gguf_qwen36_loader` | RED | + +Anchor repairs in W6a: **three**, not nine. Measured with the repository's own +checker on both trees — parent `ok=876, stale=31, broken=6 -> rot 37`; head +`ok=879, stale=28, broken=6 -> rot 34`. The three are +`KERNEL-ATTN-DFLASH-BLOCK -> cpu_ops.cpp`, `SPEC-DFLASH-GGUF -> :773 -> :1015` +and `SPEC-MTP-GGUF -> :971 -> :1425`. All three were stale BEFORE W6a. The +DFlash one landed with a label that disagreed with its own href and is corrected +here. + ## Stop conditions - vLLM registers `qwen4_exp`: **stop and reconcile onto vLLM** before continuing. @@ -807,11 +895,42 @@ change that makes any arm reachable, not later. the hash reads ids and not activations — is a CALLER obligation with no caller yet. W5 owns asserting it where the mask is built. - The 1M-token RoPE extension above the native 262144. -- The non-resident n-gram table on CUDA: the dequantizing gather op and the - `kEmbeddingTable` keep-quant policy change (Route B), and a measurement of the - page-cache cost that the <= 64 KiB/token arithmetic only bounds. -- **UNVERIFIED and owed a check against the pinned llama.cpp oracle:** llama.cpp's exact - substitution for a ragged-K Q4_K tensor, asserted here as Q5_0. +- The non-resident n-gram table on CUDA. **W6a (#1989) discharged the CPU half**: + the dequantizing gather (`vt::Embedding` over a block table) and the + `kEmbeddingTable` keep-quant policy change both landed, gated bit-exactly + against llama.cpp `b10451` decoding real bytes of the shipped tensor. What is + still owed is the **CUDA arm**: `EmbeddingKernelCuda` (`src/vt/cuda/cuda_ops.cu`) + refuses anything but f32/bf16, so `DeviceQuantGatherSupported` returns false on + CUDA and the table keeps its expand-bf16 residency there. That is the honest + state and it is also the expensive one — a device-resident quantized table + gathered on device is precisely the shape llama.cpp's #27742 does NOT have (it + pins the table to the CPU by tensor class), so the CUDA arm is where this + model's high-concurrency advantage lives, not a tidying task. Still owed with + it: a measurement of the page-cache cost that the <= 64 KiB/token arithmetic + only bounds. +- **VERIFIED 2026-08-26, no longer owed:** llama.cpp's substitution for a + ragged-K tensor is read at the pin, `src/llama-quant.cpp:374-405 @ b10451` + (`tensor_type_fallback`). `Q4_K -> Q5_0` is confirmed exactly as this spec + asserted, and `IQ4_XS -> IQ4_NL` beside it, which is why the shipped UD-IQ1_S + carries 49 IQ4_NL tensors. Both encodings landed in W6a. +- **NEW, from reading that table:** the same function maps `Q5_K -> Q5_1` (ggml + type 7) and `Q2_K`/`Q3_K` -> `Q4_0`. Q5_1 and Q4_1 (3) are still absent from + our reader, so a `-Q5_K_M` recipe of THIS model — whose `ffn_down_shexp` row is + 640 and therefore ragged for any K-quant — would refuse at header parse. Not + in W6a's scope, which the shipped file does not need; recorded rather than + quietly added. +- **A keep-quant gather for `deepseek4` and `laguna`.** W6a made + `GgufTensorRole::kEmbeddingTable` keep-quant eligible, which is a change to a + SHARED policy with three consumers. Only `qwen3_5_gguf_weights.cpp` was given + the residency; `deepseek_v4_weights.cpp` and `laguna_weights.cpp` consume + `token_embd` as a flat host f32 array (and, on a tied file, hand the same f32 + image to the final projection), so both now narrow the policy for that tensor + through `NoKeepQuant` and keep expanding it. That is correct and it is not + free: on a real deepseek4 or laguna checkpoint the vocab matrix is still + materialized in f32. Decoding it per gathered row instead needs those two + forwards to take a `vt::Tensor` rather than a `std::vector`, which is + model work and not policy work. Owed to + [#1978](https://github.com/mudler/vllm.cpp/issues/1978). - A K-divisibility assertion in whatever writes our GGUF files. - A speed denominator, once one exists. - **W4's QSA slice lands UNREACHED**, and this entry is what AGENTS.md "Nothing @@ -936,11 +1055,32 @@ change that makes any arm reachable, not later. ## Now -`READY` in the matrix, and the state cell is deliberately not moved here — see -`## Owed`, "the row's lifecycle record is owed the W4 transition". The spec is -committed; W4 has landed the QSA host reference math -([#1991](https://github.com/mudler/vllm.cpp/issues/1991)) UNREACHED, and no other -implementation exists. +`ACTIVE`. Four waves have landed and **nothing is reachable yet**, which is the +whole of the current state: W2 the hashed n-gram index and the PLE dilated conv +([#1987](https://github.com/mudler/vllm.cpp/issues/1987)), W3 the gated-residual +hyper-connection stream ([#1988](https://github.com/mudler/vllm.cpp/issues/1988)), +W4 Qwen Sparse Attention with its gather consumer +([#1991](https://github.com/mudler/vllm.cpp/issues/1991)), and W6a this wave. W1 +([#1981](https://github.com/mudler/vllm.cpp/issues/1981)) is reviewed, repaired +and gated, and lands next. + +W6a's own contribution: the GGUF reader opens all three shards of +`unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S`, a `qwen4exp` file reaches its own +config builder through the production architecture dispatch, and the n-gram +table's residency is decided rather than assumed. No forward, no token claim and +no speed claim. + +**Landed unreached, and named here because `AGENTS.md` §"Nothing lands dead" +requires it:** `Qwen4ExpHfConfigFromGguf` IS reached — the dispatch row in +`kGgufArchArms` is a production entry point and a `qwen4exp` file lands on it — +but the `HfConfig` it produces names `Qwen4ExpForConditionalGeneration`, which +`ModelRegistry` does not resolve. A user who passes the shipped GGUF today gets a +correct config and then a registry refusal by architecture name. Every other +landed slice is host reference math with no production call site at all. The +wiring is owed to **W5** +([#2031](https://github.com/mudler/vllm.cpp/issues/2031)), under +[#1978](https://github.com/mudler/vllm.cpp/issues/1978), which is also what the +benchmark and e2e gates wait on. Both decisions this spec was blocked on are **settled** (developer, 2026-08-26) and recorded in place rather than left as proposals: the transformers lane pin is diff --git a/CMakeLists.txt b/CMakeLists.txt index e1de6ecde..6bb102023 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -816,6 +816,7 @@ add_library(vllm STATIC src/vllm/model_executor/models/muse_glimmer_registry.cpp src/vllm/model_executor/models/muse_glimmer_weights.cpp src/vllm/model_executor/models/muse_glimmer_gguf_weights.cpp + src/vllm/model_executor/models/qwen4_exp_gguf_weights.cpp src/vllm/model_executor/models/muse_glimmer.cpp src/vllm/model_executor/models/muse_glimmer_vision.cpp src/vllm/model_executor/models/muse_glimmer_mm.cpp diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 1f15e0949..936421b65 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -73,7 +73,8 @@ are our reading of their documented behavior, not measurements. | Dense W4A16 MLP runs ONE merged `gate_up` Marlin GEMM (vLLM's `MergedColumnParallelLinear` topology) | ✅ `VT_DENSE_MARLIN_GATEUP`, **default ON** (opt out `=0`): the A/B measured +2.12% c1 / +1.70% c8 on the 27B, arms separated, tokens identical (#365). Replaces the split pair's 193 Marlin calls/step vs the oracle's 129 | ✅ | ☐ | ☐ | | NVFP4 shared-expert `down_proj` kept bf16 (no f32 round-trip) | ✅ `VT_SHARED_DOWN_BF16` default-ON; bit-identical (both consumers widen bf16 in-kernel and re-round on store), SACRED 315/315 + 235/235 on BOTH arms with unchanged assertion counts; +2.05% c8 / +0.79% c4 on 35B-A3B | ☐ | ☐ | ☐ | | NVFP4 `lm_head` kept packed (no dequant at load) | ✅ `VT_LMHEAD_FP4` default-ON, #213; CUDA-gated on `nvidia`@`0893e160` (continuations byte-identical packed vs dequant, 235/235; RSS -1.70 GiB on CUDA, owed a re-measure; a no-fp4-GEMM backend keeps one bf16 operand too) | ✅ | ☐ | ☐ | -| GGUF k-quants and i-quants | ✅ (CPU grouped keep-quant MoE bf16 regression in `b4f5610a` fixed 2026-08-06). **CPU quant compute is ISA-tiered:** Arm has i8mm + repack; x86_64 portable-only, MEASURED open on every axis (CIQ `G5`, #433) | ☐ | ☐ | ✅ | +| GGUF k-quants and i-quants | ✅ (CPU grouped keep-quant MoE bf16 regression in `b4f5610a` fixed 2026-08-06). **CPU quant compute is ISA-tiered:** Arm has i8mm + repack; x86_64 portable-only, MEASURED open on every axis (CIQ `G5`, #433). **IQ4_NL (20) and Q5_0 (6)** added for `qwen4exp` ([#1989](https://github.com/mudler/vllm.cpp/issues/1989)), decode bit-exact vs llama.cpp `b10451`; they are the ragged-K landing spots of upstream's own `tensor_type_fallback` (`IQ4_XS -> IQ4_NL`, `Q4_K -> Q5_0`, `llama-quant.cpp:374`). Q4_1 (3) and Q5_1 (7) remain absent, so a `-Q5_K_M` build of a ragged-K model still refuses | ☐ | ☐ | ✅ | +| GGUF gather tables kept QUANTIZED (one row dequantized per gathered token) | ✅ CPU, `qwen35`/`qwen35moe`/`qwen3next`/`qwen4exp`. `vt::Embedding` takes a block-quantized table, decoding one row per id — a port of llama.cpp's `ggml_compute_forward_get_rows_q`. Without it a 51.2 G-parameter n-gram table expands from 28.8 GB of IQ4_NL to 102.4 GB of bf16, which no device here has ([#1989](https://github.com/mudler/vllm.cpp/issues/1989)). This is a residency DEFAULT CHANGE on already-shipped GGUF models, not only a new arm: an existing `qwen35` file with a quantized `token_embd` now keeps it compressed on CPU where it used to expand. Tokens do not move (every GGUF-path gather writes a bf16 output and the bf16 round is idempotent over the old expand-then-widen), so the change is memory-only today. `deepseek4` and `laguna` are NOT reached: both consume `token_embd` as a flat host f32 array, so their loaders narrow the policy for that tensor and keep expanding it. **The CUDA arm is OWED**: `EmbeddingKernelCuda` still refuses a block table, so on CUDA such a table keeps its expand-bf16 residency | ☐ | ☐ | ✅ `get_rows` for ~20 types, CPU and CUDA | | GGUF F16 weights kept resident as F16 (no BF16 promotion) | ✅ `VT_GGUF_KEEP_F16` default-ON (CPU), the f16 GEMM computes on it directly. Default settled 2026-08-17, a memory-for-speed trade: 1.05 GiB less peak RSS for ~9% prefill and ~1.4% decode, tokens identical. `0` opts out | ☐ | ☐ | ✅ `ggml_vec_dot_f16` | | GGUF is a TWO-engine comparison at these pins (#979) | ✅ text-only `qwen35`, no `clip` projector (#821) | ☐ REMOVED from the tree in `6635279d8`, now an unpinned out-of-tree `vllm-gguf-plugin` | ☐ full stack present, `qwen3_5` unreachable behind FOUR blockers, and the load path has NO completeness guard so a clean-looking load proves nothing | ✅ native, `LLM_ARCH_QWEN35` | | EXL3 trellis (exllamav3 3.0bpw, MCG codebook, Hadamard-128 + sign/scale vectors, NO scales) | ◐ LOADS and EXECUTES end to end on a synthetic checkpoint. The rank-sliced `0xSero/deepseek-v4-flash-0731-spark` routed-expert tower coalesces TP4->TP1 at load, the `carried-*` half (block-wise FP8 + BF16 + F32) is dequantized into the host-float tower the forward composes with, and `DeepseekV4Model::Forward` runs the whole model, dispatching one `vt::Exl3MoeMlp` per MoE layer over the routed experts (`bits == 3`, `mcg` codebook). **The REAL artifact still does not run**: its DSA compressor and indexer tensors are twice the width the host forward indexes and the loader refuses them by name, and its tokenizer is not read ([#1924](https://github.com/mudler/vllm.cpp/issues/1924)). **The device half is UNVERIFIED**: the CUDA port of `had_r_128` + `exl3_gemm` has passed no compiler and no GPU (no toolkit on the implementer host, `dgx.casa` down), so it runs today on a CPU queue only and every device number is PENDING. The m<=8 GEMV, the fused MoE mgemm, the device-resident tower and every width but 3 bits are owed ([spec](../.agents/specs/model-dsv4-exl3.md)) | ☐ no EXL3 at the parity pin | ☐ | ☐ | diff --git a/include/vllm/model_executor/model_loader/gguf_keep_quant.h b/include/vllm/model_executor/model_loader/gguf_keep_quant.h index 2ec94f95a..9c9df816e 100644 --- a/include/vllm/model_executor/model_loader/gguf_keep_quant.h +++ b/include/vllm/model_executor/model_loader/gguf_keep_quant.h @@ -27,6 +27,7 @@ #include "vllm/model_executor/model_loader/gguf_reader.h" #include "vt/dtype.h" +#include "vt/device.h" // vt::DeviceType — the gather arm's device gate namespace vllm { @@ -47,8 +48,11 @@ enum class GgufTensorRole { // RMSNorm rewrite, ssm_a = log(-x), and the V-head reorders (the out_proj // reorder permutes COLUMNS, which live inside blocks). Never keep-quant. kTransformedWeight, - // Embedding table — a gather, not a GEMM (llama.cpp likewise dequantizes - // embedding rows on the fly). A quantized-gather op is a follow-up row. + // Embedding table — a gather, not a GEMM. It keeps its blocks and is + // dequantized ONE ROW PER GATHERED TOKEN by `vt::Embedding`, exactly as + // llama.cpp's `ggml_compute_forward_get_rows_q` does. Its eligibility rule is + // therefore the DECODER's (`KeepQuantGatherDType`), not the GEMM's: a gather + // needs no `vec_dot` and no activation quantizer. kEmbeddingTable, // conv1d filter [conv_dim, K]: consumed by the depthwise conv, not a GEMM. kConvWeight, @@ -102,6 +106,21 @@ using GgufRoutingAudit = // file weight type) and for every unported encoding. bool KeepQuantDType(uint32_t ggml_type, vt::DType* out); +// True when `ggml_type` is a block encoding this build can DECODE A ROW OF, +// writing its vt block dtype to `*out`. This is the GATHER's admission rule and +// it is deliberately weaker than `KeepQuantDType`: a gather table is never +// dotted, so it needs neither a `vec_dot` nor a `from_float` on some activation +// encoding. Using the GEMM predicate here would expand a perfectly gatherable +// table for want of a kernel nothing calls. +bool KeepQuantGatherDType(uint32_t ggml_type, vt::DType* out); + +// True when the running device can gather from a BLOCK-QUANTIZED table. Same +// shape and same reason as `DeviceKeepQuantSupported` for the GEMM arm: a +// device whose Embedding kernel cannot decode blocks must keep the table's +// pre-existing expand-bf16 residency rather than be handed a block tensor it +// throws on at FORWARD time with the whole model already resident. +bool DeviceQuantGatherSupported(vt::DeviceType dev); + // True when `ggml_type` is F16 (ggml type id 1) — the one native-float encoding // L6 keeps resident. F32/BF16 weights are not kept native: F32 file tensors are // only ever norms/vectors (value-transformed), and no published Qwen GGUF stores @@ -284,6 +303,26 @@ struct GgufLoadPolicy { GgufResidency Route(const GgufTensorInfo& tensor, GgufTensorRole role) const; }; +// A policy copy with the KEEP-QUANT residency disabled. +// +// A residency is only correct if some consumer can READ it, and that is a +// property of the LOADER, not only of the encoding and the device. W6a made +// `kEmbeddingTable` keep-quant eligible because `vt::Embedding` learned to +// decode a block table — but `deepseek_v4_weights.cpp` and `laguna_weights.cpp` +// consume `token_embd` as a FLAT HOST f32 array (and, when the file is tied, +// hand that same f32 image to the final projection), so a kept table is bytes +// neither of them can read. Each narrows the policy for those tensors by name. +// +// This is the same shape as `qwen3_5_gguf_weights.cpp`'s `NoNvfp4(pol)`, and +// for the same reason: a call site whose consumer cannot serve a residency +// STATES so, rather than asserting that nobody will ever elect it. Stating it +// as a policy also keeps the audit hook truthful about what each tensor got. +// +// It narrows keep-quant ONLY. `keep_f16` is deliberately left alone, so a call +// site that asserts `kExpandBf16` after this narrowing still has a live +// assertion rather than a tautology. +GgufLoadPolicy NoKeepQuant(const GgufLoadPolicy& policy); + // The PURE routing decision, WITHOUT firing the audit hook. A call site that // must look at a tensor's fate BEFORE choosing which loader to run uses this, so // the tensor is still audited EXACTLY ONCE by whichever loader it then calls. diff --git a/include/vllm/model_executor/models/qwen3_5.h b/include/vllm/model_executor/models/qwen3_5.h index ae881a361..8fb78e103 100644 --- a/include/vllm/model_executor/models/qwen3_5.h +++ b/include/vllm/model_executor/models/qwen3_5.h @@ -35,12 +35,28 @@ #include "vllm/transformers_utils/hf_config.h" #include "vllm/v1/attention/backend.h" #include "vllm/v1/attention/backends/gdn_attn.h" +#include "vt/backend.h" #include "vt/device.h" #include "vt/fp8_kv.h" // KV-FP8 W3: the PagedKvCache fp8 interpretation #include "vt/tensor.h" namespace vllm { +// THE production bridge from a loaded embedding TABLE to the gather op's +// operand. Every embed in qwen3_5.cpp goes through it, so it is the one place a +// block-quantized `token_embd` becomes a `vt::Tensor`: on a host-addressable +// device it ALIASES the block bytes and never computes a per-element size, +// which is precisely the property a keep-quant gather table depends on. +// +// It is declared here because nothing pinned that property. #1989's review (F4) +// found the W6a reachability case hand-building the tensor from +// `w.embed_tokens.bytes` instead, so a change to the bridge could break every +// block-table forward with the gate still green. The gate now enters HERE. +vt::Tensor Qwen3_5EmbeddingTable(vt::Backend& backend, vt::Queue& queue, + const OwnedTensor& embed_tokens, int64_t vocab, + int64_t hidden); + + // Process-wide count of MIXED spec+non-spec GDN batch invocations // (GdnBlockPagedMixedSpec), incremented per GDN layer per mixed step. A nonzero // value proves the concurrency split/merge path actually ran; the c>1 spec diff --git a/include/vllm/model_executor/models/qwen4_exp_gguf_weights.h b/include/vllm/model_executor/models/qwen4_exp_gguf_weights.h new file mode 100644 index 000000000..0cd3a6f7c --- /dev/null +++ b/include/vllm/model_executor/models/qwen4_exp_gguf_weights.h @@ -0,0 +1,49 @@ +// `qwen4exp` GGUF support — Qwen3.8-Flash-Next +// (`Qwen4ExpForConditionalGeneration`, HF `model_type: qwen4_exp`). +// +// WHY THIS IS ITS OWN TRANSLATION UNIT, and not a fourth key on +// `vllm::HfConfigFromGguf`. That function asserts its own three architectures +// by name and reports every failure as "qwen3_5 gguf: ...". Reusing it for a +// fourth family is exactly the defect #809 removed: a refusal that names a +// model the user never asked about, in a translation unit that owes none of the +// work. A GGUF family owns its config builder; the dispatch table in +// `entrypoints/model_loader.cpp` owns the mapping. +// +// WHY THE GGUF ARM IS THE PATH AND NOT A FOLLOW-UP. Every safetensors artifact +// of this model is larger than any device this project owns: bf16 ~360 GB, +// official FP8 ~180 GB, NVFP4 ~128 GB, against ~119.6 GiB usable on GB10. The +// UD-IQ1_S GGUF is 67.56 GiB in three shards. See `.agents/specs/ +// qwen4-exp-flash-next.md` and issue #1989. +// +// KEY NAMES ARE AN INTEROP CONTRACT, AND THEY ARE NOT SETTLED. Two competing +// llama.cpp pull requests disagree on the `ple.*` spellings and on whether the +// n-gram table is model-level or per-layer. This TU follows **#27742**, which +// is the layout the SHIPPED file uses: model-level `per_layer_token_embd.weight` +// and `blk.N.ple_{key,value,norm_key,norm_query,norm_conv,conv1d}`. Read live +// from `unsloth/Qwen3.8-Flash-Next-GGUF` `UD-IQ1_S`, 2026-08-26; the frozen +// tensor table is `tests/vllm/models/qwen4_exp_gguf_manifest.inc`. If upstream +// renames these before merging, this file changes and the manifest is regrown — +// there is no compatibility shim for a name that never shipped. +#pragma once + +#include + +#include "vllm/model_executor/model_loader/gguf_reader.h" +#include "vllm/transformers_utils/hf_config.h" + +namespace vllm { + +// llama.cpp's `general.architecture` value for this family, as written by +// #27742 and as carried by the shipped file. One spelling, one definition. +inline constexpr const char* kQwen4ExpGgufArch = "qwen4exp"; + +// True when `gguf` carries `general.architecture == kQwen4ExpGgufArch`. +bool IsQwen4ExpGguf(const GgufFile& gguf); + +// The GGUF metadata -> `HfConfig` builder for this family. Throws by name on a +// missing required key. Every value it produces is READ FROM THE FILE; the two +// exceptions are annotated at their assignment and are facts the GGUF container +// has no key for. +HfConfig Qwen4ExpHfConfigFromGguf(const GgufFile& gguf); + +} // namespace vllm diff --git a/include/vt/dtype.h b/include/vt/dtype.h index b5c329971..bad311f39 100644 --- a/include/vt/dtype.h +++ b/include/vt/dtype.h @@ -56,6 +56,17 @@ namespace vt { // routed-expert slabs (IQ2_S ffn_gate/up + MXFP4 ffn_down). Both carry a // keep-quant `vec_dot` (IQ2_S vs Q8_K, MXFP4 vs Q8_0) so they load COMPRESSED // on the same memory-safe path — expanding them to bf16 would OOM the box. +// +// kQ5_0 (ggml id 6) and kIQ4_NL (ggml id 20) are the two remaining 32-ELEMENT +// legacy-family encodings, added for Qwen3.8-Flash-Next (`qwen4exp`). They exist +// because that model's `moe_intermediate_size` is 640 and its per-layer table +// row is 160: neither is a multiple of 256, so NO K-quant can encode them and +// llama.cpp's own `tensor_type_fallback` drops IQ4_XS -> IQ4_NL and Q4_K -> Q5_0. +// The shipped `unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S` carries 49 IQ4_NL +// tensors: all 48 `ffn_down_exps` and the 20M-entry n-gram table +// `per_layer_token_embd.weight`. Both dot against Q8_0, like every other +// 32-element block. IQ4_NL is Q4_0's shape with a NON-LINEAR 16-entry codebook +// (`kValuesIq4nl`) in place of the `nibble - 8` affine step. enum class DType : uint8_t { kF32, kF16, @@ -65,6 +76,7 @@ enum class DType : uint8_t { kI64, // --- block-quantized (storage-only) --- kQ4_0, + kQ5_0, kQ8_0, kQ2_K, kQ3_K, @@ -77,6 +89,7 @@ enum class DType : uint8_t { kIQ2_S, kIQ1_S, kIQ1_XXXS, + kIQ4_NL, kMXFP4, }; diff --git a/scripts/gen-qwen4-exp-gguf-manifest.py b/scripts/gen-qwen4-exp-gguf-manifest.py new file mode 100755 index 000000000..c0dae19c5 --- /dev/null +++ b/scripts/gen-qwen4-exp-gguf-manifest.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +"""Emit a Qwen3.8-Flash-Next (`qwen4exp`) GGUF tensor manifest as a C++ fixture. + +The `unsloth/Qwen3.8-Flash-Next-GGUF` repo ships `UD-IQ1_S` as THREE shards, +67.56 GiB in total, and the split is not a detail this manifest can ignore: + + ...-00001-of-00003.gguf 10.9 MB 67 kv, 0 tensors (metadata only) + ...-00002-of-00003.gguf ~25 GB 3 kv, 595 tensors + ...-00003-of-00003.gguf ~25 GB 3 kv, 629 tensors + +Shard 1 carries every metadata key and NO tensor at all; shards 2 and 3 carry +3 keys each (`split.no`, `split.count`, `split.tensors.count`) and the whole +1224-tensor table between them. So this script takes all three and emits ONE +manifest with the tensors it can only get from 2 and 3. + +It reads only the GGUF **headers** — names, ggml dims, type ids — so the loader's +name map, its shape resolution and its ggml-type coverage are gated against the +real checkpoint without checking in (or even downloading) the payload. The +header is self-delimiting and lives at the front of each file, so shard 1 is +fetched whole and a range request over the first few MB is enough for the rest: + + B=https://huggingface.co/unsloth/Qwen3.8-Flash-Next-GGUF/resolve/8bdc666649440e9bdc97e16f3f75782c98478ff5/UD-IQ1_S + for i in 1 2 3; do # a REVISION, not `main`: this repo is being uploaded + curl -sL -r 0-16777215 -o shard$i.head \\ + "$B/Qwen3.8-Flash-Next-UD-IQ1_S-0000$i-of-00003.gguf" + done + python3 scripts/gen-qwen4-exp-gguf-manifest.py shard1.head shard2.head \\ + shard3.head > tests/vllm/models/qwen4_exp_gguf_manifest.inc + +Self-contained: deliberately does NOT use gguf-py, so it runs anywhere the file +does (same rationale as scripts/gen-muse-glimmer-gguf-manifest.py, which this is +modelled on). +""" + +import struct +import sys + +( + UINT8, INT8, UINT16, INT16, UINT32, INT32, FLOAT32, BOOL, STRING, ARRAY, + UINT64, INT64, FLOAT64, +) = range(13) + +FMT = { + UINT8: (" int: + if len(sys.argv) < 2: + raise SystemExit(f"usage: {sys.argv[0]} [ ...]") + sym = "Qwen4ExpGguf" + + version = None + kv = {} + tensors = [] + shard_of = {} + for path in sys.argv[1:]: + v, k, ts = read_shard(path) + if version is None: + version = v + elif v != version: + raise SystemExit("shards disagree on the GGUF version") + # Shard 1 holds every real key; the tensor shards hold only the three + # split keys. `split.no` is per-shard BY DEFINITION and is the one key + # that must differ, so it is excluded from the agreement check rather + # than merged; every other shared key has to match across shards. + for key, val in k.items(): + if key == "split.no": + continue + if key in kv and kv[key] != val: + raise SystemExit(f"shards disagree on {key}") + kv[key] = val + for t in ts: + shard_of.setdefault(t[0], len(shard_of)) + tensors.extend(ts) + + names = [t[0] for t in tensors] + if len(set(names)) != len(names): + raise SystemExit("a tensor name appears in more than one shard") + declared = kv.get("split.tensors.count") + if declared is not None and int(declared) != len(tensors): + raise SystemExit( + f"split.tensors.count says {declared} but {len(tensors)} were read; " + "a shard header is missing or truncated" + ) + + tensors.sort(key=lambda t: t[0]) + arch = kv.get("general.architecture", "") + shards = int(kv.get("split.count", len(sys.argv) - 1)) + out = sys.stdout + out.write( + "// GENERATED by scripts/gen-qwen4-exp-gguf-manifest.py — DO NOT EDIT BY HAND.\n" + "//\n" + "// The tensor manifest of the REAL Qwen3.8-Flash-Next GGUF from\n" + f"// `{REPO}` @ revision {REVISION},\n" + "// path `UD-IQ1_S`, read live 2026-08-26 by HTTP range request over the shard\n" + "// headers. The revision is pinned rather than `main` for the usual reason and for\n" + "// a sharper one: the repo's `lastModified` was 15:54:43Z and the read was at\n" + "// 15:56Z, so the upload was still in progress and `main` is a moving target here.\n" + f"// (GGUF v{version}, {len(tensors)} tensors across {shards} shards, architecture " + f"{arch!r},\n" + f"// file_type {kv.get('general.file_type', '?')}). Names, ggml dims and type ids only —\n" + "// no weight bytes, so CI gates the loader's name map, its shape resolution and\n" + "// its ggml-type coverage against the real 67.56 GiB checkpoint with no asset.\n" + "//\n" + "// This is the ONLY published artifact of this model that fits any device this\n" + "// project owns: bf16 is ~360 GB, the official FP8 ~180 GB and NVFP4 ~128 GB,\n" + "// against ~119.6 GiB usable on GB10. See .agents/specs/qwen4-exp-flash-next.md\n" + "// and issue #1989.\n" + "#pragma once\n\n" + "#include \n\n" + "namespace vllm_test {\n\n" + ) + out.write(f"inline constexpr int64_t k{sym}TensorCount = {len(tensors)};\n") + out.write(f"inline constexpr int64_t k{sym}Version = {version};\n") + out.write(f"inline constexpr int64_t k{sym}ShardCount = {shards};\n") + out.write(f'inline constexpr const char* k{sym}Architecture = "{arch}";\n\n') + + out.write(f"struct {sym}Tensor {{\n" + " const char* name;\n" + " int64_t dims[4]; // GGUF ne order (reversed vs torch), 0-padded\n" + " int32_t n_dims;\n" + " uint32_t ggml_type;\n" + " int32_t shard; // 1-based shard the tensor was read from\n" + "};\n\n") + order = {path: i + 1 for i, path in enumerate(sys.argv[1:])} + out.write(f"inline constexpr {sym}Tensor k{sym}Tensors[] = {{\n") + for name, dims, type_id, path in tensors: + padded = list(dims) + [0] * (4 - len(dims)) + type_name = GGML_TYPE_NAMES.get(type_id, str(type_id)) + out.write( + f' {{"{name}", {{{", ".join(str(v) for v in padded)}}}, {len(dims)}, ' + f"{type_id}u, {order[path]}}}, // {type_name}\n" + ) + out.write("};\n\n") + out.write("} // namespace vllm_test\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/record-anchor-baseline.json b/scripts/record-anchor-baseline.json index 6148998c5..d989f5523 100644 --- a/scripts/record-anchor-baseline.json +++ b/scripts/record-anchor-baseline.json @@ -12,9 +12,9 @@ "of this file on purpose: pinning it would make every change that adds or", "removes a citation rewrite this file, which is a lock, not a ratchet." ], - "total": 37, + "total": 34, "buckets": { - "stale": 31, + "stale": 28, "broken": 6 } } diff --git a/src/vllm/entrypoints/model_loader.cpp b/src/vllm/entrypoints/model_loader.cpp index 3d2a38cbb..40a0a2d67 100644 --- a/src/vllm/entrypoints/model_loader.cpp +++ b/src/vllm/entrypoints/model_loader.cpp @@ -33,6 +33,7 @@ #include "vllm/model_executor/models/deepseek_v4.h" // deepseek4 GGUF dispatch arm #include "vllm/model_executor/models/interfaces.h" // #607 L3 SkipTowerForModalities #include "vllm/model_executor/models/muse_glimmer_gguf_weights.h" // muse-glimmer GGUF arm +#include "vllm/model_executor/models/qwen4_exp_gguf_weights.h" // qwen4exp GGUF arm #include "vllm/model_executor/models/nemotron_h.h" // the OWED nemotron_h* GGUF refusal (#809) #include "vllm/model_executor/models/qwen3_5_gguf_weights.h" #include "vllm/model_executor/models/qwen3_5_mtp.h" // SPEC-MTP I5d-pre draft load @@ -1010,6 +1011,10 @@ std::unique_ptr BuildKvConnector( // sliding_window_pattern (muse_glimmer_gguf_weights.h). // * the three qwen3_5 keys -> HfConfigFromGguf, which owns all three itself // (qwen3_5_gguf_weights.cpp). +// * `qwen4exp` -> Qwen4ExpHfConfigFromGguf. It does NOT reuse HfConfigFromGguf, +// which asserts its own three architectures by name; a fourth family routed +// there would refuse as "qwen3_5 gguf: unexpected architecture", which is the +// #809 defect this table exists to prevent (see the default arm below). struct GgufArchArm { const char* arch; HfConfig (*build)(const vllm::GgufFile&); @@ -1021,6 +1026,7 @@ constexpr GgufArchArm kGgufArchArms[] = { {"qwen35", &vllm::HfConfigFromGguf}, {"qwen35moe", &vllm::HfConfigFromGguf}, {"qwen3next", &vllm::HfConfigFromGguf}, + {vllm::kQwen4ExpGgufArch, &vllm::Qwen4ExpHfConfigFromGguf}, }; std::string SupportedGgufArchitectures() { diff --git a/src/vllm/model_executor/model_loader/gguf_dequant.cpp b/src/vllm/model_executor/model_loader/gguf_dequant.cpp index e8d5423f0..25c676bbb 100644 --- a/src/vllm/model_executor/model_loader/gguf_dequant.cpp +++ b/src/vllm/model_executor/model_loader/gguf_dequant.cpp @@ -105,6 +105,7 @@ std::vector DequantGgufRowToF32(uint32_t ggml_type, const uint8_t* data, break; } case 2: // Q4_0 + case 6: // Q5_0 (32-elem legacy; the Q4_K row-size fallback) case 8: // Q8_0 case 10: // Q2_K case 11: // Q3_K @@ -115,6 +116,8 @@ std::vector DequantGgufRowToF32(uint32_t ggml_type, const uint8_t* data, case 39: // MXFP4 (OCP micro-scaling fp4; UD-IQ2_M ffn_down experts) case 18: // IQ3_XXS (~3-bit codebook; UD-IQ2_XXS ffn_down) case 19: // IQ1_S (1.5625 bpw; Qwen3.8-2.4T-A95B UD-IQ1_S experts) + case 20: // IQ4_NL (32-elem non-linear codebook; qwen4exp ffn_down_exps + // and the per_layer_token_embd n-gram table) case 66: // IQ1_XXXS (1.1875 bpw; UD-Q1_0 experts, fork-anchored) case 16: { // IQ2_XXS (~2-bit codebook; UD-IQ2_XXS DeepSeek-V4 vehicle) // The block decoders moved to vt (src/vt/cpu/cpu_quant_dequant.cpp) so diff --git a/src/vllm/model_executor/model_loader/gguf_keep_quant.cpp b/src/vllm/model_executor/model_loader/gguf_keep_quant.cpp index f4f8460d5..ad6e27540 100644 --- a/src/vllm/model_executor/model_loader/gguf_keep_quant.cpp +++ b/src/vllm/model_executor/model_loader/gguf_keep_quant.cpp @@ -28,8 +28,16 @@ int64_t KeepQuantKDim(GgufTensorRole role, const std::vector& shape) { case GgufTensorRole::kStackedExpertWeight: // [E, out, in]; each expert slice is whole rows of the same K. return shape.size() == 3 ? shape[2] : -1; - case GgufTensorRole::kTransformedWeight: case GgufTensorRole::kEmbeddingTable: + // [vocab, hidden]; a GATHER, not a GEMM. The blocks stay and + // `vt::Embedding` decodes ONE ROW per gathered id (port of llama.cpp's + // `ggml_compute_forward_get_rows_q`, ggml/src/ggml-cpu/ops.cpp:4850 + // @ b10451). K is the row length, and the same whole-blocks rule applies: + // a ragged row has no stride. This returned -1 until the dequantizing + // gather existed, and the -1 was CORRECT until then — without the op a + // kept table is a tensor nothing can read. + return shape.size() == 2 ? shape[1] : -1; + case GgufTensorRole::kTransformedWeight: case GgufTensorRole::kConvWeight: case GgufTensorRole::kVector: return -1; @@ -139,6 +147,27 @@ bool DeviceKeepF16Supported(vt::DeviceType dev) { return dev != vt::DeviceType::kROCM; } +// The GATHER's admission rule; see the header. A block dtype with a row decoder +// is enough — `HasQuantDotKernel` is not consulted, because a gather table is +// never the `x` side of a vec_dot. +bool KeepQuantGatherDType(uint32_t ggml_type, vt::DType* out) { + vt::DType dt = vt::DType::kF32; + if (!vt::BlockDTypeFromGgmlTypeId(ggml_type, &dt)) return false; + if (vt::cpu::BlockToFloat(dt) == nullptr) return false; + if (out != nullptr) *out = dt; + return true; +} + +// Device gate for the gather arm. Today ONLY the CPU kernel decodes a block +// table: `EmbeddingKernelCuda` (src/vt/cuda/cuda_ops.cu) still asserts +// f32/bf16, so on CUDA a kept table would throw at the first forward with the +// whole model resident. It refuses here instead, and the CUDA arm is owed — +// which is a real cost, because the device-resident quantized table is exactly +// the shape llama.cpp's own n-gram path does NOT have. +bool DeviceQuantGatherSupported(vt::DeviceType dev) { + return dev == vt::DeviceType::kCPU; +} + bool KeepQuantDType(uint32_t ggml_type, vt::DType* out) { vt::DType dt = vt::DType::kF32; if (!vt::BlockDTypeFromGgmlTypeId(ggml_type, &dt)) return false; @@ -161,24 +190,40 @@ GgufResidency RouteGgufTensor(bool keep_quant, bool keep_f16, bool nvfp4_fp4, // kKeepQuant) and it is not F16 (so never kKeepF16). The eligible roles are // exactly the verbatim-bytes ones keep-quant uses, and the alignment rule is // the ggml block, 64 elements — a ragged K cannot be repacked block-wise. - if (nvfp4_fp4 && KeepNvfp4DType(ggml_type)) { + // + // The GATHER role is excluded EXPLICITLY rather than by sharing + // `KeepQuantKDim`'s answer. It shared it until W6a, when that function + // started returning a real K for the embedding table, and the fp4 arm would + // then have claimed a gather table it cannot serve: `kNvfp4Fp4` produces an + // `Nvfp4Weight` operand PAIR for `vt::MatmulNvfp4`, not a block-typed tensor, + // and no gather can read one. The exclusion is by ROLE and not by capability, + // so a future NVFP4 gather has to say so here. + if (nvfp4_fp4 && KeepNvfp4DType(ggml_type) && + role != GgufTensorRole::kEmbeddingTable) { const int64_t k = KeepQuantKDim(role, shape); if (k > 0 && k % 64 == 0) return GgufResidency::kNvfp4Fp4; } - // 1. Keep-quant blocks (a block encoding in a verbatim GEMM/expert role). + // 1. Keep-quant blocks (a block encoding in a verbatim GEMM/expert/gather + // role). The GEMM roles and the GATHER role share the block-alignment rule and + // the device gate but NOT the encoding rule: a GEMM weight needs a `vec_dot`, + // a gather table needs only a row decoder, so the two ask different questions. if (keep_quant) { const int64_t k = KeepQuantKDim(role, shape); + const bool gather = role == GgufTensorRole::kEmbeddingTable; vt::DType dt = vt::DType::kF32; + const vt::DeviceType dev = vllm::platforms::CurrentPlatform().device_type(); // ggml_row_size's precondition: a row is a whole number of blocks. A weight - // whose K is ragged cannot be dotted block-wise, so it expands. The device - // gate (review #523): a format the RUNNING device cannot execute keeps its - // pre-existing expand-bf16 residency instead of flipping to a keep-quant - // block that throws at forward time on a card with no CPU fallback tier. - if (k > 0 && KeepQuantDType(ggml_type, &dt) && - k % vt::BlockElems(dt) == 0 && - DeviceKeepQuantSupported( - dt, vllm::platforms::CurrentPlatform().device_type())) { + // whose K is ragged cannot be dotted OR decoded block-wise, so it expands. + // The device gate (review #523): a format the RUNNING device cannot execute + // keeps its pre-existing expand-bf16 residency instead of flipping to a + // keep-quant block that throws at forward time on a card with no CPU + // fallback tier. + const bool encoding_ok = gather ? KeepQuantGatherDType(ggml_type, &dt) + : KeepQuantDType(ggml_type, &dt); + if (k > 0 && encoding_ok && k % vt::BlockElems(dt) == 0 && + (gather ? DeviceQuantGatherSupported(dev) + : DeviceKeepQuantSupported(dt, dev))) { return GgufResidency::kKeepQuant; } } @@ -326,6 +371,12 @@ GgufResidency GgufLoadPolicy::Route(const GgufTensorInfo& tensor, return r; } +GgufLoadPolicy NoKeepQuant(const GgufLoadPolicy& policy) { + GgufLoadPolicy p = policy; + p.keep_quant = false; + return p; +} + GgufResidency PeekRoute(const GgufLoadPolicy& policy, const GgufTensorInfo& tensor, GgufTensorRole role) { return RouteGgufTensor(policy.keep_quant, policy.keep_f16, policy.nvfp4_fp4, diff --git a/src/vllm/model_executor/model_loader/gguf_reader.cpp b/src/vllm/model_executor/model_loader/gguf_reader.cpp index b2faeadf1..820b32f6e 100644 --- a/src/vllm/model_executor/model_loader/gguf_reader.cpp +++ b/src/vllm/model_executor/model_loader/gguf_reader.cpp @@ -211,6 +211,15 @@ const GgmlTypeTraits* FindGgmlTraits(uint32_t type) { static constexpr GgmlTypeTraits t{32, 18, "Q4_0"}; return &t; } + case 6: { + // block_q5_0 (llama.cpp @ b10451 ggml-common.h:229-235): f16 d + // + u8 qh[4] + QK5_0/2 u8 qs = 2 + 4 + 16 = 22, QK5_0 = 32. Reachable + // because llama.cpp's `tensor_type_fallback` maps Q4_K -> Q5_0 for a + // tensor whose row is not a multiple of 256, which is every + // `qwen4exp` expert row (640) and its per-layer table row (160). + static constexpr GgmlTypeTraits t{32, 22, "Q5_0"}; + return &t; + } case 8: { static constexpr GgmlTypeTraits t{32, 34, "Q8_0"}; return &t; @@ -269,6 +278,19 @@ const GgmlTypeTraits* FindGgmlTraits(uint32_t type) { static constexpr GgmlTypeTraits t{256, 38, "IQ1_XXXS"}; return &t; } + case 20: { + // block_iq4_nl (llama.cpp @ b10451 ggml-common.h:447-452): f16 d + // + QK4_NL/2 u8 qs = 2 + 16 = 18, QK4_NL = 32. Q4_0's geometry with a + // 16-entry NON-LINEAR codebook (ggml-common.h:1120 kvalues_iq4nl) in + // place of the affine step. This is the encoding + // `unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S` uses for all 48 + // `ffn_down_exps` (K = 640) and for the 20M-entry n-gram table + // `per_layer_token_embd.weight` (row = 160): neither dimension is a + // multiple of 256, so no K-quant can encode them and llama.cpp's + // `tensor_type_fallback` drops IQ4_XS -> IQ4_NL. + static constexpr GgmlTypeTraits t{32, 18, "IQ4_NL"}; + return &t; + } case 22: { // block_iq2_s: f16 d + QK_K/4 qs + QK_K/16 qh = 2 + 64 + 16. // Used by the APEX "Mini" GGUFs for expert weights. diff --git a/src/vllm/model_executor/models/deepseek_v4_weights.cpp b/src/vllm/model_executor/models/deepseek_v4_weights.cpp index 0840a4155..bb793cf43 100644 --- a/src/vllm/model_executor/models/deepseek_v4_weights.cpp +++ b/src/vllm/model_executor/models/deepseek_v4_weights.cpp @@ -1388,12 +1388,30 @@ struct V4GgufCtx { return MakeBf16Owned(DequantGgufRowToBf16(t.ggml_type, t.data, rows * k), {rows, k}, /*nk=*/true); } - // A value/table tensor whose bytes are rewritten (norm/bias/scale/sink/table/ - // embed) — NEVER keep-quant. Asserts the policy agrees (totality) then dequants - // to f32 in the file's torch shape. + // A value tensor whose bytes are rewritten (norm/bias/scale/sink/hash table) + // — NEVER keep-quant. Asserts the policy agrees (totality) then dequants to + // f32 in the file's torch shape. OwnedTensor Vec(const std::string& name, GgufTensorRole role) { + return VecWith(pol, name, role); + } + // `token_embd.weight`, in BOTH of the roles this model gives it: the GATHER + // table (`hw.embed`, indexed as a flat host f32 array at deepseek_v4.cpp:1844) + // and, when the file is tied, the final projection's f32 GEMM operand. Neither + // consumer can read a table that keeps its ggml blocks, and since + // MODEL-MM-QWEN4-EXP W6a (#1989) the SHARED residency policy elects exactly + // that for any block-quantized table whose rows are whole blocks — which is + // every published deepseek4 checkpoint. So this loader NARROWS the policy for + // this one tensor by name rather than asserting that nobody elects the + // residency it cannot serve; see `NoKeepQuant` for why that is stated as a + // policy. Decoding the blocks here instead (a keep-quant gather, and a + // keep-quant tied head) is owed to #1978. + OwnedTensor EmbedF32(const std::string& name, GgufTensorRole role) { + return VecWith(NoKeepQuant(pol), name, role); + } + OwnedTensor VecWith(const GgufLoadPolicy& p, const std::string& name, + GgufTensorRole role) { const GgufTensorInfo& t = Take(name); - VT_CHECK(pol.Route(t, role) == GgufResidency::kExpandBf16, + VT_CHECK(p.Route(t, role) == GgufResidency::kExpandBf16, std::string("deepseek-v4 gguf: a ") + Name(role) + " tensor must not keep quant blocks: " + name); return MakeF32Owned(DqRowF32(g, name), t.shape); @@ -1619,8 +1637,12 @@ DeepseekV4Weights LoadDeepseekV4FromGguf(const GgufFile& g, const HfConfig& conf // ── model-level tower slots ───────────────────────────────────────────── DeepseekV4GgufWeights& tw = w.gguf; - tw.embed = ctx.Vec("token_embd.weight", GgufTensorRole::kEmbeddingTable); - tw.lm_head = tied ? ctx.Vec("token_embd.weight", GgufTensorRole::kEmbeddingTable) + tw.embed = ctx.EmbedF32("token_embd.weight", GgufTensorRole::kEmbeddingTable); + // The TIED head is a GEMM operand, not a gather, so it is routed under the + // role it actually has — token_embd is routed TWICE on a tied file, once per + // role, exactly as qwen3_5's LoadEmbedAndHead does it. Both answers are the + // f32 expansion this model's forward consumes. + tw.lm_head = tied ? ctx.EmbedF32("token_embd.weight", GgufTensorRole::kMatmulWeight) : ctx.Mw("output.weight"); tw.final_norm = ctx.Vec("output_norm.weight", GgufTensorRole::kVector); tw.hc_head_base = ctx.Vec("output_hc_base.weight", GgufTensorRole::kVector); diff --git a/src/vllm/model_executor/models/laguna_weights.cpp b/src/vllm/model_executor/models/laguna_weights.cpp index 8a4fe96e9..d447dc553 100644 --- a/src/vllm/model_executor/models/laguna_weights.cpp +++ b/src/vllm/model_executor/models/laguna_weights.cpp @@ -710,11 +710,26 @@ struct LagunaGgufCtx { return MakeBf16Owned(DequantGgufRowToBf16(t->ggml_type, t->data, rows * k), {rows, k}, /*nk=*/true); } - // A value/table tensor (norm/bias/router/embed): NEVER keep-quant, dequant f32. + // A value tensor (norm/bias/router): NEVER keep-quant, dequant f32. OwnedTensor Vec(const std::string& name, GgufTensorRole role) { + return VecWith(pol, name, role); + } + // `token_embd.weight`. Laguna gathers it as a flat host f32 array + // (`LagunaEmbed`, laguna.cpp:1594) and, on a TIED file, feeds the same f32 + // image to the final projection (`MatmulNK(src, ReadF32(weights.embed), ...)`, + // laguna.cpp:1281), so it cannot read a table that keeps its ggml blocks — + // which is what the SHARED policy elects for every published quantized laguna + // checkpoint since MODEL-MM-QWEN4-EXP W6a (#1989). The narrowing is stated by + // name here; see `NoKeepQuant`. A keep-quant gather for this model is owed to + // #1978. + OwnedTensor EmbedF32(const std::string& name) { + return VecWith(NoKeepQuant(pol), name, GgufTensorRole::kEmbeddingTable); + } + OwnedTensor VecWith(const GgufLoadPolicy& p, const std::string& name, + GgufTensorRole role) { auto [g, t] = Take(name); (void)g; - VT_CHECK(pol.Route(*t, role) == GgufResidency::kExpandBf16, + VT_CHECK(p.Route(*t, role) == GgufResidency::kExpandBf16, std::string("laguna gguf: a ") + Name(role) + " tensor must not keep quant blocks: " + name); int64_t numel = 1; @@ -756,7 +771,7 @@ LagunaWeights LoadLagunaFromGgufShards(const std::vector& shard } // ── model level ────────────────────────────────────────────────────────── - w.embed = ctx.Vec("token_embd.weight", GgufTensorRole::kEmbeddingTable); // f32 gather + w.embed = ctx.EmbedF32("token_embd.weight"); // f32 gather (and tied head) w.norm = ctx.Vec("output_norm.weight", GgufTensorRole::kVector); w.lm_head = tied ? OwnedTensor{} : ctx.Mw("output.weight"); // keep-quant Q8_0 diff --git a/src/vllm/model_executor/models/minimax_music3_depth_device.cpp b/src/vllm/model_executor/models/minimax_music3_depth_device.cpp index 8ab4f1269..86ef18128 100644 --- a/src/vllm/model_executor/models/minimax_music3_depth_device.cpp +++ b/src/vllm/model_executor/models/minimax_music3_depth_device.cpp @@ -58,7 +58,7 @@ std::atomic g_forward_count{0}; std::atomic g_resident_dtypes{0}; // Record the dtype of a buffer the forward made resident. `DType` is a `uint8_t` -// enum of 20 values, so one bit each fits a `uint64_t` with room to spare, and +// enum of 22 values, so one bit each fits a `uint64_t` with room to spare, and // the static_assert makes adding the 65th dtype a build error rather than a // silently truncated instrument. constexpr uint64_t DtypeBit(DType dt) { diff --git a/src/vllm/model_executor/models/qwen3_5.cpp b/src/vllm/model_executor/models/qwen3_5.cpp index b333a611d..8b7c889bf 100644 --- a/src/vllm/model_executor/models/qwen3_5.cpp +++ b/src/vllm/model_executor/models/qwen3_5.cpp @@ -1192,6 +1192,22 @@ Tensor ResidentWeight(Dev d, const OwnedTensor& w, std::vector shape = return MakeTensor(w.d_dev.get(), w.dtype, d.q.device, shape); } +} // namespace (closed so the bridge below has EXTERNAL linkage; the unnamed + // namespace reopens immediately after, and its names stay visible) + +// See qwen3_5.h. One line of body on purpose: this is a NAME for the bridge the +// forward already used, not a new path. Making it nameable is what lets the +// reachability gate enter through the same call the forward makes instead of +// hand-building the operand one step later. +Tensor Qwen3_5EmbeddingTable(vt::Backend& backend, vt::Queue& queue, + const OwnedTensor& embed_tokens, int64_t vocab, + int64_t hidden) { + Dev d{backend, queue}; + return ResidentWeight(d, embed_tokens, {vocab, hidden}); +} + +namespace { + // Device-resident f32 upcast of a bf16 owned weight, uploaded ONCE. Matches the // CUDA norm/conv kernels' requirement that the weight dtype equal the (f32) // activation dtype (GDN conv1d / gated-norm, attention qk-norm). `shape` is the @@ -7767,8 +7783,8 @@ DBuf MtpHeadHidden(Dev device, const Qwen3_5MTPWeights& weights, const int64_t vocab_size = config.vocab_size; const float eps = static_cast(config.rms_norm_eps); - Tensor embedding_table = - ResidentWeight(device, embed_tokens, {vocab_size, hidden_size}); + Tensor embedding_table = Qwen3_5EmbeddingTable(device.b, device.q, embed_tokens, + vocab_size, hidden_size); DBuf device_ids(device, DType::kI32, {tokens}, input_ids.data()); DBuf embedding(device, DType::kBF16, {tokens, hidden_size}); vt::Embedding(device.q, embedding.t(), embedding_table, device_ids.t()); @@ -8209,7 +8225,8 @@ static void EmbedInto(Dev d, DBuf& hidden, const std::vector& token_ids const int64_t T = static_cast(token_ids.size()); const int64_t H = config.hidden_size; const int64_t vocab = config.vocab_size; - Tensor dtab = ResidentWeight(d, weights.embed_tokens, {vocab, H}); + Tensor dtab = + Qwen3_5EmbeddingTable(d.b, d.q, weights.embed_tokens, vocab, H); // ENG-ASYNC-SCHED W4: when the async runner has already placed this step's // input ids on the device (and spliced each decode row's sampled token into // them there), embed straight from that buffer. `token_ids` is stale for @@ -8855,7 +8872,8 @@ std::vector Qwen3_5Model::ForwardDense(const std::vector& token_ const float eps = static_cast(config.rms_norm_eps); // Embed: hidden = embed_tokens[token_ids] (bf16, device-resident). res = 0. - Tensor dtab = ResidentWeight(d, weights.embed_tokens, {vocab, H}); + Tensor dtab = + Qwen3_5EmbeddingTable(d.b, d.q, weights.embed_tokens, vocab, H); DBuf dids(d, DType::kI32, {T}, token_ids.data()); DBuf hidden(d, DType::kBF16, {T, H}); vt::Embedding(d.q, hidden.t(), dtab, dids.t()); @@ -8901,7 +8919,8 @@ std::vector Qwen3_5DenseModel::ForwardDense( // For a TEXT-only step the three mRoPE position streams are identical, so the // partial NeoX RoPE in FullAttnBlock degenerates to 1-D RoPE over `positions` // (notes §2). The vision tower / image-video merger are DEFERRED. - Tensor dtab = ResidentWeight(d, weights.embed_tokens, {vocab, H}); + Tensor dtab = + Qwen3_5EmbeddingTable(d.b, d.q, weights.embed_tokens, vocab, H); DBuf dids(d, DType::kI32, {T}, token_ids.data()); DBuf hidden(d, DType::kBF16, {T, H}); vt::Embedding(d.q, hidden.t(), dtab, dids.t()); @@ -9199,7 +9218,8 @@ static void DenseEmbedInto(Dev d, DBuf& hidden, const int64_t T = static_cast(token_ids.size()); const int64_t H = config.hidden_size; const int64_t vocab = config.vocab_size; - Tensor dtab = ResidentWeight(d, weights.embed_tokens, {vocab, H}); + Tensor dtab = + Qwen3_5EmbeddingTable(d.b, d.q, weights.embed_tokens, vocab, H); DBuf dids(d, DType::kI32, {T}, token_ids.data()); ApplyDeviceTokenIdsOverride(d, dids, T); vt::Embedding(d.q, hidden.t(), dtab, dids.t()); diff --git a/src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp b/src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp index b85ebf90d..048cce64a 100644 --- a/src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp +++ b/src/vllm/model_executor/models/qwen3_5_gguf_weights.cpp @@ -773,16 +773,34 @@ void LoadEmbedAndHead(const GgufFile& g, const GgufLoadPolicy& pol, const GgufResidency embed_r = pol.Route(et, GgufTensorRole::kEmbeddingTable); // the embed's audit event - // Build the gather table in its chosen residency: keep-f16 in place, else the - // historical bf16 expansion. A quantized embedding is never keep-eligible. - if (embed_r == GgufResidency::kKeepF16) { + // Build the gather table in its chosen residency: keep-quant blocks, keep-f16 + // in place, else the historical bf16 expansion. + // + // The keep-quant arm is MODEL-MM-QWEN4-EXP W6a. Until the dequantizing gather + // existed this branch was a hard refusal, and the refusal was RIGHT: a kept + // table would have been bytes no op in the tree could read. `vt::Embedding` + // now decodes one row per gathered id, so the table's residency follows the + // same policy every other tensor's does. + if (embed_r == GgufResidency::kKeepQuant) { + VT_CHECK(et.shape.size() == 2, "qwen3_5 gguf: token_embd must be 2-D"); + // NEITHER repack applies, and both would be silent corruption rather than a + // slowdown: the i8mm interleave and the CUDA coalesced-load permutation + // rewrite the bytes INSIDE a row for a vec_dot to consume, and the gather + // decodes the row in its ggml order. `nk` stays false for the same reason + // it does on the f16 arm — this tensor is a table, not a [N,K] GEMM weight. + *embed = OwnGgufQuantBlocks(et, et.shape[0], et.shape[1], /*row_offset=*/0, + MmapSrc(g, pol), /*repack=*/false, + /*cuda_align=*/false); + embed->nk = false; + } else if (embed_r == GgufResidency::kKeepF16) { VT_CHECK(et.shape.size() == 2, "qwen3_5 gguf: token_embd must be 2-D"); // Embedding gather table: never repacked (EmbeddingKernel reads it row-wise). *embed = OwnGgufF16(et, et.shape[0], et.shape[1], 0, MmapSrc(g, pol), /*nk=*/false, /*elem_kn_repack=*/false); } else { VT_CHECK(embed_r == GgufResidency::kExpandBf16, - "qwen3_5 gguf: the embedding table cannot keep quant blocks"); + "qwen3_5 gguf: unexpected embedding-table residency " + + std::string(Name(embed_r))); *embed = OwnBf16(g, kEmbed, et.shape); } @@ -800,6 +818,15 @@ void LoadEmbedAndHead(const GgufFile& g, const GgufLoadPolicy& pol, // The two coincide (share one buffer) iff both kept f16, OR both expanded in // the file's own [N, K] order (expand_nk). share_tied_head already implies // expand_nk (FromEnv), which is why the bf16 arm needs no extra check. + // + // A tied KEEP-QUANT pair deliberately does NOT share, even though the file + // bytes are the same bytes: the head goes through `OwnMatmulWeight`, which + // may REPACK the slice for i8mm or for the CUDA coalesced load, and a table + // sharing a repacked buffer would gather permuted rows. The cost is one + // duplicated block image on a tied quantized file; the alternative is a + // corruption that only appears on the hosts where a repack is live. Making + // them share correctly needs the repack decision hoisted, which is a change to + // `OwnMatmulWeight` and not to this function. const bool f16_share = embed_r == GgufResidency::kKeepF16 && head_r == GgufResidency::kKeepF16; const bool bf16_share = embed_r == GgufResidency::kExpandBf16 && diff --git a/src/vllm/model_executor/models/qwen4_exp_gguf_weights.cpp b/src/vllm/model_executor/models/qwen4_exp_gguf_weights.cpp new file mode 100644 index 000000000..e90f3d144 --- /dev/null +++ b/src/vllm/model_executor/models/qwen4_exp_gguf_weights.cpp @@ -0,0 +1,269 @@ +// vllm.cpp ORIGINAL — the `qwen4exp` GGUF config builder. See the header for +// why this family owns its own translation unit and which llama.cpp pull +// request its key names follow. +#include "vllm/model_executor/models/qwen4_exp_gguf_weights.h" + +#include +#include + +#include "vt/dtype.h" // VT_CHECK + +namespace vllm { +namespace { + +const std::string kPrefix = "qwen4exp."; + +int64_t KvInt(const GgufValue& v, const std::string& key) { + switch (v.TypeId()) { + case kGgufU8: return std::get(v.v); + case kGgufI8: return std::get(v.v); + case kGgufU16: return std::get(v.v); + case kGgufI16: return std::get(v.v); + case kGgufU32: return std::get(v.v); + case kGgufI32: return std::get(v.v); + case kGgufU64: return static_cast(std::get(v.v)); + case kGgufI64: return std::get(v.v); + case kGgufBool: return std::get(v.v) ? 1 : 0; + default: + throw std::runtime_error("qwen4_exp gguf: key " + key + + " is not an integer"); + } +} + +double KvFloat(const GgufValue& v, const std::string& key) { + if (v.TypeId() == kGgufF32) return std::get(v.v); + if (v.TypeId() == kGgufF64) return std::get(v.v); + return static_cast(KvInt(v, key)); +} + +int64_t ReqInt(const GgufFile& g, const std::string& key) { + const GgufValue* v = g.FindKv(key); + VT_CHECK(v != nullptr, "qwen4_exp gguf: missing metadata key " + key); + return KvInt(*v, key); +} + +int64_t OptInt(const GgufFile& g, const std::string& key, int64_t dflt) { + const GgufValue* v = g.FindKv(key); + return v != nullptr ? KvInt(*v, key) : dflt; +} + +double ReqFloat(const GgufFile& g, const std::string& key) { + const GgufValue* v = g.FindKv(key); + VT_CHECK(v != nullptr, "qwen4_exp gguf: missing metadata key " + key); + return KvFloat(*v, key); +} + +// A required integer ARRAY key, read out as int64. Arrays carry the per-layer +// schedules this architecture is built out of, and reading one wrong splits the +// model into layers of the wrong kind, so a missing or wrong-typed array fails +// here rather than defaulting. +std::vector ReqIntArray(const GgufFile& g, const std::string& key) { + const GgufValue* v = g.FindKv(key); + VT_CHECK(v != nullptr, "qwen4_exp gguf: missing metadata key " + key); + VT_CHECK(v->TypeId() == kGgufArray, + "qwen4_exp gguf: key " + key + " must be an array"); + std::vector out; + for (const GgufValue& e : std::get(v->v).elems) { + out.push_back(KvInt(e, key)); + } + return out; +} + +std::vector OptIntArray(const GgufFile& g, const std::string& key) { + const GgufValue* v = g.FindKv(key); + if (v == nullptr || v->TypeId() != kGgufArray) return {}; + std::vector out; + for (const GgufValue& e : std::get(v->v).elems) { + out.push_back(KvInt(e, key)); + } + return out; +} + +} // namespace + +bool IsQwen4ExpGguf(const GgufFile& gguf) { + const GgufValue* v = gguf.FindKv("general.architecture"); + return v != nullptr && v->TypeId() == kGgufString && + std::get(v->v) == kQwen4ExpGgufArch; +} + +HfConfig Qwen4ExpHfConfigFromGguf(const GgufFile& gguf) { + VT_CHECK(IsQwen4ExpGguf(gguf), + "qwen4_exp gguf: general.architecture must be '" + + std::string(kQwen4ExpGgufArch) + "'"); + const std::string p = kPrefix; + + HfConfig c; + // The HF `model_type` and architecture string, verbatim from the released + // `Qwen/Qwen3.8-Flash-Next` config.json (read 2026-08-26). They are NOT + // derived from the GGUF architecture key: llama.cpp's `qwen4exp` is a family + // key, not a model class, and the registry keys on the HF class. + c.model_type = "qwen4_exp"; + c.architectures = {"Qwen4ExpForConditionalGeneration"}; + + c.hidden_size = ReqInt(gguf, p + "embedding_length"); + c.num_hidden_layers = ReqInt(gguf, p + "block_count"); + c.num_attention_heads = ReqInt(gguf, p + "attention.head_count"); + c.num_key_value_heads = + OptInt(gguf, p + "attention.head_count_kv", c.num_attention_heads); + c.head_dim = ReqInt(gguf, p + "attention.key_length"); + c.max_position_embeddings = ReqInt(gguf, p + "context_length"); + c.rms_norm_eps = ReqFloat(gguf, p + "attention.layer_norm_rms_epsilon"); + c.rope_theta = ReqFloat(gguf, p + "rope.freq_base"); + // `rope.dimension_count` IS `partial_rotary_factor * head_dim` already + // resolved by the converter: 0.25 * 256 == 64 on the released checkpoint. + c.rotary_dim = ReqInt(gguf, p + "rope.dimension_count"); + + // MoE. `expert_feed_forward_length` (640) is what makes every routed expert + // row indivisible by 256 and therefore un-encodable as any K-quant — the + // reason the shipped file reaches for IQ4_NL and IQ1_S/IQ2_XXS instead. + c.num_experts = ReqInt(gguf, p + "expert_count"); + c.num_experts_per_tok = ReqInt(gguf, p + "expert_used_count"); + c.moe_intermediate_size = ReqInt(gguf, p + "expert_feed_forward_length"); + c.shared_expert_intermediate_size = + OptInt(gguf, p + "expert_shared_feed_forward_length", + c.moe_intermediate_size); + c.intermediate_size = c.moe_intermediate_size; + + // Gated Delta Net. llama.cpp's converter reuses the Mamba key namespace for + // the linear-attention geometry, so the names read `ssm.*` while the values + // are GDN's — the same mapping `qwen3next` already uses in this tree. + c.linear_num_key_heads = ReqInt(gguf, p + "ssm.group_count"); + c.linear_num_value_heads = ReqInt(gguf, p + "ssm.time_step_rank"); + c.linear_key_head_dim = ReqInt(gguf, p + "ssm.state_size"); + c.linear_value_head_dim = c.linear_key_head_dim; + c.linear_conv_kernel_dim = ReqInt(gguf, p + "ssm.conv_kernel"); + // `ssm.inner_size` is the value stream width and must equal + // num_value_heads * value_head_dim; a file where it does not is malformed and + // would silently size every GDN buffer wrong. + const int64_t inner = OptInt(gguf, p + "ssm.inner_size", 0); + if (inner != 0) { + VT_CHECK(inner == c.linear_num_value_heads * c.linear_value_head_dim, + "qwen4_exp gguf: ssm.inner_size disagrees with " + "ssm.time_step_rank * ssm.state_size"); + } + // Not in the GGUF container at ALL, and not inferable from it: the released + // config.json says `output_gate_type: "sigmoid"`, where this tree's default + // (and Qwen3.5's value) is "silu". Left at the default it would be a silent + // wrong activation on the GDN output gate, which no shape check can see. + c.output_gate_type = "sigmoid"; + c.mamba_ssm_dtype = "float32"; // config.json `mamba_ssm_dtype` + + // The layer schedule. `full_attention_interval` 4 gives the documented + // 3 x linear_attention -> 1 x qwen_sparse_attention pattern, and the file + // corroborates it twice over: `attention.compress_ratios` is non-zero on + // exactly those layers, and `blk.N.indexer.*` exists on exactly those layers. + const int64_t interval = ReqInt(gguf, p + "full_attention_interval"); + VT_CHECK(interval > 0, "qwen4_exp gguf: full_attention_interval must be > 0"); + c.layer_types.reserve(static_cast(c.num_hidden_layers)); + for (int64_t il = 0; il < c.num_hidden_layers; ++il) { + const bool full = ((il + 1) % interval) == 0; + c.layer_types.emplace_back(full ? "full_attention" : "linear_attention"); + } + const std::vector ratios = + OptIntArray(gguf, p + "attention.compress_ratios"); + if (!ratios.empty()) { + VT_CHECK(static_cast(ratios.size()) == c.num_hidden_layers, + "qwen4_exp gguf: attention.compress_ratios length must equal " + "block_count"); + for (int64_t il = 0; il < c.num_hidden_layers; ++il) { + const bool full = c.layer_types[static_cast(il)] == + "full_attention"; + VT_CHECK((ratios[static_cast(il)] != 0) == full, + "qwen4_exp gguf: attention.compress_ratios disagrees with the " + "full_attention_interval schedule"); + } + } + + // Interleaved mRoPE. `rope.dimension_sections` is llama.cpp's 4-slot array + // and HF's `mrope_section` is 3 long, so the trailing zero is dropped rather + // than carried: a 4-element section list would make the sum check fail + // downstream for a reason that has nothing to do with the model. + std::vector sections = + OptIntArray(gguf, p + "rope.dimension_sections"); + while (!sections.empty() && sections.back() == 0) sections.pop_back(); + if (!sections.empty()) { + int64_t sum = 0; + for (int64_t s : sections) sum += s; + VT_CHECK(sum * 2 == c.rotary_dim, + "qwen4_exp gguf: rope.dimension_sections must sum to " + "rope.dimension_count / 2"); + c.rope_parameters.mrope_section.clear(); + for (int64_t s : sections) { + c.rope_parameters.mrope_section.push_back(s); + } + c.rope_parameters.mrope_interleaved = true; + c.rope_parameters.rope_theta = c.rope_theta; + c.has_rope_parameters = true; + } + + // vocab_size: no metadata key, so read the gather table's own leading dim, + // exactly as the qwen3_5 and muse-glimmer builders do. + c.vocab_size = OptInt(gguf, p + "vocab_size", 0); + if (c.vocab_size == 0) { + c.vocab_size = gguf.Get("token_embd.weight").shape[0]; + } + c.torch_dtype = "bfloat16"; + + // --- the architecture-specific numbers, under HF's OWN key names ---------- + // + // These are carried in `raw` rather than typed, because no other model in + // this tree has them and the fields would be dead everywhere else. The names + // are the released `Qwen/Qwen3.8-Flash-Next` config.json's, read 2026-08-26, + // NOT names invented here: an implementer of the model waves reads the same + // spellings whether the config came from a GGUF or from a config.json. + nlohmann::json raw = nlohmann::json::object(); + raw["model_type"] = c.model_type; + raw["architectures"] = c.architectures; + nlohmann::json text = nlohmann::json::object(); + text["hc_count"] = ReqInt(gguf, p + "hyper_connection.count"); + text["hc_lowrank"] = ReqInt(gguf, p + "hyper_connection.low_rank"); + text["indexer_n_heads"] = ReqInt(gguf, p + "attention.indexer.head_count"); + text["indexer_head_dim"] = ReqInt(gguf, p + "attention.indexer.key_length"); + text["indexer_budget"] = ReqInt(gguf, p + "attention.indexer.top_k"); + text["full_attention_interval"] = interval; + text["ngram_size"] = ReqInt(gguf, p + "ple.ngram_size"); + text["heads_per_ngram"] = ReqInt(gguf, p + "ple.heads_per_ngram"); + text["ple_conv_kernel_size"] = ReqInt(gguf, p + "ple.conv_kernel"); + // The PLE table's per-head row width. HF states the TOTAL (`ple_embed_dim` + // 2560); the GGUF states the per-head slice (160), and 2560 == 160 * 16 heads. + // Both are recorded so neither wave has to reconstruct the other. + const int64_t ple_row = ReqInt(gguf, p + "embedding_length_per_layer_input"); + text["ple_embed_dim_per_head"] = ple_row; + // `indexer_compress_ratio`: the file states it per LAYER; HF states the one + // value. Take the first non-zero and require the rest to agree, so a file + // with a mixed schedule is a loud failure rather than a silent first-wins. + if (!ratios.empty()) { + int64_t cr = 0; + for (int64_t r : ratios) { + if (r == 0) continue; + if (cr == 0) cr = r; + VT_CHECK(r == cr, + "qwen4_exp gguf: attention.compress_ratios is not uniform " + "across the sparse layers"); + } + if (cr != 0) text["indexer_compress_ratio"] = cr; + } + // The n-gram head table. `head_offsets` and `head_vocab_sizes` have no HF + // counterpart at all: HF derives them from `ngram_vocab_size_base` and + // `make_ngram_vocab_size_divisible_by`, while the GGUF writes the resolved + // arrays. The resolved arrays are the authority here, because they are what + // the shipped tensor was built against. + text["ple_head_offsets"] = OptIntArray(gguf, p + "ple.head_offsets"); + text["ple_head_vocab_sizes"] = OptIntArray(gguf, p + "ple.head_vocab_sizes"); + text["ple_layer_multipliers"] = OptIntArray(gguf, p + "ple.layer_multipliers"); + // `ple.layers` is recorded UNDER ITS GGUF NAME and deliberately not mapped + // onto HF's `ple_layer_ids`. The shipped file says [1] and stores the PLE + // tensors at `blk.1.*`; the released config.json says `ple_layer_ids: [2]`. + // The two conventions differ by one and nothing in either file says which end + // the offset is on. Mapping them onto one key here would bury that, and the + // wave that implements the PLE layer has to resolve it against the algorithm + // oracle rather than inherit a guess. See .agents/specs/qwen4-exp-flash-next.md. + text["gguf_ple_layers"] = ReqIntArray(gguf, p + "ple.layers"); + raw["text_config"] = text; + c.raw = std::move(raw); + + return c; +} + +} // namespace vllm diff --git a/src/vt/cpu/cpu_ops.cpp b/src/vt/cpu/cpu_ops.cpp index 32690ccb1..4ec724619 100644 --- a/src/vt/cpu/cpu_ops.cpp +++ b/src/vt/cpu/cpu_ops.cpp @@ -17,6 +17,7 @@ #include "cpu_matmul_elem.h" #include "cpu_threadpool.h" +#include "vt/quant.h" // cpu::BlockToFloat — the quantized gather's row decode #include "vt/unaligned.h" namespace vt::cpu { @@ -972,6 +973,39 @@ void MatmulNvfp4Fp4Kernel(Queue&, Tensor& out, const Tensor& a_packed, const Ten void EmbeddingKernel(Queue&, Tensor& out, const Tensor& table, const Tensor& ids) { const int64_t t = ids.shape[0], h = table.shape[1], v = table.shape[0]; + + // BLOCK-QUANTIZED table: decode ONE ROW per gathered id. Port of + // `ggml_compute_forward_get_rows_q` (llama.cpp @ b10451 + // ggml/src/ggml-cpu/ops.cpp:4850), which is likewise a per-row `to_float` + // over the row-major block buffer, parallelised across the gathered rows. + // + // The decode cost is `h` elements per token and is not the reason this arm + // exists: keeping the TABLE compressed is. The scratch row is per-chunk, not + // per-call, so the parallel arms never share it. + if (IsBlockQuant(table.dtype)) { + const ToFloatFn to_float = BlockToFloat(table.dtype); + VT_CHECK(to_float != nullptr, + std::string("embedding: no row decoder for block dtype ") + + Name(table.dtype)); + // Whole-block rows are the op's precondition (checked in vt::Embedding), so + // this stride is exact and every row starts on a block boundary. + const size_t row_bytes = RowSizeBytes(table.dtype, h); + const uint8_t* base = table.Ptr(); + ForRows(t, [&](int64_t r0, int64_t r1) { + std::vector row(static_cast(h)); + for (int64_t i = r0; i < r1; ++i) { + const int64_t id = ids.dtype == DType::kI32 ? ids.Ptr()[i] + : ids.Ptr()[i]; + VT_CHECK(id >= 0 && id < v, "embedding: id out of range"); + to_float(base + static_cast(id) * row_bytes, row.data(), h); + for (int64_t j = 0; j < h; ++j) { + StoreF32(out, i * h + j, row[static_cast(j)]); + } + } + }); + return; + } + ForRows(t, [&](int64_t r0, int64_t r1) { for (int64_t i = r0; i < r1; ++i) { int64_t id = ids.dtype == DType::kI32 ? ids.Ptr()[i] : ids.Ptr()[i]; diff --git a/src/vt/cpu/cpu_quant_blocks.h b/src/vt/cpu/cpu_quant_blocks.h index 91b2370ce..edb3446e6 100644 --- a/src/vt/cpu/cpu_quant_blocks.h +++ b/src/vt/cpu/cpu_quant_blocks.h @@ -33,6 +33,9 @@ inline constexpr int kQK4_0 = 32; inline constexpr int kQK8_0 = 32; // ggml-common.h:204 inline constexpr int kQK_MXFP4 = 32; +// llama.cpp @ b10451 ggml-common.h:229, :447 +inline constexpr int kQK5_0 = 32; +inline constexpr int kQK4_NL = 32; // ggml-common.h:213-218 struct BlockQ4_0 { @@ -41,6 +44,27 @@ struct BlockQ4_0 { }; static_assert(sizeof(BlockQ4_0) == 18, "wrong q4_0 block size/padding"); +// llama.cpp @ b10451 ggml-common.h:229-235 block_q5_0. QK5_0 == QK4_0 == 32. +// `qh` is a 32-bit little-endian bitfield read with memcpy upstream (the struct +// stores it as 4 bytes, so the block has no padding): bit j is the 5th bit of +// element j, bit j+16 the 5th bit of element j+16. `qs` packs the low 4 bits +// two-per-byte in the same split-half order q4_0 uses. Value = (nibble | bit5*16) - 16. +struct BlockQ5_0 { + uint16_t d; // delta (ggml_half) + uint8_t qh[4]; // 5-th bit of the quants + uint8_t qs[kQK5_0 / 2]; // nibbles / quants +}; +static_assert(sizeof(BlockQ5_0) == 22, "wrong q5_0 block size/padding"); + +// llama.cpp @ b10451 ggml-common.h:447-452 block_iq4_nl. Byte-for-byte q4_0's +// layout; the nibble is a CODEBOOK INDEX into kValuesIq4nl rather than an +// affine quant, which is the whole difference between the two encodings. +struct BlockIQ4_NL { + uint16_t d; // delta (ggml_half) + uint8_t qs[kQK4_NL / 2]; // 4-bit codebook indices +}; +static_assert(sizeof(BlockIQ4_NL) == 18, "wrong iq4_nl block size/padding"); + // ggml-common.h:242-245 struct BlockQ8_0 { uint16_t d; // delta (ggml_half) diff --git a/src/vt/cpu/cpu_quant_dequant.cpp b/src/vt/cpu/cpu_quant_dequant.cpp index 1f4253aff..12a44410b 100644 --- a/src/vt/cpu/cpu_quant_dequant.cpp +++ b/src/vt/cpu/cpu_quant_dequant.cpp @@ -62,6 +62,46 @@ void DequantQ4_0(const uint8_t* data, int64_t nb, float* y) { } } +// block_q5_0 = { f16 d; u8 qh[4]; u8 qs[16]; } (22 bytes) +// llama.cpp @ b10451 ggml/src/ggml-quants.c:500 dequantize_row_q5_0, ported +// verbatim including the shift order (the two halves read qh bits j and j+16 +// through DIFFERENT shift expressions, and swapping them silently corrupts the +// upper half of every block). +void DequantQ5_0(const uint8_t* data, int64_t nb, float* y) { + constexpr int qk = 32; + for (int64_t i = 0; i < nb; ++i) { + const uint8_t* blk = data + i * 22; + const float d = ReadF16(blk); + uint32_t qh = 0; + std::memcpy(&qh, blk + 2, sizeof(qh)); + const uint8_t* qs = blk + 6; + for (int j = 0; j < qk / 2; ++j) { + const uint8_t xh_0 = static_cast(((qh >> (j + 0)) << 4) & 0x10); + const uint8_t xh_1 = static_cast((qh >> (j + 12)) & 0x10); + const int32_t x0 = ((qs[j] & 0x0F) | xh_0) - 16; + const int32_t x1 = ((qs[j] >> 4) | xh_1) - 16; + y[i * qk + j + 0] = x0 * d; + y[i * qk + j + qk / 2] = x1 * d; + } + } +} + +// block_iq4_nl = { f16 d; u8 qs[16]; } (18 bytes) +// llama.cpp @ b10451 ggml/src/ggml-quants.c:2725 dequantize_row_iq4_nl. +// Q4_0's loop with the codebook lookup where the `- 8` used to be. +void DequantIQ4_NL(const uint8_t* data, int64_t nb, float* y) { + constexpr int qk = 32; + for (int64_t i = 0; i < nb; ++i) { + const uint8_t* blk = data + i * 18; + const float d = ReadF16(blk); + const uint8_t* qs = blk + 2; + for (int j = 0; j < qk / 2; ++j) { + y[i * qk + j + 0] = d * kValuesIq4nl[qs[j] & 0x0F]; + y[i * qk + j + qk / 2] = d * kValuesIq4nl[qs[j] >> 4]; + } + } +} + // block_q8_0 = { f16 d; i8 qs[32]; } (34 bytes) dequantize_row_q8_0:495 void DequantQ8_0(const uint8_t* data, int64_t nb, float* y) { constexpr int qk = 32; @@ -462,6 +502,7 @@ void ToFloatAdapter(const void* x, float* y, int64_t k) { ToFloatFn BlockToFloat(DType dtype) { switch (dtype) { case DType::kQ4_0: return &ToFloatAdapter<&DequantQ4_0, 32>; + case DType::kQ5_0: return &ToFloatAdapter<&DequantQ5_0, 32>; case DType::kQ8_0: return &ToFloatAdapter<&DequantQ8_0, 32>; case DType::kQ2_K: return &ToFloatAdapter<&DequantQ2_K, 256>; case DType::kQ3_K: return &ToFloatAdapter<&DequantQ3_K, 256>; @@ -474,6 +515,7 @@ ToFloatFn BlockToFloat(DType dtype) { case DType::kIQ2_S: return &ToFloatAdapter<&DequantIQ2_S, 256>; case DType::kIQ1_S: return &ToFloatAdapter<&DequantIQ1_S, 256>; case DType::kIQ1_XXXS: return &ToFloatAdapter<&DequantIQ1_XXXS, 256>; + case DType::kIQ4_NL: return &ToFloatAdapter<&DequantIQ4_NL, 32>; case DType::kMXFP4: return &ToFloatAdapter<&DequantMXFP4, 32>; default: return nullptr; } diff --git a/src/vt/cpu/cpu_quant_dot.cpp b/src/vt/cpu/cpu_quant_dot.cpp index 82ca1cb02..f41ee56df 100644 --- a/src/vt/cpu/cpu_quant_dot.cpp +++ b/src/vt/cpu/cpu_quant_dot.cpp @@ -84,6 +84,88 @@ void VecDotQ4_0Q8_0(int n, float* s, size_t bs, const void* vx, size_t bx, *s = sumf; } +// llama.cpp @ b10451 quants.c:365 — ggml_vec_dot_q5_0_q8_0_generic. +// The `(int8_t)` casts upstream applies to each reconstructed quant are kept: +// the value range is [-16, 15], so the cast is a no-op on the value but it is +// what fixes the type of the multiply, and dropping it changes nothing here +// only because both operands already promote to int. +void VecDotQ5_0Q8_0(int n, float* s, size_t bs, const void* vx, size_t bx, + const void* vy, size_t by, int nrc) { + const int qk = kQK8_0; + const int nb = n / qk; + + VT_CHECK(n % qk == 0, "vec_dot_q5_0_q8_0: n must be a multiple of 32"); + VT_CHECK(nrc == 1, "vec_dot_q5_0_q8_0: generic tier supports nrc == 1 only"); + (void)nrc; + (void)bx; + (void)by; + (void)bs; + + const BlockQ5_0* x = static_cast(vx); + const BlockQ8_0* y = static_cast(vy); + + float sumf = 0; + + for (int ib = 0; ib < nb; ++ib) { + uint32_t qh = 0; + std::memcpy(&qh, x[ib].qh, sizeof(qh)); + + int sumi0 = 0; + int sumi1 = 0; + + for (int j = 0; j < qk / 2; ++j) { + const uint8_t xh_0 = + static_cast(((qh & (1u << (j + 0))) >> (j + 0)) << 4); + const uint8_t xh_1 = + static_cast((qh & (1u << (j + 16))) >> (j + 12)); + + const int32_t x0 = static_cast(((x[ib].qs[j] & 0x0F) | xh_0) - 16); + const int32_t x1 = static_cast(((x[ib].qs[j] >> 4) | xh_1) - 16); + + sumi0 += (x0 * y[ib].qs[j]); + sumi1 += (x1 * y[ib].qs[j + qk / 2]); + } + + const int sumi = sumi0 + sumi1; + sumf += (F16ToF32(x[ib].d) * F16ToF32(y[ib].d)) * static_cast(sumi); + } + + *s = sumf; +} + +// llama.cpp @ b10451 quants.c:1254 — ggml_vec_dot_iq4_nl_q8_0_generic. Note the +// scale product is formed BEFORE the integer sum is folded in (`d * (s1 + s2)`), +// which is the opposite association from the q4_0 kernel above; the order is +// upstream's and is what makes our GEMM bit-reproducible against it. +void VecDotIQ4_NLQ8_0(int n, float* s, size_t bs, const void* vx, size_t bx, + const void* vy, size_t by, int nrc) { + VT_CHECK(n % kQK4_NL == 0, "vec_dot_iq4_nl_q8_0: n must be a multiple of 32"); + VT_CHECK(nrc == 1, "vec_dot_iq4_nl_q8_0: generic tier supports nrc == 1 only"); + (void)nrc; + (void)bx; + (void)by; + (void)bs; + static_assert(kQK4_NL == kQK8_0, "QK4_NL and QK8_0 must be the same"); + + const BlockIQ4_NL* x = static_cast(vx); + const BlockQ8_0* y = static_cast(vy); + + const int nb = n / kQK4_NL; + float sumf = 0; + + for (int ib = 0; ib < nb; ++ib) { + const float d = F16ToF32(y[ib].d) * F16ToF32(x[ib].d); + int sumi1 = 0; + int sumi2 = 0; + for (int j = 0; j < kQK4_NL / 2; ++j) { + sumi1 += y[ib].qs[j + 0] * kValuesIq4nl[x[ib].qs[j] & 0x0F]; + sumi2 += y[ib].qs[j + kQK4_NL / 2] * kValuesIq4nl[x[ib].qs[j] >> 4]; + } + sumf += d * static_cast(sumi1 + sumi2); + } + *s = sumf; +} + // quants.c:400 — ggml_vec_dot_q8_0_q8_0_generic void VecDotQ8_0Q8_0(int n, float* s, size_t bs, const void* vx, size_t bx, const void* vy, size_t by, int nrc) { @@ -787,6 +869,8 @@ VecDotFn QuantQ8PortableVecDot() { return &VecDotQ8_0Q8_0; } VecDotFn BlockVecDot(DType dtype) { switch (dtype) { case DType::kQ4_0: return &VecDotQ4_0Q8_0; // quants.c:174 + case DType::kQ5_0: return &VecDotQ5_0Q8_0; // b10451 quants.c:365 + case DType::kIQ4_NL: return &VecDotIQ4_NLQ8_0; // b10451 quants.c:1254 case DType::kQ8_0: return SelectQuantQ8VecDot(&VecDotQ8_0Q8_0); // quants.c:400 + A76 tier case DType::kQ2_K: return &VecDotQ2_KQ8_K; // quants.c:514 diff --git a/src/vt/cpu/cpu_quant_iq_tables.h b/src/vt/cpu/cpu_quant_iq_tables.h index 0309d33f0..36b7bfee1 100644 --- a/src/vt/cpu/cpu_quant_iq_tables.h +++ b/src/vt/cpu/cpu_quant_iq_tables.h @@ -1027,6 +1027,15 @@ inline constexpr int8_t kValuesMxfp4[16] = { 0, 1, 2, 3, 4, 6, 8, 12, 0, -1, -2, -3, -4, -6, -8, -12, }; +// llama.cpp @ b10451 ggml-common.h:1120 kvalues_iq4nl — IQ4_NL's 16-entry +// NON-LINEAR codebook. The nibble is an INDEX here, not a quant: this is the +// only difference between IQ4_NL and Q4_0, which share a block layout byte for +// byte. Shared by the `to_float` decoder and the keep-quant `vec_dot`, so there +// is one definition rather than two that can drift. +inline constexpr int8_t kValuesIq4nl[16] = { + -127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113, +}; + // ggml_e8m0_to_fp32_half (ggml-impl.h:477) — decode an E8M0 (8-bit unsigned // exponent) MXFP4 block scale to 0.5 * 2^(byte-127) == 2^(byte-128). The half // convention pairs with kValuesMxfp4 (= 2 * e2m1) so their product is the fp4 diff --git a/src/vt/cpu/cpu_quant_traits.cpp b/src/vt/cpu/cpu_quant_traits.cpp index 5aa12ec26..ac80551b6 100644 --- a/src/vt/cpu/cpu_quant_traits.cpp +++ b/src/vt/cpu/cpu_quant_traits.cpp @@ -33,6 +33,26 @@ const QuantTypeTraits* FindQuantTraits(DType dtype) { static const QuantTypeTraits t = MakeTraits(DType::kQ4_0, DType::kQ8_0); return &t; } + // llama.cpp @ b10451 ggml-cpu.c:259-264 — Q5_0 -> Q8_0 activations. Added + // for `qwen4exp`, whose 640-wide expert K rules out every K-quant and whose + // `-Q4_K_M` recipe therefore lands on Q5_0 through llama.cpp's own + // `tensor_type_fallback`. No `from_float`: nothing here quantizes an + // activation INTO Q5_0 (upstream's row does carry one, and porting the + // encoder would be dead code — see the k-quant encoders, deliberately + // unported for the same reason). + case DType::kQ5_0: { + static const QuantTypeTraits t = MakeTraits(DType::kQ5_0, DType::kQ8_0); + return &t; + } + // llama.cpp @ b10451 ggml-cpu.c:379-384 — IQ4_NL -> Q8_0 activations (NOT + // Q8_K: IQ4_NL's block is 32 elements, so it pairs with the legacy 32-element + // activation encoding, exactly like MXFP4). This is the encoding the shipped + // Qwen3.8-Flash-Next UD-IQ1_S uses for all 48 `ffn_down_exps` AND for the + // 20M-entry n-gram table; keep-quant is the memory enabler for both. + case DType::kIQ4_NL: { + static const QuantTypeTraits t = MakeTraits(DType::kIQ4_NL, DType::kQ8_0); + return &t; + } // ggml-cpu.c:262-271 — Q8_0 -> Q8_0 activations. case DType::kQ8_0: { static const QuantTypeTraits t = MakeTraits(DType::kQ8_0, DType::kQ8_0); diff --git a/src/vt/cuda/cuda_quant_dot.cu b/src/vt/cuda/cuda_quant_dot.cu index a6f60a04c..6831f91bc 100644 --- a/src/vt/cuda/cuda_quant_dot.cu +++ b/src/vt/cuda/cuda_quant_dot.cu @@ -1602,7 +1602,8 @@ bool IsCudaKeepQuantSupported(DType dt, WType* out) { case DType::kIQ1_XXXS: *out = WType::kIQ1_XXXS; return true; // MXFP4 (Q8_0-activation, 32-elem blocks) is NOT handled by this Q8_K GEMM; // it falls through to CPU like Q4_0 / Q8_0 until a Q8_0-activation GEMM lands. - default: return false; // Q4_0 / Q8_0 / MXFP4 (Q8_0-activation) -> CPU fallback + // Q5_0 and IQ4_NL join them: both are 32-element Q8_0-activation encodings. + default: return false; // Q4_0 / Q5_0 / Q8_0 / IQ4_NL / MXFP4 -> CPU fallback } } diff --git a/src/vt/dtype.cpp b/src/vt/dtype.cpp index 815de1afc..e8957e765 100644 --- a/src/vt/dtype.cpp +++ b/src/vt/dtype.cpp @@ -39,6 +39,14 @@ const BlockGeometry* FindBlockGeometry(DType dtype) { static constexpr BlockGeometry g{32, 18, 2, "q4_0"}; return &g; } + case DType::kQ5_0: { + // block_q5_0 (llama.cpp @ b10451 ggml/src/ggml-common.h:229-235): + // f16 d + u8 qh[4] + u8 qs[QK5_0/2] = 2 + 4 + 16 = 22, QK5_0 = 32. + // ggml type id 6 (ggml/include/ggml.h:396). The 5th bit of each quant + // lives in the qh bitfield, split across the two halves of the block. + static constexpr BlockGeometry g{32, 22, 6, "q5_0"}; + return &g; + } case DType::kQ8_0: { static constexpr BlockGeometry g{32, 34, 8, "q8_0"}; return &g; @@ -110,6 +118,16 @@ const BlockGeometry* FindBlockGeometry(DType dtype) { static constexpr BlockGeometry g{256, 38, 66, "iq1_xxxs"}; return &g; } + case DType::kIQ4_NL: { + // block_iq4_nl (llama.cpp @ b10451 ggml/src/ggml-common.h:447-452): + // f16 d + u8 qs[QK4_NL/2] = 2 + 16 = 18, QK4_NL = 32. ggml type id 20 + // (ggml/include/ggml.h:410). Q4_0's geometry exactly, but the nibble + // indexes the 16-entry NON-LINEAR codebook `kvalues_iq4nl` + // (ggml-common.h:1120) instead of being an affine `nibble - 8`, so the + // two decoders are NOT interchangeable despite the identical layout. + static constexpr BlockGeometry g{32, 18, 20, "iq4_nl"}; + return &g; + } case DType::kMXFP4: { // block_mxfp4 (ggml-common.h:204-209): u8 e (E8M0 shared exponent) // + u8 qs[QK_MXFP4/2] = 1 + 16 = 17, QK_MXFP4 = 32. ggml type id 39. @@ -148,10 +166,10 @@ uint32_t GgmlTypeId(DType dtype) { return RequireBlockGeometry(dtype).ggml_type; bool BlockDTypeFromGgmlTypeId(uint32_t ggml_type, DType* out) { static constexpr DType kBlockDTypes[] = { - DType::kQ4_0, DType::kQ8_0, DType::kQ2_K, DType::kQ3_K, DType::kQ4_K, - DType::kQ5_K, DType::kQ6_K, DType::kQ8_K, DType::kIQ2_XXS, - DType::kIQ3_XXS, DType::kIQ2_S, DType::kMXFP4, DType::kIQ1_S, - DType::kIQ1_XXXS}; + DType::kQ4_0, DType::kQ5_0, DType::kQ8_0, DType::kQ2_K, DType::kQ3_K, + DType::kQ4_K, DType::kQ5_K, DType::kQ6_K, DType::kQ8_K, + DType::kIQ2_XXS, DType::kIQ3_XXS, DType::kIQ2_S, DType::kMXFP4, + DType::kIQ1_S, DType::kIQ1_XXXS, DType::kIQ4_NL}; for (DType d : kBlockDTypes) { if (FindBlockGeometry(d)->ggml_type == ggml_type) { if (out != nullptr) *out = d; @@ -185,6 +203,7 @@ size_t SizeOf(DType dtype) { // so every elementwise path that reaches one fails loudly here rather than // silently mis-striding a packed block buffer. case DType::kQ4_0: + case DType::kQ5_0: case DType::kQ8_0: case DType::kQ2_K: case DType::kQ3_K: @@ -197,6 +216,7 @@ size_t SizeOf(DType dtype) { case DType::kIQ2_S: case DType::kIQ1_S: case DType::kIQ1_XXXS: + case DType::kIQ4_NL: case DType::kMXFP4: VT_CHECK(false, std::string("SizeOf: block-quantized dtype ") + Name(dtype) + " has no per-element size"); @@ -215,6 +235,7 @@ const char* Name(DType dtype) { case DType::kI32: return "i32"; case DType::kI64: return "i64"; case DType::kQ4_0: return "q4_0"; + case DType::kQ5_0: return "q5_0"; case DType::kQ8_0: return "q8_0"; case DType::kQ2_K: return "q2_K"; case DType::kQ3_K: return "q3_K"; @@ -227,6 +248,7 @@ const char* Name(DType dtype) { case DType::kIQ2_S: return "iq2_s"; case DType::kIQ1_S: return "iq1_s"; case DType::kIQ1_XXXS: return "iq1_xxxs"; + case DType::kIQ4_NL: return "iq4_nl"; case DType::kMXFP4: return "mxfp4"; } return "?"; diff --git a/src/vt/ops.cpp b/src/vt/ops.cpp index fb435ea6d..26dad1633 100644 --- a/src/vt/ops.cpp +++ b/src/vt/ops.cpp @@ -37,6 +37,7 @@ ScalarTypeId ToScalarType(DType dtype) { // scales and packed codes. Kernels consume them through the quant traits // table, never through a KernelTensorDesc scalar type. case DType::kQ4_0: + case DType::kQ5_0: case DType::kQ8_0: case DType::kQ2_K: case DType::kQ3_K: @@ -49,6 +50,7 @@ ScalarTypeId ToScalarType(DType dtype) { case DType::kIQ2_S: case DType::kIQ1_S: case DType::kIQ1_XXXS: + case DType::kIQ4_NL: case DType::kMXFP4: break; } @@ -1481,8 +1483,27 @@ void Embedding(Queue& q, Tensor& out, const Tensor& table, const Tensor& ids) { VT_CHECK(out.shape[0] == ids.shape[0] && out.shape[1] == table.shape[1], "embedding: output shape mismatch"); VT_CHECK(ids.dtype == DType::kI32 || ids.dtype == DType::kI64, "embedding: ids i32/i64"); - VT_CHECK(IsFloat(table.dtype) && IsOutFloat(out.dtype), - "embedding: float table, f32/bf16 out"); + // A BLOCK-QUANTIZED table is admitted alongside the elementwise ones: the + // kernel then dequantizes ONE ROW per gathered id instead of loading it, + // mirroring `ggml_compute_forward_get_rows_q` (llama.cpp @ b10451 + // ggml/src/ggml-cpu/ops.cpp:4850), which dispatches every quantized get_rows + // through the type's `to_float`. This is what lets a gather table stay + // COMPRESSED in memory; a 20 M-entry n-gram table has no other affordable + // residency (Qwen3.8-Flash-Next: 28.8 GB of IQ4_NL against 102.4 GB of bf16). + VT_CHECK(IsFloat(table.dtype) || IsBlockQuant(table.dtype), + "embedding: table must be float or block-quantized"); + VT_CHECK(IsOutFloat(out.dtype), "embedding: f32/bf16 out"); + // `ggml_row_size` asserts a row is a whole number of blocks; a ragged K has no + // row stride at all, so it refuses here rather than mis-striding every row + // after the first. (This is also the reason the shipped table is IQ4_NL: + // its row is 160, and no 256-element K-quant can encode it.) + if (IsBlockQuant(table.dtype)) { + VT_CHECK(table.shape[1] % BlockElems(table.dtype) == 0, + "embedding: block table K must be a whole number of blocks"); + } + // A block table's strides are logical (elements), exactly as for a + // `kMatmulBTQuant` weight: they describe [V, K] row-major, and the kernel + // converts to bytes through RowSizeBytes. VT_CHECK(table.IsContiguous() && ids.IsContiguous() && out.IsContiguous(), "embedding: contiguous required"); VT_CHECK(table.device == out.device && ids.device == table.device && table.device == q.device, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 27537f2fa..c5fbfd5c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -781,6 +781,12 @@ vllm_cpp_add_test(test_laguna_scaffold vllm/models/test_laguna_scaffold.cpp) target_include_directories(test_laguna_scaffold PRIVATE ${CMAKE_SOURCE_DIR}/src) vllm_cpp_add_test(test_laguna_nvfp4_loader vllm/models/test_laguna_nvfp4_loader.cpp) target_include_directories(test_laguna_nvfp4_loader PRIVATE ${CMAKE_SOURCE_DIR}/src) +# The `laguna` GGUF loader itself, which had no test at all — which is why the +# W6a gather-residency change (#1989) could break it invisibly. Needs +# tests/vllm on the include path for gguf_builder.h. +vllm_cpp_add_test(test_laguna_gguf_load vllm/models/test_laguna_gguf_load.cpp) +target_include_directories(test_laguna_gguf_load PRIVATE ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/vllm) # DeepSeek-V4-Flash W3: the NEW attention primitives (DSA Lightning-Indexer # top-k selection + 512-wide MLA attention sinks + grouped output-LoRA) unit- @@ -932,6 +938,20 @@ vllm_cpp_add_test(test_muse_glimmer_gguf vllm/models/test_muse_glimmer_gguf.cpp) target_include_directories(test_muse_glimmer_gguf PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) +# MODEL-MM-QWEN4-EXP W6a: the LOAD PLAN for Qwen3.8-Flash-Next's only runnable +# artifact (`unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S`, 67.56 GiB in 3 shards). +# Every ggml type in the real 1224-tensor table resolves, the table is accounted +# in both directions against a COMMITTED header-only manifest, the `qwen4exp` +# config builder reads the shipped metadata and refuses by its own name, and the +# residency of the 51.2 G-parameter n-gram table is ASSERTED with the arithmetic +# that makes it load-bearing. No forward, no token claim, no speed axis: the +# model class this config names is not registered yet (#1987/#1988 own it, and +# .agents/specs/qwen4-exp-flash-next.md `## Owed` tracks the wiring). +vllm_cpp_add_test(test_qwen4_exp_gguf_load_plan + vllm/models/test_qwen4_exp_gguf_load_plan.cpp) +target_include_directories(test_qwen4_exp_gguf_load_plan PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) + # Kimi-Linear-48B-A3B (KimiLinearForCausalLM) W1 scaffolding gate: registry-resolve # + config parse (20 KDA + 7 NoPE-MLA schedule, MLA/MoE/KDA dims, reject cases) + # checkpoint name-map faithfulness (block_sparse_moe, verified vs the HF index) + @@ -2172,6 +2192,14 @@ vllm_cpp_add_test(test_ops_qkv_merged_bias vt/test_ops_qkv_merged_bias.cpp) # cross-check against the GGUF reader's GgmlTraits, and the kMatmulBTQuant # skeleton's generic-composite fallback. CPU-only. vllm_cpp_add_test(test_ops_quant_traits vt/test_ops_quant_traits.cpp) +# MODEL-MM-QWEN4-EXP W6a: the DEQUANTIZING GATHER — `vt::Embedding` over a +# block-quantized table, one row decoded per gathered id (port of llama.cpp's +# `ggml_compute_forward_get_rows_q`). This is what lets the 51.2 G-parameter +# `per_layer_token_embd` n-gram table of Qwen3.8-Flash-Next stay IQ4_NL-resident +# at 28.8 GB instead of expanding to 102.4 GB of bf16. Gated BIT-EXACTLY against +# the pinned llama.cpp `b10451` decoding REAL bytes of that shipped tensor. +# CPU-only: `EmbeddingKernelCuda` still refuses a block table (#1989 `## Owed`). +vllm_cpp_add_test(test_ops_embedding_quant vt/test_ops_embedding_quant.cpp) # QUANT-GGUF-CIQ-GEMM G2/G3: the activation quantizers (quantize_row_q8_0/q8_K) # and the six tier-0 generic vec_dot kernels plus their kMatmulBTQuant wiring. # Gated against an INDEPENDENT f64 dequantize-then-dot reference, upstream's diff --git a/tests/scripts/test_agent_record.py b/tests/scripts/test_agent_record.py index 2b94772e3..ec8c0c1f0 100644 --- a/tests/scripts/test_agent_record.py +++ b/tests/scripts/test_agent_record.py @@ -640,7 +640,15 @@ def test_qwen4_exp_row_is_inside_the_model_ratchet(self) -> None: found = [row for row in rows if row.item_id == item_id] self.assertEqual(len(found), 1, item_id) self.assertEqual(found[0].path.name, "model-matrix.md", item_id) - self.assertEqual(found[0].field("state").strip().strip("`"), "READY", item_id) + # `ACTIVE` since W6a (#1989), the first wave to land product code. This was + # `READY` when the row was spec-only, and the assertion is kept pinned rather + # than loosened: it is the thing that fires if a later wave moves the row + # without moving the rollup counts with it, which is the shared-counter + # failure `.agents/specs/qwen4-exp-flash-next.md` records under `## Owed`. + # The row returns to a terminal state only when the port is DONE; if you are + # reading this because the assertion went red, count the matrix rows rather + # than editing the expectation to match. + self.assertEqual(found[0].field("state").strip().strip("`"), "ACTIVE", item_id) # One row, not two: no speculative-head sibling exists for this arch. siblings = [row for row in rows if "qwen4-exp" in row.item_id] diff --git a/tests/vllm/models/qwen4_exp_gguf_manifest.inc b/tests/vllm/models/qwen4_exp_gguf_manifest.inc new file mode 100644 index 000000000..99d29bc22 --- /dev/null +++ b/tests/vllm/models/qwen4_exp_gguf_manifest.inc @@ -0,0 +1,1264 @@ +// GENERATED by scripts/gen-qwen4-exp-gguf-manifest.py — DO NOT EDIT BY HAND. +// +// The tensor manifest of the REAL Qwen3.8-Flash-Next GGUF from +// `unsloth/Qwen3.8-Flash-Next-GGUF` @ revision 8bdc666649440e9bdc97e16f3f75782c98478ff5, +// path `UD-IQ1_S`, read live 2026-08-26 by HTTP range request over the shard +// headers. The revision is pinned rather than `main` for the usual reason and for +// a sharper one: the repo's `lastModified` was 15:54:43Z and the read was at +// 15:56Z, so the upload was still in progress and `main` is a moving target here. +// (GGUF v3, 1224 tensors across 3 shards, architecture 'qwen4exp', +// file_type 24). Names, ggml dims and type ids only — +// no weight bytes, so CI gates the loader's name map, its shape resolution and +// its ggml-type coverage against the real 67.56 GiB checkpoint with no asset. +// +// This is the ONLY published artifact of this model that fits any device this +// project owns: bf16 is ~360 GB, the official FP8 ~180 GB and NVFP4 ~128 GB, +// against ~119.6 GiB usable on GB10. See .agents/specs/qwen4-exp-flash-next.md +// and issue #1989. +#pragma once + +#include + +namespace vllm_test { + +inline constexpr int64_t kQwen4ExpGgufTensorCount = 1224; +inline constexpr int64_t kQwen4ExpGgufVersion = 3; +inline constexpr int64_t kQwen4ExpGgufShardCount = 3; +inline constexpr const char* kQwen4ExpGgufArchitecture = "qwen4exp"; + +struct Qwen4ExpGgufTensor { + const char* name; + int64_t dims[4]; // GGUF ne order (reversed vs torch), 0-padded + int32_t n_dims; + uint32_t ggml_type; + int32_t shard; // 1-based shard the tensor was read from +}; + +inline constexpr Qwen4ExpGgufTensor kQwen4ExpGgufTensors[] = { + {"blk.0.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.0.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.0.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.0.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.0.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.0.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.0.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.0.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.0.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.0.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.0.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.0.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.0.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.0.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.0.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.0.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.0.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.0.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.0.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.0.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.0.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.0.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.0.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.0.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.0.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.1.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.1.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.1.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.1.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.1.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.1.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.1.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.1.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.1.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.1.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.1.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.1.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.1.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.1.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.1.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.1.ple_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.1.ple_key.weight", {2560, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.1.ple_norm_conv.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.ple_norm_key.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.ple_norm_query.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.ple_value.weight", {2560, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.1.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.1.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.1.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.1.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.1.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.10.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.10.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.10.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.10.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.10.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.10.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.10.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.10.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.10.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.10.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.10.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.10.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.10.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.10.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.10.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.10.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.10.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.10.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.10.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.10.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.10.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.10.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.10.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.10.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.10.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.11.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.11.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.11.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.11.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.11.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.11.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.11.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.11.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.11.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.11.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.11.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.11.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.11.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.11.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.11.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.11.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.11.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.11.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.11.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.11.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.11.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.11.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.11.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.11.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.11.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.11.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.12.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.12.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.12.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.12.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.12.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.12.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.12.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.12.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.12.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.12.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.12.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.12.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.12.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.12.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.12.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.12.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.12.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.12.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.12.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.12.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.12.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.12.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.12.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.12.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.12.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.13.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.13.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.13.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.13.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.13.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.13.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.13.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.13.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.13.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.13.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.13.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.13.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.13.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.13.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.13.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.13.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.13.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.13.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.13.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.13.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.13.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.13.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.13.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.13.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.13.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.14.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.14.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.14.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.14.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.14.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.14.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.14.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.14.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.14.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.14.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.14.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.14.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.14.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.14.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.14.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.14.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.14.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.14.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.14.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.14.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.14.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.14.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.14.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.14.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.14.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.15.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.15.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.15.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.15.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.15.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.15.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.15.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.15.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.15.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.15.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.15.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.15.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.15.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.15.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.15.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.15.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.15.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.15.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.15.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.15.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.15.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.15.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.15.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.15.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.15.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.15.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.16.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.16.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.16.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.16.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.16.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.16.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.16.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.16.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.16.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.16.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.16.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.16.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.16.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.16.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.16.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.16.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.16.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.16.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.16.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.16.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.16.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.16.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.16.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.16.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.16.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.17.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.17.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.17.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.17.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.17.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.17.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.17.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.17.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.17.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.17.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.17.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.17.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.17.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.17.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.17.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.17.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.17.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.17.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.17.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.17.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.17.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.17.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.17.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.17.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.17.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.18.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.18.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.18.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.18.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.18.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.18.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.18.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.18.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.18.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.18.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.18.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.18.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.18.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.18.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.18.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.18.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.18.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.18.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.18.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.18.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.18.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.18.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.18.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.18.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.18.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.19.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.19.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.19.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.19.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.19.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.19.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.19.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.19.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.19.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.19.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.19.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.19.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.19.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.19.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.19.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.19.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.19.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.19.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.19.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.19.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.19.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.19.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.19.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.19.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.19.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.19.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.2.attn_gate.weight", {2560, 6144, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.2.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.2.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.2.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.2.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.2.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.2.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.2.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.2.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.2.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.2.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.2.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.2.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.2.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.2.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.2.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.2.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.2.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.2.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.2.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.2.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.2.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.2.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.2.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.2.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.20.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.20.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.20.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.20.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.20.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.20.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.20.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.20.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.20.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.20.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.20.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.20.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.20.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.20.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.20.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.20.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.20.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.20.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.20.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.20.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.20.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.20.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.20.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.20.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.20.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.21.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.21.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.21.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.21.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.21.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.21.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.21.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.21.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.21.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.21.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.21.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.21.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.21.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.21.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.21.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.21.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.21.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.21.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.21.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.21.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.21.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.21.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.21.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.21.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.21.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.22.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.22.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.22.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.22.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.22.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.22.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.22.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.22.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.22.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.22.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.22.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.22.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.22.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.22.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.22.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.22.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.22.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.22.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.22.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.22.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.22.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.22.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.22.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.22.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.22.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.23.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.23.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.23.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.23.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.23.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.23.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.23.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.23.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.23.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.23.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.23.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.23.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.23.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.23.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.23.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.23.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.23.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.23.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.23.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.23.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.23.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.23.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.23.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.23.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.23.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.23.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.24.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.24.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.24.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.24.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.24.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.24.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.24.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.24.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.24.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.24.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.24.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.24.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.24.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.24.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.24.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.24.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.24.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.24.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.24.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.24.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.24.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.24.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.24.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.24.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.24.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.25.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.25.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.25.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.25.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.25.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.25.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.25.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.25.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.25.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.25.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.25.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.25.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.25.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.25.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.25.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.25.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.25.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.25.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.25.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.25.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.25.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.25.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.25.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.25.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.25.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.26.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.26.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.26.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.26.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.26.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.26.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.26.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.26.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.26.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.26.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.26.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.26.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.26.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.26.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.26.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.26.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.26.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.26.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.26.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.26.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.26.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.26.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.26.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.26.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.26.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.27.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.27.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.27.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.27.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.27.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.27.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.27.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.27.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.27.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.27.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.27.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.27.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.27.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.27.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.27.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.27.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.27.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.27.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.27.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.27.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.27.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.27.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.27.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.27.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.27.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.27.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.28.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.28.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.28.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.28.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.28.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.28.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.28.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.28.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.28.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.28.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.28.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.28.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.28.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.28.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.28.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.28.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.28.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.28.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.28.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.28.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.28.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.28.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.28.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.28.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.28.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.29.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.29.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.29.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.29.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.29.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.29.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.29.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.29.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.29.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.29.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.29.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.29.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.29.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.29.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.29.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.29.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.29.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.29.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.29.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.29.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.29.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.29.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.29.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.29.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.29.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.3.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.3.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.3.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.3.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.3.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.3.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.3.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.3.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.3.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.3.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.3.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.3.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.3.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.3.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.3.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.3.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.3.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.3.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.3.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.3.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.3.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.3.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.3.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.3.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.3.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.3.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.30.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.30.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.30.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.30.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.30.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.30.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.30.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.30.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.30.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.30.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.30.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.30.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.30.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.30.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.30.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.30.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.30.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.30.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.30.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.30.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.30.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.30.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.30.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.30.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.30.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.31.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.31.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.31.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.31.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.31.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.31.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.31.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.31.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.31.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.31.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.31.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.31.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.31.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.31.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.31.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.31.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.31.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.31.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.31.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.31.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.31.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.31.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.31.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.31.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.31.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.31.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.32.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.32.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.32.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.32.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.32.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.32.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.32.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.32.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.32.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.32.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.32.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.32.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.32.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.32.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.32.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.32.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.32.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.32.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.32.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.32.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.32.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.32.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.32.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.32.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.32.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.33.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.33.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.33.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.33.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.33.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.33.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.33.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.33.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.33.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.33.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.33.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.33.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.33.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.33.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.33.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.33.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.33.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.33.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.33.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.33.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.33.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.33.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.33.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.33.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.33.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.34.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.34.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.34.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.34.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.34.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.34.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.34.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.34.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.34.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.34.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.34.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.34.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.34.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.34.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.34.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.34.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.34.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.34.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.34.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.34.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.34.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.34.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.34.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.34.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.34.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.35.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.35.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.35.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.35.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.35.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.35.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.35.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.35.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.35.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.35.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.35.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.35.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.35.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.35.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.35.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.35.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.35.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.35.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.35.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.35.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.35.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.35.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.35.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.35.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.35.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.35.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.36.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.36.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.36.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.36.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.36.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.36.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.36.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.36.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.36.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.36.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.36.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.36.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.36.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.36.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.36.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.36.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.36.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.36.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.36.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.36.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.36.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.36.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.36.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.36.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.36.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.37.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.37.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.37.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.37.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.37.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.37.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.37.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.37.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.37.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.37.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.37.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.37.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.37.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.37.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.37.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.37.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.37.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.37.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.37.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.37.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.37.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.37.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.37.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.37.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.37.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.38.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.38.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.38.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.38.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.38.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.38.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.38.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.38.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.38.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.38.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.38.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.38.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.38.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.38.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.38.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.38.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.38.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.38.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.38.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.38.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.38.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.38.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.38.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.38.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.38.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.39.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.39.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.39.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.39.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.39.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.39.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.39.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.39.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.39.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.39.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.39.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.39.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.39.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.39.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.39.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.39.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.39.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.39.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.39.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.39.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.39.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.39.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.39.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.39.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.39.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.39.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.4.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.4.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.4.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.4.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.4.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.4.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.4.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.4.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.4.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 2}, // IQ2_XXS + {"blk.4.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.4.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.4.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.4.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.4.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.4.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.4.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.4.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.4.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.4.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.4.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.4.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.4.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.4.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.4.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.4.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.40.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.40.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.40.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.40.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.40.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.40.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.40.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.40.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.40.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.40.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.40.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.40.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.40.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.40.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.40.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.40.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.40.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.40.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.40.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.40.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.40.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.40.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.40.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.40.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.40.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.41.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.41.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.41.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.41.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.41.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.41.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.41.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.41.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.41.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.41.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.41.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.41.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.41.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.41.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.41.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.41.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.41.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.41.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.41.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.41.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.41.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.41.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.41.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.41.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.41.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.42.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.42.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.42.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.42.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.42.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.42.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.42.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.42.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.42.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.42.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.42.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.42.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.42.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.42.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.42.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.42.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.42.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.42.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.42.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.42.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.42.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.42.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.42.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.42.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.42.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.43.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.43.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.43.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.43.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.43.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.43.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.43.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.43.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.43.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.43.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.43.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.43.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.43.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.43.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.43.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.43.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.43.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.43.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.43.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.43.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.43.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.43.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.43.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.43.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.43.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.43.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.44.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.44.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.44.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.44.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.44.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.44.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.44.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.44.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.44.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 3}, // IQ1_S + {"blk.44.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.44.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.44.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.44.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.44.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.44.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.44.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.44.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.44.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.44.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.44.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.44.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.44.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.44.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.44.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.44.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.45.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.45.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.45.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.45.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.45.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.45.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.45.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.45.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.45.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.45.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.45.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.45.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.45.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.45.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.45.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.45.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.45.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.45.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.45.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.45.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.45.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.45.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.45.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.45.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.45.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.46.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.46.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.46.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.46.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.46.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.46.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.46.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.46.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.46.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.46.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.46.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.46.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.46.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.46.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.46.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.46.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.46.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.46.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.46.ssm_a", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.46.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.46.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 3}, // F32 + {"blk.46.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 3}, // F32 + {"blk.46.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.46.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.46.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 3}, // Q6_K + {"blk.47.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.47.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.47.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.47.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.47.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.47.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.47.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 3}, // IQ4_NL + {"blk.47.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.47.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.47.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 3}, // F32 + {"blk.47.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.47.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.47.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 16u, 3}, // IQ2_XXS + {"blk.47.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 3}, // Q5_K + {"blk.47.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.47.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.47.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.47.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.47.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.47.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 3}, // F32 + {"blk.47.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.47.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 3}, // Q8_0 + {"blk.47.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.47.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.47.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 3}, // F32 + {"blk.47.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 3}, // BF16 + {"blk.5.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.5.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.5.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.5.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.5.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.5.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.5.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.5.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.5.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.5.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.5.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.5.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.5.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.5.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.5.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.5.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.5.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.5.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.5.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.5.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.5.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.5.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.5.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.5.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.5.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.6.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.6.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.6.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.6.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.6.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.6.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.6.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.6.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.6.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.6.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.6.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.6.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.6.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.6.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.6.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.6.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.6.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.6.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.6.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.6.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.6.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.6.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.6.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.6.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.6.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.7.attn_k.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.7.attn_k_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.7.attn_output.weight", {6144, 2560, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.7.attn_q.weight", {2560, 12288, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.7.attn_q_norm.weight", {256, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.7.attn_v.weight", {2560, 512, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.7.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.7.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.7.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.7.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.7.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.7.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.7.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.7.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.7.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.7.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.7.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.7.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.7.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.7.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.7.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.7.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.7.indexer.k_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.7.indexer.k_proj.weight", {2560, 128, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.7.indexer.q_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.7.indexer.q_proj.weight", {2560, 512, 0, 0}, 2, 30u, 2}, // BF16 + {"blk.8.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.8.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.8.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.8.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.8.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.8.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.8.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.8.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.8.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.8.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.8.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.8.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.8.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.8.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.8.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.8.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.8.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.8.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.8.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.8.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.8.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.8.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.8.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.8.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.8.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"blk.9.attn_gate.weight", {2560, 6144, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.9.attn_qkv.weight", {2560, 10240, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.9.ffn_down_exps.weight", {640, 2560, 512, 0}, 3, 20u, 2}, // IQ4_NL + {"blk.9.ffn_down_shexp.weight", {640, 2560, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.9.ffn_gate_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.9.ffn_gate_inp.weight", {2560, 512, 0, 0}, 2, 0u, 2}, // F32 + {"blk.9.ffn_gate_inp_shexp.weight", {2560, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.9.ffn_gate_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.9.ffn_up_exps.weight", {2560, 640, 512, 0}, 3, 19u, 2}, // IQ1_S + {"blk.9.ffn_up_shexp.weight", {2560, 640, 0, 0}, 2, 13u, 2}, // Q5_K + {"blk.9.hc_attn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.9.hc_attn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.9.hc_attn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.9.hc_attn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.9.hc_ffn_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.9.hc_ffn_inject.weight", {10240, 4, 0, 0}, 2, 0u, 2}, // F32 + {"blk.9.hc_ffn_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.9.hc_ffn_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"blk.9.ssm_a", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.9.ssm_alpha.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.9.ssm_beta.weight", {2560, 48, 0, 0}, 2, 0u, 2}, // F32 + {"blk.9.ssm_conv1d.weight", {4, 10240, 0, 0}, 2, 0u, 2}, // F32 + {"blk.9.ssm_dt.bias", {48, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.9.ssm_norm.weight", {128, 0, 0, 0}, 1, 0u, 2}, // F32 + {"blk.9.ssm_out.weight", {6144, 2560, 0, 0}, 2, 14u, 2}, // Q6_K + {"output.weight", {2560, 248320, 0, 0}, 2, 12u, 2}, // Q4_K + {"output_hc_down.weight", {10240, 320, 0, 0}, 2, 8u, 2}, // Q8_0 + {"output_hc_norm.weight", {10240, 0, 0, 0}, 1, 0u, 2}, // F32 + {"output_hc_up.weight", {320, 10240, 0, 0}, 2, 8u, 2}, // Q8_0 + {"per_layer_token_embd.weight", {160, 320001536, 0, 0}, 2, 20u, 2}, // IQ4_NL + {"token_embd.weight", {2560, 248320, 0, 0}, 2, 12u, 2}, // Q4_K +}; + +} // namespace vllm_test diff --git a/tests/vllm/models/test_deepseek_v4_gguf_load.cpp b/tests/vllm/models/test_deepseek_v4_gguf_load.cpp index ea4078335..67a530062 100644 --- a/tests/vllm/models/test_deepseek_v4_gguf_load.cpp +++ b/tests/vllm/models/test_deepseek_v4_gguf_load.cpp @@ -151,8 +151,14 @@ std::string Blk(int64_t l, const std::string& s) { // (not the scalar `swiglu_clamp`); `compress_ratios` is block_count+1 long (the // trailing MTP/nextn entry); and the hash `ffn_gate_tid2eid` is stored as ggml // I32 (type 26), not F32. +// `q8_embed` stores `token_embd.weight` BLOCK-QUANTIZED (Q8_0) instead of F32. +// Every published deepseek4 checkpoint does exactly that, and F32 is the ONE +// encoding the shared residency policy still routes `kExpandBf16` for a +// `kEmbeddingTable`, so the F32 default hid the W6a regression (#1989 review +// F1) rather than covering it. std::string BuildGguf(const Dims& d, const std::string& drop = "", - const std::string& extra = "", bool ds4_flavor = false) { + const std::string& extra = "", bool ds4_flavor = false, + bool q8_embed = false) { GgufModelBuilder b; b.AddKv(StrKv("general.architecture", "deepseek4")); const std::string p = "deepseek4."; @@ -205,7 +211,7 @@ std::string BuildGguf(const Dims& d, const std::string& drop = "", }; // model level. - add("token_embd.weight", false, {d.vocab, d.H}); + add("token_embd.weight", q8_embed, {d.vocab, d.H}); add("output.weight", true, {d.vocab, d.H}); add("output_norm.weight", false, {d.H}); add("output_hc_base.weight", false, {d.hc}); @@ -406,6 +412,95 @@ TEST_CASE("LoadDeepseekV4FromGguf: keep-quant blocks stay COMPRESSED") { CHECK(static_cast(we.gguf.layers[3].moe_down_exps.bytes.size()) == elems * 2); } +// ── #1989 review F1: the gather table's residency is a SHARED policy ───────── +// MODEL-MM-QWEN4-EXP W6a made `GgufTensorRole::kEmbeddingTable` keep-quant +// ELIGIBLE. THREE loaders route that role and only one was updated with it. +// DeepSeek-V4 is one of the two that were not: it gathers `token_embd` as a +// FLAT HOST f32 array (deepseek_v4.cpp:1844 indexes `hw.embed[tok * H + h]`) +// and, when the file is tied, hands the same f32 image to the final projection, +// so it cannot read a table that keeps its ggml blocks. The load THREW +// ("deepseek-v4 gguf: a gather-table tensor must not keep quant blocks: +// token_embd.weight") on every published checkpoint, and the suite stayed green +// only because the one fixture in this file stored `token_embd` as F32 — the +// single encoding that still routes `kExpandBf16` for that role. +// +// The invariant pinned here is stronger than "does not throw": for THIS model +// the gather table's CONTENT may not depend on the residency policy at all, so +// the keep-quant load's table is byte-identical to the expand-everything load's. +TEST_CASE("LoadDeepseekV4FromGguf: a BLOCK-QUANTIZED token_embd still loads") { + Dims d; + TempFile f(BuildGguf(d, /*drop=*/"", /*extra=*/"", /*ds4_flavor=*/false, + /*q8_embed=*/true)); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + // Non-vacuity, both halves: the fixture really is block-quantized, AND the + // shared policy really does elect to KEEP it. Without the second REQUIRE this + // case would pass on a policy that had quietly stopped keeping gather tables. + REQUIRE(g.Get("token_embd.weight").ggml_type == 8u); // ggml Q8_0 + REQUIRE(vllm::PeekRoute(KeepPolicy(), g.Get("token_embd.weight"), + vllm::GgufTensorRole::kEmbeddingTable) == + vllm::GgufResidency::kKeepQuant); + + const vllm::GgufLoadPolicy keep = KeepPolicy(); + const vllm::DeepseekV4Weights wk = + vllm::LoadDeepseekV4FromGguf(g, vllm::HfConfig{}, &keep); + const vllm::DeepseekV4Weights we = + vllm::LoadDeepseekV4FromGguf(g, vllm::HfConfig{}, &kExpandAll); + + CHECK(wk.gguf.embed.dtype == vt::DType::kF32); + REQUIRE(wk.gguf.embed.rank == 2); + CHECK(wk.gguf.embed.shape[0] == d.vocab); + CHECK(wk.gguf.embed.shape[1] == d.H); + REQUIRE(wk.gguf.embed.bytes.size() == we.gguf.embed.bytes.size()); + CHECK(std::memcmp(wk.gguf.embed.bytes.data(), we.gguf.embed.bytes.data(), + wk.gguf.embed.bytes.size()) == 0); + // ...and the table is not silently empty, which would satisfy the equality + // above without carrying a single weight. + REQUIRE(wk.host.embed.size() == static_cast(d.vocab * d.H)); + CHECK(std::any_of(wk.host.embed.begin(), wk.host.embed.end(), + [](float v) { return v != 0.0f; })); + // The GEMM tower is untouched by the narrowing: an untied head still keeps + // its blocks, so this is a role-scoped answer and not "keep-quant off". + CHECK(wk.gguf.lm_head.dtype == vt::DType::kQ8_0); + CHECK(wk.gguf.layers[0].wq_a.dtype == vt::DType::kQ8_0); + + // Reached from the production forward, not only from the loader. + vt::Queue q{vt::Device{vt::DeviceType::kCPU, 0}, nullptr}; + const std::vector tokens = {1, 5, 9}; + const std::vector positions = {0, 1, 2}; + const std::vector lk = + vllm::DeepseekV4ForwardGguf(wk, q, tokens, positions); + REQUIRE(lk.size() == tokens.size() * static_cast(d.vocab)); + for (float v : lk) CHECK(std::isfinite(v)); +} + +// The TIED file is the second `kEmbeddingTable` call site in this loader, and it +// is a GEMM operand rather than a gather — the review's "the two sites need +// DIFFERENT answers". Both must still resolve to the f32 expansion this model's +// forward consumes. +TEST_CASE("LoadDeepseekV4FromGguf: a TIED block-quantized token_embd still loads") { + Dims d; + TempFile f(BuildGguf(d, /*drop=*/"output.weight", /*extra=*/"", + /*ds4_flavor=*/false, /*q8_embed=*/true)); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + const vllm::GgufLoadPolicy keep = KeepPolicy(); + const vllm::DeepseekV4Weights wk = + vllm::LoadDeepseekV4FromGguf(g, vllm::HfConfig{}, &keep); + + CHECK(wk.gguf.embed.dtype == vt::DType::kF32); + CHECK(wk.gguf.lm_head.dtype == vt::DType::kF32); + REQUIRE(wk.gguf.lm_head.bytes.size() == wk.gguf.embed.bytes.size()); + CHECK(std::memcmp(wk.gguf.lm_head.bytes.data(), wk.gguf.embed.bytes.data(), + wk.gguf.embed.bytes.size()) == 0); + + vt::Queue q{vt::Device{vt::DeviceType::kCPU, 0}, nullptr}; + const std::vector tokens = {1, 5, 9}; + const std::vector positions = {0, 1, 2}; + const std::vector lk = + vllm::DeepseekV4ForwardGguf(wk, q, tokens, positions); + REQUIRE(lk.size() == tokens.size() * static_cast(d.vocab)); + for (float v : lk) CHECK(std::isfinite(v)); +} + // ── W2C: the forward CONSUMES the keep-quant blocks (no f32 tower) ──────────── // DeepseekV4ForwardGguf runs the whole composition with the big MLA/MoE/lm_head // GEMMs reading the COMPRESSED `w.gguf` blocks via vt::MatmulBT -> kMatmulBTQuant. diff --git a/tests/vllm/models/test_laguna_gguf_load.cpp b/tests/vllm/models/test_laguna_gguf_load.cpp new file mode 100644 index 000000000..8a72406c4 --- /dev/null +++ b/tests/vllm/models/test_laguna_gguf_load.cpp @@ -0,0 +1,249 @@ +// The `laguna` GGUF loader (`LoadLagunaFromGgufShards`), which had NO test. +// +// It is the third consumer of `GgufTensorRole::kEmbeddingTable`, and #1989's +// W6a made that role keep-quant ELIGIBLE. Laguna reads `token_embd` as a flat +// host f32 array (`LagunaEmbed`, laguna.cpp:1594) and, when the file is tied, +// feeds the SAME f32 image to the final projection (`MatmulNK(src, +// ReadF32(weights.embed), ...)`, laguna.cpp:1281), so it cannot read a table +// that keeps its ggml blocks — its loader threw on every published quantized +// checkpoint and nothing in the tree noticed, because nothing loaded a laguna +// GGUF at all. +// +// This file builds a TINY SYNTHETIC `laguna` GGUF with the real tensor naming +// and a BLOCK-QUANTIZED `token_embd.weight`, and asserts the load survives it. +#include + +#include +#include +#include +#include +#include +#include + +#include "gguf_builder.h" +#include "vllm/model_executor/model_loader/gguf_keep_quant.h" +#include "vllm/model_executor/model_loader/gguf_reader.h" +#include "vllm/model_executor/models/laguna.h" +#include "vt/dtype.h" + +using gguf_test::F32Kv; +using gguf_test::GgufModelBuilder; +using gguf_test::I32ArrayKv; +using gguf_test::StrKv; +using gguf_test::TempFile; +using gguf_test::U32Kv; + +namespace { + +// Tiny geometry. Every K is a multiple of 32 so Q8_0 encodes every weight, +// including the gather table. +struct Dims { + int64_t H = 64, vocab = 16, head_dim = 32, kv_heads = 1; + int64_t inter = 64, moe_inter = 32, shared_inter = 32; + int64_t E = 2, topk = 1, n_layer = 2, leading_dense = 1; + std::vector heads = {2, 4}; // per-layer Q heads: min == global +}; + +int64_t Prod(const std::vector& s) { + int64_t n = 1; + for (int64_t d : s) n *= d; + return n; +} + +std::vector GgmlDims(const std::vector& torch_shape) { + std::vector d; + for (auto it = torch_shape.rbegin(); it != torch_shape.rend(); ++it) + d.push_back(static_cast(*it)); + return d; +} + +// Smooth, nonzero-amax fill so Q8_0 carries a real scale. +float WFill(int64_t i) { return 0.05F * static_cast((i % 13) - 6); } + +std::string F32Data(int64_t n) { + std::string s; + s.reserve(static_cast(n) * 4); + for (int64_t i = 0; i < n; ++i) { + const float v = WFill(i); + uint32_t bits = 0; + std::memcpy(&bits, &v, 4); + for (int k = 0; k < 4; ++k) s.push_back(static_cast((bits >> (8 * k)) & 0xff)); + } + return s; +} + +// torch [.., out, in] (in % 32 == 0) as block_q8_0 = { f16 d; int8 qs[32] }. +std::string Q8Data(int64_t out, int64_t in) { + std::string s; + for (int64_t o = 0; o < out; ++o) { + for (int64_t b = 0; b < in / 32; ++b) { + float x[32]; + float amax = 0.0F; + for (int j = 0; j < 32; ++j) { + x[j] = WFill(o * in + b * 32 + j); + amax = std::max(amax, std::fabs(x[j])); + } + const float d = amax / 127.0F; + const uint16_t dh = vt::F32ToF16(d); + s.push_back(static_cast(dh & 0xff)); + s.push_back(static_cast((dh >> 8) & 0xff)); + for (int j = 0; j < 32; ++j) { + int q = d > 0.0F ? static_cast(std::lround(x[j] / d)) : 0; + q = std::max(-127, std::min(127, q)); + s.push_back(static_cast(static_cast(q))); + } + } + } + return s; +} + +std::string Blk(int64_t l, const std::string& s) { + return "blk." + std::to_string(l) + "." + s; +} + +// `q8_embed` stores `token_embd.weight` BLOCK-QUANTIZED, which every published +// laguna checkpoint does; `tied` omits `output.weight`. +std::string BuildGguf(const Dims& d, bool q8_embed, bool tied = false) { + GgufModelBuilder b; + const std::string p = "laguna."; + b.AddKv(StrKv("general.architecture", "laguna")); + b.AddKv(U32Kv(p + "embedding_length", d.H)); + b.AddKv(U32Kv(p + "block_count", d.n_layer)); + b.AddKv(U32Kv(p + "attention.head_count_kv", d.kv_heads)); + b.AddKv(U32Kv(p + "attention.key_length", d.head_dim)); + b.AddKv(U32Kv(p + "feed_forward_length", d.inter)); + b.AddKv(I32ArrayKv(p + "attention.head_count", d.heads)); + b.AddKv(U32Kv(p + "expert_count", d.E)); + b.AddKv(U32Kv(p + "expert_used_count", d.topk)); + b.AddKv(U32Kv(p + "expert_feed_forward_length", d.moe_inter)); + b.AddKv(U32Kv(p + "expert_shared_feed_forward_length", d.shared_inter)); + b.AddKv(U32Kv(p + "leading_dense_block_count", d.leading_dense)); + b.AddKv(U32Kv(p + "rope.dimension_count", d.head_dim)); + b.AddKv(F32Kv(p + "attention.layer_norm_rms_epsilon", 1e-6F)); + + auto add = [&](const std::string& name, bool q8, + const std::vector& shape) { + if (q8) { + const int64_t out = shape.size() == 3 ? shape[0] * shape[1] : shape[0]; + b.AddTensor(name, GgmlDims(shape), /*Q8_0=*/8, Q8Data(out, shape.back())); + } else { + b.AddTensor(name, GgmlDims(shape), /*F32=*/0, F32Data(Prod(shape))); + } + }; + + add("token_embd.weight", q8_embed, {d.vocab, d.H}); + add("output_norm.weight", false, {d.H}); + if (!tied) add("output.weight", true, {d.vocab, d.H}); + + for (int64_t l = 0; l < d.n_layer; ++l) { + const int64_t nh = d.heads[static_cast(l)]; + add(Blk(l, "attn_norm.weight"), false, {d.H}); + add(Blk(l, "ffn_norm.weight"), false, {d.H}); + add(Blk(l, "attn_q.weight"), true, {nh * d.head_dim, d.H}); + add(Blk(l, "attn_k.weight"), true, {d.kv_heads * d.head_dim, d.H}); + add(Blk(l, "attn_v.weight"), true, {d.kv_heads * d.head_dim, d.H}); + add(Blk(l, "attn_output.weight"), true, {d.H, nh * d.head_dim}); + add(Blk(l, "attn_gate.weight"), true, {nh, d.H}); + add(Blk(l, "attn_q_norm.weight"), false, {d.head_dim}); + add(Blk(l, "attn_k_norm.weight"), false, {d.head_dim}); + if (l < d.leading_dense) { + add(Blk(l, "ffn_gate.weight"), true, {d.inter, d.H}); + add(Blk(l, "ffn_up.weight"), true, {d.inter, d.H}); + add(Blk(l, "ffn_down.weight"), true, {d.H, d.inter}); + } else { + add(Blk(l, "ffn_gate_inp.weight"), false, {d.E, d.H}); + add(Blk(l, "exp_probs_b.bias"), false, {d.E}); + add(Blk(l, "ffn_gate_exps.weight"), true, {d.E, d.moe_inter, d.H}); + add(Blk(l, "ffn_up_exps.weight"), true, {d.E, d.moe_inter, d.H}); + add(Blk(l, "ffn_down_exps.weight"), true, {d.E, d.H, d.moe_inter}); + add(Blk(l, "ffn_gate_shexp.weight"), true, {d.shared_inter, d.H}); + add(Blk(l, "ffn_up_shexp.weight"), true, {d.shared_inter, d.H}); + add(Blk(l, "ffn_down_shexp.weight"), true, {d.H, d.shared_inter}); + } + } + return b.Build(); +} + +vllm::GgufLoadPolicy KeepPolicy() { + vllm::GgufLoadPolicy pol; + pol.keep_quant = true; + return pol; +} +const vllm::GgufLoadPolicy kExpandAll; // all defaults -> dequant everything + +} // namespace + +TEST_CASE("LoadLagunaFromGguf: an F32 token_embd loads and the tower keeps quant") { + Dims d; + TempFile f(BuildGguf(d, /*q8_embed=*/false)); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + const vllm::GgufLoadPolicy keep = KeepPolicy(); + const vllm::LagunaWeights w = + vllm::LoadLagunaFromGgufShards({&g}, &keep); + + CHECK(w.has_gguf_weights); + CHECK(w.params.hidden_size == d.H); + CHECK(w.params.vocab_size == d.vocab); + CHECK(w.params.num_hidden_layers == d.n_layer); + CHECK(w.accounted_tensors == static_cast(g.Tensors().size())); + // The GEMM tower kept its blocks; the gather table and the norms did not. + CHECK(w.embed.dtype == vt::DType::kF32); + CHECK(w.norm.dtype == vt::DType::kF32); + CHECK(w.lm_head.dtype == vt::DType::kQ8_0); + REQUIRE(w.layers.size() == 2); + CHECK(w.layers[0].mlp.down_proj.dtype == vt::DType::kQ8_0); + CHECK(w.layers[1].moe.experts_down.dtype == vt::DType::kQ8_0); +} + +// ── #1989 review F1 ────────────────────────────────────────────────────────── +// The case the shared-policy change broke. Before the narrowing this threw +// "laguna gguf: a embedding_table tensor must not keep quant blocks: +// token_embd.weight" for every encoding except F32 — i.e. for every real file. +TEST_CASE("LoadLagunaFromGguf: a BLOCK-QUANTIZED token_embd still loads") { + Dims d; + TempFile f(BuildGguf(d, /*q8_embed=*/true)); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + // Non-vacuity, both halves: the fixture really is quantized, AND the shared + // policy really does elect to keep a table of that shape. + REQUIRE(g.Get("token_embd.weight").ggml_type == 8u); // ggml Q8_0 + REQUIRE(vllm::PeekRoute(KeepPolicy(), g.Get("token_embd.weight"), + vllm::GgufTensorRole::kEmbeddingTable) == + vllm::GgufResidency::kKeepQuant); + + const vllm::GgufLoadPolicy keep = KeepPolicy(); + const vllm::LagunaWeights wk = vllm::LoadLagunaFromGgufShards({&g}, &keep); + const vllm::LagunaWeights we = vllm::LoadLagunaFromGgufShards({&g}, &kExpandAll); + + // Laguna reads this table as a flat f32 array, so its residency must not + // depend on the policy: the two loads produce the SAME bytes. + CHECK(wk.embed.dtype == vt::DType::kF32); + REQUIRE(wk.embed.rank == 2); + CHECK(wk.embed.shape[0] == d.vocab); + CHECK(wk.embed.shape[1] == d.H); + REQUIRE(wk.embed.bytes.size() == we.embed.bytes.size()); + CHECK(std::memcmp(wk.embed.bytes.data(), we.embed.bytes.data(), + wk.embed.bytes.size()) == 0); + // ...and it is not silently zero, which would satisfy that equality empty. + const auto* vals = reinterpret_cast(wk.embed.bytes.data()); + const size_t n = wk.embed.bytes.size() / sizeof(float); + CHECK(std::any_of(vals, vals + n, [](float v) { return v != 0.0F; })); + // The narrowing is role-scoped, not "keep-quant off": the GEMM tower still + // keeps its blocks in the same load. + CHECK(wk.lm_head.dtype == vt::DType::kQ8_0); + CHECK(wk.layers[1].moe.experts_down.dtype == vt::DType::kQ8_0); +} + +// A TIED laguna file has no `output.weight`, and the final projection then reads +// the SAME f32 `w.embed` image (laguna.cpp:1281). The table therefore has to +// expand on this arm too. +TEST_CASE("LoadLagunaFromGguf: a TIED block-quantized token_embd still loads") { + Dims d; + TempFile f(BuildGguf(d, /*q8_embed=*/true, /*tied=*/true)); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + const vllm::GgufLoadPolicy keep = KeepPolicy(); + const vllm::LagunaWeights w = vllm::LoadLagunaFromGgufShards({&g}, &keep); + + CHECK(w.embed.dtype == vt::DType::kF32); + CHECK(w.lm_head.Empty()); // tied: the head IS `embed` + CHECK(w.params.vocab_size == d.vocab); +} diff --git a/tests/vllm/models/test_qwen4_exp_gguf_load_plan.cpp b/tests/vllm/models/test_qwen4_exp_gguf_load_plan.cpp new file mode 100644 index 000000000..932034bbf --- /dev/null +++ b/tests/vllm/models/test_qwen4_exp_gguf_load_plan.cpp @@ -0,0 +1,462 @@ +// MODEL-MM-QWEN4-EXP W6a — the LOAD PLAN for Qwen3.8-Flash-Next's only +// runnable artifact, `unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S`. +// +// A load plan is name, shape and dtype resolution. NOTHING HERE IS A TOKEN +// CLAIM and nothing here is a speed claim: no forward runs, no weight byte is +// read, and the model class this config names is not registered yet — the PLE +// and hyper-connection waves (#1987, #1988) own that, and the wiring is owed +// under `## Owed` in .agents/specs/qwen4-exp-flash-next.md. +// +// What the four groups of cases prove, and why each one exists: +// +// (1) THE SHARDS OPEN. Every ggml type id in the real 1224-tensor table +// resolves through the reader's `GgmlTraits`, and the row size the reader +// computes agrees with `vt::RowSizeBytes` element for element. Before this +// row, `GgufFile::OpenOne` died at header parse with "unknown ggml type id +// 20" on shard 2, so this is the case the whole wave turns on. +// (2) THE TENSOR TABLE IS ACCOUNTED, in both directions and against the +// COMMITTED manifest, so CI needs none of the 67.56 GiB. +// (3) THE CONFIG BUILDER reads the file's own metadata, is REACHED through the +// production architecture dispatch, and refuses by its OWN name. +// (4) THE RESIDENCY of `per_layer_token_embd` is ASSERTED rather than assumed, +// including the arithmetic that makes the decision load-bearing. +#include "vllm/model_executor/models/qwen4_exp_gguf_weights.h" + +#include + +#include +#include +#include +#include +#include + +#include "../gguf_builder.h" +#include "vllm/model_executor/model_loader/gguf_keep_quant.h" +#include "vllm/model_executor/model_loader/gguf_reader.h" +#include "vt/dtype.h" +#include "vt/quant.h" + +#include "qwen4_exp_gguf_manifest.inc" + +namespace { + +using gguf_test::F32Kv; +using gguf_test::GgufModelBuilder; +using gguf_test::StrKv; +using gguf_test::TempFile; +using gguf_test::U32Kv; + +// The stand-in `token_embd` shape; see BuildQwen4ExpMetaGguf. +constexpr uint64_t kFixtureH = 8; +constexpr uint64_t kFixtureVocab = 16; + +int64_t Numel(const vllm_test::Qwen4ExpGgufTensor& t) { + int64_t n = 1; + for (int i = 0; i < t.n_dims; ++i) n *= t.dims[i]; + return n; +} + +// The KV set the config builder needs, at the SHIPPED values. Read live from +// shard 1 of `unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S` on 2026-08-26; the +// tensor table beside it is the committed manifest. +using gguf_test::I32ArrayKv; + +std::string BuildQwen4ExpMetaGguf(bool with_token_embd = true) { + GgufModelBuilder b; + b.AddKv(StrKv("general.architecture", "qwen4exp")); + b.AddKv(U32Kv("qwen4exp.embedding_length", 2560)); + b.AddKv(U32Kv("qwen4exp.block_count", 48)); + b.AddKv(U32Kv("qwen4exp.attention.head_count", 24)); + b.AddKv(U32Kv("qwen4exp.attention.head_count_kv", 2)); + b.AddKv(U32Kv("qwen4exp.attention.key_length", 256)); + b.AddKv(U32Kv("qwen4exp.attention.value_length", 256)); + b.AddKv(U32Kv("qwen4exp.context_length", 262144)); + b.AddKv(F32Kv("qwen4exp.attention.layer_norm_rms_epsilon", 1e-6F)); + b.AddKv(F32Kv("qwen4exp.rope.freq_base", 10000000.0F)); + b.AddKv(U32Kv("qwen4exp.rope.dimension_count", 64)); + b.AddKv(I32ArrayKv("qwen4exp.rope.dimension_sections", {11, 11, 10, 0})); + b.AddKv(U32Kv("qwen4exp.expert_count", 512)); + b.AddKv(U32Kv("qwen4exp.expert_used_count", 10)); + b.AddKv(U32Kv("qwen4exp.expert_feed_forward_length", 640)); + b.AddKv(U32Kv("qwen4exp.expert_shared_feed_forward_length", 640)); + b.AddKv(U32Kv("qwen4exp.ssm.group_count", 16)); + b.AddKv(U32Kv("qwen4exp.ssm.time_step_rank", 48)); + b.AddKv(U32Kv("qwen4exp.ssm.state_size", 128)); + b.AddKv(U32Kv("qwen4exp.ssm.conv_kernel", 4)); + b.AddKv(U32Kv("qwen4exp.ssm.inner_size", 6144)); + b.AddKv(U32Kv("qwen4exp.full_attention_interval", 4)); + b.AddKv(U32Kv("qwen4exp.hyper_connection.count", 4)); + b.AddKv(U32Kv("qwen4exp.hyper_connection.low_rank", 320)); + b.AddKv(U32Kv("qwen4exp.attention.indexer.head_count", 4)); + b.AddKv(U32Kv("qwen4exp.attention.indexer.key_length", 128)); + b.AddKv(U32Kv("qwen4exp.attention.indexer.top_k", 2048)); + b.AddKv(U32Kv("qwen4exp.embedding_length_per_layer_input", 160)); + b.AddKv(U32Kv("qwen4exp.ple.ngram_size", 3)); + b.AddKv(U32Kv("qwen4exp.ple.heads_per_ngram", 8)); + b.AddKv(U32Kv("qwen4exp.ple.conv_kernel", 4)); + b.AddKv(I32ArrayKv("qwen4exp.ple.layers", {1})); + std::vector ratios; + for (int i = 0; i < 48; ++i) ratios.push_back(((i + 1) % 4) == 0 ? 4 : 0); + b.AddKv(I32ArrayKv("qwen4exp.attention.compress_ratios", ratios)); + if (with_token_embd) { + // The builder reads exactly ONE thing off this tensor — `shape[0]`, i.e. + // ne1 — for `vocab_size`, because the container has no vocab key. The + // shipped tensor is [2560, 248320] and 2.5 G elements of payload cannot go + // in a fixture, so this stand-in exercises the RULE at a small shape and + // the manifest case asserts the real ne1 the rule would read. + b.AddTensor("token_embd.weight", {kFixtureH, kFixtureVocab}, /*F32=*/0, + std::string(static_cast(kFixtureH * kFixtureVocab) * 4, + '\0')); + } + return b.Build(); +} + +} // namespace + +// --- (1) the shards OPEN ---------------------------------------------------- + +TEST_CASE("every ggml type in the shipped file resolves in the reader") { + // The blocker this row removed. Type 20 (IQ4_NL) appears 49 times — all 48 + // `ffn_down_exps` plus the n-gram table — and had no entry, so + // `GgufFile::OpenOne` threw "unknown ggml type id 20" before reaching a single + // tensor of shard 2. + std::set seen; + for (const auto& t : vllm_test::kQwen4ExpGgufTensors) seen.insert(t.ggml_type); + REQUIRE_FALSE(seen.empty()); + for (uint32_t type : seen) { + CAPTURE(type); + // Resolves at all... + const vllm::GgmlTypeTraits& g = vllm::GgmlTraits(type); + CHECK(g.block_elems > 0); + CHECK(g.block_bytes > 0); + // ...and, where it is a block encoding this tree executes, vt's INDEPENDENT + // geometry table agrees. A disagreement here mis-strides every row. + vt::DType dt = vt::DType::kF32; + if (vt::BlockDTypeFromGgmlTypeId(type, &dt)) { + CHECK(vt::BlockElems(dt) == g.block_elems); + CHECK(vt::BlockBytes(dt) == g.block_bytes); + } + } + // The set itself, longhand, so a re-quantized upload that changes an encoding + // is a visible diff rather than a silently different file. + const std::set expect = {0, 8, 12, 13, 14, 16, 19, 20, 30}; + CHECK(seen == expect); + CHECK(seen.count(20) == 1); // IQ4_NL, the one this row added +} + +TEST_CASE("every tensor's byte size is computable, and the row rule holds") { + int64_t iq4nl_tensors = 0; + for (const auto& t : vllm_test::kQwen4ExpGgufTensors) { + CAPTURE(t.name); + const vllm::GgmlTypeTraits& g = vllm::GgmlTraits(t.ggml_type); + // ggml's own size rule: the tensor is a whole number of blocks, and the + // INNER dim (ne0, the row) is what has to be block-aligned. + CHECK(t.dims[0] % g.block_elems == 0); + CHECK(Numel(t) % g.block_elems == 0); + if (t.ggml_type == 20) ++iq4nl_tensors; + } + // 48 `ffn_down_exps` + the n-gram table. `moe_intermediate_size` 640 and the + // table row 160 are both indivisible by 256, which is exactly why no K-quant + // could carry them and why llama.cpp's `IQ4_XS -> IQ4_NL` fallback fires. + CHECK(iq4nl_tensors == 49); +} + +// --- (2) the tensor table is ACCOUNTED -------------------------------------- + +TEST_CASE("the manifest is the shipped file: counts, split, and structure") { + CHECK(vllm_test::kQwen4ExpGgufTensorCount == 1224); + CHECK(vllm_test::kQwen4ExpGgufShardCount == 3); + CHECK(vllm_test::kQwen4ExpGgufVersion == 3); + CHECK(std::string(vllm_test::kQwen4ExpGgufArchitecture) == "qwen4exp"); + CHECK(std::size(vllm_test::kQwen4ExpGgufTensors) == + static_cast(vllm_test::kQwen4ExpGgufTensorCount)); + + // Shard 1 is metadata ONLY. That is not a curiosity: a reader that assumed + // every shard carries tensors would take its 0-tensor header for a truncated + // file. Nothing in the manifest may come from it. + std::map per_shard; + for (const auto& t : vllm_test::kQwen4ExpGgufTensors) per_shard[t.shard]++; + CHECK(per_shard[1] == 0); + CHECK(per_shard[2] == 595); + CHECK(per_shard[3] == 629); + CHECK(per_shard[2] + per_shard[3] == vllm_test::kQwen4ExpGgufTensorCount); + + // Names are unique across shards, and every one is either model-level or a + // `blk.N.` tensor with N inside the 48-layer trunk. + std::set names; + for (const auto& t : vllm_test::kQwen4ExpGgufTensors) { + CAPTURE(t.name); + CHECK(names.insert(t.name).second); + const std::string n = t.name; + if (n.rfind("blk.", 0) == 0) { + const size_t dot = n.find('.', 4); + REQUIRE(dot != std::string::npos); + const int layer = std::stoi(n.substr(4, dot - 4)); + CHECK(layer >= 0); + CHECK(layer < 48); + } + } +} + +TEST_CASE("the four load-bearing tensors are exactly what the row assumed") { + std::map by_name; + for (const auto& t : vllm_test::kQwen4ExpGgufTensors) by_name[t.name] = &t; + + // The n-gram table: the reason this row exists. + REQUIRE(by_name.count("per_layer_token_embd.weight") == 1); + const auto& ple = *by_name["per_layer_token_embd.weight"]; + CHECK(ple.ggml_type == 20u); // IQ4_NL + CHECK(ple.n_dims == 2); + CHECK(ple.dims[0] == 160); // ne0: the row + CHECK(ple.dims[1] == 320001536); // ne1: 16 heads x ~20 M entries, padded + + // The routed experts: down is IQ4_NL, gate/up are the sub-2-bit encodings. + const auto& down = *by_name["blk.0.ffn_down_exps.weight"]; + CHECK(down.ggml_type == 20u); + CHECK(down.n_dims == 3); + CHECK(down.dims[0] == 640); + CHECK(down.dims[1] == 2560); + CHECK(down.dims[2] == 512); + const auto& gate = *by_name["blk.0.ffn_gate_exps.weight"]; + CHECK((gate.ggml_type == 19u || gate.ggml_type == 16u)); // IQ1_S / IQ2_XXS + + // `token_embd` is Q4_K, and ne1 is what the config builder reads for + // `vocab_size` — 248320, the released config.json's value, from an + // independent source. + REQUIRE(by_name.count("token_embd.weight") == 1); + CHECK(by_name["token_embd.weight"]->dims[0] == 2560); + CHECK(by_name["token_embd.weight"]->dims[1] == 248320); + CHECK(by_name["token_embd.weight"]->ggml_type == 12u); // Q4_K + + // The QSA indexer projections are left BF16 — an UNQUANTIZED tensor in a + // 1.6-bit file, which is a deliberate recipe choice and not an oversight. + REQUIRE(by_name.count("blk.3.indexer.q_proj.weight") == 1); + CHECK(by_name["blk.3.indexer.q_proj.weight"]->ggml_type == 30u); + CHECK(by_name["blk.3.indexer.k_proj.weight"]->ggml_type == 30u); +} + +TEST_CASE("the indexer and PLE layer schedules agree with the metadata") { + // Two independent statements of the same schedule: the config builder derives + // `layer_types` from `full_attention_interval`, and the FILE places the + // indexer tensors. They must name the same layers, or the port would run a + // sparse-attention layer where the file has none. + std::set indexer_layers; + std::set ple_layers; + for (const auto& t : vllm_test::kQwen4ExpGgufTensors) { + const std::string n = t.name; + if (n.rfind("blk.", 0) != 0) continue; + const size_t dot = n.find('.', 4); + const int layer = std::stoi(n.substr(4, dot - 4)); + const std::string tail = n.substr(dot + 1); + if (tail.rfind("indexer.", 0) == 0) indexer_layers.insert(layer); + if (tail.rfind("ple_", 0) == 0) ple_layers.insert(layer); + } + std::set expect_indexer; + for (int il = 0; il < 48; ++il) { + if (((il + 1) % 4) == 0) expect_indexer.insert(il); + } + CHECK(indexer_layers == expect_indexer); + CHECK(indexer_layers.size() == 12); + // The PLE lives at ONE layer, and the file says blk.1 while the released + // config.json says `ple_layer_ids: [2]`. The disagreement is recorded rather + // than resolved here — see qwen4_exp_gguf_weights.cpp, which carries the GGUF + // value under its GGUF name for exactly this reason. + CHECK(ple_layers == std::set{1}); +} + +// --- (3) the CONFIG BUILDER ------------------------------------------------- + +TEST_CASE("Qwen4ExpHfConfigFromGguf reads the shipped metadata") { + TempFile f(BuildQwen4ExpMetaGguf()); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + REQUIRE(vllm::IsQwen4ExpGguf(g)); + const vllm::HfConfig c = vllm::Qwen4ExpHfConfigFromGguf(g); + + // The HF identity, which is NOT the GGUF architecture key. + CHECK(c.model_type == "qwen4_exp"); + REQUIRE(c.architectures.size() == 1); + CHECK(c.architectures[0] == "Qwen4ExpForConditionalGeneration"); + + // Every value below is the released `Qwen/Qwen3.8-Flash-Next` config.json's, + // read 2026-08-26 — an INDEPENDENT source from the GGUF the builder read. + CHECK(c.hidden_size == 2560); + CHECK(c.num_hidden_layers == 48); + CHECK(c.num_attention_heads == 24); + CHECK(c.num_key_value_heads == 2); + CHECK(c.head_dim == 256); + CHECK(c.max_position_embeddings == 262144); + CHECK(c.rms_norm_eps == doctest::Approx(1e-6)); + CHECK(c.rope_theta == doctest::Approx(10000000.0)); + CHECK(c.rotary_dim == 64); // partial_rotary_factor 0.25 x head_dim 256 + CHECK(c.num_experts == 512); + CHECK(c.num_experts_per_tok == 10); + CHECK(c.moe_intermediate_size == 640); + CHECK(c.shared_expert_intermediate_size == 640); + CHECK(c.linear_num_key_heads == 16); + CHECK(c.linear_num_value_heads == 48); + CHECK(c.linear_key_head_dim == 128); + CHECK(c.linear_value_head_dim == 128); + CHECK(c.linear_conv_kernel_dim == 4); + // The RULE (ne1 of `token_embd.weight`) at the fixture's shape; the real + // value the same rule reads off the shipped file is asserted on the manifest. + CHECK(c.vocab_size == static_cast(kFixtureVocab)); + // Not in the GGUF container at all; carried because the default would be a + // silent wrong activation that no shape check can see. + CHECK(c.output_gate_type == "sigmoid"); + + // The 3 x linear -> 1 x full schedule, both ends checked. + REQUIRE(static_cast(c.layer_types.size()) == c.num_hidden_layers); + CHECK(c.layer_types[0] == "linear_attention"); + CHECK(c.layer_types[2] == "linear_attention"); + CHECK(c.layer_types[3] == "full_attention"); + CHECK(c.layer_types[47] == "full_attention"); + + // Interleaved mRoPE: the GGUF's 4-slot array becomes HF's 3-slot section. + REQUIRE(c.has_rope_parameters); + CHECK(c.rope_parameters.mrope_interleaved); + REQUIRE(c.rope_parameters.mrope_section.size() == 3); + CHECK(c.rope_parameters.mrope_section[0] == 11); + CHECK(c.rope_parameters.mrope_section[1] == 11); + CHECK(c.rope_parameters.mrope_section[2] == 10); + + // The architecture-specific numbers, under the released config.json's OWN + // key names, so the model waves read one spelling. + const auto& text = c.raw.at("text_config"); + CHECK(text.at("hc_count").get() == 4); + CHECK(text.at("hc_lowrank").get() == 320); + CHECK(text.at("indexer_n_heads").get() == 4); + CHECK(text.at("indexer_head_dim").get() == 128); + CHECK(text.at("indexer_budget").get() == 2048); + CHECK(text.at("indexer_compress_ratio").get() == 4); + CHECK(text.at("ngram_size").get() == 3); + CHECK(text.at("heads_per_ngram").get() == 8); + CHECK(text.at("ple_conv_kernel_size").get() == 4); + CHECK(text.at("ple_embed_dim_per_head").get() == 160); + // Under its GGUF name, deliberately: the file says 1 where config.json says + // 2, and mapping them onto one key would bury the disagreement. + REQUIRE(text.at("gguf_ple_layers").size() == 1); + CHECK(text.at("gguf_ple_layers")[0].get() == 1); +} + +TEST_CASE("Qwen4ExpHfConfigFromGguf refuses by ITS OWN name") { + // #809's rule: a refusal names the model that owes the work. The old + // fall-through reported every unsupported architecture as "qwen3_5 gguf:", + // which sent the reader into an unrelated translation unit. + GgufModelBuilder b; + b.AddKv(StrKv("general.architecture", "qwen4exp")); + TempFile f(b.Build()); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + std::string message; + try { + (void)vllm::Qwen4ExpHfConfigFromGguf(g); + } catch (const std::exception& e) { + message = e.what(); + } + REQUIRE_FALSE(message.empty()); + CHECK(message.find("qwen4_exp gguf:") != std::string::npos); + CHECK(message.find("qwen4exp.embedding_length") != std::string::npos); + CHECK(message.find("qwen3_5 gguf:") == std::string::npos); + + // And it refuses a file that is not its architecture at all. + GgufModelBuilder other; + other.AddKv(StrKv("general.architecture", "qwen35moe")); + TempFile f2(other.Build()); + const vllm::GgufFile g2 = vllm::GgufFile::Open(f2.path()); + CHECK_FALSE(vllm::IsQwen4ExpGguf(g2)); + CHECK_THROWS(vllm::Qwen4ExpHfConfigFromGguf(g2)); +} + +TEST_CASE("a malformed schedule is a LOUD failure, not a wrong model") { + // Both cross-checks the builder makes, each mutated one value away from the + // shipped file. Neither is a shape error, so neither would be caught later: + // a wrong `ssm.inner_size` sizes every GDN buffer wrong, and a + // `compress_ratios` that disagrees with the interval puts the sparse-attention + // indexer on the wrong layers. Both still produce a model that emits text. + { + std::string bytes = BuildQwen4ExpMetaGguf(); + GgufModelBuilder b; // rebuild with the one key changed + // (rebuild rather than patch bytes: the offsets are recomputed for us) + b.AddKv(StrKv("general.architecture", "qwen4exp")); + for (const char* skip : {""}) (void)skip; + // Minimal set + the bad inner_size. + b.AddKv(U32Kv("qwen4exp.embedding_length", 2560)); + b.AddKv(U32Kv("qwen4exp.block_count", 48)); + b.AddKv(U32Kv("qwen4exp.attention.head_count", 24)); + b.AddKv(U32Kv("qwen4exp.attention.key_length", 256)); + b.AddKv(U32Kv("qwen4exp.context_length", 262144)); + b.AddKv(F32Kv("qwen4exp.attention.layer_norm_rms_epsilon", 1e-6F)); + b.AddKv(F32Kv("qwen4exp.rope.freq_base", 10000000.0F)); + b.AddKv(U32Kv("qwen4exp.rope.dimension_count", 64)); + b.AddKv(U32Kv("qwen4exp.expert_count", 512)); + b.AddKv(U32Kv("qwen4exp.expert_used_count", 10)); + b.AddKv(U32Kv("qwen4exp.expert_feed_forward_length", 640)); + b.AddKv(U32Kv("qwen4exp.ssm.group_count", 16)); + b.AddKv(U32Kv("qwen4exp.ssm.time_step_rank", 48)); + b.AddKv(U32Kv("qwen4exp.ssm.state_size", 128)); + b.AddKv(U32Kv("qwen4exp.ssm.conv_kernel", 4)); + b.AddKv(U32Kv("qwen4exp.ssm.inner_size", 6145)); // 48 * 128 == 6144 + b.AddKv(U32Kv("qwen4exp.full_attention_interval", 4)); + TempFile f(b.Build()); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + CHECK_THROWS(vllm::Qwen4ExpHfConfigFromGguf(g)); + } +} + +// --- (4) the RESIDENCY decision --------------------------------------------- + +TEST_CASE("per_layer_token_embd is kept QUANTIZED, and the arithmetic says why") { + // The decision this row exists to make, ASSERTED rather than assumed. + const int64_t rows = 320001536; + const int64_t k = 160; + const vt::DType dt = vt::DType::kIQ4_NL; + + // Eligible: the row is a whole number of blocks (160 == 5 x 32). A K-quant + // could not have encoded it at all, which is why the file uses IQ4_NL. + CHECK(k % vt::BlockElems(dt) == 0); + CHECK(k % 256 != 0); + CHECK(vllm::RouteGgufTensor(/*keep_quant=*/true, /*keep_f16=*/false, + /*nvfp4_fp4=*/false, /*cpu_ref=*/false, + vllm::GgufTensorRole::kEmbeddingTable, + /*ggml_type=*/20u, {rows, k}) == + vllm::GgufResidency::kKeepQuant); + + // The numbers that make it load-bearing rather than a preference. 51.2 G + // parameters: 28.8 GB of IQ4_NL blocks against 102.4 GB expanded to bf16, on + // a box with ~119.6 GiB usable. The expansion is not a slow load; it is the + // end of the box, and it is 86 % of one on its own. + const int64_t params = rows * k; + CHECK(params == 51200245760LL); + const size_t blocks_bytes = + static_cast(rows) * vt::RowSizeBytes(dt, k); + CHECK(blocks_bytes == 28800138240ULL); + CHECK(static_cast(params) * 2 == 102400491520ULL); + // 3.5556x, i.e. 32/9. Written as an exact integer identity so it cannot drift. + CHECK(blocks_bytes * 32 == static_cast(params) * 2 * 9); + + // The gather op is what makes the residency legal, and its admission rule is + // the ROW DECODER. Stated here so the dependency is visible from the decision. + vt::DType decoded = vt::DType::kF32; + REQUIRE(vllm::KeepQuantGatherDType(20u, &decoded)); + CHECK(decoded == dt); + CHECK(vt::cpu::BlockToFloat(decoded) != nullptr); +} + +TEST_CASE("the OTHER IQ4_NL tensors are GEMM weights and route as such") { + // `ffn_down_exps` is IQ4_NL too, and it is a stacked expert weight, not a + // gather. Its K is 640 — a whole number of 32-element blocks and NOT of 256, + // which is the same reason the table is IQ4_NL. Asserted so the two IQ4_NL + // populations in this file are not conflated. + CHECK(640 % vt::BlockElems(vt::DType::kIQ4_NL) == 0); + CHECK(640 % 256 != 0); + CHECK(vllm::RouteGgufTensor(true, false, false, false, + vllm::GgufTensorRole::kStackedExpertWeight, 20u, + {512, 2560, 640}) == + vllm::GgufResidency::kKeepQuant); + // Which requires the GEMM predicate, not the gather one: a `vec_dot` against + // Q8_0 activations. Without it these 48 tensors expand to bf16 and the box is + // gone for a second, independent reason. + vt::DType dt = vt::DType::kF32; + REQUIRE(vllm::KeepQuantDType(20u, &dt)); + CHECK(dt == vt::DType::kIQ4_NL); + CHECK(vt::cpu::QuantTraits(dt).vec_dot_type == vt::DType::kQ8_0); +} diff --git a/tests/vllm/test_gguf_dequant.cpp b/tests/vllm/test_gguf_dequant.cpp index 6dc0020b0..d811867f5 100644 --- a/tests/vllm/test_gguf_dequant.cpp +++ b/tests/vllm/test_gguf_dequant.cpp @@ -493,6 +493,32 @@ TEST_CASE("DequantGgufRowToF32 IQ1_XXXS row matches the pinned FORK oracle") { std::size(vllm_test::kIq1xxxsGoldenBits)); } +// --- IQ4_NL (20) / Q5_0 (6): the two 32-element encodings added for +// `qwen4exp` (Qwen3.8-Flash-Next). IQ4_NL is what the shipped UD-IQ1_S uses for +// all 48 `ffn_down_exps` AND for the 20M-entry `per_layer_token_embd` n-gram +// table, because `moe_intermediate_size` 640 and the table row 160 are not +// multiples of 256 and no K-quant can encode them. Q5_0 is the sibling landing +// spot of llama.cpp's own `Q4_K -> Q5_0` row-size fallback. +// +// Gated against ORACLE-produced goldens, not against "does not throw" and not +// against a hand-transcribed expectation: IQ4_NL's 16-entry codebook and Q5_0's +// split `qh` bit plane are both fields a transcription can get wrong while +// every self-consistent check in this tree stays green. +// Provenance in tests/vt/iq4nl_q5_0_golden_vectors.h. +#include "../vt/iq4nl_q5_0_golden_vectors.h" + +TEST_CASE("DequantGgufRowToF32 IQ4_NL row matches the pinned oracle") { + CheckGgufDequantAgainstOracle(20, vllm_test::kIq4nlGoldenBlocks, + vllm_test::kIq4nlGoldenBits, + std::size(vllm_test::kIq4nlGoldenBits)); +} + +TEST_CASE("DequantGgufRowToF32 Q5_0 row matches the pinned oracle") { + CheckGgufDequantAgainstOracle(6, vllm_test::kQ50GoldenBlocks, + vllm_test::kQ50GoldenBits, + std::size(vllm_test::kQ50GoldenBits)); +} + TEST_CASE("DequantGgufRowToF32 IQ3_XXS row matches the pinned oracle") { // Same omission, found by the same review, in the same shared branch, so it // is fixed in the same flow rather than filed and deferred (issue #1023). diff --git a/tests/vllm/test_gguf_keep_quant.cpp b/tests/vllm/test_gguf_keep_quant.cpp index ffa55534d..4888d3817 100644 --- a/tests/vllm/test_gguf_keep_quant.cpp +++ b/tests/vllm/test_gguf_keep_quant.cpp @@ -60,9 +60,10 @@ using vllm::RouteGgufTensor; namespace { // ggml type ids (ggml/include/ggml.h:390-432). -constexpr uint32_t kF32 = 0, kF16 = 1, kQ4_0 = 2, kQ8_0 = 8, kQ2_K = 10, - kQ3_K = 11, kQ4_K = 12, kQ5_K = 13, kQ6_K = 14, kQ8_K = 15, - kIQ2_S = 22, kIQ4_XS = 23, kBF16 = 30, kMXFP4 = 39; +constexpr uint32_t kF32 = 0, kF16 = 1, kQ4_0 = 2, kQ5_0 = 6, kQ8_0 = 8, + kQ2_K = 10, kQ3_K = 11, kQ4_K = 12, kQ5_K = 13, kQ6_K = 14, + kQ8_K = 15, kIQ4_NL = 20, kIQ2_S = 22, kIQ4_XS = 23, + kBF16 = 30, kMXFP4 = 39; // Every executable weight encoding, with a K that is a whole number of blocks. struct Encoding { @@ -419,9 +420,12 @@ TEST_CASE("routing table is TOTAL: every role x every encoding is explicit") { GgufTensorRole::kTransformedWeight, GgufTensorRole::kEmbeddingTable, GgufTensorRole::kConvWeight, GgufTensorRole::kVector, }; - const uint32_t all_types[] = {kF32, kF16, kBF16, kQ4_0, kQ8_0, kQ3_K, - kQ4_K, kQ5_K, kQ6_K, kQ8_K, kIQ2_S, kIQ4_XS, - kMXFP4}; + // Q5_0 (6) and IQ4_NL (20) join the list with the encodings MODEL-MM-QWEN4-EXP + // W6a added (#1989 review F8): a case that calls itself TOTAL and omits the + // two newest encodings is total over yesterday's surface. + const uint32_t all_types[] = {kF32, kF16, kBF16, kQ4_0, kQ5_0, + kQ8_0, kQ3_K, kQ4_K, kQ5_K, kQ6_K, + kQ8_K, kIQ4_NL, kIQ2_S, kIQ4_XS, kMXFP4}; int kept = 0; int expanded = 0; @@ -443,8 +447,9 @@ TEST_CASE("routing table is TOTAL: every role x every encoding is explicit") { // narrower than the loader's CPU-derived list — ROCm implements exactly // {Q8_0, Q4_K, Q5_K, Q6_K}; the rest keep expand_bf16 there. const bool cpu_capable = - type == kQ4_0 || type == kQ8_0 || type == kQ3_K || type == kQ4_K || - type == kQ5_K || type == kQ6_K || type == kIQ2_S || type == kMXFP4; + type == kQ4_0 || type == kQ5_0 || type == kQ8_0 || type == kQ3_K || + type == kQ4_K || type == kQ5_K || type == kQ6_K || type == kIQ2_S || + type == kMXFP4 || type == kIQ4_NL; const bool rocm = vllm::platforms::CurrentPlatform().device_type() == vt::DeviceType::kROCM; @@ -452,8 +457,21 @@ TEST_CASE("routing table is TOTAL: every role x every encoding is explicit") { !rocm || type == kQ8_0 || type == kQ4_K || type == kQ5_K || type == kQ6_K; const bool block_capable = cpu_capable && device_capable; - const int64_t blk = - (type == kQ4_0 || type == kQ8_0 || type == kMXFP4) ? 32 : 256; + const int64_t blk = (type == kQ4_0 || type == kQ5_0 || type == kQ8_0 || + type == kMXFP4 || type == kIQ4_NL) + ? 32 + : 256; + // MODEL-MM-QWEN4-EXP W6a: the GATHER role is now keep-capable too, and + // it asks a DIFFERENT question. Its admission is the row decoder, so + // IQ4_XS — tabulated by the reader, decodable by nobody in this tree — + // stays expanded while everything with a `to_float` keeps, INCLUDING + // encodings the GEMM arm rejects for want of a `vec_dot`. On CUDA the + // whole gather arm is off, because `EmbeddingKernelCuda` cannot decode + // blocks; a kept table there would throw at the first forward. + const bool gather_cpu_capable = cpu_capable || type == kQ8_K; + const bool gather_device_capable = + vllm::platforms::CurrentPlatform().device_type() == + vt::DeviceType::kCPU; bool expect_keep = false; if (block_capable) { if (role == GgufTensorRole::kMatmulWeight && shape.size() == 2) { @@ -463,6 +481,10 @@ TEST_CASE("routing table is TOTAL: every role x every encoding is explicit") { expect_keep = shape[2] % blk == 0; } } + if (gather_cpu_capable && gather_device_capable && + role == GgufTensorRole::kEmbeddingTable && shape.size() == 2) { + expect_keep = shape[1] % blk == 0; + } const GgufResidency expected = expect_keep ? GgufResidency::kKeepQuant : GgufResidency::kExpandBf16; @@ -485,22 +507,36 @@ TEST_CASE("routing table is TOTAL: every role x every encoding is explicit") { } // Both outcomes are actually exercised (a table that never keeps anything // would pass every assertion above vacuously). The kept count is - // device-dependent (review #523): 8 block-capable encodings x 2 keep-capable - // roles where the device covers the CPU list; 4 x 2 on ROCm. - const bool rocm_host = - vllm::platforms::CurrentPlatform().device_type() == vt::DeviceType::kROCM; - CHECK(kept == (rocm_host ? 8 : 16)); - CHECK(expanded == 13 * 36 - (rocm_host ? 8 : 16)); + // device-dependent (review #523): 10 block-capable encodings x 2 keep-capable + // GEMM roles where the device covers the CPU list; 4 x 2 on ROCm (ROCm's + // kernel set is {Q8_0, Q4_K, Q5_K, Q6_K} and neither Q5_0 nor IQ4_NL is in + // it). The GATHER role adds 11 more on CPU ONLY (the 10 plus Q8_K, which has + // a decoder and no vec_dot) and nothing anywhere else, since only the CPU + // Embedding kernel decodes blocks. Written as three named terms rather than + // one number so a future change to any one of them says which one moved. + const vt::DeviceType host = vllm::platforms::CurrentPlatform().device_type(); + const int gemm_kept = host == vt::DeviceType::kROCM ? 8 : 20; + const int gather_kept = host == vt::DeviceType::kCPU ? 11 : 0; + CHECK(kept == gemm_kept + gather_kept); + CHECK(expanded == 15 * 36 - (gemm_kept + gather_kept)); } TEST_CASE("tensors that are value- or layout-rewritten NEVER keep quant") { // These are the routes that would silently CORRUPT a model: the (w-1) norm - // rewrite, ssm_a = log(-x), the V-head reorders, the embedding gather and the - // conv filter. Every encoding, both keep-quant-capable ranks. + // rewrite, ssm_a = log(-x), the V-head reorders and the conv filter. Every + // encoding, both keep-quant-capable ranks. + // + // `kEmbeddingTable` LEFT this set in MODEL-MM-QWEN4-EXP W6a. It was here for + // a reason that has since been removed rather than for a correctness reason: + // a gather is not a value rewrite, and the only thing stopping the table from + // staying compressed was the absence of a dequantizing gather. That op now + // exists (`vt::Embedding` over a block table), so the table's residency moved + // to its own case below. The other four are here on their VALUE, and no op + // can move them. for (const Encoding& e : kEncodings) { CAPTURE(e.name); for (GgufTensorRole role : - {GgufTensorRole::kTransformedWeight, GgufTensorRole::kEmbeddingTable, + {GgufTensorRole::kTransformedWeight, GgufTensorRole::kConvWeight, GgufTensorRole::kVector}) { CAPTURE(vllm::Name(role)); CHECK(RouteGgufTensor(true, false, false, false, role, e.ggml_type, {8, e.k}) == @@ -511,6 +547,68 @@ TEST_CASE("tensors that are value- or layout-rewritten NEVER keep quant") { } } +TEST_CASE("a quantized GATHER TABLE keeps its blocks, per encoding and per K") { + // The residency this row exists for. `unsloth/Qwen3.8-Flash-Next-GGUF + // UD-IQ1_S` ships `per_layer_token_embd.weight` as IQ4_NL [160, 320001536]: + // 51.2 G parameters, 28.8 GB of blocks against 102.4 GB expanded to bf16 on a + // box with ~119.6 GiB. Expanding it is not a slow load, it is the end of the + // box, so this is a memory-safety decision and not a performance one. + // + // The eligibility rule is the GATHER's, not the GEMM's: the encoding needs a + // ROW DECODER, and the row needs to be a whole number of blocks. It does NOT + // need a `vec_dot`, because nothing dots a gather table. + for (const Encoding& e : kEncodings) { + CAPTURE(e.name); + CHECK(RouteGgufTensor(true, false, false, false, + GgufTensorRole::kEmbeddingTable, e.ggml_type, + {8, e.k}) == GgufResidency::kKeepQuant); + // A RAGGED row cannot be decoded block-wise, so it expands. This is the + // same `ggml_row_size` precondition the GEMM arm obeys, and it is why the + // 160-wide table in the shipped file is IQ4_NL (32) and not a K-quant (256). + CHECK(RouteGgufTensor(true, false, false, false, + GgufTensorRole::kEmbeddingTable, e.ggml_type, + {8, e.k - 1}) == GgufResidency::kExpandBf16); + // Rank is still part of the role's contract: a table is 2-D. + CHECK(RouteGgufTensor(true, false, false, false, + GgufTensorRole::kEmbeddingTable, e.ggml_type, + {2, 8, e.k}) == GgufResidency::kExpandBf16); + // And the oracle switch still wins over everything. + CHECK(RouteGgufTensor(true, false, false, true, + GgufTensorRole::kEmbeddingTable, e.ggml_type, + {8, e.k}) == GgufResidency::kExpandBf16); + } + // IQ4_NL at the SHIPPED shape: 160 is 5 whole 32-element blocks. + CHECK(RouteGgufTensor(true, false, false, false, + GgufTensorRole::kEmbeddingTable, 20u, + {320001536, 160}) == GgufResidency::kKeepQuant); + // IQ4_XS (23) is tabulated by the READER but has no decoder in this tree, so + // it is the case that separates "the reader knows this id" from "this build + // can gather it". It must expand, or the table would be kept as bytes nothing + // can read. + CHECK(RouteGgufTensor(true, false, false, false, + GgufTensorRole::kEmbeddingTable, 23u, + {8, 256}) == GgufResidency::kExpandBf16); +} + +TEST_CASE("the gather table's admission is the DECODER, not the vec_dot") { + // The discriminating case, and the reason this is not the same predicate as + // the GEMM arm's. If the table were admitted through `KeepQuantDType` it + // would inherit `HasQuantDotKernel`, which asks for a `vec_dot` AND a + // `from_float` on the activation encoding — neither of which a gather uses. + // Every encoding below has a row decoder; that is the whole requirement. + vt::DType dt = vt::DType::kF32; + for (uint32_t type : {kQ4_0, kQ8_0, kQ3_K, kQ4_K, kQ5_K, kQ6_K, 20u}) { + CAPTURE(type); + REQUIRE(vllm::KeepQuantGatherDType(type, &dt)); + REQUIRE(vt::cpu::BlockToFloat(dt) != nullptr); + } + // Not a block encoding at all -> never a gather keep. + for (uint32_t type : {kF32, kF16, kBF16}) { + CAPTURE(type); + CHECK_FALSE(vllm::KeepQuantGatherDType(type, &dt)); + } +} + TEST_CASE("GgufLoadPolicy::FromEnv reads VT_CPU_REF and VT_GGUF_KEEP_QUANT") { ::unsetenv("VT_CPU_REF"); ::unsetenv("VT_GGUF_KEEP_QUANT"); diff --git a/tests/vllm/test_gguf_qwen36_loader.cpp b/tests/vllm/test_gguf_qwen36_loader.cpp index 9e0dd9fdb..9a49945ce 100644 --- a/tests/vllm/test_gguf_qwen36_loader.cpp +++ b/tests/vllm/test_gguf_qwen36_loader.cpp @@ -19,8 +19,12 @@ #include "gguf_builder.h" #include "vllm/model_executor/model_loader/gguf_keep_quant.h" #include "vllm/model_executor/model_loader/gguf_reader.h" +#include "vllm/model_executor/models/qwen3_5.h" #include "vllm/model_executor/models/qwen3_5_gguf_weights.h" +#include "vt/backend.h" #include "vt/dtype.h" +#include "vt/ops.h" +#include "vt/tensor.h" using gguf_test::F32Kv; using gguf_test::GgufModelBuilder; @@ -71,6 +75,37 @@ void AddF32(GgufModelBuilder& b, const std::string& name, b.AddTensor(name, ggml_dims, /*F32=*/0, F32Data(Prod(torch_shape), fill)); } +// The value element j of every Q4_0 block decodes to, given delta 1.0 and +// nibble (j % 16) + ((j / 16) * 0 ...). See Q4_0Data: element j in the LOW half +// takes nibble (j % 16), element j+16 the HIGH nibble of the same byte, so the +// two halves differ and a kernel that confused them cannot pass. +float Q4_0Value(int64_t row, int64_t j) { + const int64_t within = j % 32; + const int64_t lo = within % 16; + const int nibble = static_cast(within < 16 ? (lo + row) % 16 + : (lo + row + 3) % 16); + return static_cast(nibble - 8); +} + +// [vocab, H] of Q4_0 blocks, row-major, H a multiple of 32. +std::string Q4_0Data(int64_t vocab, int64_t H) { + std::string out; + for (int64_t r = 0; r < vocab; ++r) { + for (int64_t blk = 0; blk < H / 32; ++blk) { + const uint16_t d = vt::F32ToF16(1.0F); + out.push_back(static_cast(d & 0xFF)); + out.push_back(static_cast(d >> 8)); + for (int64_t j = 0; j < 16; ++j) { + const int64_t base = blk * 32 + j; + const int lo = static_cast(Q4_0Value(r, base) + 8.0F); + const int hi = static_cast(Q4_0Value(r, base + 16) + 8.0F); + out.push_back(static_cast((hi << 4) | lo)); + } + } + } + return out; +} + uint16_t Bf16(const vllm::OwnedTensor& t, int64_t i) { return reinterpret_cast(t.bytes.data())[i]; } @@ -99,7 +134,7 @@ void AddMoe(GgufModelBuilder& b, int64_t il, const Dims& d, float base) { AddF32(b, p + "ffn_down_shexp.weight", {d.H, d.Is}, [=](int64_t i) { return base + i; }); } -std::string BuildGguf(const Dims& d) { +std::string BuildGguf(const Dims& d, bool embed_q4_0 = false) { GgufModelBuilder b; b.AddKv(StrKv("general.architecture", "qwen35moe")); b.AddKv(U32Kv("qwen35moe.embedding_length", d.H)); @@ -125,7 +160,17 @@ std::string BuildGguf(const Dims& d) { const int64_t conv_dim = 2 * key_dim + value_dim; // Model level. token_embd raw; output_norm/output convert-transformed. - AddF32(b, "token_embd.weight", {d.vocab, d.H}, [](int64_t i) { return float(i); }); + if (embed_q4_0) { + // Q4_0 blocks: one f16 delta of 1.0 per 32-element block, and nibble n + // decoding to n - 8. Element j of a block therefore holds `Q4_0Value(j)` + // exactly, with no rounding anywhere, so a gather's output is checkable to + // the bit. H must be a whole number of 32-element blocks for this. + b.AddTensor("token_embd.weight", + {static_cast(d.H), static_cast(d.vocab)}, + /*Q4_0=*/2, Q4_0Data(d.vocab, d.H)); + } else { + AddF32(b, "token_embd.weight", {d.vocab, d.H}, [](int64_t i) { return float(i); }); + } AddF32(b, "output_norm.weight", {d.H}, [](int64_t i) { return 5.0F + i; }); // raw w = 4+i AddF32(b, "output.weight", {d.vocab, d.H}, [](int64_t i) { return float(i); }); @@ -413,6 +458,84 @@ std::string BuildSharedHeadGguf(bool tied, int64_t vocab, int64_t H) { } // namespace +TEST_CASE("a QUANTIZED token_embd stays block-resident and gathers correctly") { + // MODEL-MM-QWEN4-EXP W6a, and the REACHABILITY case for the dequantizing + // gather: the whole chain, from the file's bytes to a gathered row, through + // the production loader. `vt::Embedding`'s own unit test proves the kernel; + // this proves that a real GGUF reaches it. + // + // Until W6a this file's embedding table expanded to bf16 unconditionally, and + // for a vocab matrix that is affordable. For `qwen4exp`'s 51.2 G-parameter + // `per_layer_token_embd` it is 102.4 GB and the end of the box. + Dims d; + d.H = 64; // a whole number of 32-element Q4_0 blocks + TempFile f(BuildGguf(d, /*embed_q4_0=*/true)); + const vllm::GgufFile g = vllm::GgufFile::Open(f.path()); + const vllm::HfConfig c = vllm::HfConfigFromGguf(g); + + vllm::GgufLoadPolicy keep; + keep.keep_quant = true; + const vllm::Qwen3_5MoeWeights w = vllm::LoadQwen3_5MoeFromGguf(g, c, &keep); + + // (1) The table kept its BLOCKS. Shape stays logical [vocab, H]; the byte + // count is the ggml row size, not vocab*H*2. + REQUIRE(w.embed_tokens.rank == 2); + CHECK(w.embed_tokens.shape[0] == d.vocab); + CHECK(w.embed_tokens.shape[1] == d.H); + CHECK(w.embed_tokens.dtype == vt::DType::kQ4_0); + CHECK_FALSE(w.embed_tokens.nk); + CHECK(w.embed_tokens.bytes.size() == + static_cast(d.vocab) * vt::RowSizeBytes(vt::DType::kQ4_0, d.H)); + // The memory claim, stated as a number rather than as prose: a bf16 expansion + // is 32/9 == 3.5556x the blocks (2 bytes/elem against 18 bytes per 32). + CHECK(w.embed_tokens.bytes.size() * 32 == + static_cast(d.vocab) * static_cast(d.H) * 2 * 9); + // Neither repack may have touched it — either would permute inside a row and + // the gather reads rows in ggml order. + CHECK_FALSE(w.embed_tokens.repacked); + CHECK_FALSE(w.embed_tokens.q8_0_aligned); + + // (2) Gathering from it through the PRODUCTION op returns the file's values. + // The ids run backwards and repeat, so a kernel that walked the table in + // order cannot pass. + // + // The operand comes from `Qwen3_5EmbeddingTable`, which is the bridge the + // forward itself uses (qwen3_5.cpp, five call sites) — NOT a hand-built + // `vt::Tensor` over `w.embed_tokens.bytes`. That was #1989 review F4: the + // chain stopped one call short of the forward, so a change to the bridge + // could have broken every block-table gather with this case still green. + const std::vector ids = {7, 0, 7, 3}; + std::vector id_buf = ids; + vt::Queue bridge_q{vt::Device{vt::DeviceType::kCPU, 0}, nullptr}; + vt::Tensor table = vllm::Qwen3_5EmbeddingTable( + vt::GetBackend(vt::DeviceType::kCPU), bridge_q, w.embed_tokens, d.vocab, + d.H); + // The bridge must hand the op the BLOCKS, in place: same dtype, same shape, + // and the very bytes the loader owns. A bridge that widened or copied them + // would defeat the residency this row exists for. + CHECK(table.dtype == vt::DType::kQ4_0); + CHECK(table.shape[0] == d.vocab); + CHECK(table.shape[1] == d.H); + CHECK(table.Ptr() == w.embed_tokens.bytes.data()); + vt::Tensor tids = vt::Tensor::Contiguous( + id_buf.data(), vt::DType::kI32, vt::Device{vt::DeviceType::kCPU, 0}, + {static_cast(ids.size())}); + std::vector out(ids.size() * static_cast(d.H), 0.0F); + vt::Tensor tout = vt::Tensor::Contiguous( + out.data(), vt::DType::kF32, vt::Device{vt::DeviceType::kCPU, 0}, + {static_cast(ids.size()), d.H}); + vt::Queue q{vt::Device{vt::DeviceType::kCPU, 0}, nullptr}; + vt::Embedding(q, tout, table, tids); + for (size_t t = 0; t < ids.size(); ++t) { + for (int64_t j = 0; j < d.H; ++j) { + CAPTURE(t); + CAPTURE(j); + CHECK(out[t * static_cast(d.H) + static_cast(j)] == + Q4_0Value(ids[t], j)); + } + } +} + TEST_CASE("LoadGgufSharedEmbedAndHeadBf16: untied head comes from output.weight") { constexpr int64_t kVocab = 6; constexpr int64_t kH = 4; diff --git a/tests/vllm/test_model_loader_gguf.cpp b/tests/vllm/test_model_loader_gguf.cpp index e99aa8f91..ed0bc0066 100644 --- a/tests/vllm/test_model_loader_gguf.cpp +++ b/tests/vllm/test_model_loader_gguf.cpp @@ -126,6 +126,26 @@ TEST_CASE("A supported GGUF architecture still reaches its own builder") { CHECK(message.find("is not supported by this build") == std::string::npos); } +TEST_CASE("a qwen4exp GGUF reaches ITS OWN builder through the dispatch") { + // MODEL-MM-QWEN4-EXP W6a, and the REACHABILITY case for the new arm: the + // dispatch row is only real if a `qwen4exp` file actually lands on + // `Qwen4ExpHfConfigFromGguf`. It is proven the way the `qwen35` case above is + // — by the message. A file with the architecture key and no geometry fails on + // the FIRST MISSING KEY, and only that builder reports that key with that + // prefix, so this shows the arm was TAKEN rather than that something threw. + const std::string message = RefusalFor(GgufWithArchitecture("qwen4exp")); + REQUIRE_FALSE(message.empty()); + CHECK(message.find("qwen4_exp gguf: missing metadata key") != + std::string::npos); + CHECK(message.find("qwen4exp.embedding_length") != std::string::npos); + // Not the unsupported-architecture refusal any more... + CHECK(message.find("is not supported by this build") == std::string::npos); + // ...and emphatically not qwen3_5's, which is the #809 defect: that builder + // asserts its own three architectures by name, so routing a fourth family + // there would blame a model the user never mentioned. + CHECK(message.find("qwen3_5 gguf:") == std::string::npos); +} + TEST_CASE("FromModelDir rejects an unknown dense architecture before loading") { // The rejection must fire during architecture resolution, BEFORE any tokenizer // or weight I/O — so the arch must be one the registry does NOT know. (Note: diff --git a/tests/vt/iq4nl_q5_0_golden_vectors.h b/tests/vt/iq4nl_q5_0_golden_vectors.h new file mode 100644 index 000000000..a3f29bcd8 --- /dev/null +++ b/tests/vt/iq4nl_q5_0_golden_vectors.h @@ -0,0 +1,174 @@ +// Golden decode vectors for IQ4_NL (ggml type 20) and Q5_0 (ggml type 6), +// produced by the ORACLE ITSELF rather than by this tree. Same contract as +// `iq1_golden_vectors.h`, and it exists for the same reason: every other check +// on a new decoder in this tree routes through `vt::cpu::BlockToFloat`, which +// is the function under test, so it is independent only in the summation. +// +// Provenance of the IQ4_NL INPUTS. REAL bytes, not synthetic ones, because a +// synthetic block cannot catch a misreading of the FILE. They are the first +// 180 bytes of `per_layer_token_embd.weight` in +// `unsloth/Qwen3.8-Flash-Next-GGUF` @ revision 8bdc666649440e9bdc97e16f3f75782c98478ff5, +// path `UD-IQ1_S/Qwen3.8-Flash-Next-UD-IQ1_S-00002-of-00003.gguf`, read live +// 2026-08-26 with an HTTP range request rather than by downloading the 25 GB +// shard. The revision is pinned because that repo was still being uploaded when +// these bytes were read. That tensor is IQ4_NL `[160, 320001536]`, so 180 bytes is exactly TEN +// 32-element blocks == TWO WHOLE GATHER ROWS of the n-gram table this row +// exists to make loadable. The absolute file offset was computed from the +// shard's own header: data section starts at 37696 (header ends 37683, aligned +// up to `general.alignment` = 32) and the tensor's recorded offset is +// 364584960, so the bytes begin at 364622656. +// +// Provenance of the Q5_0 INPUTS. SYNTHETIC, and deliberately so: NO tensor in +// any published Qwen3.8-Flash-Next GGUF is Q5_0. It is carried because +// llama.cpp's `tensor_type_fallback` maps `Q4_K -> Q5_0` for a row that is not +// a multiple of 256, which every `qwen4exp` expert row (640) and table row +// (160) is, so a `-Q4_K_M` build of this model lands on it. The bytes come +// from a fixed LCG (seed 0x9E3779B9, `x = x*1664525 + 1013904223`, high byte) +// with an f16-exact delta per block, which is reproducible anywhere and covers +// both halves of the `qh` bit plane — the field a naive port gets wrong, +// because the two halves read it through DIFFERENT shift expressions. +// +// Provenance of the EXPECTED VALUES. Both blobs were decoded by the pinned +// oracle's own `dequantize_row_iq4_nl` / `dequantize_row_q5_0`, compiled from +// ggml-org/llama.cpp @ `b10451` == 10bf611e533d81f739128304991c5e133c6aebd8 +// (.agents/oracles/llama-cpp.md), and the f32 results are stored here as raw +// bit patterns. +// +// The source tree was NOT the developer's working checkout, which carries +// local modifications: it was extracted with `git archive b10451 ggml`, so the +// bytes compiled are the tag's own. Reproduce with: +// +// git -C archive b10451 ggml | tar -x -C $W +// gcc -O2 -DGGML_VERSION='"b10451"' -DGGML_COMMIT='"10bf611e5"' +// -I $W/ggml/include -I $W/ggml/src -I $W/ggml/src/ggml-cpu +// -o harness harness.c stubs.c $W/ggml/src/ggml-quants.c +// $W/ggml/src/ggml.c -lm +// +// (one command; the line breaks above are for width only) +// +// where `harness.c` calls the two `dequantize_row_*` entry points on the bytes +// below and `stubs.c` supplies the five backend symbols `ggml.c` references and +// this dequant-only harness never calls (they abort if reached). +// +// The comparison is BIT-EXACT, not approximate. Both decoders are the same f32 +// expression on both sides, so any difference at all means a decode parameter +// diverged rather than that rounding moved. +#pragma once + +#include + +namespace vllm_test { + +inline constexpr uint8_t kIq4nlGoldenBlocks[180] = { + 0x51, 0x8C, 0x6B, 0x84, 0x6D, 0x84, 0x9A, 0xB6, 0x6A, 0xE6, 0x37, 0x57, + 0x41, 0x0D, 0xBA, 0xF3, 0x12, 0x07, 0x73, 0x0C, 0x43, 0x86, 0x6D, 0x87, + 0x5E, 0xB6, 0x06, 0x66, 0xBC, 0xC9, 0xCB, 0x0D, 0x48, 0x96, 0x1E, 0x56, + 0x51, 0x0C, 0xFA, 0x8E, 0xCF, 0x54, 0x09, 0xCE, 0x78, 0x9C, 0xAC, 0x39, + 0x11, 0xCC, 0x5A, 0x54, 0x26, 0x70, 0x5E, 0x8D, 0x5A, 0xBF, 0x60, 0x4A, + 0x56, 0x5E, 0xCA, 0xA4, 0xA5, 0x65, 0xB9, 0xBC, 0x79, 0x8B, 0x9A, 0x86, + 0x0D, 0x8C, 0x8E, 0x79, 0xC7, 0x83, 0x4E, 0x13, 0x4B, 0x61, 0x14, 0x3C, + 0x03, 0x68, 0xD8, 0x9D, 0xEF, 0x7A, 0x17, 0x08, 0xAA, 0x1C, 0x9A, 0x6C, + 0xC2, 0x9C, 0x3C, 0x19, 0x46, 0x47, 0x7D, 0xAB, 0x49, 0x2C, 0x0A, 0x46, + 0xE1, 0x09, 0xFB, 0x77, 0x93, 0xCC, 0x60, 0xA4, 0xC9, 0x98, 0x47, 0xBB, + 0xA9, 0x75, 0x92, 0x96, 0x71, 0xD8, 0xD8, 0x0B, 0x3C, 0x85, 0x88, 0xD6, + 0x6A, 0x87, 0x67, 0x56, 0xDD, 0xC5, 0x97, 0x5E, 0x99, 0x47, 0x53, 0x02, + 0xE3, 0x89, 0x38, 0x97, 0x9C, 0x64, 0xD8, 0xCE, 0x29, 0x7B, 0xA8, 0x7D, + 0x4C, 0x4F, 0x00, 0xBC, 0xC9, 0xA4, 0x3D, 0x89, 0x7B, 0x9B, 0xFD, 0x93, + 0x84, 0x49, 0x95, 0xB6, 0x62, 0x74, 0x07, 0xB5, 0x63, 0x5D, 0x46, 0xD4, + +}; + +inline constexpr uint32_t kIq4nlGoldenBits[320] = { + 0xBC240600u, 0x3C538100u, 0xBC94EA80u, 0x3C538100u, 0xBBD7D200u, 0x3BBDEC00u, + 0xBBD7D200u, 0x3BBDEC00u, 0x3B2CA800u, 0x3B2CA800u, 0x3CE07400u, 0xBC94EA80u, + 0xBBD7D200u, 0x3C8C4880u, 0x3CB32180u, 0x3B2CA800u, 0x3BBDEC00u, 0xB98A2000u, + 0x3BBDEC00u, 0xB98A2000u, 0xBB607400u, 0xBC240600u, 0x3BBDEC00u, 0xBCC01480u, + 0x3C8C4880u, 0x3C171300u, 0x3C538100u, 0x3D090BC0u, 0xBC240600u, 0xBCF3E080u, + 0x3CE07400u, 0x3D090BC0u, 0xBC909980u, 0xBBC3C400u, 0x3C997F80u, 0xBB31F800u, + 0x3CC5FD80u, 0xBBC3C400u, 0xBBC3C400u, 0xBBC3C400u, 0x3C6BCF00u, 0x3B675C00u, + 0x3C291200u, 0x3C997F80u, 0x398E6000u, 0xBBC3C400u, 0x3CC5FD80u, 0xBBC3C400u, + 0xBC5A0300u, 0x398E6000u, 0xBBC3C400u, 0x398E6000u, 0xBC1BB900u, 0x3C291200u, + 0xBD0D4340u, 0xBBC3C400u, 0x3C291200u, 0x3C6BCF00u, 0x3C6BCF00u, 0xBD0D4340u, + 0xBC5A0300u, 0x3B675C00u, 0xBCE75C00u, 0xBC1BB900u, 0x3BD7D200u, 0x3CC01480u, + 0x3CF3E080u, 0xBC538100u, 0x3B607400u, 0x3CC01480u, 0x398A2000u, 0x3C64C500u, + 0x3C64C500u, 0x3B607400u, 0xBCE07400u, 0x3C64C500u, 0x3BD7D200u, 0xBC538100u, + 0xBBBDEC00u, 0xBD090BC0u, 0x3CF3E080u, 0x398A2000u, 0x3C64C500u, 0xBC171300u, + 0xBD090BC0u, 0x3C64C500u, 0xBB2CA800u, 0x3B607400u, 0x3BD7D200u, 0xBC8C4880u, + 0xBCE07400u, 0x3C64C500u, 0xBC171300u, 0xBC171300u, 0xBCB32180u, 0xBB2CA800u, + 0xBC062E00u, 0xBD179F80u, 0x3D2A6880u, 0xBC062E00u, 0x3BEC2800u, 0xBCEED700u, + 0xBC062E00u, 0x3C837F00u, 0x3C3BDA00u, 0x3C3BDA00u, 0xBB8B8C00u, 0xBC8E3B00u, + 0xBB8B8C00u, 0xBC4BF400u, 0xBC062E00u, 0x3BEC2800u, 0x3C3BDA00u, 0xBC4BF400u, + 0x3BEC2800u, 0x3C837F00u, 0x3C3BDA00u, 0x3C3BDA00u, 0xBC8E3B00u, 0xBC062E00u, + 0xBC062E00u, 0x3BEC2800u, 0xBC4BF400u, 0xBC4BF400u, 0x3B56B000u, 0xB9ABC000u, + 0xBB8B8C00u, 0xB9ABC000u, 0xBCB44280u, 0xBB52A400u, 0x3B220800u, 0x3C83A680u, + 0xBCB44280u, 0x3C83A680u, 0xBC19EE00u, 0x3CD2A400u, 0x3C467D00u, 0xBC56B100u, + 0x3C83A680u, 0xB981A000u, 0xB981A000u, 0xBC8BC080u, 0xBCE4DE80u, 0xBBCA8A00u, + 0xB981A000u, 0x3B220800u, 0xBC56B100u, 0xB981A000u, 0x3C467D00u, 0x3CD2A400u, + 0x3C467D00u, 0x3BB23C00u, 0x3CD2A400u, 0x3C83A680u, 0x3D009CC0u, 0x3BB23C00u, + 0xBC8BC080u, 0xBB52A400u, 0xBCB44280u, 0x3B220800u, 0x3B4C7E00u, 0x3BD8C300u, + 0x3B4C7E00u, 0x3BD8C300u, 0xBC29BA80u, 0x3BD8C300u, 0x3BD8C300u, 0x3AD4AC00u, + 0xBB33F400u, 0xBAA39800u, 0x3C0D1980u, 0x3B9B6A00u, 0x3AD4AC00u, 0x3BD8C300u, + 0x3B4C7E00u, 0xBB33F400u, 0x3B4C7E00u, 0xBC54AC00u, 0x3AD4AC00u, 0xBB33F400u, + 0x3BD8C300u, 0x3AD4AC00u, 0xBC04EB80u, 0xBC54AC00u, 0xBBC86700u, 0xBBC86700u, + 0xBAA39800u, 0x3B4C7E00u, 0xBBC86700u, 0xBC29BA80u, 0xBC81DA40u, 0xBBC86700u, + 0x3BDF6600u, 0xBAEB2800u, 0xBC3F1080u, 0x3C1BCA80u, 0xBCBAA7C0u, 0xBC100880u, + 0x3B18DA00u, 0x393C2000u, 0xBAEB2800u, 0x3BDF6600u, 0x3B18DA00u, 0xBBCDC300u, + 0xBC73F980u, 0xBB815600u, 0xBC98DA00u, 0x393C2000u, 0x3CA61440u, 0xBAEB2800u, + 0x3B18DA00u, 0x3C1BCA80u, 0xBB815600u, 0x3B92F900u, 0x3C1BCA80u, 0x3B18DA00u, + 0xBC100880u, 0x3BDF6600u, 0x3B92F900u, 0xBAEB2800u, 0x3B18DA00u, 0x3B18DA00u, + 0xBAEB2800u, 0x3C4AD280u, 0x3C4FDC00u, 0xBC094400u, 0x397B0000u, 0xBBAC9000u, + 0x3BC41800u, 0xBB1CE000u, 0xBB1CE000u, 0xBBAC9000u, 0x3C874E00u, 0xBC094400u, + 0xBB1CE000u, 0x3CAE8600u, 0x3B4BF000u, 0xBB1CE000u, 0xBC7EEC00u, 0xBCA2C200u, + 0xBC7EEC00u, 0x397B0000u, 0x397B0000u, 0x3C874E00u, 0xBBAC9000u, 0x397B0000u, + 0xBBAC9000u, 0xBC094400u, 0x3C874E00u, 0x3C4FDC00u, 0x3B4BF000u, 0xBC094400u, + 0x3B4BF000u, 0xBC402C00u, 0xBC094400u, 0xBCF90A00u, 0xB93C6000u, 0x3AEB7800u, + 0xBC1BFF80u, 0x3C103980u, 0xB93C6000u, 0xBC82FAC0u, 0xBB190E00u, 0xBBDFB200u, + 0xB93C6000u, 0xBC4B1780u, 0xBC1BFF80u, 0xBCA64CC0u, 0x3CBAE740u, 0xBC1BFF80u, + 0xBB190E00u, 0x3C103980u, 0x3C3F5180u, 0xBB190E00u, 0xBB190E00u, 0x3B818200u, + 0xBC4B1780u, 0xBC1BFF80u, 0x3C744C80u, 0x3AEB7800u, 0xBB932B00u, 0x3AEB7800u, + 0x3C103980u, 0x3C103980u, 0x3CBAE740u, 0xBBDFB200u, 0xBC1BFF80u, 0xBB932B00u, + 0xBBC70E00u, 0xBBC70E00u, 0xBC34B880u, 0x3C2A3E80u, 0x3C005680u, 0xBB083200u, + 0x3BB75700u, 0x3B667C00u, 0x3C596380u, 0x3C005680u, 0x3AD18800u, 0x3BB75700u, + 0x3C2A3E80u, 0xBC34B880u, 0x3B667C00u, 0x3C005680u, 0x3AD18800u, 0xBB083200u, + 0xBC93FB40u, 0xBB083200u, 0xB927A000u, 0x3C005680u, 0xBB083200u, 0xBBC70E00u, + 0x3B667C00u, 0x3AD18800u, 0x3CA650C0u, 0xBBC70E00u, 0x3B667C00u, 0x3BB75700u, + 0x3C005680u, 0xBC34B880u, +}; + +inline constexpr uint8_t kQ50GoldenBlocks[88] = { + 0x00, 0x2C, 0x42, 0xE9, 0x3F, 0x60, 0xD5, 0x16, 0x96, 0x47, 0xE0, 0xBC, + 0x13, 0x2E, 0x87, 0xFC, 0xEE, 0x8A, 0xF6, 0xFE, 0xD9, 0x02, 0x00, 0x30, + 0x99, 0xD1, 0xB0, 0xCF, 0x41, 0x52, 0xFD, 0x51, 0xFC, 0x1A, 0x43, 0xAB, + 0x86, 0xF1, 0x15, 0x62, 0x87, 0x62, 0x4E, 0x49, 0x00, 0x32, 0x1D, 0x4A, + 0x05, 0x3C, 0x5C, 0x9F, 0x08, 0x69, 0xBC, 0xB1, 0xF9, 0x32, 0xA7, 0xAB, + 0x5B, 0xEC, 0x9E, 0x1F, 0x2A, 0xF4, 0x00, 0x34, 0xA7, 0xCE, 0xCC, 0xF0, + 0xF4, 0x08, 0x72, 0x30, 0x06, 0x54, 0x3C, 0x7D, 0xA8, 0x34, 0xB6, 0xD0, + 0x8A, 0x37, 0x7B, 0xC3, +}; + +inline constexpr uint32_t kQ50GoldenBits[128] = { + 0xBF300000u, 0x3EC00000u, 0xBF200000u, 0xBF100000u, 0xBF800000u, 0xBE800000u, + 0x3E400000u, 0xBE000000u, 0x3EE00000u, 0xBE800000u, 0xBE000000u, 0x3F200000u, + 0xBF200000u, 0x3F600000u, 0x3F100000u, 0x3E000000u, 0x3F500000u, 0x3D800000u, + 0x3F100000u, 0x3E800000u, 0x3F600000u, 0x3F300000u, 0xBF700000u, 0xBF600000u, + 0xBF000000u, 0xBD800000u, 0xBE000000u, 0xBF000000u, 0xBD800000u, 0x3F700000u, + 0x3F500000u, 0xBF800000u, 0x3E000000u, 0xBFE00000u, 0xBEC00000u, 0x3E000000u, + 0x3FC00000u, 0xBF400000u, 0xBFD00000u, 0x3FB00000u, 0x3F400000u, 0xBFF00000u, + 0xBFB00000u, 0xBFE00000u, 0x3F600000u, 0xBFE00000u, 0x3FE00000u, 0x3F900000u, + 0xBFC00000u, 0xBFB00000u, 0xBE000000u, 0xBFB00000u, 0x3FF00000u, 0x3E000000u, + 0xBFC00000u, 0x3FA00000u, 0x3F800000u, 0x3FF00000u, 0x3E000000u, 0x3F400000u, + 0xBF800000u, 0xBFA00000u, 0x3F000000u, 0x3F000000u, 0x40100000u, 0xBE400000u, + 0x3FC00000u, 0x3FD80000u, 0x40100000u, 0xC0340000u, 0xBFA80000u, 0xC0280000u, + 0xBFD80000u, 0x40040000u, 0xBF700000u, 0x40100000u, 0xBEC00000u, 0xBE400000u, + 0x3FF00000u, 0xC0100000u, 0x3F700000u, 0xBFA80000u, 0x00000000u, 0xBFF00000u, + 0xBF700000u, 0xBF700000u, 0xBE400000u, 0xC01C0000u, 0xBF900000u, 0xBF900000u, + 0x3F700000u, 0x40280000u, 0x3FD80000u, 0x3E400000u, 0xC0280000u, 0xBE400000u, + 0x3F800000u, 0x40000000u, 0x3F000000u, 0xC0800000u, 0xC0200000u, 0x3F800000u, + 0xBF800000u, 0x40500000u, 0xC0000000u, 0x3F800000u, 0x3FC00000u, 0x00000000u, + 0xBFC00000u, 0xC0100000u, 0x40300000u, 0x3F400000u, 0xBE800000u, 0xC0800000u, + 0x3FE00000u, 0x3F400000u, 0xC0800000u, 0xC0300000u, 0x3F400000u, 0x3FE00000u, + 0xBFC00000u, 0xC0500000u, 0xBFA00000u, 0xBF400000u, 0x40000000u, 0x3F400000u, + 0x3FE00000u, 0x40400000u, +}; + +} // namespace vllm_test diff --git a/tests/vt/test_ops_embedding_quant.cpp b/tests/vt/test_ops_embedding_quant.cpp new file mode 100644 index 000000000..e17f221e5 --- /dev/null +++ b/tests/vt/test_ops_embedding_quant.cpp @@ -0,0 +1,223 @@ +// The DEQUANTIZING GATHER: `vt::Embedding` over a block-quantized table. +// +// Why this op exists at all. A gather table is not a GEMM weight, so it was +// deliberately excluded from keep-quant residency and expanded to bf16 at load +// (`gguf_keep_quant.cpp`, `KeepQuantKDim` returning -1 for `kEmbeddingTable`). +// That is affordable for a vocab matrix and fatal for an n-gram table: +// `unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S` ships +// `per_layer_token_embd.weight` as IQ4_NL `[160, 320001536]`, 51.2 G parameters +// — 28.8 GB of blocks, 102.4 GB expanded to bf16 on a box with ~119.6 GiB. +// +// llama.cpp does this without difficulty and this op is a port of how: +// `ggml_compute_forward_get_rows_q` (ggml/src/ggml-cpu/ops.cpp:4850 +// @ b10451) dequantizes ONE ROW per gathered id through the type's `to_float`, +// never the table. The per-token cost is `nc` elements of decode; the resident +// table, not the decode, is the expense. +// +// WHAT GATES IT HERE, and why it is not a self-consistency check. The expected +// values are the PINNED ORACLE's own `dequantize_row_iq4_nl` output over REAL +// bytes of that shipped tensor (tests/vt/iq4nl_q5_0_golden_vectors.h), compared +// BIT-EXACTLY. Comparing the gather against `vt::cpu::BlockToFloat` would prove +// only that the gather calls the decoder it calls. +#include + +#include +#include +#include +#include + +#include "vt/dtype.h" +#include "vt/ops.h" + +#include "iq4nl_q5_0_golden_vectors.h" + +using vt::Device; +using vt::DeviceType; +using vt::DType; +using vt::Queue; +using vt::Tensor; + +namespace { + +Device Cpu() { return Device{DeviceType::kCPU, 0}; } + +float BitsToF32(uint32_t bits) { + float f = 0.0F; + std::memcpy(&f, &bits, sizeof(f)); + return f; +} + +uint32_t F32ToBits(float f) { + uint32_t bits = 0; + std::memcpy(&bits, &f, sizeof(bits)); + return bits; +} + +// Gather `ids` out of a block table and compare BIT-EXACTLY against the golden +// f32 the oracle produced for the whole table. +void CheckGatherAgainstOracle(DType dt, const uint8_t* blocks, int64_t rows, + int64_t k, const uint32_t* golden, + const std::vector& ids) { + Tensor table = Tensor::Contiguous(const_cast(blocks), dt, Cpu(), + {rows, k}); + std::vector id_buf = ids; + Tensor tids = Tensor::Contiguous(id_buf.data(), DType::kI32, Cpu(), + {static_cast(ids.size())}); + std::vector out(ids.size() * static_cast(k), 0.0F); + Tensor tout = Tensor::Contiguous(out.data(), DType::kF32, Cpu(), + {static_cast(ids.size()), k}); + Queue q{Cpu(), nullptr}; + vt::Embedding(q, tout, table, tids); + + for (size_t t = 0; t < ids.size(); ++t) { + for (int64_t j = 0; j < k; ++j) { + CAPTURE(t); + CAPTURE(j); + CHECK(F32ToBits(out[t * static_cast(k) + static_cast(j)]) == + golden[static_cast(ids[t]) * static_cast(k) + + static_cast(j)]); + } + } +} + +} // namespace + +TEST_CASE("Embedding gathers IQ4_NL rows bit-exactly against the pinned oracle") { + // 180 real bytes of `per_layer_token_embd.weight` == 10 IQ4_NL blocks == TWO + // whole 160-element rows of the shipped n-gram table. The ids repeat and run + // backwards so a kernel that ignored `id` and walked the table in order, or + // that mis-strided by ELEMENTS instead of `RowSizeBytes`, cannot pass. + CheckGatherAgainstOracle(DType::kIQ4_NL, vllm_test::kIq4nlGoldenBlocks, + /*rows=*/2, /*k=*/160, vllm_test::kIq4nlGoldenBits, + {1, 0, 1, 1, 0}); +} + +TEST_CASE("Embedding gathers Q5_0 rows bit-exactly against the pinned oracle") { + // 4 Q5_0 blocks == 88 bytes == two 64-element rows. + CheckGatherAgainstOracle(DType::kQ5_0, vllm_test::kQ50GoldenBlocks, + /*rows=*/2, /*k=*/64, vllm_test::kQ50GoldenBits, + {0, 1, 0}); +} + +TEST_CASE("Embedding gathers a block table into bf16, rounding ONCE") { + // The residency this op unlocks is not only cheaper, it is SHORTER: the + // expand-bf16 path rounds at load and the gather then widens that bf16 back + // to f32, so a value crosses bf16 twice. Gathering from blocks rounds exactly + // once, at the store. The gate is that the bf16 output equals the ORACLE f32 + // rounded once — not that it equals the old two-step value. + const int64_t k = 160; + Tensor table = Tensor::Contiguous( + const_cast(vllm_test::kIq4nlGoldenBlocks), DType::kIQ4_NL, + Cpu(), {2, k}); + std::vector ids = {1, 0}; + Tensor tids = Tensor::Contiguous(ids.data(), DType::kI32, Cpu(), {2}); + std::vector out(2 * static_cast(k), 0); + Tensor tout = + Tensor::Contiguous(out.data(), DType::kBF16, Cpu(), {2, k}); + Queue q{Cpu(), nullptr}; + vt::Embedding(q, tout, table, tids); + + for (size_t t = 0; t < ids.size(); ++t) { + for (int64_t j = 0; j < k; ++j) { + CAPTURE(t); + CAPTURE(j); + const float want = BitsToF32( + vllm_test::kIq4nlGoldenBits[static_cast(ids[t]) * + static_cast(k) + + static_cast(j)]); + CHECK(out[t * static_cast(k) + static_cast(j)] == + vt::F32ToBF16(want)); + } + } +} + +TEST_CASE("an f32-output gather from BLOCKS is NOT the bf16 round-trip") { + // The residency this row flips is memory-only ONLY while every GGUF-path + // gather writes bf16, and that is a property of today's call sites, not of + // the op (#1989 review F3). On an f32 OUTPUT the two residencies genuinely + // disagree: keeping the blocks decodes straight to f32, while expanding at + // load rounds to bf16 first and the gather then merely widens that. This + // case makes the disagreement executable, so a future f32-output gather + // cannot inherit "tokens do not move" from a claim that was never about it. + // + // Both sides are measured against the SAME pinned oracle, so the case also + // says WHICH one is right: the block gather is bit-exact, and the round-trip + // is the lossy one. + const int64_t k = 160; + Tensor table = Tensor::Contiguous( + const_cast(vllm_test::kIq4nlGoldenBlocks), DType::kIQ4_NL, + Cpu(), {2, k}); + std::vector ids = {0, 1}; + Tensor tids = Tensor::Contiguous(ids.data(), DType::kI32, Cpu(), {2}); + std::vector out(2 * static_cast(k), 0.0F); + Tensor tout = Tensor::Contiguous(out.data(), DType::kF32, Cpu(), {2, k}); + Queue q{Cpu(), nullptr}; + vt::Embedding(q, tout, table, tids); + + int differs = 0; + for (size_t i = 0; i < out.size(); ++i) { + const uint32_t oracle = vllm_test::kIq4nlGoldenBits[i]; + // The kept-blocks answer IS the oracle, bit for bit. + CHECK(F32ToBits(out[i]) == oracle); + // The expand-bf16 answer is that value rounded through bf16 and widened — + // what an f32-output gather would have read before this residency existed. + const float round_trip = vt::BF16ToF32(vt::F32ToBF16(BitsToF32(oracle))); + if (F32ToBits(round_trip) != oracle) ++differs; + } + // Not "may differ": on this real tensor it DOES, for most of the 320 values. + CHECK(differs > 0); + CAPTURE(differs); + CHECK(differs > static_cast(out.size()) / 2); +} + +TEST_CASE("Embedding REFUSES a block table whose row is not whole blocks") { + // `ggml_row_size`'s precondition. A ragged K has no row stride at all, so the + // gather must refuse rather than mis-stride every row after the first. 160 is + // 5 whole IQ4_NL blocks; 100 is not a whole number of 32-element blocks. + // + // The assertion is on the MESSAGE, and that is the whole point of this case + // (#1989 review F7). A bare CHECK_THROWS passes on ANY throw, and there is a + // second refusal one level down — `vt::RowSizeBytes` (src/vt/dtype.cpp:187) + // rejects the same ragged K from inside the kernel. Neutering the op's own + // precondition therefore left a bare CHECK_THROWS green: the case named a + // guard it did not measure. Naming the text discriminates the two. + Tensor bad = Tensor::Contiguous( + const_cast(vllm_test::kIq4nlGoldenBlocks), DType::kIQ4_NL, + Cpu(), {2, 100}); + std::vector ids = {0}; + Tensor tids = Tensor::Contiguous(ids.data(), DType::kI32, Cpu(), {1}); + std::vector out(100, 0.0F); + Tensor tout = Tensor::Contiguous(out.data(), DType::kF32, Cpu(), {1, 100}); + Queue q{Cpu(), nullptr}; + CHECK_THROWS_WITH_AS( + vt::Embedding(q, tout, bad, tids), + doctest::Contains("embedding: block table K must be a whole number of blocks"), + std::runtime_error); +} + +TEST_CASE("Embedding REFUSES an out-of-range id BEFORE decoding a row") { + // This case used to be called "REFUSES a block table with no decoder" and it + // never measured that (#1989 review F7): Q8_K HAS a `BlockToFloat`, and so + // does every other `vt::DType` block encoding (all 16 of `kBlockDTypes` are + // listed in `BlockToFloat`), so the kernel's `to_float != nullptr` guard is + // unreachable BY CONSTRUCTION today and no test can execute it. Saying that + // here is more useful than a case that claims to pin it. + // + // What IS on the block arm and IS reachable is the per-id bounds check, which + // runs before the decode so a bad id cannot read past the table. Q8_K is a + // fine table dtype for that: 2 rows of 256 elements, 292 B per block. + std::vector blocks(2 * 292, 0); + Tensor table = Tensor::Contiguous(blocks.data(), DType::kQ8_K, Cpu(), {2, 256}); + std::vector ids = {5}; // out of range for a 2-row table + Tensor tids = Tensor::Contiguous(ids.data(), DType::kI32, Cpu(), {1}); + std::vector out(256, 0.0F); + Tensor tout = Tensor::Contiguous(out.data(), DType::kF32, Cpu(), {1, 256}); + Queue q{Cpu(), nullptr}; + CHECK_THROWS_WITH_AS(vt::Embedding(q, tout, table, tids), + doctest::Contains("embedding: id out of range"), + std::runtime_error); + // An IN-RANGE id on the same table decodes without complaint, so the refusal + // above is about the id and not about the dtype. + ids[0] = 1; + CHECK_NOTHROW(vt::Embedding(q, tout, table, tids)); +} diff --git a/tests/vt/test_ops_quant_dot.cpp b/tests/vt/test_ops_quant_dot.cpp index 986ff2eb3..be93a249f 100644 --- a/tests/vt/test_ops_quant_dot.cpp +++ b/tests/vt/test_ops_quant_dot.cpp @@ -168,6 +168,20 @@ const WeightCase kWeightCases[] = { {vt::DType::kIQ1_S, 256, 50, 0, -1, -1, "iq1_s", 6e-4}, {vt::DType::kIQ1_XXXS, 256, 38, 0, -1, -1, "iq1_xxxs", 6e-4}, {vt::DType::kMXFP4, 32, 17, -1, -1, 0, "mxfp4"}, + // MODEL-MM-QWEN4-EXP W6a. `unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S` stores + // all 48 `ffn_down_exps` as IQ4_NL, so those GEMMs run `VecDotIQ4_NLQ8_0`; + // Q5_0 arrives with it because llama.cpp's own `tensor_type_fallback` + // (src/llama-quant.cpp:374-405 @ b10451) maps `Q4_K -> Q5_0` for the same + // non-multiple-of-256 rows that force `IQ4_XS -> IQ4_NL`. Both were shipped + // in #1989 with no numerical gate at all: review mutations R12 (swap + // IQ4_NL's two nibble halves) and R13 (change Q5_0's upper-half `qh` shift) + // each survived all eight suites, this one included, because these two rows + // were missing. Both dot against Q8_0, which already has a `from_float`, so + // the harness above reaches them unchanged — MXFP4 is the precedent. + // q5_0 :229-235 d@0 qh@2 (u8[4]) qs@6 (u8[16]) (22B) + // iq4_nl :447-452 d@0 qs@2 (u8[16]: 32 codebook nibbles) (18B) + {vt::DType::kQ5_0, 32, 22, 0, -1, -1, "q5_0"}, + {vt::DType::kIQ4_NL, 32, 18, 0, -1, -1, "iq4_nl"}, }; // Random raw blocks: every quant/scale payload byte is arbitrary (all legal), diff --git a/tests/vt/test_ops_quant_traits.cpp b/tests/vt/test_ops_quant_traits.cpp index 558f82374..94fae8424 100644 --- a/tests/vt/test_ops_quant_traits.cpp +++ b/tests/vt/test_ops_quant_traits.cpp @@ -56,6 +56,15 @@ const BlockCase kBlockCases[] = { {vt::DType::kQ5_K, 13, 256, 4 + 12 + 32 + 128, vt::DType::kQ8_K, "q5_K"}, {vt::DType::kQ6_K, 14, 256, 128 + 64 + 16 + 2, vt::DType::kQ8_K, "q6_K"}, {vt::DType::kQ8_K, 15, 256, 4 + 256 + 32, vt::DType::kQ8_K, "q8_K"}, + // Q5_0 and IQ4_NL, added for MODEL-MM-QWEN4-EXP W6a, are the two 32-element + // legacy-family encodings this table lacked. Both dot against Q8_0, not + // Q8_K, because their block is 32 elements wide (ggml-cpu.c:259-264 and + // :379-384 @ llama.cpp b10451). Sizes written out from ggml-common.h + // @ b10451, NOT copied from either table under test: + // q5_0 :229-235 f16 d + 4 qh + 32/2 qs = 2+4+16 = 22 + // iq4_nl :447-452 f16 d + 32/2 qs = 2+16 = 18 + {vt::DType::kQ5_0, 6, 32, 2 + 4 + 16, vt::DType::kQ8_0, "q5_0"}, + {vt::DType::kIQ4_NL, 20, 32, 2 + 16, vt::DType::kQ8_0, "iq4_nl"}, }; // Deterministic pseudo-random block bytes. Any bit pattern is a legal block for @@ -79,6 +88,9 @@ std::vector RandomBlocks(const BlockCase& c, int64_t nblocks, switch (c.dtype) { case vt::DType::kQ4_0: case vt::DType::kQ8_0: + // q5_0 and iq4_nl both open with the f16 delta, like q4_0/q8_0. + case vt::DType::kQ5_0: + case vt::DType::kIQ4_NL: put_f16(0, 0.0125F); break; case vt::DType::kQ3_K: