[EXL3] Route prefill batches through the planned Trellis MoE (+58-64% prefill) - #1
davidsyoung wants to merge 2 commits into
Conversation
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.
|
👋 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 If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. 🚀 |
|
Companion sparkinfer build-compat PR (independent; needed to build this stack on public PyPI DSL 4.6.0): brandonmmusic-max/b12x#1 |
|
Re-targeting to the upstream repo (local-inference-lab/vllm) per review — closing this one. |
Stacked on your
exl3-backendbranch (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_slicedserves every batch withm > 32through the eagerexllamav3_extparity 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_moeplan atmax_tokens=max_num_batched_tokenswithblock_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 — sparkinferbindalready accepts anytokens ∈ [1, max_tokens], so zero kernel changes are neededm < 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=0restores 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.0bpwon your publishedverdictai/glm52-exl3-sparkinferruntime 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):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.pypattern — 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.