Skip to content

feat(deepseek-v4): default-off native CompressedSparseAttention (HCA ratio-128) export [C1, DRAFT] - #593

Merged
justinchuby merged 3 commits into
mainfrom
deckard/deepseek-v4-csa-hca-c1
Aug 25, 2026
Merged

feat(deepseek-v4): default-off native CompressedSparseAttention (HCA ratio-128) export [C1, DRAFT]#593
justinchuby merged 3 commits into
mainfrom
deckard/deepseek-v4-csa-hca-c1

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Summary

Slice C1 of the DeepSeek-V4-Flash CSA/HCA custom-kernel path. Adds a default-off, property-gated Mobius exporter feature that emits the already-merged frozen pkg.nxrt::CompressedSparseAttention v1 op (ratio-128 "Heavily Compressed Attention" subset) for property-matching layers, replacing the zero-valued shape-anchor preservation with real op dataflow and threaded compressed state IO.

This is exporter-only. No onnx-genai runner/KV changes; no PagedAttention-compatibility claim.

Design: deckard-deepseek-v4-csa-hca-cuda-slice.md (§5 property ABI, §8 tiny-test spec, §10 C1 slice).

What changed

  • mobius.models._deepseek_v4_csa (new): plan_native_csa property gate + emit_hca_attention op emission. The gate matches native-op properties (compression ratio, block size, head/latent/rope dims, dtype, carry planes), not a model-name/shape allowlist.
  • ArchitectureConfig.native_csa: bool = False — export-time opt-in (no HF equivalent; config_overrides={"native_csa": True}).
  • DeepSeekV4Attention._forward_native_hca — emits the 11-input / 3-output op with pre-rotated query/current_kv (dense sliding-window ring over the existing dense KV IO), real unrotated compressor activations, f32 casts for the f32-cache-format op inputs, and threaded past_*/present_* compressed state. The shared inverse RoPE + output projection run unchanged on Y.
  • DeepSeekV4Task — threads deterministic past_compressed_kv.{i} / past_compression_carry.{i} inputs and present_* outputs (explicit shapes, since the custom op has no Python shape inference), with a dynamic record axis and fixed carry planes so present state chains back into past inputs for ≥16 decode steps.

Fail-closed (never silent dense)

Requesting native CSA for ratio-4 CSA, an MTP layer, quantized compressor weights, or an unknown ratio raises NativeCsaExportError. Ratio-0 dense layers and feature-off legitimately return None (dense) — those are not a suppressed fallback.

Off = byte-identical

With the feature off, the exported graph is byte-identical to the existing dense correctness export (verified) and emits zero pkg.nxrt ops / no compressed IO.

Tests (shape-faithful / structural — no weight download, no ORT run)

The frozen op has no Python shape inference and no Rust EP in the Python env, so tests assert structure: exact op attrs + 11/3 IO, threaded compressed-state IO names/shapes/dtypes, decode chainability, f32 inputs under a FLOAT16 model dtype (GLM DSA precedent), no dead anchor for the HCA layer, byte-identical disabled baseline, defaults-off + opt-in, and typed rejects (ratio-4 / quantized / MTP / unknown-ratio). Existing DeepSeek-V4 tests unchanged.

Local validation (this env):

  • deepseek_v4_flash_test.py + deepseek_v4_test.py: 30 passed
  • _configs/tasks/glm_moe_dsa related tests: 168 passed
  • ruff check + ruff format --check: clean

Scope / non-goals

  • ❌ No onnx-genai runner or KV/cache changes (later slice).
  • ❌ No PagedAttention-compatibility claim.
  • ❌ ratio-4 learned-indexer/top-k selection is a follow-up slice.

Status

DRAFT — stop for independent review. Do not merge. CI is async; local tests above are the validation of record.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 59a595e5028076

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 68 68 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 66 66 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 105 105 +0.0%
gpt2 model_size_bytes 388 KB 388 KB +0.0%
gpt2 num_nodes 54 54 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 60 60 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 56 56 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 94 94 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 58 58 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 54 54 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 60 60 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 56 56 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 264 264 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 126 126 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 428 428 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 176 176 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 59a595e5028076

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 0
gemma4_text model 0
gpt2 model 0
llama model 0
llama (static-cache) model 0
mamba (ssm-text-generation) model 0
phi3 model 0
phi3 (static-cache) model 0
qwen model 0
qwen (static-cache) model 0
qwen2 model 0
qwen2 (static-cache) model 0
qwen2_moe model 0
qwen2_moe (static-cache) model 0
qwen3 model 0
qwen3 (static-cache) model 0
qwen3_5_moe (hybrid-text-generation) model 0
qwen3_5_text (hybrid-text-generation) model 0
qwen3_5_vl (hybrid-qwen-vl) decoder 0
qwen3_5_vl (hybrid-qwen-vl) embedding 0
qwen3_5_vl (hybrid-qwen-vl) vision_encoder 0
qwen3_moe model 0
qwen3_moe (static-cache) model 0
qwen3_next (hybrid-text-generation) model 0
t5 (seq2seq) decoder 0
t5 (seq2seq) encoder 0
whisper (speech-to-text) decoder 0
whisper (speech-to-text) encoder 0

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

@justinchuby

Copy link
Copy Markdown
Member Author

B1 — native CSA export extended to the full interleaved ratio-4 + ratio-128 schedule (commit 3e10955)

The C1 slice emitted the frozen pkg.nxrt::CompressedSparseAttention v1 op only for ratio-128 (HCA) and typed-rejected the 21 ratio-4 CSA layers, making a full native_csa=True export of the official checkpoint impossible. B1 extends the exporter to the whole official deepseek-ai/DeepSeek-V4-Flash@60d8d707… schedule: both ratios emit, or the export typed-rejects before serialization — never partial-native + silent dense.

Real-config verification (metadata + graph only, no weight download)

Built the real 43-layer target graph (head_dim=512, 64 heads) with native_csa=Truebuilt with no typed reject (1166.8 s, peak RSS ~1.5 GB):

  • 41 CompressedSparseAttention nodes = 21 ratio-4 (all 19-in / 6-out, fp8_e4m3_block64) + 20 ratio-128 (all 11-in / 3-out, f32) — exactly the official schedule.
  • Threaded state IO: past_/present_compressed_kv ×41, …compression_carry ×41, and the ratio-4-only past_/present_index_key ×21, …index_carry ×21, selected_indices ×21 — deterministic layer-suffixed names, dynamic record axes.
  • plan_native_csa widths on the real config are exact: ratio-4 stored_width=583, index_stored_width=68, compressor_width=1024, index_compressor_width=256, carry_slots=8; ratio-128 stored_width=512, carry_slots=128; MTP = None.

Design (clean, no back-compat shims — per user directive)

HcaLayerPlan → CsaLayerPlan (every native-op property for both ratios); plan_native_csa returns a ratio-4 plan; emit_csa_attention branches 11/3 vs 19/6 and raises on any missing learned-indexer input; the indexer's wq_b(q_lora)/weights_proj/compressor tensors — previously dead anchors — are now real dataflow (op applies index RoPE/Hadamard/FP4 internally). MTP recurrence / unknown ratio / unsupported quant / invalid fp8-fp4 packing still fail closed. Feature stays off by default; disabled export byte-identical.

Tests

35 shape-faithful/structural tests pass (no weight download / no ORT run): ratio-4 19/6 with exact attrs; interleaved [0,4,128] emits both; ratio-4 index cache (uint8 fp4 width 17) + f32 index carry + int32 selected_indices; learned indexer wiring (index_query = Reshape(MatMul)); no dead index anchor; prefill + ≥16-decode chainability; typed rejects for invalid packing dims / missing index config; ratio-4 plan contract; ratio-128 + existing DeepSeek-V4 tests migrated atomically. ruff check + ruff format --check clean.

Blocker — B2 (FP8/FP4 weight loading, @Roy-1's domain)

Real-checkpoint weight loading via the stable streaming API stream_safetensors_to_model (#576, lazy per-shard, no all-shard state_dict) typed-refuses up front:

ValueError: Checkpoint appears quantized (fp8 / scaled weights, e.g.
['layers.0.attn.wkv.scale', 'layers.0.attn.wkv.weight', ...]); the pass-through
streaming loader cannot dequantize it and would drop the weight scale.
  at _weight_loading.py:519 in stream_safetensors_to_model

Census: 34,542 of 69,187 tensors are F8_E4M3 block-fp8 (attention wkv/wq_*/wo_*, indexer.wq_b, experts). B2 is reached only after the native gate accepts both ratios, fires at layer 0 (before any CSA layer would execute), and is a typed refusal, never a silent dense fallback. I coordinate through Roy-1's stable API only and don't touch _weight_loading.py / quantization primitives.

Honest status

Structure (op wiring/attrs/IO/state carry) for the full official schedule is complete and validated. No performance claim — no ratio-4 layer has executed sparse and no ≥16-decode has run; real-checkpoint weights remain B2-blocked. Staying DRAFT for independent review; not merging, not waiting on CI (local validation done). Run artifacts on disk5 converted/deckard-csa-c2-ratio4/.

justinchuby added a commit that referenced this pull request Aug 24, 2026
…on primitives (DRAFT) (#602)

## Summary

DeepSeek-V4 real-checkpoint blocker **B2**: generic **block-FP8 /
packed-FP4 weight-loading + expert-emission primitives**, classified by
tensor *properties* (never by model name). Separate artifact from PR
#591 (native BQMoE fusion) — touches none of its files, and does not
touch `deepseek_v4.py` (Deckard/CSA #593).

### Root cause
`QuantizationConfig.from_transformers` treated `quant_method='fp8'` as
ordinary per-tensor float8 and returned `None`, so the model built bf16
`Linear` initializers. The real experts are **FP4-packed in int8**
(E2M1, block-32, UE8M0 micro-scales), so a packed `[2048,2048]` expert
was compared to its logical `[2048,4096]` initializer → confusing
`Weight shape mismatch`. Projections are **block-FP8** (E4M3 weight + 2D
UE8M0 `[128,128]` block scales).

### What this adds (`mobius.integrations._block_quant`)
- **`BlockQuantScheme`** — property parser over `quantization_config` +
top-level `expert_dtype`. Per-tensor fp8 (no `weight_block_size`) is
*not* owned.
- **`QuantizedTensorDescriptor`** — clean, breaking contract: logical
**and** packed shape, weight qtype, block geometry, scale
name/dtype/shape/layout, exact byte counts, routed/shared role.
- **`classify_tensor` / `validate_descriptor`** — distinguish
`ORDINARY`, `BLOCK_FP8`, `FP4_PACKED`, `UNSUPPORTED`. Logical-vs-packed
shape, scale pairing, and wrong/missing/orphan scales fail closed. **No
dequantization, no copy-to-float.**
- **`read_raw_tensor_bytes` / `LazyRawTensor`** — byte-exact,
header-only, bounded lazy per-shard loading (one tensor resident).
- **`stack_expert_bank` / `PackedExpertBank`** — byte-exact expert-major
bank packing (reusable lowering primitive; ragged banks are a hard
error).
- **`runtime_representation_gap` / `plan_routed_expert_bank`** — the
**emission gate**.

### ABI verdict (why it typed-rejects, no fake fusion)
onnx-genai `nxrt` CPU kernels (`block_quantized_{matmul,moe}.rs`) accept
only the **interleaved llama.cpp `block_mxfp4`** layout (QK=32, 17
bytes/block) and the `iq*` GGUF formats:
- **block-FP8** projections → no `block_fp8` BlockFormat + no
E4M3×2D-UE8M0 dequant path → **typed reject**.
- **FP4 experts** are numerically MXFP4 but stored **planar** (separate
I8 nibbles + separate E8M0 block-32 scale), while nxrt MXFP4 needs a
**single interleaved tensor** → **typed reject** (planar→interleaved
transcode is unproven; no planar-FP4 bank ABI).

`plan_routed_expert_bank` raises `BlockQuantExportError` naming the
exact gap rather than emitting an unrunnable node. **No dense fallback,
no env-flag emission.**

### `from_transformers` change (property-guarded)
Block-scaled fp8 / fp4-expert checkpoints now surface a precise typed
blocker instead of `None → shape-mismatch`. Ordinary per-tensor fp8
still returns `None`; GPTQ/AWQ/GGUF/ModelOpt paths unchanged.

### Preflight (B2 moved)
`ArchitectureConfig.from_file(<real checkpoint>)` now raises
`BlockQuantExportError` with the exact layout + ABI gap — the failure
moved from a confusing shape-mismatch to an actionable typed reject.

### Tests
Real `quantization_config` + a measured slice of the checkpoint index
metadata + tiny synthetic packed safetensors (real `I8` / `F8_E4M3` /
`F8_E8M0` dtypes): classification, logical-vs-packed validation, byte
preservation, scale pairing (missing/duplicate/orphan/wrong),
shared-vs-routed, bounded lazy load, byte-exact bank stacking, emission
typed-reject. An opt-in suite exercises the mounted checkpoint headers
directly. `ruff check` + `ruff format` clean.

### Deckard #593 handoff (stable public API)
`from mobius.integrations._block_quant import (BlockQuantScheme,
QuantizedTensorDescriptor, QuantKind, classify_tensor,
validate_descriptor, build_descriptors, pair_weight_scales,
read_raw_tensor_bytes, LazyRawTensor, stack_expert_bank,
PackedExpertBank, plan_routed_expert_bank, runtime_representation_gap,
BlockQuantError, BlockQuantValidationError, BlockQuantExportError)`. No
edits to `deepseek_v4.py`.

### Remaining typed blockers (not this PR)
1. nxrt has **no block-FP8 BlockFormat**.
2. FP4 experts are **planar**, nxrt MXFP4 is **interleaved** — needs a
proven byte-exact planar→interleaved transcode **or** a planar-FP4 bank
ABI extension.
3. End-to-end runnable export additionally needs Deckard's model wiring
(#593).

No A100 benchmark (no runnable shape-faithful path yet). **Draft — do
not merge.**

🤖 Flagged for squad review (needs review): touches the shared
`QuantizationConfig.from_transformers` config seam.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby

Copy link
Copy Markdown
Member Author

#602 consumption — local stacked-branch composition (fail-closed) ✅

Per the directive to consume Mobius #602's generic B2 API
(mobius.integrations._block_quant) without waiting for merge, I validated
the #593 × #602 composition on a local, unpushed stacked test branch
(deckard/deepseek-v4-csa-blockquant-stack = #593 3e10955 + #602 a38d38d7).
#593 itself stays independent of the unreviewed contract (_block_quant.py
absent from this branch; HEAD unchanged; nothing merged/pushed here).

What the composition proves (fail-closed, no perf claim):

Real-checkpoint operational evidence (rev 60d8d707…, metadata/header-only,
no weights): the real config.json trips the fail-closed gate; _block_quant
classifies real layer-0 tensors by property — attn.wkv/attn.wq_b
block_fp8 (F8_E4M3 + F8_E8M0 2D block scale), ffn.experts.0.w1
fp4_packed (I8 planar + F8_E8M0 block-32 micro-scale) — each with a non-None
runtime_representation_gap; plan_routed_expert_bank typed-rejects the fp4
bank with the exact nxrt ABI gap.

Tests/lint on the stack: 80 passed (35 #593 CSA + 40 #602 + 5 new
composition); ruff check + format --check clean. One caller update (no BC
shim): DeepSeekV4Attention.forward returns a 3-tuple on #593, so #602's
fused-GQA window test helper unpacks three values.

Gate for a non-fail-closed export remains the onnx-genai native block-FP8 /
planar-FP4 CSA runtime slice (separate; not yet advertising typed support).
When #602's review approves, it merges into #593 and these composition tests
migrate atomically. Staying DRAFT.

justinchuby and others added 3 commits August 25, 2026 00:19
…ratio-128) export [C1]

Slice C1 of the DeepSeek-V4-Flash CSA/HCA custom-kernel path: add a
default-off, property-gated Mobius exporter feature that emits the already
-merged frozen `pkg.nxrt::CompressedSparseAttention` v1 op (ratio-128 "Heavily
Compressed Attention" subset) for property-matching layers, replacing the
zero-valued shape-anchor preservation with real op dataflow and threaded
compressed state IO. Export-only; no runner/KV changes.

What
- New `mobius.models._deepseek_v4_csa`: `plan_native_csa` property gate +
  `emit_hca_attention` op emission. The gate matches native-op *properties*
  (compression ratio, block size, head/latent/rope dims, dtype, carry planes),
  not a model-name/shape allowlist.
- `ArchitectureConfig.native_csa: bool = False` opt-in (no HF equivalent;
  set via `config_overrides={"native_csa": True}`).
- `DeepSeekV4Attention._forward_native_hca`: emits the 11-input/3-output op
  with pre-rotated query/current_kv (dense sliding-window ring over the
  existing dense KV IO), real (unrotated) compressor activations, f32 casts
  for the f32-cache-format op inputs, and threaded past_*/present_* state.
- `DeepSeekV4Task` threads deterministic `past_compressed_kv.{i}` /
  `past_compression_carry.{i}` inputs and `present_*` outputs (explicit shapes
  since the custom op has no Python shape inference), with dynamic record axes
  and fixed carry planes so present state chains back for >=16 decode steps.

Fail-closed, never silent dense
- Requesting native CSA for ratio-4 CSA, an MTP layer, quantized compressor
  weights, or an unknown ratio raises `NativeCsaExportError`; ratio-0 dense
  and feature-off legitimately return None (dense).
- Feature off is byte-identical to the existing dense correctness export
  (verified) and emits zero `pkg.nxrt` ops / no compressed IO.

Does NOT claim PagedAttention compatibility. onnx-genai runner/KV wiring is a
later slice.

Tests
- Shape-faithful/structural (no weight download, no ORT run): exact op attrs
  + 11/3 IO, threaded compressed-state IO names/shapes/dtypes, decode
  chainability, f32 inputs under a FLOAT16 model dtype, no dead anchor for the
  HCA layer, byte-identical disabled baseline, defaults-off + opt-in, and typed
  rejects (ratio-4/quantized/MTP/unknown-ratio). Existing DeepSeek-V4 tests
  unchanged and passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ratio-4 CSA [B1/C2]

Extend the default-off native pkg.nxrt::CompressedSparseAttention v1 exporter
from ratio-128-only HCA to the full official interleaved DeepSeek-V4-Flash
schedule (21 ratio-4 CSA + 20 ratio-128 HCA + dense). Ratio-4 layers were
previously a typed reject, which made a full native_csa=True export of the
official checkpoint architecturally impossible; now both ratios emit the frozen
op or the export typed-rejects before serialization (never partial-native +
silent dense).

Contract re-derived from the frozen kernel
(crates/onnx-runtime-ep-cpu/src/kernels/compressed_sparse_attention.rs), the
shape-inference handler (crates/onnx-runtime-shape-inference/src/handlers/
custom_ops.rs), and the real safetensors index at rev 60d8d707...:

- ratio-4 CSA: 19 inputs / 6 outputs, cache_format='fp8_e4m3_block64'
  (stored_width=(head_dim-rope)/64*65 + rope*2), a learned FP4 indexer
  (index cache 'fp4_e2m1_block32', width index_head_dim/32*17), attention carry
  8 slots x 2 planes x 2*head_dim, index carry 8 x 2 x 2*index_head_dim, plus
  the transient int32 selected_indices top-k output.
- ratio-128 HCA unchanged: 11 inputs / 3 outputs, f32 cache, carry 128 slots.

Clean property-based refactor (no back-compat shims, per user directive):
- HcaLayerPlan -> CsaLayerPlan carrying every native-op property for both
  ratios; _fp8_block64_width/_fp4_width packing helpers fail-closed on invalid
  divisibility.
- plan_native_csa returns a ratio-4 plan; emit_csa_attention branches by ratio
  and raises on any missing learned-indexer input.
- DeepSeekV4IndexerTensors.forward gains a live dataflow mode feeding raw
  index_query = reshape(wq_b(q_lora)), index_weight, and the index compressor
  (the op applies index RoPE/Hadamard/FP4 internally) -- the same tensors
  previously kept as dead anchors, now real dataflow.
- _forward_native_csa wires both ratios; the task threads uint8 compressed/
  index caches + f32 carries + selected_indices with deterministic
  layer-suffixed names and dynamic record axes.
- MTP recurrence / unknown ratio / unsupported quant still fail closed. Feature
  stays off by default; disabled export byte-identical.

Tests (shape-faithful/structural; no weight download, no ORT run)
- New valid-dim _ratio4_config; ratio-4 emits 19/6 with exact attrs; an
  interleaved [0,4,128] schedule emits both ratios (19/6 and 11/3); ratio-4
  threads uint8 index cache (fp4 width 17) + f32 index carry + int32
  selected_indices; learned indexer wiring (index_query = Reshape of MatMul,
  index compressor MatMuls, learned ape/norm); no dead index anchor; prefill +
  >=16 decode chainability for both ratio-4 caches; typed rejects for invalid
  fp8/fp4 packing dims and missing index config; ratio-4 plan contract
  (stored_width=193, index_stored_width=17, carry_slots=8, ...). Existing
  ratio-128 and DeepSeek-V4 tests migrated atomically and passing (35 total).

No performance claim: no ratio-4 layer has executed sparse and no >=16 decode
has run. Real-checkpoint weight loading remains blocked on generic FP8/FP4
dequant (B2), reached only after the native gate accepts both ratios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…able export on nxrt runtime capability

Rebased #593 (native pkg.nxrt::CompressedSparseAttention export, ratio-4 CSA +
ratio-128 HCA) onto Mobius main now that #578 (6992181) and #602 (d16cd4a,
mobius.integrations._block_quant block-FP8 / packed-FP4 loading contract) are
merged. Consumes the merged _block_quant API directly; the temporary stacked
duplicate-parse branch is retired.

#602 rejects the block-scaled-FP8 + packed-FP4 DeepSeek-V4-Flash checkpoint at
config resolution (the INT4/per-tensor path cannot load it). That is correct
for a normal export, but it also blocks a native-CSA export before any graph
exists. Split the behavior into the two stages the directive requires:

  * Non-native (default) export keeps #602's early, loud config-resolution
    reject -- unchanged, its tests still pass.
  * native_csa opts into DEFERRING that reject: ArchitectureConfig.from_transformers
    catches the typed BlockQuantExportError, records the parsed block_quant_scheme
    on the config, and lets graph construction PROGRESS past the former generic
    "Weight shape mismatch" (build_from_module emits the CSA nodes + compressed
    state IO). The runnable FULL EXPORT then fails closed at a runtime-capability
    gate (assert_native_runtime_supports_block_quant, enforced at the top of
    DeepSeekV4 preprocess_weights, before apply_weights) while nxrt cannot
    execute block-FP8 / planar-FP4 weights. The gap string is sourced from
    _block_quant.runtime_representation_gap, so the gate tracks the real nxrt
    format strings and opens automatically when the native runtime slice lands
    -- no change here. Never a silent dense fallback, never partial-native.

No BC shims: ArchitectureConfig gains a block_quant_scheme field; deepseek_v4.py
adds no FP8/FP4 weight parsing (delegates to _block_quant). No performance claim
-- ratio-4 sparse execution and >=16-decode remain blocked on the unmerged
native block-FP8 / planar-FP4 runtime.

Tests (deepseek_v4_flash_test.py, 45 pass): non-native fail-closed at config
resolution; fp4-experts-alone owned; native_csa defers (scheme recorded, quant
None); full export typed-rejects at the runtime-capability gate; graph
construction progresses past block-quant (CSA node built); preprocess_weights
enforces the gate; property-gated not a blanket V4/native_csa refusal;
per-tensor fp8 not over-owned. #602 (_block_quant_test) + config
(_base_test/_extractors_test) suites unchanged (69 pass).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby
justinchuby force-pushed the deckard/deepseek-v4-csa-hca-c1 branch from 3e10955 to 5028076 Compare August 25, 2026 01:52
@justinchuby

Copy link
Copy Markdown
Member Author

Rebased onto main (#578 + #602 merged); two-stage block-quant composition

Rebased this branch onto latest main now that #578 (6992181) and #602 (d16cd4a, mobius.integrations._block_quant block-FP8 / packed-FP4 loading contract) are merged. Consumes _block_quant directly from main; the temporary stacked duplicate-parse branch is retired. New linear history: 5028076 + db491a3 (ratio-4) + c2e15ce (C1) on d16cd4a/6992181. Force-with-lease pushed (3e10955…5028076). Still DRAFT — do not merge.

Behavior split into the two stages the directive requires"graph construction should progress past the former generic shape mismatch, full export must typed-reject at the runtime capability gate":

Path Stage Behavior
Non-native (default) config resolution keeps #602's early, loud BlockQuantExportError (unchanged)
native_csa config resolution defers the reject: records block_quant_scheme, quantization=None
native_csa graph construction (build_from_module) PROGRESSES — emits CSA nodes + compressed state IO (no shape mismatch)
native_csa full export (preprocess_weightsapply_weights) typed-rejects at the runtime-capability gate

The gate (assert_native_runtime_supports_block_quant) sources its gap string from _block_quant.runtime_representation_gap, so it tracks the real nxrt format strings and opens automatically when the native block-FP8 / planar-FP4 runtime slice lands — no change here. Never a silent dense fallback, never partial-native. The CLI convert path is exactly two-staged (build_from_module then preprocess_weights+apply_weights), so the gate replaces the former generic "Weight shape mismatch" at the correct stage; the streaming loader refuses preprocess_weights-needing models and routes them to the eager path, so DeepSeek-V4's full export always passes through the gate.

No BC shims (per the no-backward-compat directive): ArchitectureConfig gains a block_quant_scheme field; deepseek_v4.py adds no FP8/FP4 weight parsing — it delegates to _block_quant.

Validation (local; not waiting on CI):

  • deepseek_v4_flash_test.py: 45 pass (37 + 8 composition tests) — non-native fail-closed at config resolution; fp4-experts-alone owned; native_csa defers (scheme recorded, quant None); full export typed-rejects at the runtime-capability gate; graph construction progresses past block-quant (CSA node built); preprocess_weights enforces the gate; property-gated (not a blanket V4/native_csa refusal); per-tensor fp8 not over-owned.
  • DeepSeek-V4 B2: block-FP8 / packed-FP4 weight-loading + expert-emission primitives (DRAFT) #602 + config suites unchanged: _block_quant_test + _base_test + _extractors_test = 69 pass.
  • ruff format + ruff check clean on the 4 touched files (_configs/_base.py, models/_deepseek_v4_csa.py, models/deepseek_v4.py, models/deepseek_v4_flash_test.py).

No performance claim. Ratio-4 sparse execution and ≥16-decode stay blocked on the unmerged native block-FP8 / planar-FP4 runtime; this slice only makes graph construction progress and the runnable export fail closed at the honest capability gate. Stopping for independent review.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

@justinchuby

Copy link
Copy Markdown
Member Author

Roy — independent review, HEAD 50280766a42e85c3321c0092f81df49ad1097157

Verdict: APPROVE EXPORT SCHEMA SLICE

Read-only review, no edits. Worktree rebased cleanly on latest main (base = current tip, one file auto-merged with #599, zero conflicts).

Schema/contract — verified against authoritative Rust kernel source

Cross-checked _deepseek_v4_csa.py's op-contract constants against crates/onnx-runtime-ep-cpu/src/kernels/compressed_sparse_attention.rs in onnx-genai: exact match on required inputs (11 base, +8 for ratio-4 = 19), input/output arity bounds (11..20 / 3..6), attribute set, sink_mode='logit_only', both cache formats (f32 for HCA, fp8_e4m3_block64/fp4_e2m1_block32 packed widths for CSA — _fp8_block64_width/_fp4_width mirror the Rust stored_width formulas exactly), and carry_slots (128 for HCA, fixed 8 for CSA per the Rust CARRY_SLOTS constant). The optional 20th input (attention_bias) is genuinely unused by DeepSeek, not a gap.

Full official schedule / no partial-native fallback

test_native_csa_emits_both_ratios_for_interleaved_schedule (compress_ratios=[0,4,128]) proves a single graph emits both frozen-op variants with ratio-correct arity/attrs, never a partial-native+dense mix. _forward_native_csa fires unconditionally whenever layer.csa_plan is not None — there is no silent-fallback branch. Disabled path is proven byte-identical (_fill_and_serialize(model_unset) == _fill_and_serialize(model_false), plus zero CSA nodes / zero compress-named IO).

No zero anchors (compressor and indexer)

Single guard (self.compressor is not None and self.csa_plan is None, deepseek_v4.py:982) wraps both the compressor's and the indexer's zero-shape-anchor construction — there is no separate indexer-only anchor path that could leak through when the compressor anchor is skipped. Traced _forward_native_csa: when plan.is_ratio4, self.indexer(op, hidden_states, query_lora) is called with real activations (confirmed by test_native_csa_ratio4_wires_learned_indexer/test_native_csa_ratio4_no_dead_index_anchor — producer op-type and initializer-presence assertions, not just "doesn't crash").

Two-stage #602 composition

Verified in code and by 6 dedicated tests (defers/progresses/full-export-typed-reject/preprocess_weights-enforces/property-gated-not-blanket/per-tensor-fp8-not-owned): native_csa=True defers BlockQuantExportError at config resolution (records block_quant_scheme, quantization=None); graph construction progresses (CSA node built); but assert_native_runtime_supports_block_quant — the literal first statement of preprocess_weights — still fails closed today, because onnx-genai #2054 (which would add block_fp8/fp4_planar to NXRT_BLOCK_FORMATS) is closed, not merged (gh pr view 2054: state: CLOSED, mergedAt: null). The gate fires only on the block-quant properties, never on the model name or on native_csa alone — confirmed by the explicit over-firing guard tests.

CLI/streaming bypass

native_csa has zero CLI exposure in this slice — opt-in only via config_overrides={"native_csa": True} (Python API), a stronger safety margin than a CLI flag would be. stream_safetensors_to_model (pre-existing, unmodified) cannot silently bypass the gate for a real DeepSeek-V4 checkpoint: it independently refuses any quantized source (fp8/*_scale_inv detection) and any checkpoint whose keys don't already 1:1 match graph initializer names — which a model needing preprocess_weights renaming always trips — so it fails closed via its own pre-existing guards, not via this PR's logic.

MTP exclusion — defense in depth

Two independent mechanisms both fail closed: (1) MTP layers are constructed with layer_id = num_hidden_layers + index, always past compress_ratios' length, defaulting to dense; (2) plan_native_csa(..., is_mtp=True) explicitly rejects with NativeCsaExportError regardless of ratio (test_plan_native_csa_rejects_mtp) — property-based on the is_mtp flag, not name/index-length coincidence.

Tests / lint — independently reproduced

  • deepseek_v4_flash_test.py: 45 passed (exact match to claim).
  • _block_quant_test.py + _base_test.py + _extractors_test.py: 69 passed (exact match to claim).
  • Broader regression sweep (models/, _configs/, integrations/_block_quant_test.py, integrations/_weight_loading_test.py, tasks/): 832 passed, 4 skipped, 0 failed.
  • ruff check + ruff format --check on all 5 touched files: clean.

Should-fix, non-blocking

The Rust kernel's execute_ratio4 hard-asserts head_dim==512 && qk_rope_head_dim==64 at runtime; the Python export-time gate (_fp8_block64_width) only enforces the weaker generic packing constraint (head_dim − qk_rope_head_dim) % 64 == 0, not the exact 512/64 pair. An out-of-family config could pass export and only fail later at ORT execution time. Confirmed moot for the actual target model today: _v4_hf_config/test_real_config_fields_extract pin DeepSeek-V4-Flash's real architecture to exactly head_dim=512, qk_rope_head_dim=64 ("mirrors the official rev"), and any divergence still fails closed (never silently wrong), just later than ideal. Recommend a follow-up export-time assert mirroring the exact Rust constraint for ratio-4 layers — not a merge blocker.

Exact dependencies to state

  1. onnx-genai #2054 (or a successor landing block_fp8/fp4_planar into NXRT_BLOCK_FORMATS/runtime_representation_gap) — required before the runtime-capability gate can ever open for a real block-quant DeepSeek-V4-Flash checkpoint. Currently closed/unmerged; gate is genuinely fail-closed today, not a stale promise.
  2. Runtime state threading (tasks/_deepseek_v4.py): _compressed_inputs/_compressed_outputs correctly thread past_compressed_kv/past_compression_carry (+ past_index_key/past_index_carry for ratio-4) per-layer with dynamic record axes and fixed carry-plane shapes; MTP's _build_mtp never touches this threading (2-output module.mtp[0](...) signature, consistent with guaranteed-dense MTP). No further onnx-genai dependency beyond #2054 — the CSA/HCA op schema/kernel itself already exists in onnx-genai independent of the block-quant runtime-format gate.

Posting this as the review verdict.

@justinchuby
justinchuby marked this pull request as ready for review August 25, 2026 04:21
@justinchuby
justinchuby requested review from a team and a lite review from Copilot August 25, 2026 04:21
@justinchuby
justinchuby merged commit 0f1fc0c into main Aug 25, 2026
24 checks passed
@justinchuby
justinchuby deleted the deckard/deepseek-v4-csa-hca-c1 branch August 25, 2026 04:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an exporter-only, default-off path for DeepSeek-V4(-Flash) that can emit the frozen pkg.nxrt::CompressedSparseAttention v1 custom op for property-matching layers, and threads the additional compressed state I/O through the task graph so the exported ONNX can carry/return the native compressed state alongside the existing dense KV cache.

Changes:

  • Add a new DeepSeek-V4 CSA/HCA planning + emission module (_deepseek_v4_csa.py) that property-gates native op emission and provides a typed runtime-capability gate for deferred block-quant schemes.
  • Extend DeepSeek-V4 model + task wiring to thread past_*present_* compressed state tensors, and to replace the prior “dead shape anchor” with real compressor/indexer dataflow when native CSA is enabled.
  • Add extensive structural tests covering node arity/attrs, compressed-state I/O wiring, enable/disable byte-identity, and fail-closed rejects.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/mobius/tasks/_deepseek_v4.py Threads native-CSA compressed-state inputs/outputs through the DeepSeek-V4 task graph.
src/mobius/models/deepseek_v4.py Adds native-CSA planning and op emission path inside attention; replaces anchors with real compressor/indexer dataflow when enabled.
src/mobius/models/deepseek_v4_flash_test.py Adds structural tests for CSA/HCA op emission, state I/O threading, enable/disable behavior, and typed rejects.
src/mobius/models/_deepseek_v4_csa.py New module implementing property gate, op emission helper, and runtime block-quant capability gate.
src/mobius/_configs/_base.py Adds ArchitectureConfig.native_csa toggle and defers block-quant rejection only when native CSA is requested.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +501 to +507
# DeepSeek-V4 native compressed-sparse-attention export toggle. Not an
# upstream HF field -- a Mobius export-time opt-in (default False) that
# replaces the dense CSA/HCA correctness fallback with the frozen
# ``pkg.nxrt::CompressedSparseAttention`` v1 op for property-matching
# ratio-128 (HCA) layers. Off by default so every shipped graph stays
# byte-identical and ``pkg.nxrt``-free unless explicitly requested via
# ``config_overrides={"native_csa": True}``. When requested, layers that
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants