[TRTLLM-12373][feat] RMSNorm nvfp4 quant fusion for DS V3.2 / Kimi-K2.5#14848
Conversation
1914278 to
87e9fee
Compare
|
/bot run |
|
PR_Github #51547 [ run ] triggered by Bot. Commit: |
|
PR_Github #51547 [ run ] completed with state
|
7b79444 to
10513bb
Compare
10513bb to
26b70b6
Compare
… MLA
Fold the standalone NVFP4 input-quantize (FP16/BF16 -> NVFP4) into its producing
RMSNorm at the DSv3.2/Kimi-K2.5 MLA sites, on the attention-DP path:
- layer-boundary residual-add + RMSNorm -> next layer's kv_a_proj
- layer-0 prologue (residual-less) input_layernorm -> kv_a_proj
- dense post_attention_layernorm -> gate_up_proj
- residual-less q_a_layernorm -> q_b_proj (reads a row-strided column slice
of kv_a_proj_with_mqa's output without a contiguous copy)
The fused kernel and its two thop ops (fused_add_rmsnorm_fp4_quantize /
fused_rmsnorm_fp4_quantize) live in self-contained new files
(kernels/rmsNormFp4QuantKernels.{h,cu}, thop/rmsNormFp4Quant.cpp) with their own
RmsNormFp4QuantParams struct and a private copy of the small reduce_fusion device
helpers, so the AllReduce files are untouched.
RMSNorm.forward dispatches the fold via a single is_nvfp4/nvfp4_scale branch with
a problem-size gate (_WS_M_THRESHOLD=4096, calibrated on GB200 at N=7168): the
warp-specialized fused_add_rms_norm_quant serves the large contiguous residual
edge, the one-CTA-per-row reduce_fusion ops serve small-M, residual-less, and
row-strided edges. The fold is self-gating -- it fires only when the consuming
Linear is static-NVFP4 (a scale is attached) -- so no env var or extra flag.
Producing norms attach .nvfp4_scale at post_load_weights, matching the
llama/nemotron convention.
fused_add_rmsnorm_fp4_quantize returns the residual sum as a fresh tensor instead
of mutating hidden_states in place: the op took hidden_states as Tensor(a!) and
returned that same tensor, which torch.compile's functionalization pass rejects
(an output may not alias an input). Under torch_compile=True this produced a
graph-break that failed to pickle over the executor IPC, hanging the proxy. The
thop now seeds a fresh empty_like buffer, so the schema drops the (a!) alias and
the op is functionalizable; the kernel is unchanged. register_fake impls for both
ops let torch.compile fake-trace them.
Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
Address review feedback: allocate residual_out (and the add-op's norm_out) with at::detail::empty_cuda instead of torch::empty_like, matching quant_out/scale_out and the ws op (fusedAddRMSNormQuant.cpp). empty_like carries the input's layout and can lower to a separate node in the torch.compile trace; empty_cuda is a plain fresh allocation. The register_fake impl uses new_empty for the same reason. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…ant kernel Address review feedback: - Launch rmsNormFp4QuantKernel through tensorrt_llm::common::launchWithPdlWhenEnabled (envUtils.h) so the kernel's existing PDL primitives (cudaGridDependencySynchronize / cudaTriggerProgrammaticLaunchCompletion) are actually enabled under TLLM_ENABLE_PDL. - Rename rms_norm_fp4_quant_kernel -> rmsNormFp4QuantKernel and launch_rms_norm_fp4_quant_kernel -> launchRmsNormFp4QuantKernel to match the codebase lowerCamelCase kernel-naming convention. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…logue check Address review feedback on the fused RMSNorm-quant tests: - rms_norm_ref now calls the production flashinfer RMSNorm kernel (the exact unfused-path kernel the fusion replaces) when available, instead of a PyTorch formula, so the cross-path comparison is apples-to-apples. - Add assert_fp4_bitexact_from_norm: re-quantizing the fused op's own returned norm_out with fp4_quantize must reproduce its quant_out/scale_out bit-for-bit (FP4 compared directly; SF compared on the unswizzled valid [m, n/16] region to skip the uninitialized swizzle padding). This proves the epilogue is exact once the normed input is fixed; the remaining RMSNorm ULP differences across the two paths stay covered by the match-rate check. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…eference Call torch.ops.trtllm.fp4_quantize with the exact production static-NVFP4 signature from NVFP4LinearMethod._input_prepare (linear.py): fp4_quantize(input, input_scale, scaling_vector_size, False), relying on isSfSwizzledLayout=True default. The bit-exact fused-epilogue check (assert_fp4_bitexact_from_norm) thus compares against the same quant op production runs. Behavior unchanged (the dropped explicit True equals the schema default). Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
Address review feedback on readability of the fused-quant scale attachment: - nvfp4_scale is now declared as an Optional attribute initialized to None in RMSNorm.__init__, with a docstring explaining why it cannot be set at construction time (the consuming Linear's calibrated input_scale only exists after weight loading; the owning model attaches it in post_load_weights / MLA._resolve_qa_fused_scale). - Replace all getattr(..., "nvfp4_scale")/hasattr probing with direct attribute access (rms_norm.py, modeling_deepseekv3.py, mla.py) — including the two pre-existing hasattr checks in modeling_nemotron_h.py, which the explicit attribute would otherwise turn into always-True. - Docstring nit: double -> single backticks in _static_nvfp4_input_scale / is_static_nvfp4_input_eligible. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…into params struct Address review feedback on rmsNormFp4QuantKernels: 1. Fix partial-warp __shfl_xor_sync UB in the quantize epilogue: for hidden_size values where the last iteration leaves a warp partially active (e.g. 32, 128, 8208), lanes past the bound exited the loop while their neighbors entered cvt_warp_fp16_to_fp4's full-mask shuffles. The loop bound is now warp-uniform (offset - lane_id * kPackedSize), so all 32 lanes stay converged; inactive lanes are masked on loads, stores, and the SF output pointer and feed zeros to the shuffle. Safe because hidden_size % 16 == 0 pair-aligns the active region (an inactive lane's xor-1 partner is always inactive too). 2. Move the remaining direct kernel arguments (quant_out, scale_out, norm_out, scale_factor_ptr, sf_layout, input_row_stride) into RmsNormFp4QuantParams so the kernel, launcher, and thop callers all use a single params struct; launcher signature is now residualRmsNormFp4Quant(params, dtype, stream). Also add an explicit null-check on the mandatory quant/scale output pointers. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…stale comment link Address review nits: - Replace the magic number 16 in the FP4 padding-slice helper with a named NVFP4_SF_VEC_SIZE constant in quantization/utils/fp4_utils.py (aligned with SF_VEC_SIZE in cpp/tensorrt_llm/kernels/quantization.h). - Remove a stale reference to a local benchmark script from the _WS_M_THRESHOLD comment in rms_norm.py; the measurement summary stays. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
… move slice helper into mla.py Address review nits: - Rename Fp4QuantizedTensor.bf16_hidden_states to unquantized_hidden_states since the field can carry either a BF16 or FP16 tensor. - Move _slice_hidden_states_to_num_tokens from attention.py to mla.py (its only user) and drop the imports attention.py no longer needs. - Drop a comment in RMSNorm.__init__ that referred to "this PR"; the block comment above it already documents the SM-support behavior. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
cbe6091 to
a101813
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #59092 [ run ] triggered by Bot. Commit: |
|
PR_Github #59092 [ run ] completed with state
|
|
/bot run |
juney-nvidia
left a comment
There was a problem hiding this comment.
Approved since this PR has already received the approvals before and gets invalidated due to the latest changes of Code owner rule.
|
PR_Github #59147 [ run ] triggered by Bot. Commit: |
|
PR_Github #59147 [ run ] completed with state |
Summary by CodeRabbit
Release Notes
New Features
Tests
Description
On NVFP4 DeepSeek-V3.2 / Kimi-K2.5, every MLA RMSNorm that feeds a static-NVFP4
Linearis immediately followed by a standalone NVFP4 input-quantize. This PRfolds that
RMSNorm -> fp4_quantizepair into a single kernel that computes theRMSNorm in registers and emits the NVFP4 (packed E2M1 + per-block E4M3 scale)
representation in the same pass, eliminating the separate quantize kernel's full
read/write + launch.
Folded edges (attention-DP path):
residual_add -> RMSNorm -> next layer's kv_a_projinput_layernorm -> kv_a_projpost_attention_layernorm -> gate_up_projq_a_layernorm -> q_b_proj(reads theqcolumn-slice ofkv_a_proj_with_mqa's output in place, no.contiguous()copy)Kernel-time impact
Measured on GB200, on-GPU durations from nsys single-layer slices
(DSv3.2 DEP8, real MLA shapes), per fused edge:
Per decoder layer that is ~7.3 us saved (dense, 3 edges) / ~3.5 us (MoE, 2 edges)
of GPU kernel time, plus the launch-count reduction (-4 kernels/dense-layer,
-3/MoE-layer).
Per-decoder-layer kernel composition (nsys single-layer slice)
Unfused =
RMSNormKernel(flashinfer) +quantize_with_block_size(standaloneNVFP4); Fused =
rms_norm_fp4_quant_kernel.Test Coverage
PR Checklist
Please review the following before submitting your PR:
PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.
PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.
Test cases are provided for new code paths (see test instructions)
If PR introduces API changes, an appropriate PR label is added - either
api-compatibleorapi-breaking. Forapi-breaking, includeBREAKINGin the PR title.Any new dependencies have been scanned for license and vulnerabilities
CODEOWNERS updated if ownership changes
Documentation updated as needed
Update tava architecture diagram if there is a significant design change in PR.
The reviewers assigned automatically/manually are appropriate for the PR.
Please check this after reviewing the above items as appropriate for this PR.
GitHub Bot Help
To see a list of available CI bot commands, please comment
/bot help.