Repository navigation
feat(rl): orchestrator.pad_to_seq_len, and pad with real tokens to avoid MoE hot spots - #4184
Conversation
Mirrors SFT's data.pad_to_seq_len (#4160): pad every packed micro batch to seq_len so rows have one static shape and torch.compile / DeepGEMM JIT stop recompiling on new lengths. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Identical pad tokens route to the same experts, so the EP rank hosting them takes the whole pad run, and router replay padded with zero bytes sent every pad slot to expert 0. Text rows in RL and SFT now pad by repeating their own tokens, and RL's routed-experts payload repeats its real routes. Multimodal rows keep a constant pad id. Co-authored-by: Mika Senghaas <mika@primeintellect.ai> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first README misreported the flag-off SFT median step time. Report compile counts only, since short runs inflate step-time differences. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks for getting this landed. I appreciate the broader coverage here—SFT, I’ve closed #3350 as superseded for the original zero-filled replay-padding hotspot. I’d still like us to resolve attribution for the earlier contribution: that PR identified the expert-0 hotspot and provided a proposed fix before this PR was opened. Could we work out an appropriate way to give that work coauthor credit? That is the recognition I’m asking for; the current “Related: #3350” reference doesn’t fully convey the original diagnosis and implementation. I’m not claiming authorship of the distinct implementation here. I understand this has already merged and that changing the existing commit’s trailers would involve rewriting history. I’m happy to work with you on a practical way to make the attribution right. If coauthor credit isn’t appropriate under the project’s contribution policy, could you explain how that policy applies here so we can resolve this with a shared understanding? Thanks—I’d like to get this sorted out constructively and keep contributing. |
`max_vio` measures expert-level imbalance: the busiest expert's tokens over the mean, with the top_k busiest experts dropped so that padding experts don't dominate it (#1332). With expert parallelism an MoE layer waits for the EP rank that receives the most routes, and `max_vio` tracks that poorly: hot experts that share a rank add up, hot experts on different ranks don't, and the dropped experts are often the ones that make a rank busy. This PR logs the rank-level imbalance next to `max_vio`. ## What it computes `get_global_moe_stats` already sums each micro step's per-layer expert counts over the EP group. From those counts, per micro step and layer: - **rank imbalance**: routes received by the busiest EP rank over the mean rank. EP rank r holds experts `[r*E/ep, (r+1)*E/ep)`, so the counts are viewed as `[..., ep, E/ep]` and summed. Padding routes count, since they run through the expert GEMMs like any other token. Every rank of an EP group holds the same values. A layer waits for the busiest rank of its EP group and, through its FSDP collectives, for the slowest EP group. So the logged value is the max over DP groups at each (micro step, layer), and the DP mean is logged next to it. Both ride in the existing all-reduces: the DP max is stacked into the `max_vio` MAX all-reduce and the DP mean into the `routing_confidence` SUM all-reduce. There is no new buffer, recording path or collective, and no host sync per micro step. Per-DP-group values are not logged, since that would need a gather. Logged in SFT and RL: - `ep_imbalance/rank/{mean,max}`: max over DP groups, then the mean and max over the step's layers and micro steps. - `ep_imbalance/rank_dp_mean/{mean,max}`: the same with the DP mean. - The console step line shows `EP Imbalance` (`ep_imbalance/rank/mean`). ## Measurement: SFT on DeepSeek-V4-Base The run is DeepSeek-V4-Flash-Base in bf16: 43 MoE layers (the first 3 hash-routed), 256 experts, top-6. It trains on long-context chat data packed into 524288-token rows. With cp = 8 and ep = 8, each EP group is one CP group and trains one row per micro step, 64K tokens per rank. It ran on 48 H200s (6 EP groups) with 4 micro steps per rank for 60 steps, which gives 61,920 (EP group, micro step, layer) values. | | mean | p50 | p90 | p99 | max | |---|---|---|---|---|---| | rank imbalance per EP group, micro step and layer | 1.30 | 1.27 | 1.50 | 1.72 | 3.38 | | … hash-routed layers 0-2 | 1.15 | 1.15 | 1.18 | 1.28 | 2.04 | | … learned layers 3-42 | 1.31 | 1.28 | 1.51 | 1.73 | 3.38 | | `max_vio` per layer, same counts | 2.94 | 2.87 | 4.05 | 5.36 | 22.8 | | `ep_imbalance/rank/mean` per step | 1.39 | 1.39 | 1.41 | 1.48 | 1.54 | | `ep_imbalance/rank/max` per step | 1.90 | 1.87 | 2.01 | 2.71 | 3.38 | | `ep_imbalance/rank_dp_mean/mean` per step | 1.30 | 1.30 | 1.31 | 1.32 | 1.34 | | `max_vio/mean` per step | 5.79 | 5.58 | 6.47 | 8.79 | 9.73 | - **Step time tracks the new metric more closely than `max_vio` (suggestive, not causal).** Over steps 2-60, forward plus backward time ranged from 185 to 208 s. It correlates r = 0.83 with `ep_imbalance/rank/mean` and r = 0.65 with `max_vio/mean`. On the 32 steps with more than 99.8% of tokens real (no padding spikes), the correlations are 0.73 and 0.59. - **`max_vio` and rank imbalance are loosely related.** Per (layer, micro step, EP group) r = 0.35. The learned-layer values with `max_vio` between 2 and 5 (94% of them) span 1.10 to 1.72 in rank imbalance (p1 to p99). The layer with the highest mean `max_vio` (4.5) has rank imbalance 1.23. The most imbalanced layer (1.65) has `max_vio` 3.4. - **Per layer:** the hash-routed layers sit at 1.12-1.16 and the learned layers at 1.16-1.65. In the hottest learned layers (1.45-1.65), the same rank is the busiest in 97-100% of micro steps. Most of the imbalance is static: a fixed per-layer permutation of experts over ranks, fit on steps 1-30, brings steps 31-60 from 1.30 to 1.11. - **The max over DP groups matters:** 1.39 against 1.30 for the DP mean, with only 6 groups. - **The tail comes from the run's older packing, not from the metric.** That trainer revision padded rows with token 0, and the maxima (rank imbalance 3.38, `max_vio` 22.8, and the slowest step) come from a single row that was 57% padding. At head, packs pad by repeating their own tokens (#4184), so that tail should not reproduce; the mean-level numbers (1.30 per value, 1.39 per-step DP max) are properties of the routing and carry over. The counts were recorded with this change applied to the earlier trainer revision that the run's packing needs. Every (row, layer) counts exactly row length × 6 routes, so activation-checkpoint recompute does not double count. ## Testing - A manual 4-rank gloo run of `get_global_moe_stats` at head (two EP groups of two, random counts; not committed) matches a brute-force reference for `ep_imbalance/rank` (max over DP groups) and `ep_imbalance/rank_dp_mean`, and `max_vio` and `routing_confidence` match the previous formulas. - SFT on 8 H200s with a small GLM MoE (8 experts, top-4, fake data, ep = 4, so two EP groups), on the previous revision: every key is logged, and the DP mean comes out below the DP max (1.38 vs 1.48). - The measurement above, with this change applied to an earlier trainer revision that the run's packing needed. - `ruff check`, `ruff format --check`.
This PR gives RL the same
pad_to_seq_lenflag that SFT supports (#4160). The flag pads every packed row toseq_len, so torch.compile and DeepGEMM stop recompiling on new lengths. Full-length padding makes an existing MoE problem worse (identical pad tokens all route to the same experts), so this PR also changes RL and SFT to pad with the row's own tokens instead of a constant id.Co-authored-by: Mika Senghaastrailer when squashing. Related: fix(trainer): distribute routing replay padding across experts #3350.We ran FP8 e2e checks on one H200 node per run, each with an empty DeepGEMM cache (harness in a31524e):
--data.pad-to-seq-len--orchestrator.pad-to-seq-lenThe flag only makes the row length static: the number of micro batches per step,
max_seqlen, and multimodal shapes still vary. The throughput and MFU metrics count padding, so they are not comparable with and without the flag.🤖 Generated with Claude Code