Skip to content

[EXL3] Route prefill batches through the planned Trellis MoE (+58-64% prefill) - #1

Closed
davidsyoung wants to merge 2 commits into
brandonmmusic-max:exl3-backendfrom
davidsyoung:exl3-prefill-trellis-plan
Closed

davidsyoung wants to merge 2 commits into
brandonmmusic-max:exl3-backendfrom
davidsyoung:exl3-prefill-trellis-plan

Conversation

@davidsyoung

Copy link
Copy Markdown

Stacked on your exl3-backend branch (local-inference-lab#139) — merging here lands the commits inside vllm-project#139 with no history rewrite. No change to weight loading, formats, or the decode path; this only extends the rank-sliced batch dispatch.

Problem

_apply_rank_sliced 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, capping prefill at ~2.0–2.3k tok/s while the same weights decode at full speed through the planned Trellis window.

Change

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

  • m ∈ [min, max] → decode plan (unchanged)
  • max < m ≤ capacity → prefill plan — sparkinfer bind already accepts any tokens ∈ [1, max_tokens], so zero kernel changes are 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 the memory profiler sees it and the KV pool auto-shrinks instead of risking request-time OOM.

Measured

Matched A/B serving brandonmusic/GLM-5.2-EXL3-TR3-3.0bpw on your published verdictai/glm52-exl3-sparkinfer runtime image (4× 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 (99.7→101.8 aggregate across our runs); 57k-token needle retrieval exact; zero request errors.

Second commit adds CPU contract tests (mocked plan/ext APIs, no GPU/sparkinfer/ext needed) following the test_exl3.py pattern — happy to drop it if you'd rather keep the branch minimal.

Related build-compat PR on the sparkinfer side (independent, for PyPI DSL 4.6.0 users): see the companion PR on exl3-trellis-fused.

David Young added 2 commits July 22, 2026 12:45
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/b12x#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.
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.
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

Just a reminder: PRs would not trigger full CI run by default.

Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging.

To run CI, PR reviewers can either: Add ready label to the PR or enable auto-merge.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

🚀

@davidsyoung

Copy link
Copy Markdown
Author

Companion sparkinfer build-compat PR (independent; needed to build this stack on public PyPI DSL 4.6.0): brandonmmusic-max/b12x#1

@davidsyoung

Copy link
Copy Markdown
Author

Re-targeting to the upstream repo (local-inference-lab/vllm) per review — closing this one.

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.

1 participant