Support discard-output recompute for MoE shared experts under A2A overlap - #10
Merged
wplf merged 2 commits intoJun 3, 2026
Merged
Conversation
wplf
force-pushed
the
jinliangl/shared-experts-ckpt-clean
branch
from
June 2, 2026 03:42
1714b44 to
2772bd9
Compare
…n-overlap) Switch shared_experts selective recompute from a standard checkpoint (keeps the output) to CheckpointWithoutOutput: discard the shared-expert output in the forward and regenerate it in backward from a grad hook. The recompute is registered AFTER any pre_mlp_layernorm recompute (so the shared expert's input is restored first) and before its backward — via the moe node's expert_output in the A2A-overlap fine-grained callables, or via mlp_output_with_bias[0] in TransformerLayer._forward_post_mlp on the single-call path. Split CheckpointWithoutOutput.discard_output_and_register_recompute into discard_output() + register_recompute_hook() for the overlap path (which frees the output and registers the hook in different callables); disabled under MoE cudagraph partial capture. Validated on Qwen3.5-VL 397B proxy (8xGB200, EP=8, mbs=4): both paths run with stable loss / no NaN; controlled memory 85.94 -> 84.75 GB (-1.19 GB); 3-way numerical parity within the run-to-run FP-noise floor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wplf
force-pushed
the
jinliangl/shared-experts-ckpt-clean
branch
from
June 2, 2026 03:46
2772bd9 to
35e6f5a
Compare
wplf
marked this pull request as ready for review
June 2, 2026 09:04
|
Please add unit test for shared expert recompute + ep overlap, you can refer to tests under |
The memory-opt test lists "shared_experts" in recompute_modules but never set moe_shared_expert_intermediate_size, so the model had no shared experts and the shared-experts discard-output recompute path was never exercised. Set shared_expert_intermediate_size=512 so the recompute actually fires (in both the overlap and non-overlap paths compared by the test).
wplf
force-pushed
the
jinliangl/shared-experts-ckpt-clean
branch
from
June 3, 2026 09:33
a4f43c8 to
c87283b
Compare
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.
What
Switches
shared_expertsselective recompute from a standard checkpoint (which keeps the output activation) toCheckpointWithoutOutput: the shared-expert output activation is discarded in the forward and regenerated in backward from a grad hook. Supported on both the A2A-overlap (fine-grained callables) and the single-call (non-overlap) MoE paths.tensor_parallel/random.py: splitCheckpointWithoutOutput.discard_output_and_register_recomputeintodiscard_output()+register_recompute_hook()so the output can be freed and the recompute hook registered at different points.moe/moe_layer.py:shared_experts_computeusesCheckpointWithoutOutputwhen discard-output recompute is enabled (gated only off MoE cudagraph partial capture, where the shared-expert output is a graph output that must keep its storage).models/gpt/fine_grained_callables.py(A2A-overlap path): register the recompute on the moe node'sexpert_output(right after thepre_mlp_normrecompute, same tensor) and free the output in the combine node afterpostprocess()consumes it.transformer/transformer_layer.py(non-overlap path): in_forward_post_mlp, free the output and register the recompute onmlp_output_with_bias[0]right after thepre_mlp_normrecompute.Ordering invariant (both paths)
The shared expert's input is
pre_mlp_layernorm_output, which thelayernormrecompute discards and only restores in its own backward hook. So the shared-expert recompute must fire after thepre_mlp_normrecompute (input restored) and before the shared expert's backward. Both paths register the hook on the same tensor as — and immediately after —pre_mlp_norm, guaranteeing the order. A hook placed too early (e.g. on the combine node output, which detaches between schedule nodes) hits a freedpre_mlp_layernorm_output→ cuBLAS error. The two paths are mutually exclusive (overlap bypasses_forward_post_mlp; non-overlap bypasses the fine-grained callables), so there is no double registration.Memory savings
Discard-output frees one extra activation that a standard checkpoint retains: the shared-expert output, shape
[seq, mbs, hidden](~0.5 GB/MoE-layer at seq=4096, hidden=4096, bf16, mbs=4). The realized peak saving is smaller than the per-layer × num-layers sum because only the few layers whose shared-expert output is co-resident at the instantaneous peak (bwd → optimizer) are freed.Controlled measurement on the Qwen3.5-VL 397B proxy (8×GB200, EP=8, mbs=4, full stack:
layernorm + gdn_norm_out + moe_act + shared_expertsrecompute + offload + precision-aware optimizer + A2A overlap) — same recipe, the only difference is the shared_experts checkpoint mechanism:Notes:
micro_batch_size(output ∝ mbs) and with how many layers' shared-expert outputs are co-resident at the peak.layernorm/gdn_norm_out, rather than a large standalone win.Validation (Qwen3.5-VL 397B proxy, 8×GB200, EP=8, mbs=4, multimodal entry)
Full stack, 20 iters (10 for non-overlap), stable loss, no NaN:
recompute_modules=[shared_experts], no layernorm nesting)Numerical parity (3-way, same seed=1234, A2A overlap on, mbs=4)
REF (shared_experts NOT recomputed) vs TEST (discard-output) vs REF-dup (REF re-run, FP-noise floor), per-iter
lm loss:|REF − TEST|is the same magnitude as the|REF − REF-dup|non-determinism floor with no systematic drift; grad norms match to 3 digits. So discard-output recompute is numerically equivalent to not recomputing, within run-to-run FP noise. (Bitwise-deterministic comparison isn't possible: SM100 attention backward with head_dim=256 has no deterministic kernel, so--deterministic-modeasserts out for this model.)🤖 Generated with Claude Code