[WIP][LoRA] BF16 support for the experimental_sgl_trtllm MoE-LoRA path & lora expert_outer_shared - #4
yushengsu-thu wants to merge 9 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>
There was a problem hiding this comment.
Pull request overview
Adds BF16 (unquantized) support to the experimental_sgl_trtllm MoE-LoRA path by introducing a decomposed, bf16 end-to-end pipeline (permute → raw gate_up GEMM2 → LoRA-aware activation → down GEMM2 → finalize) that reuses existing TRTLLM-gen bf16 cubins and existing permute/activation/finalize kernels, while keeping existing FP8/FP4 paths additive/untouched.
Changes:
- Add a new BF16 MoE-LoRA launcher + TVM FFI export in the fused MoE kernel source.
- Add Python binding for
trtllm_bf16_routed_moe_loraand route dispatch to the new BF16 LoRA function via a minimalFlashInferTrtllmBf16MoeQuantInfo. - Make CUDA-graph MoE buffer sizing robust to BF16 BlockMajorK-prepared 4D expert weight shapes.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
python/sglang/srt/lora/trtllm_lora_temp/lora_layer.py |
Detect BF16/unquantized checkpoints and dispatch to the BF16 experimental TRTLLM MoE-LoRA path. |
python/sglang/srt/lora/trtllm_lora_temp/lora_dispatch.py |
Add BF16 experimental TRTLLM MoE-LoRA dispatch implementation (single-stream). |
python/sglang/srt/lora/backend/base_backend.py |
Generalize CUDA-graph MoE buffer init to handle BF16 quant-info naming + 4D BlockMajorK expert weights. |
python/sglang/jit_kernel/trtllm_lora_temp/data/csrc/trtllm_fused_moe_kernel_launcher.cu |
Implement BF16 decomposed MoE-LoRA pipeline and export sgl_trtllm_bf16_routed_moe_lora via TVM FFI. |
python/sglang/jit_kernel/trtllm_lora_temp/core.py |
Add Python wrapper for the new BF16 routed MoE-LoRA kernel. |
python/sglang/jit_kernel/trtllm_lora_temp/__init__.py |
Export trtllm_bf16_routed_moe_lora from the jit-kernel package. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if quant_config is None and not getattr(quant_method, "block_quant", False): | ||
| from sglang.srt.layers.moe.moe_runner.flashinfer_trtllm import ( | ||
| FlashInferTrtllmBf16MoeQuantInfo, | ||
| ) |
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>
Qwen3.5-35B-A3B-FP8 / Qwen3.5-35B-A3B-FP8-expert_shared — perf resultsGB300 (sm_103), TP4/EP4, single node,
All three columns are throughput (tok/s, higher = faster);
Notes
|
Qwen3-30B-A3B-Instruct-2507-BF16 / Qwen3-30B-A3B-Instruct-2507-BF16-expert_shared — perf matrix (decode)Measured on
no-LoRA baseline (default fused backend), decode tok/s (bs16/32/64): fus ON 3932.5 / 6991.4 / 11757.9 · fus OFF 3782.0 / 6757.8 / 11437.3 decode tok/s (value = lora; % = lora ÷ no-LoRA at the matching fusion)
Takeaways
|
…/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>
dfa3493 to
850faa8
Compare
…ora expert_outer_shared Squashed from yushengsu-thu#4 Adds BF16 base-model support to the experimental_sgl_trtllm MoE-LoRA fast path (previously FP8/NVFP4-only; a bf16 checkpoint crashed at launch with "experimental_sgl_trtllm LoRA currently requires FP8 block quant"). Follows the FP4 decomposed-pipeline strategy with the two NvFP4 quant stages removed, so it is bf16 end-to-end and needs zero new device kernels (reuses moe::dev::permute / activation / finalize and the trtllm-gen Gemm2 bf16 cubins the plain bf16 path already uses): routing -> permute (bf16 gather) -> gate_up raw Gemm2 grouped GEMM (Bf16xBf16->Bf16, K=hidden, N=2*inter) -> activation (de-interleave + SwiGLU, adds gate_up_lora_delta pre-SwiGLU, bf16 out, no quant) -> down Gemm2 -> finalize. Includes the two-stream decode overlap (O1-bf16): gate_up LoRA delta on the shared side stream concurrent with routing/permute/gate_up GEMM, joining via lora_ready_event before the activation kernel; down-LoRA stays serial. Also routes the shared_outer (TML) adapter through the fused merged-align path and two prefill-routing micro-opts (opt3/opt5). FP8/FP4 paths are additive-only.
|
merged: sgl-project#28953 |
|
get merged: jybsuper@21ef24a |
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: sgl-project#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: sgl_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: sgl_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 (TML) LoRA — where the shared-expert LoRA enters
The
--experts-shared-outer-lorasadapter (TML layout) is not a separate dispatch path — it is asub-mode of the two
merged_experts_fused_moe_lora_addcalls above, toggled byexperts_shared_outer_loras_a/b. The shared-expert LoRA is merged into the same gate_up / downLoRA deltas as the routed-expert LoRA, via virtual-experts, but routed differently:
virtual_id = topk_id + lora_id * num_experts, EP-masked(non-owned
topk_idsslots dropped to-1on ranks > 0).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 appliesthe shared expert). In the bf16 dispatch it is
_a = shared_outeron gate_up and_b = shared_outeron down;
use_direct_expand_addsilently falls back for the shared-outer expand.The attention block is identical to Normal LoRA above (shared-outer only changes the MoE block,
marked
DIFF); both flows are shown end-to-end from the attention block for parity with §1.This is for standard attention (the Qwen3-30B bf16 model here):
experts_shared_outer_lorasis read only insideFusedMoEWithLoRA(the MoE expert LoRA layer), so the attention LoRA kernels (sgemm_lora_a/b,qkv_lora_b) are byte-identical to Normal LoRA. 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 --- 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, bf16 shared-outer (TML) LoRA --- Note over M: gate_up LoRA is routed-expert plus shared-outer, into ONE gate_up_lora_delta M->>M: merged_experts_fused_moe_lora_add (gate_up)<br/>shared_outer_a True, shared_outer_b False, mul_routed_weight False Note over M: routed path, shrink A then expand B, expert-id routed<br/>virtual_id is topk_id plus lora_id x num_experts, EP-masked Note over M: DIFF shared-outer A, num_experts_for_weight 1,<br/>topk_ids zeroed so routes by LORA-id (base 0),<br/>never EP-masked, summed into the SAME gate_up_lora_delta M->>M: gate_up_lora_delta, shape tokens x top_k x 2 inter M->>M: trtllm_bf16_routed_moe_lora<br/>routing → permute (bf16 gather) → gate_up GEMM (raw bf16) M->>M: activation de-interleave + SwiGLU(+gate_up_lora_delta)<br/>→ writes activation_lora_input M->>M: down GEMM → finalize → moe_result Note over M: down LoRA is routed-expert plus shared-outer, atomic-add to moe_result M->>M: merged_experts_fused_moe_lora_add (down)<br/>shared_outer_a False, shared_outer_b True,<br/>mul_routed_weight True, fuse_sum_all_reduce True Note over M: DIFF shared-outer B (down expand), lora-id routed,<br/>not EP-masked, direct-expand-add disabled for shared-outer M->>M: downstream all_reduce (in qwen3_moe)(B) Two-stream
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, bf16 shared-outer (TML) LoRA, two-stream (decode, O1-bf16) --- par O1-bf16: gate_up LoRA (routed + shared-outer) on side S->>S: merged_experts_fused_moe_lora_add (gate_up)<br/>shared_outer_a True, shared_outer_b False, mul_routed_weight False<br/>→ ONE gate_up_lora_delta Note over S: routed path: shrink A → expand B, expert-id routed<br/>virtual_id = topk_id + lora_id x num_experts, EP-masked Note over S: DIFF shared-outer A: num_experts_for_weight 1,<br/>topk_ids zeroed → routes by LORA-id (base 0), never EP-masked,<br/>summed into the SAME gate_up_lora_delta and main: bf16 base MoE routing / permute / gate_up GEMM M->>M: sgl_trtllm_bf16_routed_moe_lora<br/>routing → permute (bf16 gather) M->>M: gate_up GEMM (raw Gemm2 runner, bf16, K hidden, N 2x inter) end S-->>M: join (lora_ready_event) right before activation M->>M: activation kernel (bf16 in/out)<br/>de-interleave + SwiGLU(+gate_up_lora_delta)<br/>→ writes activation_lora_input M->>M: down GEMM (Gemm2 runner) → finalize → moe_result Note over M: down LoRA (routed + shared-outer) stays SERIAL on main<br/>(down/finalize overlap intentionally not wired — gemm2_done_event=0) M->>M: merged_experts_fused_moe_lora_add (down)<br/>shared_outer_a False, shared_outer_b True,<br/>mul_routed_weight True, fuse_sum_all_reduce True Note over M: DIFF shared-outer B (down expand): lora-id routed,<br/>not EP-masked, direct-expand-add disabled for shared-outer M->>M: downstream all_reduce (in qwen3_moe)Notes
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.🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #27513652422
Latest PR Test (Extra): ❌ Run #27513652342