[LoRA] bf16 MoE-LoRA for trtllm backend with two-stream overlap + routing opts - #28466
Closed
yushengsu-thu wants to merge 11 commits into
Closed
yushengsu-thu wants to merge 11 commits into
yushengsu-thu wants to merge 11 commits into
Conversation
…uantized models The experimental_sgl_trtllm fused MoE-LoRA kernels are FP8/NVFP4-only (no bf16 fused kernel exists), so a bf16 model + LoRA crashes with "experimental_sgl_trtllm LoRA currently requires FP8 block quant." When the checkpoint is unquantized (bf16) and LoRA is enabled and the requested MoE runner is experimental_sgl_trtllm, set moe_runner_backend=triton once in scheduler.init_moe_gemm_config — BEFORE initialize_moe_config publishes the global backend. This makes the whole stack (base FusedMoE weight layout, topk, moe_align_block_size, the LoRA runner) consistently use the Triton decomposed virtual-experts MoE-LoRA path, which supports bf16. A per-layer patch is insufficient because the base FusedMoE pads/permutes weights into the trtllm block layout at build time when the global backend is experimental. FP8/NVFP4 checkpoints (quantization_config present) are unaffected and keep the fused experimental path. A true bf16 *fused* MoE-LoRA CUDA kernel (for the experimental fast path on bf16) is left as future work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… for unquantized models" This reverts commit f2f9819.
Adds a true bf16 LoRA pipeline to the experimental_sgl_trtllm backend (it was FP8/NVFP4-only and asserted "requires FP8 block quant" on bf16 checkpoints). Mirrors the FP4 decomposed (unfused-activation) design with the two NvFP4 quant stages REMOVED — bf16 end-to-end: routing -> moe::dev::permute (bf16 gather) -> gate_up raw grouped GEMM (Gemm2::Runner, Bf16xBf16->Bf16, K=hidden, N=2*inter, gated-interleaved out) -> moe::dev::activation (de-interleaves on read, adds gate_up_lora_delta pre-SwiGLU, captures activation_lora_input, bf16 out) -> down GEMM (Gemm2::Runner, K=inter, N=hidden) -> finalize. - jit_kernel csrc: new Bf16LoraLauncher + sgl_trtllm_bf16_routed_moe_lora wrapper + TVM FFI export. No new device kernels: reuses moe::dev::permute / activation / finalize and the trtllm-gen Gemm2 cubins the plain bf16 path already uses. Weights are the SAME prepared bf16 tensors (shuffled + BlockMajorK) — layout untouched. - jit_kernel core.py/__init__.py: trtllm_bf16_routed_moe_lora binding (finalize reuses the dtype-agnostic bf16 finalize already shared by fp4). - srt lora_layer.py: bf16 (unquantized) detection -> minimal FlashInferTrtllmBf16MoeQuantInfo; FP8 asserts now only gate quantized checkpoints. dispatch routes Bf16 quant-info to the new fused fn. - srt lora_dispatch.py: fused_experts_none_to_experimental_sgl_trtllm_bf16_lora (single-stream; mirrors the fp4 flow: virtual-experts gate_up delta via merged_experts_fused_moe_lora_add, bf16 hidden fed directly, down-LoRA merged after finalize with fuse_sum_all_reduce). FP8/FP4 paths are byte-for-byte untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…en the quant-info is the bf16 one The generic MoE CUDA-graph buffer init assumed the FP8/FP4 field names (w13_weight/w2_weight); FlashInferTrtllmBf16MoeQuantInfo names them gemm1_weights/gemm2_weights. Fall back accordingly (shapes equivalent). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ights bf16 BlockMajorK-prepared weights are 4-D [E, N, K/128, 128]; read dims 0/1 instead of unpacking exactly 3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror of the FP8/FP4 two-stream MoE dispatches for the bf16 path: - moe_overlap.py: fused_experts_none_to_experimental_sgl_trtllm_bf16_lora_two_stream gate_up LoRA shrink/expand on the shared side stream, concurrent with the bf16 op's routing + permute + gate_up GEMM; the op waits on lora_ready_event right before its activation kernel (events already plumbed in the .cu). Down-LoRA stays serial (FP8/FP4 bench verdict: down/finalize overlap is net-negative and corrupts base decode under graph replay). - __init__.py: save/patch the bf16 fn alongside fp8/fp4 in install_two_stream_overrides (master switch SGLANG_EXPERIMENTAL_LORA_OPTI=1). FP8/FP4 paths untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/sort fusion) At decode bs16 the bf16 MoE-LoRA path spends ~10.2us/layer in the unfused routing-align pair (_fused_virtual_topk_ids + moe_align_block_size_small_batch) because the fused single-launch align/scatter path in _get_routing is gated `not shared_outer and ep_local` — and the expert_shared config is shared_outer. The fused kernel already supports shared_outer end to end: compute_virtual_id uses base=0 (lora-id routing), the launcher only guards num_experts<=1024, and the python wrapper already sizes num_experts_for_weight=1. The only blocker was the python gate. Widen it to `(shared_outer or ep_local)` and pass `compact=not shared_outer` (compact mis-maps the shared_outer id as base-offset; moe_lora_merged_align also gates compact_eff internally as a backstop). Non-shared_outer behavior is unchanged (`(False or ep_local)` == old `ep_local`). Perf — Qwen3-30B-A3B-Instruct-2507-BF16 expert_shared, GB300 TP4/EP4, decode (SGLANG_OPT_LORA_FUSED_MERGED_ALIGN 0 vs 1; in=out=2048): | bs | decode tok/s off -> on | decode | e2e | prefill | |----|------------------------|--------|-------|---------| | 16 | 2315 -> 2569 | +11.0% | -9.2% | flat | | 32 | 4228 -> 4647 | +9.9% | -8.2% | flat | | 64 | 7290 -> 7929 | +8.8% | -7.1% | flat | Profile (graph-off): moe_align_block_size_small_batch 384 -> 0 launches (eliminated), replaced by the single fused_align_scatter launch. Correctness: the routing change is dtype-independent. dev/check_fused_align_equiv proves the fused path is bitwise-equal to the old fallback for the bf16/FP8 shape (128 experts, EP4) and the NVFP4/Kimi shape (384 experts, EP8), shared_outer and per-expert, 50/50 each -> identical MoE-LoRA output, so FP8/NVFP4 accuracy is unchanged. NOTE SGLANG_OPT_LORA_FUSED_MERGED_ALIGN defaults True and is shared by FP8/NVFP4, so this is on-by-default for them (covered by the equivalence proof); it cannot regress perf (strictly fewer launches, gated to decode numel<=2048/<512 tokens; prefill keeps the old path). Results: lora_perf_lora_profile/runs/Qwen3-30B-A3B-Instruct-2507-BF16-expert_shared-20260609-133913/opt1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fill _get_lora_info runs once per layer per forward and rebuilt num_experts / max_lora_rank / hidden_size every time. These are layer-static, so cache them on first call and reuse — trims per-layer Python on the EAGER prefill path (decode is cuda-graph, sees this only at capture). Only scalars cached; tensor refs read fresh so an adapter swap can't serve stale weights. Gated SGLANG_OPT_LORA_LEAN_INFO (default True). Pairs with SGLANG_OPT_FUSED_MOE_ACTIVATION_VEC=1 (vectorized MoE activation) as the opt3 "drop elem/upcast" cluster (prefill-targeted). Measured (Qwen3-30B-A3B-Instruct-2507-BF16 expert_shared, GB300 TP4/EP4, single×two matrix): NO CLEAR WIN — prefill +0.9..2.9% but within ~±4-8% run-to-run noise, decode ~0..1.7%. opt2 already removed the elem/copy bulk; the residual copies live in the decomposed .cu op (=> the in-MoE fold). Change is harmless (safe, default-on); kept for completeness. Results: lora_perf_lora_profile/runs/.../opt3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
At prefill the A (shrink) stage routes with BLOCK_SIZE_M 32 while the B (expand) stage uses the tuned fused-moe config (64), so the per-layer routing_cache key (num_experts, shared_outer, block_size) never matches across stages and the Triton align/sort (moe_align_block_size + count_and_sort_expert_tokens) reruns per stage — 4x per layer at prefill (~119us/layer measured, bf16 bs16, 4096-tok chunks). Matching the A stage's routing block to the B stage's makes the cache hit: align/sort drops 4x -> 2x per layer (the remaining two are genuinely different sorts: shared-outer A routes by lora id, per-expert B by expert id). Measured (bf16 Qwen3-30B-A3B expert_shared r32, GB300 TP4/EP4): prefill +7.4~8.2% (single) / +9.4~11.1% (two-stream) @ bs16/32/64, decode flat (the <512 gate keeps the opt1 fused merged-align path), align/sort kernel time -50% (48.5 -> 24.2 ms per profile window), -2688 launches. acc: KL vs trainer at the vLLM noise floor. Dtype-agnostic: the chain is shared by the fp8/nvfp4/bf16 dispatch fns. Flag: SGLANG_OPT_LORA_PREFILL_ROUTING_REUSE (default True; =0 for A/B). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
yushengsu-thu
requested review from
BBuf,
DarkSharpness,
Fridge003,
HydraQYH,
Ying1123,
celve,
jybsuper,
lifuhuang and
yuan-luo
as code owners
June 16, 2026 22:44
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
The experimental_sgl_trtllm bf16 dispatch fn had its parameter/return annotations quoted as forward refs, but the module already has `from __future__ import annotations`, so the quotes are redundant. ruff (F821, --fix) strips them; this matches what CI's pre-commit produces and turns the failing lint check green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds BF16 support to the
experimental_sgl_trtllmMoE-LoRA path. Previously this path was FP8/NVFP4-only and a bf16 checkpoint crashed at launch withAssertionError: experimental_sgl_trtllm LoRA currently requires FP8 block quant.The fused MoE-LoRA CUDA kernels (
trtllm_fp8/fp4_block_scale_routed_moe_lora) are not dtype-templated and no bf16 variant exists; the bf16 base kernel (trtllm_bf16_routed_moe) is monolithic with no LoRA hooks. This PR follows the same strategy the FP4 path used when it hit the missing-unfused-cubin wall: a decomposed pipeline — with the two NvFP4 quant stages removed, so it is bf16 end-to-end and needs zero new device kernels (it reusesmoe::dev::permute/moe::dev::activation/moe::dev::finalizeand the trtllm-genGemm2bf16 cubins that the plain bf16 path already uses).Phase 2 (included): the bf16 MoE op is wired into the existing two-stream LoRA overlap (
install_two_stream_overrides, master switchSGLANG_EXPERIMENTAL_LORA_OPTI=1) — the gate_up LoRA delta runs on the shared side stream concurrent with the op's routing + permute + gate_up GEMM, joining vialora_ready_eventright before the activation kernel (its only consumer), exactly like the FP8/FP4 O1 overlaps.FP8/FP4 paths are byte-for-byte untouched (additive only). Weight layout untouched (consumes the same shuffled + BlockMajorK bf16 tensors as
trtllm_bf16_moe).Files changed (what & why)
python/sglang/jit_kernel/trtllm_lora_temp/data/csrc/trtllm_fused_moe_kernel_launcher.cu(+412)Bf16LoraLauncher+sgl_trtllm_bf16_routed_moe_lorawrapper + TVM FFI export. Pipeline: routing →moe::dev::permute(bf16 gather) → gate_up as rawGemm2::Runner(Bf16,Bf16,Bf16, shuffled, BlockMajorK)(K=hidden, N=2*inter, gated-interleaved out) →moe::dev::activation(de-interleaves on read, addsgate_up_lora_deltapre-SwiGLU, capturesactivation_lora_input, bf16 out, no quant) → downGemm2::Runner(K=inter, N=hidden) → finalize (or returns the triple for the shared bf16 lora-finalize).lora_ready_event(waited right before activation) /gemm2_done_event(recorded after the down GEMM) for the two-stream overlap.python/sglang/jit_kernel/trtllm_lora_temp/core.py(+71),__init__.pytrtllm_bf16_routed_moe_loraPython binding. Finalize reuses the existing dtype-agnostic bf16trtllm_fp8_block_scale_moe_lora_finalize(the FP4 path already shares it).python/sglang/srt/lora/trtllm_lora_temp/lora_layer.py(+28/-1)FlashInferTrtllmBf16MoeQuantInfo; dispatch routes the bf16 quant-info to the new fused fn. FP8 asserts now gate only quantized checkpoints.python/sglang/srt/lora/trtllm_lora_temp/lora_dispatch.py(+152)fused_experts_none_to_experimental_sgl_trtllm_bf16_lora: mirrors the FP4 flow — virtual-experts gate_up delta via the dtype-agnosticmerged_experts_fused_moe_lora_add, bf16 hidden fed directly (noper_token_group_quant_fp8),do_finalize=True, down-LoRA merged after withfuse_sum_all_reduce. This is the single-stream variant (prefill / non-decode batches, and the fallback of the two-stream wrapper).python/sglang/srt/lora/trtllm_lora_temp/moe_overlap.py(+182)fused_experts_none_to_experimental_sgl_trtllm_bf16_lora_two_stream— bf16 sibling of the FP8/FP4 two-stream MoE dispatches (O1-bf16). Side-stream gate_up delta +lora_ready_eventjoin before activation; down-LoRA stays serial on the main stream (the down/finalize overlap is bench-verified net-negative on FP8/FP4 and corrupts base decode under cuda-graph replay, so it is intentionally not wired —gemm2_done_event=0).python/sglang/srt/lora/trtllm_lora_temp/__init__.py(+16/-5)install_two_stream_overrides(+get_original_bf16_moe_lora_funcfor the non-decode fallback).python/sglang/srt/lora/backend/base_backend.py(+13/-3)gemm1/gemm2_weightswhen the quant-info is the bf16 one, and don't assume 3-D expert weights (bf16 BlockMajorK-prepared weights are 4-D[E, N, K/128, 128]).Verification (GB300 / sm_103, Qwen/Qwen3-30B-A3B-Instruct-2507 bf16, TP4/EP4, dummy rank-16 full-coverage adapter,
--lora-use-virtual-experts --lora-backend triton, allreduce-fusion force-disabled)lora_path=alphaand base both coherent — and token-for-token identical to the same command run with--moe-runner-backend triton(the verified bf16 reference path). Re-verified after phase 2 (two-stream active).bench_one_batch_server, in=out=2048):Two-stream adds +7.9% / +6.4% / +6.2% over phase-1 single-stream (ITL 6.61/7.14/8.17 → 6.12/6.71/7.69 ms); no-lora cells are unchanged (base path unaffected). lora/no-lora decode ratio: 57.1/56.5/53.7% (triton) → 63.9/66.2/68.7% (phase 1) → 68.9/70.4/73.2% (phase 2).
Flow
Diagram style/reference: #26602 (the FP8 two-stream original). Differences from the FP8 flow are marked in-diagram with
DIFF.Which diagram applies when: the side-stream overlaps (O7 qkv / O8 o_proj / O9 merged-column / O1 MoE) are installed by
install_two_stream_overrides()under the master switchSGLANG_EXPERIMENTAL_LORA_OPTI=1and fire per batch only for decode-shaped batches (tokens <=SGLANG_TWO_STREAM_MAX_TOKENS, default 256). Prefill and large batches always take the single-stream flow; decode takes the two-stream flow.1) Normal LoRA (routed-expert)
(A) Single-stream flow (prefill / non-decode batches; also the pipeline structure itself)
sequenceDiagram autonumber participant M as Main CUDA stream Note over M: --- Attention block --- M->>M: qkv_proj quant_method.apply M->>M: sgemm_lora_a_fwd (qkv shrink) M->>M: qkv_lora_b_fwd (expand + atomic-add) M->>M: FlashAttention (fmha) M->>M: o_proj quant_method.apply M->>M: sgemm_lora_a_fwd (o_proj shrink) M->>M: sgemm_lora_b_fwd (expand + atomic-add) M->>M: tensor_model_parallel_all_reduce Note over M: --- MoE block --- NEW bf16 path --- M->>M: merged_experts_fused_moe_lora_add<br/>(gate_up shrink + expand)<br/>→ gate_up_lora_delta Note over M: DIFF vs FP8, no per_token_group_quant_fp8 step M->>M: trtllm_bf16_routed_moe_lora<br/>routing → permute bf16 hidden M->>M: gate_up GEMM as raw Gemm2 runner<br/>(bf16 weights, K hidden, N 2x inter)<br/>DIFF, replaces the fused-act PermuteGemm1 M->>M: activation kernel (bf16 in, bf16 out)<br/>de-interleave + SwiGLU(+gate_up_lora_delta)<br/>→ writes activation_lora_input Note over M: DIFF vs FP8, no post-activation quant, stays bf16 M->>M: down GEMM (Gemm2 runner) → finalize → moe_result M->>M: merged_experts_fused_moe_lora_add<br/>(down LoRA shrink + expand, atomic-add to moe_result) M->>M: downstream all_reduce (in qwen3_moe)(B) Two-stream decode fast path (phase 2, this PR)
Attention overlaps (O7/O8) are the source PR's, unchanged by this PR; the MoE par block (O1-bf16) is new.
sequenceDiagram autonumber participant M as Main CUDA stream participant S as LoRA side stream Note over M,S: --- Attention block (same as the FP8 PR) --- par O7: shrink on side S->>S: sgemm_lora_a_fwd<br/>(qkv shrink) and base GEMM on main M->>M: qkv_proj quant_method.apply end S-->>M: join M->>M: qkv_lora_b_fwd (expand + atomic-add) M->>M: FlashAttention (fmha) par O8: shrink on side S->>S: sgemm_lora_a_fwd<br/>(o_proj shrink) and base GEMM on main M->>M: o_proj quant_method.apply end S-->>M: join M->>M: sgemm_lora_b_fwd (expand + atomic-add) M->>M: tensor_model_parallel_all_reduce Note over M,S: --- MoE block --- NEW bf16 two-stream (O1-bf16) --- par O1-bf16: gate_up LoRA on side S->>S: merged_experts_fused_moe_lora_add<br/>(gate_up shrink + expand)<br/>→ gate_up_lora_delta and main: bf16 op routing/permute/GEMM M->>M: trtllm_bf16_routed_moe_lora<br/>routing → permute bf16 hidden<br/>DIFF, no per_token_group_quant_fp8 M->>M: gate_up GEMM as raw Gemm2 runner<br/>(bf16 weights, K hidden, N 2x inter)<br/>DIFF, replaces the fused-act PermuteGemm1 end S-->>M: join (lora_ready_event) right before activation M->>M: activation kernel (bf16 in, bf16 out)<br/>de-interleave + SwiGLU(+gate_up_lora_delta)<br/>→ writes activation_lora_input<br/>DIFF, no post-activation quant M->>M: down GEMM (Gemm2 runner) → finalize → moe_result M->>M: merged_experts_fused_moe_lora_add<br/>(down LoRA, atomic-add to moe_result, serial on main) M->>M: downstream all_reduce (in qwen3_moe)2) Shared-outer LoRA — where the shared-expert LoRA enters
The
--experts-shared-outer-lorasadapter ( layout) is not a separate dispatch path — it is a sub-mode of the twomerged_experts_fused_moe_lora_addcalls above, toggled byexperts_shared_outer_loras_a/b. The shared-expert LoRA is merged into the same gate_up / down LoRA deltas as the routed-expert LoRA, via virtual-experts, but routed differently:virtual_id = topk_id + max(lora_id, 0) * num_experts, EP-masked (non-ownedtopk_idsslots dropped to the-1sentinel on ranks > 0; the kernel also forces-1when the token has no adapter).virtual_experts.py):num_experts_for_weight = 1(one weight set),topk_idszeroed → routed by LoRA-id (base = 0), and never EP-masked (every rank applies the shared expert). It is_a = shared_outeron gate_up and_b = shared_outeron down;use_direct_expand_addis disabled (generic-kernel fallback) for the shared-outer-B expand.The attention block is identical to Normal LoRA above —
experts_shared_outer_lorasis read only inside the MoE expert-LoRA dispatch; the attention LoRA kernels (sgemm_lora_a/b,qkv_lora_b, inattention.py) are byte-identical. MLA models (Kimi / DeepSeek-V3.1) carry an extrakv_battention-side LoRA correction (deepseek_mla_correction) not drawn here — also not gated byexperts_shared_outer_loras.(A) Single-stream
sequenceDiagram autonumber participant M as Main CUDA stream Note over M: ATTENTION block lives in attention.py / monkey-patched via __init__.py<br/>experts_shared_outer_loras never touches it → identical to Normal LoRA M->>M: qkv_proj quant_method.apply M->>M: sgemm_lora_a_fwd qkv shrink M->>M: qkv_lora_b_fwd expand + atomic-add M->>M: FlashAttention M->>M: o_proj quant_method.apply M->>M: sgemm_lora_a_fwd o_proj shrink M->>M: sgemm_lora_b_fwd expand + atomic-add M->>M: tensor_model_parallel_all_reduce Note over M: MoE block - bf16 shared-outer, single-stream do_finalize=True<br/>fast-path returns plain bf16 when no active LoRA + not capturing<br/>asserts use_virtual_lora_store and silu+gated M->>M: 1. merged_experts_fused_moe_lora_add gate_up<br/>shared_outer_a=lora_info flag, shared_outer_b=False, mul_routed_weight=False<br/>use_direct_expand_add = max_lora_rank≤64 → ONE gate_up_lora_delta [tokens x top_k x 2*inter] Note over M: routed path: shrink A then expand B, expert-id routed<br/>virtual_id = topk_id + max(lora_id,0)*num_experts, EP-masked, -1 sentinels<br/>DIFF shared-outer A: num_experts_for_weight=1, topk_ids zeroed → LORA-id routed base 0, never EP-masked<br/>single call - shared-A shrink feeds per-expert B expand into the one delta M->>M: 2. trtllm_bf16_routed_moe_lora: routing → permute bf16 gather → gate_up GEMM raw bf16 N=2*inter M->>M: 3. activation: de-interleave + SwiGLU<br/>+ gate_up_lora_delta added pre-SwiGLU → writes activation_lora_input [tokens x top_k x inter] M->>M: 4. down GEMM bf16 → finalize → moe_result M->>M: 5. merged_experts_fused_moe_lora_add down<br/>shared_outer_a=False, shared_outer_b=lora_info flag, mul_routed_weight=True<br/>fuse_sum_all_reduce=True atomic-add to moe_result, use_direct_expand_add = max_lora_rank≤64 Note over M: DIFF shared-outer B down expand: lora-id routed, not EP-masked<br/>use_direct_expand_add disabled for shared-outer-B → generic kernel fallback<br/>fuse_sum_all_reduce is a top_k atomic-add sum, NOT a TP/EP collective M->>M: 6. downstream all_reduce in qwen3_moe ep or tp(B) Two-stream
sequenceDiagram autonumber participant M as Main CUDA stream participant S as LoRA side stream Note over M,S: ATTENTION block lives in attention.py / monkey-patched via __init__.py<br/>O7/O8 overlaps are NOT in this MoE function → identical to FP8 PR attention par O7 S->>S: sgemm_lora_a_fwd qkv shrink and M->>M: qkv_proj apply end M->>M: qkv_lora_b_fwd expand M->>M: FlashAttention par O8 S->>S: sgemm_lora_a_fwd o_proj shrink and M->>M: o_proj apply end M->>M: sgemm_lora_b_fwd expand M->>M: tensor_model_parallel_all_reduce Note over M,S: MoE block - bf16 shared-outer two-stream O1-bf16<br/>only when use_virtual_lora_store AND is_two_stream_active, else delegates to original bf16 func<br/>asserts silu+gated, remaps routing DeepSeekV3 → TopK par O1-bf16 S->>S: merged_experts_fused_moe_lora_add gate_up<br/>shared_outer_a=lora_info flag, shared_outer_b=False, mul_routed_weight=False<br/>use_direct_expand_add = max_lora_rank≤64 → ONE gate_up_lora_delta [M x top_k x 2*inter] and M->>M: trtllm_bf16_routed_moe_lora: routing → permute bf16 gather → gate_up GEMM raw Gemm2 bf16 K=hidden N=2*inter end Note over M,S: routed: shrink A → expand B, virtual_id = topk_id + max(lora_id,0)*num_experts, EP-masked, -1 sentinels<br/>DIFF shared-outer A: num_experts_for_weight=1, topk_ids zeroed → LORA-id routed base 0, never EP-masked<br/>single call - shared-A shrink feeds per-expert B expand into the one delta S-->>M: join via lora_ready_event right before activation, only consumer of gate_up_delta M->>M: activation bf16 in/out: de-interleave + SwiGLU<br/>+ gate_up_lora_delta added pre-SwiGLU → writes activation_lora_input [M x top_k x inter] M->>M: down GEMM Gemm2 bf16 → finalize → moe_result M->>M: SERIAL not overlapped, gemm2_done_event=0<br/>merged_experts_fused_moe_lora_add down<br/>shared_outer_a=False, shared_outer_b=lora_info flag, mul_routed_weight=True<br/>fuse_sum_all_reduce=True atomic-add to moe_result, use_direct_expand_add = max_lora_rank≤64 Note over M: bf16 path has NO down-overlap scaffolding at all, unlike FP4 sibling<br/>DIFF shared-outer B down: lora-id routed, not EP-masked, use_direct_expand_add disabled → generic kernel fallback M->>M: downstream all_reduce in qwen3_moe ep or tpNotes
gemm2_done_event) is plumbed in the launcher but intentionally NOT wired in the dispatch — on FP8/FP4 it was bench-verified net-neutral-to-negative and corrupted the base decode path under cuda-graph replay.f2f98194d/bb812e6ce, a global triton routing that was withdrawn); net diff vs base is the implementation above only.Reproduction — real-adapter shared-outer runs (GB300)
The §2 verification above used a dummy rank-16 adapter; below are the real-adapter shared-outer
launches actually benchmarked on the
experimental_sgl_trtllmMoE-LoRA path this PR extends, one permodel. Both carry
--lora-use-virtual-experts --experts-shared-outer-loras --max-lora-rank 32 --lora-backend triton;SGLANG_EXPERIMENTAL_LORA_OPTI=1installs the two-stream overlap (fires ondecode-shaped batches).
alpha=<path>is the trained r32 shared-outer adapter for each model.Qwen3-30B-A3B-Instruct-2507 (bf16 — this PR's path) — 1 node, TP4 / EP4
Kimi-K2.5-NVFP4 (fp4) — 2 nodes, TP8 / EP8 (MNNVL)
Run on each of the 2 nodes with
--node-rank 0/--node-rank 1;$DISTADDRis the head pod'sdist-init address (
<head-host>:20000).🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #27978055381
Latest PR Test (Extra): ❌ Run #27978055273