Skip to content

[Quant] Add EXL3 Trellis backend for rank-sliced GLM/DeepSeek MoE#139

Open
brandonmmusic-max wants to merge 15 commits into
local-inference-lab:dev/gilded-gnosisfrom
brandonmmusic-max:exl3-backend
Open

[Quant] Add EXL3 Trellis backend for rank-sliced GLM/DeepSeek MoE#139
brandonmmusic-max wants to merge 15 commits into
local-inference-lab:dev/gilded-gnosisfrom
brandonmmusic-max:exl3-backend

Conversation

@brandonmmusic-max

@brandonmmusic-max brandonmmusic-max commented Jul 20, 2026

Copy link
Copy Markdown

Summary

Adds native EXL3 (exllamav3 Trellis) loading and execution to Gilded Gnosis
vLLM. The backend serves native rank-sliced checkpoints without reconstructing
or requantizing routed-expert weights at load time.

The implementation provides:

  • --quantization exl3 detection and tensor-storage parsing
  • dense linear and language-model-head support with a bit-faithful parity path
  • routed MoE execution through Sparkinfer's planned EXL3 Trellis API
  • TP-aware rank-sliced loaders for GLM/DeepSeek fused expert projections
  • CUDA graph compatible custom-op registration and fake implementations
  • per-shard fused/fallback selection with fail-closed validation
  • removal of an unnecessary routed-output copy on the production path

Release validation also exposed target/draft lifetime hazards in DCP speculative
execution. This branch now includes:

  • distinct target and draft PCIe graph channels and communicators
  • DCP-aware draft KV-cache metadata, global top-k mapping, and draft sharding
  • independent target/draft workspace pools with explicit ownership

The Sparkinfer kernel/API dependency is
local-inference-lab/b12x#49.

Rebase and release pins

  • base: dev/gilded-gnosis at 89b4a98d1ffebb2dda1e1ac5e55238e3a9cfbd58
  • PR head: 26c4bfdd3ff2be0433e6fe07e0c3be535f5bb318
  • Sparkinfer head: d4438d490691f79022fdfc8149e1c5f161d15445
  • CUDA 13.2, Torch 2.12, SM120a

The exact validated image is:

verdictai/glm52-exl3-sparkinfer:v26-gg-v20final-scopefix-archkey-vllm5517197-sibe0edca-cu132-sm120a@sha256:2bb9e804a283d1da3b7e3425ff87375121285141d0d0a40d3dc09d41bf881a10

What this rebase actually changed (please review on its merits)

This branch was force-pushed after a rebase onto dev/gilded-gnosis @ 89b4a98d
(previous head 2cb2e085 -> dc0286f). The PR's own diff is the same size
(16 files, +2452/-41) and all 14 commits replayed without conflicts, but this is
not a no-op rebase -- the branch now sits on 20 newer upstream commits, so
the code these edits apply to has moved. Concretely:

  • vllm/v1/attention/backends/mla/indexer.py was modified by both sides.
    Upstream changed it +31/-2 in that span (including the new
    _align_block_table_width), and this PR's use_flattening predicate change
    (and not use_b12x_sparse_indexer()) was auto-merged onto that newer file by
    git rather than re-authored
    . Git reported no conflict because the two hunks are
    ~20 lines apart, but the semantics of the merged result were not human-reviewed --
    this is the single highest-value thing to look at in this rebase.
  • Upstream also moved mla_attention.py, b12x_mla_sparse.py, b12x_attn.py and
    the DCP workspace tests in the same span. Those are not in this PR's diff, but the
    EXL3 Trellis MoE and indexer paths interact with them at runtime, so behavioural
    review of that boundary is welcome.
  • The other 15 files in this PR had no upstream movement in that span and replayed
    unchanged.

For traceability: the EXL3 delta carried by this branch is byte-identical to the
tree the validated v23 image was built from (verified per file against each base --
10 changed lines in deepseek_v2.py, 20 in indexer.py), so the validation results
below correspond to exactly this contribution.

Finalized Gilded Gnosis v20 common base (2026-07-25)

This branch is rebased onto its own base branch for review and merge. It has also
been built and validated as an EXL3 source layer on the finalized v20 common
base. Per the v20 release note the base packages no EXL3/Trellis loader or
kernels, so EXL3 is enabled by rebasing the EXL3 layer onto the pinned stack --
not by pointing the voipmonitor/vllm tag at an EXL3 checkpoint.

  • base image: voipmonitor/vllm:gilded-gnosis-v20-vllm5517197-sibe0edca-fi801d57a-cu132-20260725
  • EXL3 vLLM layer rebased onto vLLM 551719766029e78824a30d97ae6ac63917405b5f
  • EXL3 Sparkinfer layer rebased onto SparkInfer be0edcaae6f5d284bb29a82325aba7a0ead6960f
  • Of the 12 runtime overlay files, only models/deepseek_v2.py and
    v1/attention/backends/mla/indexer.py differ between the previous and the
    finalized v20 tree. Both were re-derived from the v20-final versions with the
    EXL3 edits replayed on top, so the new DCP/indexer/prefill work is preserved
    rather than overwritten by the overlay.

Validated image:

verdictai/glm52-exl3-sparkinfer:v26-gg-v20final-scopefix-archkey-vllm5517197-sibe0edca-cu132-sm120a@sha256:2bb9e804a283d1da3b7e3425ff87375121285141d0d0a40d3dc09d41bf881a10

Runtime validation, 4x RTX PRO 6000 Blackwell (SM120a), TP4/DCP4, MTP-3, NVFP4 DS-MLA KV:

  • engine reports v0.11.2.dev280+gilded.gnosis.v20.vllm5517197.sibe0edca.fi801d57a.cu132.20260725
  • vLLM is using nccl==2.30.4
  • EXL3 planned: Trellis m=1..32 block_m=8, prefill trellis block_m=64 arena=1054.2MiB capacity=3072 chunk=128 topk=8
  • v20 DCP prefill auto-policy resolved for TP4/DCP4 (DCP_*=auto): query split 1,
    full CKV gather 1, top-k owner merge 1, indexer shards 0, CKV prefetch depth 1,
    and DCP_PREFILL_WORKSPACE=1 (VLLM_DCP_PROJECT_BEFORE_MERGE=1 +
    VLLM_B12X_MLA_DCP_GATHER_IN_WORKSPACE=1)
  • Preallocated 30.8 MiB for 2 persistent CKV execution lane(s) and
    Using native CKV layer prefetch with depth=1 and 2 workspace slots
  • GPU KV cache 998,656 tokens (1.90x at 524,288 per request). This is ~1 GiB lower
    than the pre-fix image because the draft model now gets its own rank-sliced
    runtime arena instead of sharing the target's -- the intended cost of the
    target/draft scratch isolation.
  • Estonia c2 x5: 5/5 pass, 0 fail, correct rate 1.00
  • LAVD c5 x5: 3 EXACT / 2 NEAR / 0 FAIL, correct rate 1.00

Earlier images in this series measured LAVD at 2E/3N/0F (pre-fix) and, on one
5-run sample of the first scoped build, 2E/2N/1F; a second sample of that build
returned 2E/3N/0F. The single failure there was a wrong ledger total at 7,114
completion tokens against a 24,576 cap -- an answer-quality miss rather than
truncation, and within this profile's run-to-run spread. This build shows no
failures on either suite.

Note: the base's Loading safetensors using InstantTensor loader assertion does
not appear on this path -- the EXL3 checkpoint is loaded by the EXL3 rank-sliced
loader, not the base InstantTensor path. All other boot assertions hold.

Production validation

Validated on a 332.19 GB GLM-5.2 checkpoint with 256 routed experts and four
RTX PRO 6000 Blackwell 96 GB GPUs.

Validated serving configuration:

  • TP4 + DCP4 A2A
  • MTP1 greedy
  • asynchronous scheduling disabled
  • NVFP4 DeepSeek-MLA KV cache with calibrated outer scales
  • 524,288 logical KV tokens (2,048 blocks x 64 x DCP4)
  • full and piecewise CUDA graphs through concurrency 8
  • all 81 model shards loaded and EXL3 initialized

Correctness and quality:

  • workspace ownership tests: 3 passed
  • exact-image Sparkinfer Trellis tests: 10 passed
  • LAVD: 10/10 twice, 20/20 combined, zero cap hits
  • Estonia: 5/5, zero cap hits
  • five-run, 2,047-position DCP4 KLD:
    • NVFP4: mean 0.1124021, sample SD 0.0025948
    • FP8: mean 0.1036666, sample SD 0.0018374

Exact-image performance:

Test Result
8K / 64K / 128K cold prefill 1,502 / 1,249 / 1,182 tok/s
C1 / C2 / C3 / C4 sustained decode 48.9 / 112.9 / 154.0 / 188.2 aggregate tok/s
C5 / C6 / C7 / C8 sustained decode 218.2 / 239.4 / 253.9 / 266.8 aggregate tok/s

Release constraint

Asynchronous scheduling remains disabled in the published configuration. The
source-level target/draft isolation fixes are necessary, but hardware testing
still showed a quality regression when async retirement overlapped speculative
side-stream work and the module-global CKV gather state. The validated release
therefore uses synchronous scheduling, MTP1, DCP draft sharding enabled, global
top-k enabled, and VLLM_B12X_MLA_SPEC_EXTEND_AS_DECODE=0.

Notes and limits

  • Fused Trellis requires the MCG codebook and compatible K/N alignment;
    unsupported shards fail closed to the parity implementation.
  • The routed-MoE path is performance-validated on SM120a. Other architectures
    retain generic/parity behavior and are not claimed as performance targets.
  • The optional FlashInfer JIT cache step can be skipped when the target wheel
    does not expose a compatible cache builder.
  • The vLLM package banner retains the base build's g60c82d972 string. The
    image's ai.verdict.vllm.revision OCI label records the installed PR head.

CI status

CodeRabbit passes. The repository pre-run gate currently blocks the normal
pre-commit job because it requires a verified, ready, or
ready-run-all-tests label (or four prior merged PRs). None of those labels
exists in the repository, and the contributor token cannot create one, so a
maintainer label/configuration action is required to start that job.

AI assistance disclosure

Implementation, debugging, and documentation were developed with Codex and
Claude Code assistance. The author selected the design, reviewed the changes,
and ran the reported hardware validation.

Summary by CodeRabbit

  • New Features

    • Added EXL3 quantization support for dense, language-model head, and mixture-of-experts layers.
    • Added support for rank-sliced EXL3 checkpoints and compatible weight loading.
    • Docker builds can now optionally skip the FlashInfer JIT cache.
  • Bug Fixes

    • Improved structured-output handling during speculative decoding, including reasoning-marker detection and content trimming.
    • Corrected expert weight routing for quantized checkpoints.
    • Improved DeepSeek attention behavior and sparse-indexer compatibility.
    • Improved checkpoint loading for rank-sliced model weights.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

EXL3 quantization support is added for dense and routed-expert layers, including metadata handling, checkpoint loading, CUDA execution, Trellis dispatch, and tests. Structured-output speculative advancement, DeepSeek attention gating, MLA flattening, and optional Docker cache installation are also updated.

Changes

EXL3 quantization

Layer / File(s) Summary
Configuration and runtime primitives
vllm/model_executor/layers/quantization/exl3.py, vllm/model_executor/layers/quantization/__init__.py, vllm/config/model.py
EXL3 configuration hydration, method registration, metadata validation, lazy extension loading, and the exl3_gemm operator are added.
Linear weights and execution
vllm/model_executor/layers/quantization/exl3.py
Linear EXL3 tensors are registered, validated, sharded, and executed with packed-dimension handling.
Routed-expert execution
vllm/model_executor/layers/quantization/exl3.py
MoE tensors support slab allocation, tensor parallelism, Trellis planning, parity fallback, and explicit expert routing.
Checkpoint loading and MoE routing
vllm/model_executor/models/deepseek_v2.py, vllm/model_executor/models/glm4_moe.py, vllm/model_executor/layers/fused_moe/routed_experts.py
Rank-sliced names are normalized during model loading, and rank-3 per-expert tensors are no longer treated as fused weights.
Validation
tests/quantization/test_exl3.py, tests/quantization/test_exl3_prefill_plan.py
Tests cover EXL3 metadata, checkpoint normalization, expert storage, Trellis plans, environment overrides, and CUDA capture behavior.

Structured-output advancement

Layer / File(s) Summary
Token-aware reasoning advancement
vllm/v1/structured_output/__init__.py, vllm/v1/core/sched/scheduler.py
should_advance consumes generated token deltas and records speculative reasoning boundaries for same-step advancement.
Speculative structured-output tests
tests/v1/structured_output/test_reasoning_structured_output.py
Tests cover reasoning completion, explicit token deltas, and trimming content after the reasoning marker.

DeepSeek attention gating

Layer / File(s) Summary
Indexer and flattening conditions
vllm/models/deepseek_v32/nvidia/attention.py, vllm/v1/attention/backends/mla/indexer.py
Sparse indexer work now requires index_k, and B12X capability-120 execution changes flattening selection.

Build installation control

Layer / File(s) Summary
Optional FlashInfer cache installation
docker/Dockerfile
A build argument conditionally enables FlashInfer JIT cache installation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModelLoader
  participant Exl3Config
  participant Exl3MoEMethod
  participant SparkinferTrellis
  participant exllamav3_ext
  ModelLoader->>Exl3Config: normalize rank-sliced checkpoint names
  Exl3Config->>Exl3MoEMethod: configure EXL3 MoE tensors
  Exl3MoEMethod->>SparkinferTrellis: plan rank-sliced dispatch
  SparkinferTrellis->>exllamav3_ext: execute Trellis or parity path
  exllamav3_ext-->>Exl3MoEMethod: return routed expert outputs
Loading

Possibly related PRs

Suggested reviewers: lukealonso

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding EXL3 Trellis backend support for rank-sliced GLM/DeepSeek MoE checkpoints.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vllm/model_executor/layers/quantization/exl3.py`:
- Around line 464-472: The _moe_prefix_is_exl3 method currently hardcodes
projection names, so remapped MoE checkpoints are not detected. Update this
check to use the RoutedExperts layer’s ckpt_gate_proj_name, ckpt_up_proj_name,
and ckpt_down_proj_name values, matching the names consumed by
_validate_codebooks, and thread those names through the method as needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 87dd2ded-af12-4ca3-992c-dad738c0d7be

📥 Commits

Reviewing files that changed from the base of the PR and between 0d1e91b and a846c2d.

📒 Files selected for processing (3)
  • vllm/model_executor/layers/fused_moe/routed_experts.py
  • vllm/model_executor/layers/quantization/__init__.py
  • vllm/model_executor/layers/quantization/exl3.py

Comment thread vllm/model_executor/layers/quantization/exl3.py Outdated
@brandonmmusic-max brandonmmusic-max changed the title [Quant] EXL3 (exllamav3 trellis) backend: fused b12x kernel primary, bit-faithful parity fallback [Quant] Add EXL3 Trellis backend for rank-sliced GLM/DeepSeek MoE Jul 22, 2026
@brandonmmusic-max
brandonmmusic-max changed the base branch from dev/fathomless-firmament to dev/gilded-gnosis July 22, 2026 00:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vllm/v1/worker/workspace.py`:
- Around line 36-43: Update the use_workspace_lane() docstring with Google-style
Args: documentation for lane and a Raises: section documenting the ValueError
for negative lanes, while preserving the existing behavior and explanatory text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1692d79f-0266-4848-898d-b40a56b3e55b

📥 Commits

Reviewing files that changed from the base of the PR and between 207973e and 424f224.

📒 Files selected for processing (9)
  • tests/v1/structured_output/test_reasoning_structured_output.py
  • tests/v1/worker/test_workspace.py
  • vllm/models/deepseek_v32/nvidia/attention.py
  • vllm/v1/core/sched/scheduler.py
  • vllm/v1/structured_output/__init__.py
  • vllm/v1/worker/gpu/model_runner.py
  • vllm/v1/worker/gpu/warmup.py
  • vllm/v1/worker/gpu_worker.py
  • vllm/v1/worker/workspace.py

Comment thread vllm/v1/worker/workspace.py
davidsyoung pushed a commit to davidsyoung/vllm that referenced this pull request Jul 22, 2026
Builds on the rank-sliced EXL3 (ExLlamaV3 trellis) support added in
local-inference-lab#139 by @brandonmusic, paired with the
trellis3_t256 planned MoE from local-inference-lab/sparkinfer#49. No change
to that work's weight loading, formats, or decode path; this commit
only extends its batch dispatch.

The vllm-project#139 backend serves every batch with m > 32 through the eager
exllamav3_ext parity path: fp16-in/fp32-out staging (~25 GB extra HBM
traffic per 4096-token step at GLM-5.2 geometry), python chunk loops,
and one expert re-stream per 128-row chunk. That covers 100% of
prefill, so prefill throughput is capped at ~2.0-2.3k tok/s while the
same weights decode at full speed through the planned Trellis window.

Build a second trellis_moe plan at max_tokens=max_num_batched_tokens
with block_size_m=64 (env VLLM_EXL3_PREFILL_BLOCK_M, allowed
{8,16,32,48,64}) beside the existing decode plan (32/8), and dispatch
three ways in _apply_rank_sliced:

* m in [min, max]      -> decode plan (unchanged)
* max < m <= capacity  -> prefill plan (sparkinfer bind already accepts
                          any tokens in [1, max_tokens]; zero kernel
                          changes needed)
* m < min              -> parity path, whose persistent staging
                          (xh/out32/token_sorted/weight_sorted) shrinks
                          from capacity rows to one chunk while the
                          prefill plan is live (~110 MiB/GPU returned)

VLLM_EXL3_PREFILL_TRELLIS=0 restores the exact single-plan parity
behavior (one-variable serving A/B lever). The parity branch now raises
during CUDA graph capture instead of silently recording eager ext
calls. The prefill arena (~1.05 GiB/GPU at capacity 3072) allocates
during the profile pass, so vLLM's memory profiler sees it and the KV
pool auto-shrinks instead of risking request-time OOM.

Matched A/B serving brandonmusic/GLM-5.2-EXL3-TR3-3.0bpw on the
published verdictai/glm52-exl3-sparkinfer runtime image (4x RTX PRO
6000 Blackwell, TP4/DCP4/MTP3, identical boot geometry, only the
kill-switch flipped, fresh JIT cache both arms; llm_decode_bench
prefill-only, exact /tokenize, C1, 60 s/context):

  ctx    parity   trellis   delta
  8k      2,287     3,742   +63.6%
  32k     2,218     3,557   +60.4%
  64k     2,078     3,381   +62.7%
  128k    1,920     3,032   +57.9%

Decode C1 unchanged-to-better; 57k-token needle retrieval exact.
davidsyoung pushed a commit to davidsyoung/vllm that referenced this pull request Jul 22, 2026
Optional companion to the prefill-plan commit; mocked plan/ext APIs,
no GPU, sparkinfer, or exllamav3_ext required. Covers plan
construction order, the three-way dispatch boundaries, chunk-capped
parity staging, the VLLM_EXL3_PREFILL_TRELLIS=0 kill-switch,
VLLM_EXL3_PREFILL_BLOCK_M override, elastic re-plan above scheduler
capacity, and the parity-path capture guard. Follows the existing
CPU-test pattern of tests/quantization/test_exl3.py from
local-inference-lab#139.
brandonmmusic-max pushed a commit to brandonmmusic-max/vllm that referenced this pull request Jul 22, 2026
Builds on the rank-sliced EXL3 (ExLlamaV3 trellis) support added in
local-inference-lab#139 by @brandonmusic, paired with the
trellis3_t256 planned MoE from local-inference-lab/sparkinfer#49. No change
to that work's weight loading, formats, or decode path; this commit
only extends its batch dispatch.

The vllm-project#139 backend serves every batch with m > 32 through the eager
exllamav3_ext parity path: fp16-in/fp32-out staging (~25 GB extra HBM
traffic per 4096-token step at GLM-5.2 geometry), python chunk loops,
and one expert re-stream per 128-row chunk. That covers 100% of
prefill, so prefill throughput is capped at ~2.0-2.3k tok/s while the
same weights decode at full speed through the planned Trellis window.

Build a second trellis_moe plan at max_tokens=max_num_batched_tokens
with block_size_m=64 (env VLLM_EXL3_PREFILL_BLOCK_M, allowed
{8,16,32,48,64}) beside the existing decode plan (32/8), and dispatch
three ways in _apply_rank_sliced:

* m in [min, max]      -> decode plan (unchanged)
* max < m <= capacity  -> prefill plan (sparkinfer bind already accepts
                          any tokens in [1, max_tokens]; zero kernel
                          changes needed)
* m < min              -> parity path, whose persistent staging
                          (xh/out32/token_sorted/weight_sorted) shrinks
                          from capacity rows to one chunk while the
                          prefill plan is live (~110 MiB/GPU returned)

VLLM_EXL3_PREFILL_TRELLIS=0 restores the exact single-plan parity
behavior (one-variable serving A/B lever). The parity branch now raises
during CUDA graph capture instead of silently recording eager ext
calls. The prefill arena (~1.05 GiB/GPU at capacity 3072) allocates
during the profile pass, so vLLM's memory profiler sees it and the KV
pool auto-shrinks instead of risking request-time OOM.

Matched A/B serving brandonmusic/GLM-5.2-EXL3-TR3-3.0bpw on the
published verdictai/glm52-exl3-sparkinfer runtime image (4x RTX PRO
6000 Blackwell, TP4/DCP4/MTP3, identical boot geometry, only the
kill-switch flipped, fresh JIT cache both arms; llm_decode_bench
prefill-only, exact /tokenize, C1, 60 s/context):

  ctx    parity   trellis   delta
  8k      2,287     3,742   +63.6%
  32k     2,218     3,557   +60.4%
  64k     2,078     3,381   +62.7%
  128k    1,920     3,032   +57.9%

Decode C1 unchanged-to-better; 57k-token needle retrieval exact.

(cherry picked from commit d57d775)
brandonmmusic-max pushed a commit to brandonmmusic-max/vllm that referenced this pull request Jul 22, 2026
Optional companion to the prefill-plan commit; mocked plan/ext APIs,
no GPU, sparkinfer, or exllamav3_ext required. Covers plan
construction order, the three-way dispatch boundaries, chunk-capped
parity staging, the VLLM_EXL3_PREFILL_TRELLIS=0 kill-switch,
VLLM_EXL3_PREFILL_BLOCK_M override, elastic re-plan above scheduler
capacity, and the parity-path capture guard. Follows the existing
CPU-test pattern of tests/quantization/test_exl3.py from
local-inference-lab#139.

(cherry picked from commit f80a56d)
@brandonmmusic-max

brandonmmusic-max commented Jul 22, 2026

Copy link
Copy Markdown
Author

Rebased onto Gilded Gnosis v20 canonical (6722c1d)

Force-pushed a rebase of this PR onto dev/gilded-gnosis 6722c1d (the v20 canonical head). The local GG fixes this branch previously carried are dropped as superseded by canonical — head-major/cuBLAS (#147), graph-resource isolation (#149), same-model draft cap (#155), and the MTP verifier-decode dispatch (#164). What remains on top of v20 is the EXL3-specific work:

  • EXL3 Trellis MoE backend for rank-sliced GLM/DeepSeek routed experts
  • MTP tool-call + DSA-crash fix — the structured-output grammar advances from the authoritative step delta (tool calling engages under speculative decoding), and has_indexer is derived from index_k so MTP draft steps that skip top-k no longer trip the fused-norm-rope assertion
  • Dual-plan Trellis prefill — prefill batches route through the planned Trellis MoE
  • SM120 + B12X flattening fix — MTP next_n>2 uses the native (B, next_n) sparse-indexer path instead of the DeepGEMM next_n<=2 flatten fallback, which had corrupted MTP-3 code generation

Validation (4x RTX PRO 6000 Blackwell 96 GB, TP4/DCP4, MTP-3 greedy, NVFP4 KV, FULL_AND_PIECEWISE cudagraphs)

Test v20 Prior base
Code generation (Python/HTML, temp 0) 94–100% valid — reported ~50% syntax-error symptom eliminated ~50% (reported)
LAVD r10/c5 10/10 (6 exact, 4 near, 0 fail) 8/10 (2 fail)

The LAVD fail count 2 → 0 reflects the v20 canonical MTP fixes plus the flattening fix. Tested image: verdictai/glm52-exl3-sparkinfer:v20-gg6722c1d-si1a88b38-cu132-sm120a.

Re #163: the dual-plan Trellis prefill from #163 is incorporated in this rebase, so #163 can be closed or rebased onto v20 as preferred.
Full results on current base for a full validation sweet a/b for kv cache, set on reasoning high for the PR’s serve.sh, docker compose |Test |nvfp4_ds_mla |fp8 |
|---------------------------|--------------------|--------------------|
|Estonia (133K needle) |PASS 30 / FAIL 0|PASS 30 / FAIL 0|
|LAVD (ledger) |18E/11N/1F — 97%|15E/13N/2F — 93% |
|Hotel-lights (low tier)|15E/15F — 50% |18E/12F — 60% |
|KLD vs BF16 |0.116138 |0.101198 |
|Decode C1 / C8 |87.5 / 308.1 |86.6 / 312.2 |
|Prefill 8k / 128k |2,551 / 1,833 |2,660 / 1,771 |
Full server.sh, docker compose yaml and image and full results. Al run on four in pro 6ks, capped at 300 watts each, so on full workstation or higher power, decode and prefill would be faster. here https://huggingface.co/brandonmusic/GLM-5.2-EXL3-TR3-3.0bpw/blob/main/RELEASE_TEST_SUITE.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vllm/v1/attention/backends/mla/indexer.py`:
- Around line 399-402: Update the B12X activation condition in the relevant
build path around the sparse-indexer capability check to use the canonical
backend-aware predicate, including attention_config.backend == "B12X_MLA_SPARSE"
when the environment variable is unset. Preserve the existing SM120 and next_n
behavior while ensuring the native (B, next_n) path remains disabled for all
supported B12X configurations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e1796822-6dd0-4a09-a332-cead2920cc76

📥 Commits

Reviewing files that changed from the base of the PR and between 2fdc6d3 and 31ff5a2.

📒 Files selected for processing (14)
  • docker/Dockerfile
  • tests/quantization/test_exl3.py
  • tests/quantization/test_exl3_prefill_plan.py
  • tests/v1/structured_output/test_reasoning_structured_output.py
  • vllm/config/model.py
  • vllm/model_executor/layers/fused_moe/routed_experts.py
  • vllm/model_executor/layers/quantization/__init__.py
  • vllm/model_executor/layers/quantization/exl3.py
  • vllm/model_executor/models/deepseek_v2.py
  • vllm/model_executor/models/glm4_moe.py
  • vllm/models/deepseek_v32/nvidia/attention.py
  • vllm/v1/attention/backends/mla/indexer.py
  • vllm/v1/core/sched/scheduler.py
  • vllm/v1/structured_output/__init__.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • vllm/v1/core/sched/scheduler.py
  • vllm/model_executor/layers/fused_moe/routed_experts.py
  • vllm/model_executor/layers/quantization/init.py
  • vllm/models/deepseek_v32/nvidia/attention.py
  • vllm/v1/structured_output/init.py

Comment thread vllm/v1/attention/backends/mla/indexer.py Outdated
@brandonmmusic-max

Copy link
Copy Markdown
Author

Credit goes to davidsyoung for prefill improvement

@davidsyoung

Copy link
Copy Markdown

Note on the incorporated prefill work (cross-ref #163):

As of the v20 rebase, this branch carries the dual-plan Trellis prefill dispatch verbatim from #163:

so reviewing/merging this PR covers that change completely. #163 stays open only as the isolated review scope against the pre-rebase base (2fdc6d3676); it collapses to an empty diff once this merges and I'll close it then.

Validation data for the incorporated change, measured on the pre-v20 stack (pinned digest verdictai/glm52-exl3-sparkinfer@sha256:01e8eec4… + the #163 overlay), TP4/DCP4, 4× RTX PRO 6000 Blackwell:

  • prefill 8k/32k/64k/128k: 2,287/2,218/2,078/1,920 → 3,742/3,557/3,381/3,032 tok/s (+58–64%), decode C1 unchanged
  • long-context retrieval on the same stack: 9/9 exact needles at 162K/190K/241K prompt tokens × depths 0.30/0.60/0.93

The public test image I published earlier (davidyoung/glm52-exl3-sparkinfer:v1-prefill-trellis-plan-20260722) predates this rebase and 31ff5a2a4 (the use_flattening next_n>2 MTP fix) — treat it as superseded for testing once the v20-based image is up. We serve MTP3 (next_n=3), so we'll revalidate tool-calling + decode on the new image and report back.

@brandonmmusic-max

Copy link
Copy Markdown
Author

@coderabbitai review

Fixes are in — the flattening gate now uses the canonical backend-aware use_b12x_sparse_indexer() predicate (6a1a9f9b3a), so --attention-backend B12X_MLA_SPARSE is honored with or without the env var. Done with changes; please re-verify.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Target module outline =="
ast-grep outline vllm/v1/attention/backends/mla/indexer.py --items all --type class,function --view expanded || true

echo
echo "== Targeted flattening gate and imports =="
rg -n -C 12 'use_b12x_sparse_indexer|use_flattening|B12X_MLA_SPARSE|VLLM_USE_B12X_SPARSE_INDEXER' \
  vllm/v1/attention/backends/mla/indexer.py

echo
echo "== Predicate definition and call sites =="
rg -n -C 8 'def use_b12x_sparse_indexer\b|use_b12x_sparse_indexer\s*\(' vllm --glob '*.py'

echo
echo "== Current PR diff for the relevant module =="
git diff --unified=40 'HEAD^' -- vllm/v1/attention/backends/mla/indexer.py

echo
echo "== Reported commit diff (when present in local history) =="
git show --format=fuller --stat --oneline 6a1a9f9b3a -- vllm/v1/attention/backends/mla/indexer.py || true
git show --format= --unified=40 6a1a9f9b3a -- vllm/v1/attention/backends/mla/indexer.py || true

Length of output: 43841


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== B12X request-detection implementation =="
sed -n '600,665p' vllm/model_executor/layers/sparse_attn_indexer.py

echo
echo "== Backend configuration symbols and B12X selection references =="
rg -n -C 6 'B12X_MLA_SPARSE|_b12x_sparse_indexer_requested|attention_backend' \
  vllm/model_executor/layers/sparse_attn_indexer.py \
  vllm/v1/attention/backends/mla/indexer.py \
  vllm --glob '*.py'

echo
echo "== Direct env-gated B12X branches in the metadata builder =="
sed -n '600,665p' vllm/v1/attention/backends/mla/indexer.py
sed -n '750,805p' vllm/v1/attention/backends/mla/indexer.py
sed -n '910,955p' vllm/v1/attention/backends/mla/indexer.py
sed -n '1080,1145p' vllm/v1/attention/backends/mla/indexer.py

echo
echo "== Existing focused tests for B12X flattening/backend selection =="
rg -n -C 5 'use_flattening|use_b12x_sparse_indexer|B12X_MLA_SPARSE|next_n' \
  tests --glob '*.py' | head -n 300

Length of output: 142171


@brandonmmusic-max Re-verified. The flattening gate now correctly uses use_b12x_sparse_indexer(), whose request detection recognizes both the environment setting and explicit B12X_MLA_SPARSE backend selection (including enum and string forms), while enforcing the CUDA/SM120 requirement.

Thus, with --attention-backend B12X_MLA_SPARSE and next_n > 2, use_flattening remains disabled even when VLLM_USE_B12X_SPARSE_INDEXER is unset. The reported issue is addressed. I did not run CUDA tests in this sandbox.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@brandonmmusic-max

Copy link
Copy Markdown
Author

Validation — rank-sliced tr3 MTP layer now loads & serves

The two fixes in 2cb2e08 let the MTP draft head load a rank-sliced EXL3 (hybrid_tr3_tail) MoE layer. Reproduced end-to-end on 4× RTX PRO 6000 (SM120, TP4/DCP4, MTP-3) with a tr3-converted MTP layer-78 head merged into GLM-5.2-EXL3-TR3-3.0bpw.

Image: verdictai/glm52-exl3-sparkinfer:v21-mtp78tr3-gg6722c1d-si1a88b38-cu132-sm120a (sha256:9b1befc1…)

Runtime note: a tr3 MTP layer also needs VLLM_EXL3_TRELLIS_MIN_M=1 so the MTP-N draft's small-m (m=1…N) GEMMs stay inside the Trellis cudagraph decode window; the default 4 raises EXL3 eager parity path entered during CUDA graph capture (m=3).

Load

Clean load, MTP-3 active. Before the fixes: KeyError: model.layers.78.mtp_block.mlp.experts.routed_experts.w2_rank0.mcg.

Throughput (gpu-util 0.96, auto-profiled KV)

  • KV cache: 1,132,544 tokens, max concurrency 2.16× @ 524K ctx — vs ~680K / ~1.3× with the BF16 MTP head (the tr3 head frees ~3.9 GiB/rank).
  • Prefill (C=1): 8k 2,521 / 64k 1,916 / 128k 1,765 tok/s.
  • Decode C1–C8: 89.7 / 152.6 / 183.8 / 225.3 / 238.0 / 271.9 / 278.6 / 293.5 tok/s.

Accuracy (unchanged — MTP is lossless)

  • Estonia (133K retrieval, c2×10): PASS 10/10.
  • LAVD (ledger consistency, c5×10): EXACT 5 / NEAR 5 / FAIL 0.

brandonmmusic-max and others added 8 commits July 25, 2026 17:28
Adds --quantization exl3: any exllamav3 EXL3 checkpoint loads through a
QuantizationConfig + LinearMethodBase pair that reproduces turboderp's
dense no_reconstruct forward argument-for-argument at the exl3_gemm
boundary (input suh sign-flip + Hadamard-128, direct trellis GEMM, output
svh + Hadamard-128), with mcg/mul1 sentinel semantics, legacy su/sv
expansion, reference pad/trim, and tp_import-discipline TP slicing
(KV replication; Qwen3.5 [(0,1,2),3] tuple shards).

Registry delta is exactly three lines (Literal member, lazy import,
mapping entry).

RoutedExperts.load_weights: qualify is_fused by the matched mapping
entry, not tensor rank alone — per-expert EXL3 trellis tensors are
rank-3 [K/16, N/16, 16*bpw] and were mangled by the fused
transpose/chunk path (fail-loud downstream, but wrong). Fused entries
are exactly those whose weight_name carries no per-expert index;
behavior for genuinely fused checkpoints is unchanged.

Backend enforces eager execution at build time: exl3_gemm autotunes with
timing launches on first call per (m-bucket, k, n, K) hash, which is
incompatible with CUDA-graph capture, and m-bucketing defeats warmup
coverage. Quantized lm_head with added vocabulary raises (TP slicing
would silently misalign). MoE expert loader is marked
supports_moe_loading for llama4-style loader paths.

GPU validation gates (owner-run): smoke-load Qwen3-0.6B-exl3, generate
on Qwen3.6-27B-exl3_3.30bpw, top-logprob comparison vs exllamav3 pinned
to no_reconstruct=True.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Modules carrying the MCG codebook marker are prepared once at
weight-processing time via b12x prepare_trellis256_dense_weight (zero-copy,
bits inferred from native storage) and applied through
run_trellis256_dense — the fused CuTeDSL kernel whose output is
byte-identical across batch size m. Legacy default-codebook and MUL1
shards, and any shard the fused prepare rejects, fall back per-shard to
the bit-faithful exllamav3_ext parity op, which remains the oracle.

VLLM_EXL3_FUSED=0 forces the parity path (A/B switch);
VLLM_EXL3_LOG_PATH_SELECTION=1 logs the per-shard selection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A multimodal wrapper offers the quantized head as language_model.lm_head
while the checkpoint storage map keys it as top-level lm_head. The
segment-collapse loop only considered interior segments, so the head
silently fell back to UnquantizedLinearMethod and loading the checkpoint's
lm_head.mcg then failed. Collapse leading model/language_model segments
too; exact-prefix candidates still take precedence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fused dense entry performs its outer rotations through
exllamav3_ext.had_r_128, imported by module name inside b12x. Load the
extension through the parity path's VLLM_EXL3_ABI_SHIM/VLLM_EXL3_EXT_PATH
contract first, so an all-fused model does not import an unshimmed
site-packages copy (undefined at::cuda::getCurrentCUDABlasHandle on
torch 2.12).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-up (CodeRabbit): _moe_prefix_is_exl3 hardcoded
gate_proj/up_proj/down_proj while _validate_codebooks keys off the layer's
ckpt_*_proj_name fields, so a remapped-projection MoE checkpoint would pass
validation logic but silently miss EXL3 detection. Thread the RoutedExperts
layer through and read its names, defaulting to the standard trio for layer
variants that do not carry them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: OpenAI Codex <codex@openai.com>

Signed-off-by: Brandon Music <brandon.m.music@gmail.com>
Brandon Music and others added 6 commits July 25, 2026 17:28
…MTP crash

Port two upstream vLLM fixes so tool calling works under MTP speculative decoding:

- structured_output: advance the grammar from the authoritative new_token_ids
  step delta instead of the counter-derived window, so the reasoning-end
  (</think>) marker is detected under async scheduling + spec decode and the
  tool-call grammar engages instead of emitting unconstrained output.
  (vllm-project#48516, supersedes vllm-project#44993.)

- deepseek_v32 DSA: derive has_indexer from index_k as well, so MTP draft steps
  1+ (skip_topk under index_share_for_mtp_iteration) no longer crash
  fused_norm_rope's index_k assertion. (vllm-project#48528.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Builds on the rank-sliced EXL3 (ExLlamaV3 trellis) support added in
local-inference-lab#139 by @brandonmusic, paired with the
trellis3_t256 planned MoE from local-inference-lab/sparkinfer#49. No change
to that work's weight loading, formats, or decode path; this commit
only extends its batch dispatch.

The vllm-project#139 backend serves every batch with m > 32 through the eager
exllamav3_ext parity path: fp16-in/fp32-out staging (~25 GB extra HBM
traffic per 4096-token step at GLM-5.2 geometry), python chunk loops,
and one expert re-stream per 128-row chunk. That covers 100% of
prefill, so prefill throughput is capped at ~2.0-2.3k tok/s while the
same weights decode at full speed through the planned Trellis window.

Build a second trellis_moe plan at max_tokens=max_num_batched_tokens
with block_size_m=64 (env VLLM_EXL3_PREFILL_BLOCK_M, allowed
{8,16,32,48,64}) beside the existing decode plan (32/8), and dispatch
three ways in _apply_rank_sliced:

* m in [min, max]      -> decode plan (unchanged)
* max < m <= capacity  -> prefill plan (sparkinfer bind already accepts
                          any tokens in [1, max_tokens]; zero kernel
                          changes needed)
* m < min              -> parity path, whose persistent staging
                          (xh/out32/token_sorted/weight_sorted) shrinks
                          from capacity rows to one chunk while the
                          prefill plan is live (~110 MiB/GPU returned)

VLLM_EXL3_PREFILL_TRELLIS=0 restores the exact single-plan parity
behavior (one-variable serving A/B lever). The parity branch now raises
during CUDA graph capture instead of silently recording eager ext
calls. The prefill arena (~1.05 GiB/GPU at capacity 3072) allocates
during the profile pass, so vLLM's memory profiler sees it and the KV
pool auto-shrinks instead of risking request-time OOM.

Matched A/B serving brandonmusic/GLM-5.2-EXL3-TR3-3.0bpw on the
published verdictai/glm52-exl3-sparkinfer runtime image (4x RTX PRO
6000 Blackwell, TP4/DCP4/MTP3, identical boot geometry, only the
kill-switch flipped, fresh JIT cache both arms; llm_decode_bench
prefill-only, exact /tokenize, C1, 60 s/context):

  ctx    parity   trellis   delta
  8k      2,287     3,742   +63.6%
  32k     2,218     3,557   +60.4%
  64k     2,078     3,381   +62.7%
  128k    1,920     3,032   +57.9%

Decode C1 unchanged-to-better; 57k-token needle retrieval exact.

(cherry picked from commit d57d775)
Optional companion to the prefill-plan commit; mocked plan/ext APIs,
no GPU, sparkinfer, or exllamav3_ext required. Covers plan
construction order, the three-way dispatch boundaries, chunk-capped
parity staging, the VLLM_EXL3_PREFILL_TRELLIS=0 kill-switch,
VLLM_EXL3_PREFILL_BLOCK_M override, elastic re-plan above scheduler
capacity, and the parity-path capture guard. Follows the existing
CPU-test pattern of tests/quantization/test_exl3.py from
local-inference-lab#139.

(cherry picked from commit f80a56d)
MTP next_n>2 was force-flattened onto the DeepGEMM next_n<=2 fallback, which
corrupts B12X sparse-indexer code generation. The B12X/sparkinfer indexer
supports native (B, next_n) on SM120, so keep it on the native path (FULL
cudagraphs preserved).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MTP draft head (deepseek_mtp) could not load a rank-sliced EXL3
(hybrid_tr3_tail) MoE layer -- e.g. a tr3-converted GLM-5.2 MTP layer 78 --
for two reasons:

1. get_draft_quant_config() built the draft Exl3Config via
   get_quantization_config() but never called maybe_update_config(), which is
   what hydrates rank_sliced_metadata from hybrid_tr3_tail (the target model
   receives this call in config/vllm.py). Without it the draft config's
   rank_sliced_metadata stayed None and the MTP experts fell back to the stock
   FusedMoE path.

2. DeepSeekMTP.load_weights() did not normalize rank-sliced weight names, so a
   checkpoint tensor `...experts.0.down_proj.rank0.mcg` mapped to
   `...routed_experts.w2_rank0.mcg` while Exl3MoEMethod registers the fused
   `w2_mcg` param -> KeyError. The target model (deepseek_v2.load_weights)
   already strips `.rank{r}` via normalize_rank_sliced_weight_name; mirror it.

With both fixes a tr3 MTP layer loads and serves. (Running a tr3 MTP layer also
needs VLLM_EXL3_TRELLIS_MIN_M=1 so the MTP-N draft's small-m GEMMs stay inside
the Trellis cudagraph window -- a runtime env, not a code change.)

Validated on GLM-5.2-EXL3-TR3-3.0bpw with an EXL3-TR3 MTP layer-78 head (TP4,
DCP4, MTP-3): loads clean, Estonia 10/10 PASS, LAVD EXACT 5 / NEAR 5 / FAIL 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsVTetPQQc5gP7LveJCWRE
@brandonmmusic-max

brandonmmusic-max commented Jul 25, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

Correcting my earlier wording on this comment, which understated the change and could have led to a shallower review.

This branch was rebased onto dev/gilded-gnosis @ 89b4a98d (head dc0286f) for the finalized Gilded Gnosis v20 base. The PR's own diff is the same size (16 files, +2452/-41), but the branch was replayed onto 20 newer upstream commits, so the code this PR's edits sit on top of has changed. Specifically:

  • vllm/v1/attention/backends/mla/indexer.py was modified by both sides. Upstream changed it +31/-2 in that span (including the new _align_block_table_width), and this PR's use_flattening predicate change (and not use_b12x_sparse_indexer()) was auto-merged onto that newer version by git rather than re-authored. That auto-merge is the main thing worth a careful look.
  • Upstream also moved mla_attention.py, b12x_mla_sparse.py, b12x_attn.py and the DCP workspace tests in the same span. Those are not in this PR's diff, but the EXL3 MoE/indexer paths interact with them at runtime.

So please review the rebased state on its merits rather than assuming a no-op rebase — particularly the indexer auto-merge and any interaction between the EXL3 Trellis path and the newer DCP/prefill work.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

@brandonmmusic-max I’ll review the rebased PR state against dev/gilded-gnosis at dc0286f, with particular attention to rebase conflicts and interactions with the finalized v20 base.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@brandonmmusic-max

brandonmmusic-max commented Jul 25, 2026

Copy link
Copy Markdown
Author

Rebased for the finalized Gilded Gnosis v20 base

Force-pushed exl3-backend — rebased onto dev/gilded-gnosis @ 89b4a98d, new head dc0286f (was 2cb2e085).

Diff size is unchanged (16 files, +2452/-41) and all 14 commits replayed without conflicts — but this is not a no-op rebase: the branch now sits on 20 newer upstream commits. In particular vllm/v1/attention/backends/mla/indexer.py was changed by both sides (upstream +31/-2 in that span, adding _align_block_table_width), so this PR'''s use_flattening predicate was auto-merged onto the newer file by git rather than re-authored. Upstream also moved mla_attention.py, b12x_mla_sparse.py and b12x_attn.py in the same span, which the EXL3 indexer/MoE paths touch at runtime.

Also validated against the finalized v20 common base

Per the v20 release note, EXL3 is enabled by rebasing the EXL3 source layer onto the pinned stack (the base ships no EXL3/Trellis loader), not by pointing the base tag at an EXL3 checkpoint. So in addition to the branch above, the EXL3 layer was rebased onto the v20-final pins and built:

Of the 12 runtime overlay files, only models/deepseek_v2.py and v1/attention/backends/mla/indexer.py changed in the finalized base. Both were re-derived from the v20-final versions with the EXL3 edits replayed on top, so v20's new DCP/indexer/prefill work is preserved rather than overwritten by the overlay — verified by diffing each against the v20-final tree (they differ by exactly the 10- and 20-line EXL3 additions and nothing else).

Validated image:

verdictai/glm52-exl3-sparkinfer:v23-gg-v20final-vllm5517197-sibe0edca-cu132-sm120a@sha256:da185fe8bd71f5dee866e651695b1265f05338fc467394fcd75585d78ea20c15

Runtime validation (4x RTX PRO 6000 Blackwell SM120a, TP4/DCP4, MTP-3, NVFP4 DS-MLA KV)

  • engine reports v0.11.2.dev280+gilded.gnosis.v20.vllm5517197.sibe0edca.fi801d57a.cu132.20260725
  • vLLM is using nccl==2.30.4
  • EXL3 rank-sliced runtime planned: Trellis m=1..32 block_m=8, prefill trellis block_m=64 arena=1054.2MiB capacity=3072 chunk=128 topk=8
  • v20 DCP prefill auto-policy resolved for TP4/DCP4: query split 1, full CKV gather 1, top-k owner merge 1, indexer shards 0, CKV prefetch depth 1, DCP_PREFILL_WORKSPACE=1 (VLLM_DCP_PROJECT_BEFORE_MERGE=1 + VLLM_B12X_MLA_DCP_GATHER_IN_WORKSPACE=1)
  • Preallocated 30.8 MiB for 2 persistent CKV execution lane(s), Using native CKV layer prefetch with depth=1 and 2 workspace slots
  • GPU KV cache 1,115,904 tokens (2.13x at 524,288 per request)
  • Estonia c2 x5: 5/5 pass, 0 fail. LAVD c5 x5: 2 EXACT / 3 NEAR / 0 FAIL. Correct rate 1.00 on both — no regression.

Two notes for the runtime side:

  1. This EXL3 preset invokes vllm serve directly for the EXL3 flags, so it bypasses /usr/local/bin/serve-gilded-gnosis.sh and DCP_*=auto never resolves. The resolved TP4/DCP4 values are now set explicitly in serve.sh / docker-compose.yml (pulled from the launcher via DRY_RUN=1). Worth calling out in the Compose contract for direct-serve integrations.
  2. Loading safetensors using InstantTensor loader does not appear on this path — the EXL3 checkpoint loads through the EXL3 rank-sliced loader. All other boot assertions hold.

@brandonmmusic-max

brandonmmusic-max commented Jul 25, 2026

Copy link
Copy Markdown
Author

One CI note: pre-run-check is failing on this PR with

PR must have the 'verified', 'ready', or 'ready-run-all-tests' label
(the ready labels also trigger tests) or the author must have at least
4 merged PRs (found 1).

This is the repo's label gate rather than anything in the diff — it was already failing on the previous head (2cb2e085) before today's rebase, so it is pre-existing and not introduced by the rebase. I only have pull access here, so I can't apply the label myself.

Also noting CodeRabbit reports Review rate limited on this PR, which is why it has not re-reviewed the rebased code yet.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== PR/base topology and changed files =="
git status --short
git branch --show-current
git merge-base HEAD origin/dev/gilded-gnosis || true
git diff --stat "$(git merge-base HEAD origin/dev/gilded-gnosis)" HEAD
git diff --name-status "$(git merge-base HEAD origin/dev/gilded-gnosis)" HEAD

echo
echo "== Target module structure =="
ast-grep outline vllm/v1/attention/backends/mla/indexer.py --items all --type class,function --view expanded
ast-grep outline vllm/model_executor/layers/quantization/exl3.py --items all --type class,function --view expanded
for f in mla_attention.py b12x_mla_sparse.py b12x_attn.py; do
  fd -a -t f "^${f}$" vllm -x ast-grep outline {} --items all --type class,function --view expanded \;
done

echo
echo "== PR diff: indexer auto-merge target =="
base="$(git merge-base HEAD origin/dev/gilded-gnosis)"
git diff --unified=60 "$base" HEAD -- vllm/v1/attention/backends/mla/indexer.py

echo
echo "== Current flattening/alignment context and B12X predicate =="
rg -n -C 18 '_align_block_table_width|use_flattening|use_b12x_sparse_indexer|next_n' \
  vllm/v1/attention/backends/mla/indexer.py \
  vllm/model_executor/layers/sparse_attn_indexer.py

echo
echo "== EXL3 rank-sliced planning, capture, DCP/prefill, and workspace references =="
rg -n -C 10 'rank_sliced|_apply_rank_sliced|prefill|plan|capture|workspace|dcp|DCP|block_m|Trellis' \
  vllm/model_executor/layers/quantization/exl3.py \
  vllm/model_executor/models/deepseek_v2.py \
  vllm/model_executor/models/glm4_moe.py \
  vllm/v1/attention/backends/mla/indexer.py \
  vllm/v1/attention/backends/mla \
  --glob '*.py'

echo
echo "== Focused test coverage and current diff for relevant tests =="
rg -n -C 8 'align_block_table|use_flattening|use_b12x_sparse_indexer|rank_sliced|prefill.*trellis|Trellis|DCP' \
  tests --glob '*.py'
git diff --unified=30 "$base" HEAD -- \
  tests/quantization/test_exl3.py \
  tests/quantization/test_exl3_prefill_plan.py \
  vllm/model_executor/layers/quantization/exl3.py

Length of output: 2675


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Available repository refs and remotes =="
git remote -v
git branch -a
git show -s --format='HEAD %H%nparents %P%nsubject %s' HEAD
git for-each-ref --format='%(refname:short) %(objectname) %(subject)' \
  refs/heads refs/remotes | head -n 200

echo
echo "== Pull request metadata =="
gh pr view 139 --repo local-inference-lab/vllm \
  --json number,headRefName,headRefOid,baseRefName,baseRefOid,mergeStateStatus \
  || true

echo
echo "== Resolve a usable comparison base and inspect rebase-sensitive paths =="
base=""
for candidate in \
  "origin/dev/gilded-gnosis" \
  "dev/gilded-gnosis" \
  "origin/89b4a98d" \
  "89b4a98d"; do
  if git rev-parse --verify --quiet "${candidate}^{commit}" >/dev/null; then
    base="$(git merge-base HEAD "$candidate")"
    echo "Resolved candidate: $candidate -> merge base: $base"
    break
  fi
done

if [ -z "$base" ]; then
  echo "No local base ref was found; showing HEAD source context without a diff."
else
  echo "== Changed files and relevant PR diffs =="
  git diff --stat "$base" HEAD
  git diff --name-status "$base" HEAD
  git diff --unified=60 "$base" HEAD -- \
    vllm/v1/attention/backends/mla/indexer.py \
    vllm/model_executor/layers/quantization/exl3.py \
    tests/quantization/test_exl3.py \
    tests/quantization/test_exl3_prefill_plan.py
fi

echo
echo "== Module maps =="
ast-grep outline vllm/v1/attention/backends/mla/indexer.py --items all --type class,function --view expanded
ast-grep outline vllm/model_executor/layers/quantization/exl3.py --items all --type class,function --view expanded

echo
echo "== Current indexer alignment/flattening and B12X detection =="
rg -n -C 20 '_align_block_table_width|use_flattening|use_b12x_sparse_indexer|next_n' \
  vllm/v1/attention/backends/mla/indexer.py \
  vllm/model_executor/layers/sparse_attn_indexer.py

echo
echo "== EXL3 planned dispatch, capture safety, DCP and workspace interactions =="
rg -n -C 12 'def _apply_rank_sliced|def process_weights_after_loading|def apply\(|prefill|capture|workspace|DCP|dcp|plan|block_m|trellis' \
  vllm/model_executor/layers/quantization/exl3.py \
  vllm/v1/attention/backends/mla \
  --glob '*.py'

echo
echo "== Relevant test definitions and assertions =="
rg -n -C 8 'def test_.*(flatten|align|b12x|rank_sliced|prefill|capture)|use_flattening|_align_block_table_width|_apply_rank_sliced' \
  tests --glob '*.py'

Length of output: 202941


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== GitHub PR base/head and changed-file list =="
gh api repos/local-inference-lab/vllm/pulls/139 \
  --jq '{base: (.base.ref + "@" + .base.sha), head: (.head.ref + "@" + .head.sha), mergeable: .mergeable, changed_files: .changed_files}'
gh api --paginate repos/local-inference-lab/vllm/pulls/139/files \
  --jq '.[] | [.filename, .status, .additions, .deletions] | `@tsv`'

echo
echo "== Local commits and all available parent diff summaries =="
git show -s --format='HEAD=%H%nparents=%P%nsubject=%s' HEAD
git log --oneline -n 20
git diff --stat HEAD^ HEAD

echo
echo "== Current indexer: imports, alignment helper, and flattening gate =="
rg -n '^(def _align_block_table_width|class DeepseekV32IndexerMetadataBuilder|    def |from .*sparse_attn_indexer import|import .*sparse_attn_indexer)' \
  vllm/v1/attention/backends/mla/indexer.py
sed -n '1,110p' vllm/v1/attention/backends/mla/indexer.py
sed -n '600,700p' vllm/v1/attention/backends/mla/indexer.py
sed -n '730,830p' vllm/v1/attention/backends/mla/indexer.py
sed -n '1080,1165p' vllm/v1/attention/backends/mla/indexer.py

echo
echo "== Current B12X request predicate =="
rg -n -C 15 'def use_b12x_sparse_indexer|def _b12x_sparse_indexer_requested|B12X_MLA_SPARSE' \
  vllm/model_executor/layers/sparse_attn_indexer.py

echo
echo "== EXL3 rank-sliced method boundaries =="
rg -n '^\s*def (_apply_rank_sliced|_ensure_rank_sliced|_plan|_get|apply|process_weights_after_loading)|^\s*class Exl3MoEMethod' \
  vllm/model_executor/layers/quantization/exl3.py
rg -n -C 14 'def _apply_rank_sliced|is_current_stream_capturing|capture|prefill_plan|decode_plan|workspace|bind\(' \
  vllm/model_executor/layers/quantization/exl3.py

echo
echo "== Exact PR patches from GitHub for the relevant files =="
for path in \
  vllm/v1/attention/backends/mla/indexer.py \
  vllm/model_executor/layers/quantization/exl3.py \
  tests/quantization/test_exl3_prefill_plan.py \
  tests/quantization/test_exl3.py; do
  echo "--- $path ---"
  gh api "repos/local-inference-lab/vllm/pulls/139/files?per_page=100" \
    --jq ".[] | select(.filename == \"$path\") | .patch" \
    | sed -n '1,260p'
done

Length of output: 59085


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Indexer constructor: final auto-merged context =="
sed -n '337,450p' vllm/v1/attention/backends/mla/indexer.py

echo
echo "== Indexer alignment use sites and focused regression test =="
rg -n -C 10 '_align_block_table_width' \
  vllm/v1/attention/backends/mla/indexer.py \
  tests/model_executor/layers/test_sparse_attn_indexer_b12x.py
sed -n '1088,1165p' tests/model_executor/layers/test_sparse_attn_indexer_b12x.py

echo
echo "== EXL3 runtime cache key, plan allocation, and all binding paths =="
sed -n '1325,1575p' vllm/model_executor/layers/quantization/exl3.py
sed -n '1570,1708p' vllm/model_executor/layers/quantization/exl3.py
rg -n -C 6 '_RANK_SLICED_RUNTIMES|exl3_trellis_weights|exl3_pointer_tables|exl3_expert_map' \
  vllm/model_executor/layers/quantization/exl3.py

echo
echo "== Final MTP-layer loading patch and supporting weight-name/model loading paths =="
git diff --unified=50 HEAD^ HEAD -- \
  vllm/model_executor/models/deepseek_mtp.py \
  vllm/model_executor/models/utils.py
rg -n -C 12 'normalize_rank_sliced_weight_name|mtp_block|load_weights' \
  vllm/model_executor/models/deepseek_mtp.py \
  vllm/model_executor/models/deepseek_v2.py \
  vllm/model_executor/models/glm4_moe.py \
  vllm/model_executor/models/utils.py

echo
echo "== Test collection references for target/draft resource separation and EXL3 MTP loading =="
rg -n -C 5 'target.*draft|draft.*target|rank.sliced|hybrid_tr3_tail|mtp_block|_RANK_SLICED_RUNTIMES' \
  tests/quantization/test_exl3.py \
  tests/quantization/test_exl3_prefill_plan.py \
  tests/model_executor \
  --glob '*.py' | head -n 600

Length of output: 96012


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Line-numbered EXL3 runtime key and mutable buffers =="
nl -ba vllm/model_executor/layers/quantization/exl3.py | sed -n '1355,1665p'

echo
echo "== All cache lifecycle/ownership references =="
rg -n -C 10 '_RANK_SLICED_RUNTIMES|rank_sliced_runtime|trellis_scratch|prefill_scratch|token_sorted|expert_count' \
  vllm tests --glob '*.py'

echo
echo "== Existing model/draft execution and graph resource isolation hooks =="
rg -n -C 8 'draft.*(workspace|scratch|graph|capture)|target.*(workspace|scratch|graph|capture)|(workspace|scratch|graph|capture).*draft' \
  vllm tests --glob '*.py' | head -n 800

Length of output: 1042


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Line-numbered EXL3 runtime key and mutable-buffer use =="
awk 'NR >= 1355 && NR <= 1665 { printf "%6d  %s\n", NR, $0 }' \
  vllm/model_executor/layers/quantization/exl3.py

echo
echo "== Cache lifecycle and ownership tests (bounded) =="
rg -n -C 8 '_RANK_SLICED_RUNTIMES|rank_sliced_runtime|trellis_scratch|prefill_scratch|token_sorted|expert_count' \
  vllm/model_executor/layers/quantization/exl3.py \
  tests/quantization \
  tests/model_executor \
  --glob '*.py' | head -n 500

echo
echo "== Target/draft graph-resource isolation mechanisms relevant to this runtime =="
rg -n -C 6 'draft.*(workspace|scratch|graph|capture)|target.*(workspace|scratch|graph|capture)|(workspace|scratch|graph|capture).*draft' \
  vllm/v1 \
  vllm/model_executor \
  tests/model_executor \
  tests/distributed \
  --glob '*.py' | head -n 500

Length of output: 82057


@brandonmmusic-max I reviewed the rebased state against 89b4a98d.

The indexer.py auto-merge looks correct: it preserves the upstream _align_block_table_width allocation/stride fix and its regression test, while the new use_flattening condition uses the canonical backend-aware B12X predicate. Thus, explicit B12X_MLA_SPARSE selection retains the native (B, next_n) MTP path even without the environment flag.

I found one issue in the EXL3 runtime integration:

  • vllm/model_executor/layers/quantization/exl3.py, Line 1387: _RANK_SLICED_RUNTIMES is module-global and its cache key contains only device, shape, and planner settings—not the RoutedExperts layer/model identity. The cached value includes mutable Trellis scratch buffers (Lines 1475–1477, passed to bind at Lines 1580–1604) as well as mutable parity staging/sort buffers (Lines 1485–1534, mutated at Lines 1620–1673).

    A target MoE layer and rank-sliced MTP draft layer with matching dimensions therefore reuse the same runtime entry and scratch allocations. This defeats the target/draft resource isolation needed for their independently captured graphs; shared scratch can make graph lifetime/execution depend on the other model.

    Please scope the runtime to its owning layer/model—e.g., make it layer-owned, or include a stable layer/weight-storage identity in the key—and add a regression test that creates two same-shape layers and verifies their runtime/scratch objects are distinct.

I did not run CUDA tests in this sandbox.

The rank-sliced runtime cache was keyed only on device, dtype, shape, topk and
planner settings. The cached value owns mutable Trellis/prefill scratch plus
parity staging and sort buffers, so any two layers that hash equal share those
allocations.

A target MoE layer and a rank-sliced MTP draft layer collide on every component
of that key: same hidden/intermediate size, same local expert count, same topk,
identical planner env, and both resolve max_num_batched_tokens from the same
scheduler config. The draft therefore reused the target's scratch, defeating the
target/draft resource isolation that their independently captured CUDA graphs
depend on and coupling graph lifetime across the two models.

Key on the owning quant config instead. The draft is built with its own
Exl3Config while every layer of one model shares a single config, so each model
gets exactly one runtime. This is deliberately coarser than per-layer ownership:
the prefill arena alone is ~1 GiB, so a per-layer cache would cost tens of GiB
per rank on a 75+ layer model.

Adds CPU regression tests asserting that two same-shape layers owned by
different models produce distinct, stable scopes and cache keys that differ only
by that scope.
@brandonmmusic-max

Copy link
Copy Markdown
Author

@coderabbitai Confirmed and fixed in 26c4bfd — thanks, this was a real bug and it lands exactly on the MTP-78 work in this PR.

I verified the collision rather than assuming it: for GLM-5.2 the MTP draft layer is architecturally a copy of a decoder layer, so every component of the old key matches between a target MoE layer and the draft — same exl3_hidden_size, exl3_intermediate_size_per_partition, local_num_experts, topk, identical planner env, and both resolve exl3_max_num_batched_tokens from the same scheduler_config (exl3.py L1078-1084). So the draft was reusing the target's trellis_scratch / prefill_scratch and the parity staging and sort buffers, exactly as you describe.

One correction to the suggested remedy. Making the runtime layer-owned would break the model. The cached value holds the prefill arena, which is ~1054 MiB on our production config (the runtime logs prefill trellis block_m=64 arena=1054.2MiB). With 75+ rank-sliced MoE layers that is tens of GiB per rank on top of 78 GiB of weights — it would OOM immediately. The global cache is load-bearing for memory, which is presumably why it was written that way; the defect is its granularity, not its existence. (Noting you flagged that you did not run CUDA in the sandbox, so this isn't something the static read would have surfaced.)

So the fix scopes the runtime to its owning model rather than to each layer:

  • New _runtime_scope_id(quant_config) assigns a stable id per Exl3Config and is now the leading element of the cache key.
  • The draft model is constructed with its own Exl3Config (see models/utils.py, where this PR hydrates the draft quant config), while every layer of one model shares a single config.
  • Net effect: all target layers keep sharing one runtime, the draft gets its own. Cost is one additional arena, not 76.

Regression tests added in tests/quantization/test_exl3.py, CPU-only so they run without a GPU:

  • test_rank_sliced_runtime_scope_is_per_owning_model — two owning configs get distinct scopes, and each scope is stable across repeat calls (so target layers still share).
  • test_rank_sliced_runtime_key_differs_across_models_with_same_shape — two same-shape layers owned by different models produce different keys, and asserts target_key[1:] == draft_key[1:], proving the scope is the only thing preventing the collision.

Both pass locally. I have not been able to run the CUDA suite against this head yet — pre-run-check is failing on this repo's label gate (needs ready/verified, or 4+ merged PRs; author has 1), so CI has not exercised the rebase. Flagging that rather than implying it is verified.

Also confirming your read on the indexer.py auto-merge matches mine: upstream's _align_block_table_width and its regression test are preserved, and the use_flattening change uses the backend-aware predicate so explicit B12X_MLA_SPARSE selection keeps the native (B, next_n) MTP path without the env flag.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Commit availability and affected paths =="
git show --no-ext-diff --format='commit %H%n%s' --name-status 26c4bfd -- \
  vllm/model_executor/layers/quantization/exl3.py \
  vllm/model_executor/models/utils.py \
  tests/quantization/test_exl3.py

echo
echo "== EXL3 cache scope/key definitions and cache access =="
rg -n -C 14 \
  'def _runtime_scope_id\b|_RANK_SLICED_RUNTIMES|runtime_key|rank_sliced.*key|Exl3Config' \
  vllm/model_executor/layers/quantization/exl3.py

echo
echo "== Draft quantization-config construction and propagation =="
rg -n -C 14 \
  'Exl3Config|quant_config|draft.*quant|draft_quant|maybe_update_config' \
  vllm/model_executor/models/utils.py

echo
echo "== Claimed CPU-only regression tests =="
rg -n -C 18 \
  'def test_rank_sliced_runtime_scope_is_per_owning_model\b|def test_rank_sliced_runtime_key_differs_across_models_with_same_shape\b|_runtime_scope_id|_rank_sliced.*key|_RANK_SLICED_RUNTIMES' \
  tests/quantization/test_exl3.py

echo
echo "== Exact fix patch =="
git show --no-ext-diff --format= --unified=55 26c4bfd -- \
  vllm/model_executor/layers/quantization/exl3.py \
  vllm/model_executor/models/utils.py \
  tests/quantization/test_exl3.py

Length of output: 34815


@brandonmmusic-max Re-verified 26c4bfd; the fix addresses the finding.

_runtime_scope_id(self.quant_config) is now the leading component of the rank-sliced runtime cache key, while the remaining key components preserve sharing among same-model layers. get_draft_quant_config() separately constructs and hydrates the draft EXL3 config, so target and MTP draft layers cannot reuse mutable Trellis, prefill, parity-staging, or sort buffers even when all shape/planner settings match.

The new CPU tests correctly cover distinct/stable owner scopes and prove that two otherwise identical keys differ only by scope, while same-owner keys remain equal. I did not run CUDA tests in this sandbox.


✏️ Learnings added
Learnt from: brandonmmusic-max
URL: https://github.com/local-inference-lab/vllm/pull/139

Timestamp: 2026-07-25T22:31:26.607Z
Learning: In `vllm/model_executor/layers/quantization/exl3.py`, `_RANK_SLICED_RUNTIMES` intentionally shares a runtime among all rank-sliced EXL3 MoE layers owned by one `Exl3Config`, because the planned Trellis prefill arena is approximately 1 GiB and per-layer runtime allocation would exhaust GPU memory. The cache key must nevertheless include a stable per-owning-`Exl3Config` scope: target and MTP draft models require separate mutable Trellis/prefill scratch and parity staging buffers for independently captured CUDA graphs.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@brandonmmusic-max

Copy link
Copy Markdown
Author

Runtime bumped to v24 (target/draft scratch isolation)

The validated runtime referenced in this PR is now:

verdictai/glm52-exl3-sparkinfer:v24-gg-v20final-scopefix-vllm5517197-sibe0edca-cu132-sm120a@sha256:8753406fd7a05693c314e3f7b08df5c338bdeb548b1e487b7865a465e6b480b9

It supersedes the earlier v23 image, which predates the rank-sliced runtime scoping fix. The pins in the description are updated accordingly.

Why: the EXL3 rank-sliced runtime cache was keyed only on device/dtype/shape/topk/planner settings, and the cached entry owns mutable Trellis/prefill scratch. The MTP-78 draft layer matches a target MoE layer on every one of those components, so it was reusing the target's scratch. The key is now scoped to the owning quant config, giving each model its own runtime.

Measured effect (the fix is visible in memory, not in the log -- the planner line uses info_once and is deduplicated):

Rank-sliced runtimes GPU KV cache Concurrency @524K
v23 (shared) 1 (target+draft collide) 1,115,904 tokens 2.13x
v24 (scoped) 2 (isolated) 996,352 tokens 1.90x

The 119,552-token KV reduction corresponds to ~1056 MiB against a 1054.2 MiB arena, which is direct evidence that a second runtime is allocated. The reduced KV capacity is the intended cost of the isolation, and it is also why the fix is scoped per model rather than per layer -- per-layer ownership would have needed tens of GiB per rank across 75+ layers.

Quality on v24: Estonia c2 x5 = 5/5 pass. LAVD c5 across two independent 5-run samples = 4 EXACT / 5 NEAR / 1 FAIL over 10 runs (sample 1: 2E/3N/1F, sample 2: 2E/3N/0F, matching the pre-fix image exactly). The single miss was a wrong ledger total at 7,114 completion tokens against a 24,576 cap, so answer quality rather than truncation, and within this profile's historical spread.

Reporting the aggregate including the failure rather than only the clean sample.

@brandonmmusic-max

Copy link
Copy Markdown
Author

Runtime bumped to v26 — supersedes the v24 note above

verdictai/glm52-exl3-sparkinfer:v26-gg-v20final-scopefix-archkey-vllm5517197-sibe0edca-cu132-sm120a@sha256:2bb9e804a283d1da3b7e3425ff87375121285141d0d0a40d3dc09d41bf881a10

The description pins have been updated to this image. v24 and the intermediate v25 are superseded — please disregard both.

What changed since v24. v24 carried the per-model rank-sliced runtime scoping (target/draft scratch isolation). v25 additionally carried a change to the compile-cache architecture key that CodeRabbit correctly identified as a no-op: torch.cuda.get_device_capability() / get_device_name() already resolve against torch.cuda.current_device(), so passing that ordinal explicitly changed nothing, and the process-wide key could still freeze whichever GPU was current on the first call. That attempt also broke test_device_arch_key_retries_after_unavailable, whose torch stubs take no device argument.

v26 carries the real fix:

  • the architecture key is memoized per device ordinal, so each entry is bound to the device it was measured on;
  • the ordinal is threaded through _static_compile_cache_context, which is lru_cached on the compile callable and would otherwise have re-frozen the identity at that layer regardless;
  • the returned key still omits the ordinal, so GPUs of the same architecture keep sharing compiled artifacts rather than fragmenting the JIT cache per rank.

This is not academic on the validation rig: its four boards report two different device names (Max-Q and non-Max-Q) while sharing compute capability 12.0.

Validation on v26 (4x RTX PRO 6000 SM120a, TP4/DCP4, MTP-3, NVFP4 DS-MLA KV):

Suite Config Result
Estonia c2, 5 runs 5/5 pass, 0 fail, correct rate 1.00
LAVD c5, 5 runs 3 EXACT / 2 NEAR / 0 FAIL, correct rate 1.00

GPU KV cache 998,656 tokens (1.90x at 524,288). That is ~1 GiB below the pre-isolation 1,115,904 (2.13x), which is the intended cost of giving the draft model its own Trellis/prefill arena.

Unit tests: tests/_lib/test_compile_cache.py 6 passed, including a new case that models a mixed rig and asserts the second board does not inherit the first board's architecture identity. The one failure elsewhere in tests/_lib/ reproduces with these changes stashed and is pre-existing.

CI status unchanged: pre-run-check still fails on this repo's label gate, so the test suite has not run against this head. Everything above is local validation.

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