SIGN IN SIGN UP

fix(moe): Kimi-K3 correctness fixes (routing, NCCL warmup, PP, per-GPU tps) (#3731)

* fix(kimi-k3): honor backend.fake_balanced_gate in KimiK3MoE

KimiK3MoE.__init__ unconditionally built KimiK3Gate, silently ignoring
backend.fake_balanced_gate, which the base MoE class honors. With
random- or zero-initialized weights the learned gate produces near-equal
scores, so topk returns experts [0..topk) for every token and all
traffic collapses onto each EP group's first rank; benchmark configs
that requested forced load balance were degenerate. In a 64xGB200
Kimi-K3 run this routing collapse alone cost ~6x throughput
(34.4 -> 248.5 tok/s/GPU once fixed, wall-clock verified).

Build FakeBalancedGate(moe_config, noise=backend.fake_gate_noise) when
the flag is set, exactly as the base MoE does.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

* fix(moe): tolerate never-forwarded FSDP modules in MoE PP post-backward

With FusedLinearCrossEntropy under pipeline parallelism the lm_head
weight is consumed inside the loss function, so its FSDP module never
runs forward and its FSDPCommContext is never lazily initialized.
_run_post_backward_hooks then crashes the last pipeline stage with
AttributeError: 'FSDPCommContext' object has no attribute
'post_forward_order'.

Guard the per-state post_backward() calls and the root final callback
against this AttributeError. A never-forwarded FSDP group has no
gradients to reduce, so skipping it is safe; verified on a 2-stage
Kimi-K3 pipeline probe by comparing per-step losses against a non-fused
control run (no gradient desynchronization).

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

* fix(train_ft): report per-GPU tokens/sec using world size under pipeline parallelism

tps is the global tokens/sec of the step (num_tokens_in_batch is summed
over the DP group), but tps_per_gpu divided it by dp_size * cp_size
only. Ranks belonging to the same DP replica across the pp (and tp)
dimensions were not counted, so the printed per-GPU throughput -- and
any MFU derived from it -- was inflated by the pp*tp factor.

Measured on a 256-GPU Kimi-K3 run with pp8 (dp=32): the log line
reported 275.2 tok/s/GPU while wall-clock-verified global throughput
divided by 256 GPUs was 34.4 tok/s/GPU -- exactly 8x, the pp factor.

Divide by the recipe's world size instead, matching the divisor the MFU
computation already uses.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

* fix(moe): narrow the never-forwarded FSDP guard to the lazy-init signature

Address review feedback: a broad 'except AttributeError' could silently
swallow unrelated failures inside post_backward() and drop a real group's
gradient reduction. Match the never-forwarded signature explicitly
(missing 'post_forward_order'), log the skip at debug level, and re-raise
anything else. Adds a regression test asserting unrelated AttributeErrors
propagate.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

* fix(kimi-k3): enable comm-group prewarm in the K3 recipe

prewarm.comm_groups exists exactly to prevent the NCCL 'cuda failure 2
(out of memory)' that fires when the grad-norm communicator initializes
lazily at the step-0 memory peak (its docstring quotes this failure), but
it is opt-in and the K3 recipe never enables it -- so recipe users still
hit the crash at memory-tight settings (reproduced at 2.78T, pp8,
pp_microbatch_size=2: all last-stage ranks die identically at the end of
step 0). Enable it where it matters.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

* fix(recipes): keep the ci section last in the K3 SFT config

The nightly-recipe validator requires the top-level ci section to be
the last section; the prewarm section added earlier landed after it.
Move prewarm above ci.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

---------

Signed-off-by: Yisong Li <yisongbetter@gmail.com>
Co-authored-by: Charlie Truong <chtruong@nvidia.com>
Y
yisongbetter committed
f0f6b1071b41868da7ec3ff4de67a7f3e90098f5
Parent: cdc9147
Committed by GitHub <noreply@github.com> on 8/29/2026, 5:58:31 PM