Repository navigation
[#20545][perf] Kimi K3: fold the MoE-tail additions into the next attention-residual - #20546
Draft
lucifer1004 wants to merge 1 commit into
Draft
lucifer1004 wants to merge 1 commit into
lucifer1004 wants to merge 1 commit into
Conversation
…xt attention-residual Add an optional second addend to trtllm::attn_res_add_rmsnorm_fwd that materializes bf16(prefix + bf16(routed + shared)), and an opt-in path (KIMI_K3_MOE_TAIL_ADD_FOLD=1) in which a Kimi K3 MoE layer hands its prefix, routed and shared outputs to the next attention-residual + RMSNorm instead of adding them itself. Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
This was referenced Oct 19, 2026
This branch has not been deployed
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.
Description
Fixes #20545.
In Kimi K3 decode, every MoE layer ends with
prefix_sum + (routed_out + shared_out): two elementwise kernels whose result is read first by the next attention-residual + RMSNorm. This PR lets that consumer do both additions inside the kernel it already runs. Opt-in viaKIMI_K3_MOE_TAIL_ADD_FOLD=1.Op (
attnResFwd.cu/.h,attnResOp.cpp,cpp_custom_ops.py)trtllm::attn_res_add_rmsnorm_fwdgets an optional trailingTensor? layer_residual_add2=None. With it, the updated prefix sum isbf16(layer_residual + bf16(layer_residual_add + layer_residual_add2)). That is the result of the two BF16 tensor additions, bit for bit, subnormals included: both additions useadd.rn.f32, so--use_fast_mathdoes not flush them.FUSE_LAYER_ADD2template flag on the existing single-CTA (N <= 4) and split-K (N >= 5) kernels, not a copy of them. Without the argument, the op and the existing instantiations are unchanged.Model (
modeling_kimi_linear.py)KimiK3MoERuntime.forward(..., return_parts=True)returns(routed_out, shared_out)unsummed.KimiLinearModel.forwardpassesdefer_moe_tail_add=Trueto MoE layers. Such a layer returns a_MoeTailPending(prefix, routed, shared)when its token count is inside the fused window (KIMI_K3_FUSED_ATTN_RES_MAX_TOKENS, H = 7168, BF16 CUDA tensors)._apply_attn_res_add_and_rmsnorm(..., addend2=shared).Performance, Rubin, TP8 across 2 nodes (MNNVL), Kimi K3 with speculative decoding, CUDA graphs, single request, decode step time. Measured with the same kernel arithmetic and the same hand-off on our integration branch, as a separate op. There, the two additions were already a single kernel, so on main, where they are two, the saving should be at least this large.
Accuracy and needle checks unchanged. In a single-GPU microbenchmark (Rubin single GPU, 200-SM engineering sample), the fused op costs 0.58-0.76 µs more than the attention-residual alone with the single-CTA kernel and 0.10-0.17 µs more with split-K, against a median of 1.6 µs for the standalone (single-kernel) addition it replaces in production.
The fold applies only where the fused per-token epilogue is used, i.e. up to
KIMI_K3_FUSED_ATTN_RES_MAX_TOKENStokens per rank (default 1; #20372 proposes 32). The numbers above were taken with a window covering the decode batch.Relation to other work:
KimiK3MoERuntime.forward. The two are independent but will conflict textually; whichever lands second gets rebased.Test Coverage
tests/unittest/_torch/modules/kimi_k3_attn_res/test_attn_res_rmsnorm_op.py(inl0_b200.ymlandl0_gb300_multi_gpus.yml):prefix + (routed + shared)in PyTorch (N = 1 to 12, 1 / 8 / 16 / 32 tokens);addend2, so the caller adds first.tests/unittest/_torch/modules/kimi_k3_attn_res/test_attn_res_op.py: a 25-layer decoder chain with deferred MoE tails is bitwise equal to the eager chain, including the snapshot bank and a capture tap. This test runs on CPU through the fallback, so it covers the hand-off rather than the kernel.test_attn_res_rmsnorm_op.py: 56 passed, 1 skipped (on main the 18 new cases fail), including the bitwise pre-summed comparison and the subnormal (non-FTZ) cases;test_attn_res_op.py: 13 passed;test_custom_ops.py -k test_register_fakefailed only ontrtllm::mxe2m1_moe_routing_workspace, an op of [#20540][perf] Kimi K3 MoE decode: router GEMM and routing kernel on a router stream #20541 that was compiled into the shared library while this branch's Python does not register [#20540][perf] Kimi K3 MoE decode: router GEMM and routing kernel on a router stream #20541's fakes. This PR's own ops all have fakes; with this PR's C++ alone the test is expected to pass (not run separately).tests/unittest/_torch/modeling/test_kimi_linear_modeling.pyandtests/unittest/_torch/speculative/hw_agnostic/test_kimi_k3_dflash_scaffold.pypass; a mutation in the hand-off makes the new CPU test fail.PR Checklist