Repository navigation
Conversation
This was referenced Oct 10, 2026
samsja
added a commit
that referenced
this pull request
Oct 10, 2026
Replaces the three switches `model.fsdp_cpu_offload`, `model.optim_cpu_offload` and `model.full_offload` with one field: ```toml [model] offload = "optimizer" # default; also "full", "none" ``` This applies to both the RL trainer and SFT. The `fsdp` switch is not migrated: the mode is removed outright (rationale below). The `[model.full_offload] numa_bind` knob is dropped: no recipe, deployment, template or doc sets it. Full offload now always pins each rank's CPUs to its GPU's NUMA node. That was already the default. Ranks whose GPU reports no NUMA node still skip binding with a warning. On hosts without GPU sysfs topology (`/sys/bus/pci/devices/<bus>/numa_node`), binding is no longer optional: full offload fails there instead of being switched off. Why: the three switches could only be used one at a time, enforced by `cpu_offload_mutual_exclusion`. Because `optim_cpu_offload` defaulted to `true`, enabling full offload also meant writing `optim_cpu_offload = false`, otherwise validation failed (see the old `configs/advanced/deepseek-v4-flash/sft.toml` comment). With one value the conflicting combinations cannot be expressed, so that validator is deleted. The default behaviour does not change: optimizer-state offload stays on. Why drop `fsdp`: the mode was the stock torch FSDP2 `CPUOffloadPolicy` with no in-tree config, example, or test user; Muon rejected it and it broke NCCL weight broadcast. It was kept only for long-context runs whose fp32 masters/moments no longer fit on GPU (the reason it was restored in #3052); the `full` mode added in #3234 keeps FP32 masters, moments, and gradients in host RAM, covering that memory pressure instead. Gone with it: the `CPUOffloadPolicy` selection in `setup_fsdp`, CPU-side model init in `setup_model`/`load_dcp_from_hf`, `_move_buffers_to_cuda`, and the fsdp leg of the Gloo special case. Changes: - `ModelConfig.offload: Literal["none", "optimizer", "full"] = "optimizer"` replaces the three fields. `OptimizerInBackwardOffloadConfig` (and with it `numa_bind`) and its bool-normalizing `BeforeValidator` are gone. - Validators in `TrainerConfig` and `SFTConfig`: - full offload accepts only AdamW or SignSGD and disables clipping, now reading `offload == "full"`; - the `Muon optimizer does not support FSDP CPU offload` validators are deleted with the mode. - Runtime: - `setup_optimizer(..., offload=...)` replaces `cpu_offload` + `full_offload_config`, and its "both enabled" check is gone. - `FullCPUOffloadOptimizer` drops its unused `offload_config` argument. - `setup_full_cpu_optimizer_offload()` takes no arguments and always binds. - `model.py`: FSDP always uses the default `OffloadPolicy()`; CPU-side model init in `setup_model`/`load_dcp_from_hf` and `_move_buffers_to_cuda` are gone. - Tomls: `glm-4.5-air/swe-budget.toml` and `ci/integration/reverse-text-sft/full-offload-resume.toml` → `offload = "full"`; `glm-5.3/sft/h200/16k-12-node.toml` → `offload = "none"`; `glm-5.3/sft/h200/base.toml` → `offload = "optimizer"`. Also updated `docs/scaling.md`, the `deepseek-v4-flash/sft.toml` comment, and `skills/training/start-run/SKILL.md`, which documented the removed fields. Breaking: the old keys now fail with `extra_forbidden`, and `offload = "fsdp"` fails the `Literal`. There is no shim. Interaction with #3849 (one trainer-common config base): #3849 moves the full-offload and Muon/FSDP validators from `TrainerConfig`/`SFTConfig` onto `BaseTrainerConfig`. This PR edits the full-offload validator's condition and deletes the Muon/FSDP one, so the conflict is textual only: keep #3849's single copy of the full-offload validators with `self.model.offload == "full"` and drop its Muon/FSDP copy. Whichever lands second takes a small rebase. Other open PRs that set or read the old keys need a rebase: - tomls: #3883, #3879, #3766, #3765, #3729, #3629, #3627, #3616, #3544 - code: #3885 (`optim_cpu_offload`), #3739 (`full_offload` validators), #3724, #3719, #3699, #3610, #3487 (`fsdp_cpu_offload` / `enable_gloo`) Test plan - `pytest tests/unit/orchestrator tests/unit/test_configs.py tests/unit/train/test_cpu_adam.py tests/unit/train/test_state_offload.py -m "not gpu"`: 336 passed, 1 skipped (Qwen3-VL snapshot not cached on this host), 4 deselected (GPU; this host has no GPU). The existing offload config tests are updated to the new field. `test_load_configs` covers the 4 edited tomls. - `cli(TrainerConfig|SFTConfig, ...)`: `--model.offload full|none` and the default (`optimizer`) resolve as expected; `optim_cpu_offload` and `offload = "fsdp"` are rejected. - `import prime_rl.trainer.rl.train, prime_rl.trainer.sft.train` works. - `ruff check` and `ruff format --check` on the changed Python files: clean. - Not run: GPU or multi-node training with `offload = "full"`. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking TOML/API surface touches distributed training memory paths (FSDP offload, optimizer-in-backward, Gloo); behavior should match prior modes when mapped correctly, but mis-migrated configs will fail at parse time. > > **Overview** > **Breaking config change:** `model.fsdp_cpu_offload`, `model.optim_cpu_offload`, and `model.full_offload` are replaced by a single **`model.offload`** setting (`"optimizer"` default, or `"full"`, `"fsdp"`, `"none"`). RL and SFT share the same field; example TOMLs and scaling docs are updated accordingly. > > The mutual-exclusion validator between the old flags is removed because only one mode can be expressed. **`[model.full_offload]` / `numa_bind` is dropped** — `"full"` always runs NUMA binding and CPU optimizer thread setup via parameterless `setup_full_cpu_optimizer_offload()`. Runtime paths (`setup_optimizer`, FSDP CPU offload in `model.py`, Gloo when offload is `"full"` or `"fsdp"`) key off the new enum; `FullCPUOffloadOptimizer` no longer takes an offload config object. > > Default training behavior is unchanged: optimizer-state CPU offload remains the default (`"optimizer"`). Old TOML keys are not aliased and should fail validation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 35da73a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: sami jaghouar <sami.jaghouar@gmail.com>
samsja
pushed a commit
that referenced
this pull request
Oct 19, 2026
…nly CP style (#4134) Removes ring attention context parallelism. Ulysses is now the only CP implementation, so the `cp_style` option is gone. - Deletes `layers/ring_attn.py`, `substitute_ring_attn`, `substitute_gpt_oss_ring_attention`, the ring branches in `utils/cp.py`, and the `ring-flash-attn` dependency plus its `_compat` shim. - Removes `model.cp_style` and `resolve_cp_style`. `setup_context_parallel` always installs the Ulysses attention patch. `setup_cp_params` / `setup_cp_attention_params` lose their `cp_group` and `cp_style` arguments. - DeepSeek-V4 and GLM-5 DSA keep their model-internal CP: queries stay sequence-sharded and keys are all-gathered inside the attention layer. This never used the ring patch. For DeepSeek-V4, the KV all-gather is 1 KB/token (MQA, head_dim 512), and the sparse attention work is already balanced. Ulysses would move 64 heads × 512 of queries and outputs each way, and its kernels need ≥32 heads per rank, so DeepSeek-V4 stays on the all-gather. - `CPSupport` / `cp_support` becomes `cp_blocker(config, cp) -> str | None`. The default rejects a `cp` that cannot shard the attention heads at model setup, for example `cp=3 must divide the 32 attention heads and divide or be a multiple of the 4 KV heads for Ulysses CP`. Before, `cp_style="auto"` fell back to ring in this case. DeepSeek-V4 and GLM-5 DSA override it to `None`. - Removes `cp_style = "ulysses"` from the example/advanced configs. Configs that still set `cp_style` now fail validation. **Breaking:** `trainer.model.cp_style` is removed. Models whose head count is not divisible by `cp` can no longer use CP. Related open PRs: - #4068 changes how ring attention params are passed. This PR deletes that code path. - #3446 defers the ring-flash-attn imports. This PR removes the dependency. - #3724 touches CP padding in `utils/cp.py` / the train loops. Expect conflicts in `setup_cp_params` call sites. - #4057 (DeepSeek-V4.1) overrides `cp_support` with an empty style set. After this PR, that override becomes `cp_blocker` returning its reason. ### Validation SFT, 5 steps, real weights truncated to 4 layers, fake packed variable-length data (seq_len 16384, batch 4). Each run is `main` vs this PR on 4 H200s each, with the same seed. On `main`, `cp_style="auto"` resolved to Ulysses for the FlashAttention models and to ring (the KV all-gather) for DeepSeek-V4. | model / CP | loss main | loss PR | max Δloss main vs PR | max Δloss main vs main | max rel Δ grad norm main vs PR | max rel Δ grad norm main vs main | peak mem main / PR | |---|---|---|---|---|---|---|---| | Qwen3-30B-A3B, cp=2 (Ulysses) | 16.4002 16.2154 16.0340 15.7772 14.8917 | 16.4002 16.2157 16.0356 15.7765 14.8915 | 0.0016 | 0.0018 | 0.84% | 0.99% | 14.6 / 14.6 GiB | | GLM-4.5-Air, cp=4 (Ulysses) | 15.3254 14.8527 13.9303 13.5545 12.9239 | 15.3254 14.8528 13.9303 13.5546 12.9235 | 0.0004 | 0.0001 | 0.20% | 0.17% | 40.6 / 40.6 GiB | | GLM-5.3 DSA, cp=4 (own KV all-gather) | 13.3890 13.3431 13.2668 13.2369 13.1076 | 13.3890 13.3431 13.2667 13.2369 13.1076 | 0.0001 | 0.0001 | 0.03% | 0.01% | 81.3 / 81.3 GiB | | DeepSeek-V4-Flash, cp=2 (own KV all-gather) | 20.2111 19.9461 19.3640 19.2330 18.6930 | 20.2111 19.9463 19.3641 19.2329 18.6930 | 0.0002 | 0.0001 | 0.04% | 0.07% | 124.1 / 124.1 GiB | "main vs main" is a second pair of `main` runs from the same configs and seed. In every model, the main-vs-PR deltas are within or near that run-to-run noise. Step 1 loss is identical in every run. The Qwen3-30B step-1 grad norm ranges 254.31–255.22 across all four of its runs. Tests: - `pytest -m "not gpu"` on tests/unit/orchestrator, tests/unit/test_configs.py, tests/unit/train, tests/unit/utils: all pass except `test_cpu_adam.py::test_native_cpu_adamw_matches_fused_torch_and_preserves_gradients`, which fails on `main` too. - GPU: `test_gpt_oss.py -k context_parallel` (torchrun ×2, Ulysses with sharded sinks), `test_deepseek_v4_kernels.py -k context_parallel` (12 passed), `qwen3_5/test_model.py` (5 passed). - `ruff check` / `ruff format`. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Breaking distributed-training behavior: removes `cp_style` and ring fallback, so invalid `cp` vs head counts now error instead of using ring; large deletion in the CP/attention stack affects all long-context training jobs. > > **Overview** > **Removes ring context parallelism** so Ulysses is the only trainer CP path: deletes `ring_attn.py`, ring attention patching, the `ring-flash-attn` GPU dependency, and related `_compat` shims. > > **Config and API cleanup:** Drops `trainer.model.cp_style` and `resolve_cp_style`; `setup_context_parallel` always installs Ulysses. `CPContext` no longer carries a style; `setup_cp_params` / `setup_cp_attention_params` no longer take `cp_group` or `cp_style`. Example/advanced TOMLs lose explicit `cp_style = "ulysses"`. > > **Validation model change:** `CPSupport` / `cp_support` becomes `cp_blocker(config, cp) -> str | None` on model classes—the default enforces Ulysses head-sharding rules at setup (configs that previously relied on `cp_style="auto"` falling back to ring will now fail). DeepSeek-V4 and GLM-5 DSA override with `None` because they keep **model-internal** CP (sequence-sharded queries, all-gathered keys), unchanged by this refactor. > > Docs in `scaling.md` are updated to describe Ulysses-only CP and sparse-attention all-gather behavior instead of ring vs Ulysses. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c167f05. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
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.
Summary
Remove CP-divisibility tokens and duplicated zero-loss DP microbatches from RL model inputs. Distribute the original packed row into balanced, potentially empty CP shards, and represent an inactive DP lane with a zero-token slot that still participates in distributed execution.
The implementation is generalized across the custom model families and HF inactive language-backbone execution. It does not use a Qwen-only allowlist. Both
model.cp_unpaddedandmodel.inactive_micro_batchesdefault totrue; managed RL propagates the matching settings to the orchestrator. Existing model/backend and head/group-divisibility requirements still apply.This is a draft: two strict numerical comparisons and two integration checks remain open. Details and the distinction between passing checks, failed comparisons, and unrun checks are below.
Twenty-update real RL follow-up: warmed-up trainer throughput is essentially flat. With updates 1–10 excluded and 11–20 measured, padded versus unpadded is 6468.0 → 6486.5 original tokens/s (+0.29%) on Qwen3-30B, CP4 Ulysses / EP4, using the original
torch.compile(fullgraph=False). Neither run has recompilation after update 1 or late communicator initialization; measured-half drift is −3.33% / −3.04%. The earlier three-update 11–16% trainer-throughput deficit did not persist. End-to-end RL throughput is 529.8 → 481.4 tokens/s (−9.14%), accompanied by longer rollout waits in the unpadded run. Independent sampled batches, policy trajectories and separate nodes prevent attributing either difference to padding removal. The 18.4% dense / 4.1% MoE latency gains below come from controlled DP2/CP2 benchmarks with an inactive data lane. This real CP4/EP4 topology has one data lane and removed only 102 appended tokens from about 1.96M original tokens in the measured baseline window.Motivation and design
There are two independent sources of unnecessary model work:
The data contract should express the actual tokens and the distributed schedule separately. For example, nine tokens over four CP ranks have lengths
[3, 2, 2, 2]; one token has lengths[1, 0, 0, 0]. The same partition must apply to every token-aligned stream and to reconstruction in backward.A rank with zero local tokens cannot simply skip its model call: other ranks can still need its FSDP parameters, EP experts and collective participation. Inactive slots therefore preserve the required hooks, communication and zero gradients while forwarding no fabricated input tokens.
This work preserves loss membership and component normalization, packed sample boundaries, sampling/router replay, optimizer behavior and the configured optimization/reduction dtypes. Sampling-set
-1storage, internal kernel alignment, and SFT fixed-pack dataset padding remain separate storage contracts. No dependency, submodule, oroptimization_dtype/reduce_dtypechanges are included.Implementation
Partition and collective contract
CPPartitionmetadata with balanced lengths, offsets and checked token-axis slicing.CPContextand pass changing token/partition metadata explicitly through forwards and checkpoint recomputation.Model and kernel integration
Packing, transport and training
inactivefield toMicroBatch; remaining empty slots contain no input tokens, images or sample identities.What actually ran: RL versus SFT
The end-to-end jobs were RL. No end-to-end SFT training job was launched. SFT validation covered its shared CP integration, summed-CE/IGNORE_INDEX behavior, fake dataset and loss tests, and eager/compiled model gradients.
Real RL configuration
Qwen/Qwen3-30B-A3B-Thinking-2507, custom implementation, 48 layersmath, taskset IDi3_math; agent harnessnull, subprocess runtimeCompileConfig(fullgraph=False, mode=None)enabled; trainer log confirms all 48 layers were compiledmax_inflight_activations=51e-6; native full CPU optimizer/gradient offloadseq_len=32768, inferencemax_model_len=32768,max_completion_tokens=31000batch_size=8,group_size=4, initial/max inflight 8Jobs were launched with
uv run rl @ base.toml @ overlays..., using the suppliedmath32k_sl1node_off8.tomlexample plus smoke/length/unpadded/resume overlays. These were short execution/regression runs, not convergence or model-quality experiments. Evaluation and W&B were disabled for the smoke runs.The real RL jobs establish CP4/EP4 execution, offload and resume. Because there is one trainer data lane, the separate DP2/CP2 tests below establish inactive-DP-lane correctness. The real runs consumed different sampled batches, so their throughput is not a controlled speed comparison.
Validation performed
GPU validation used interactive
srunallocations anduv run/torchrun, on H200 hardware. Environment: Torch2.13.0+cu130, CUDA 13.0, NCCL 2.29.7, FA2 2.8.3, FA3 3.0.0, FLA 0.5.2, ring-flash-attn 0.1.8, prime-kernels0.1.0+cu130torch2.13.0, Transformers 5.6.2.Validation was performed in targeted subsets and standalone diagnostics during development. There was no single final execution of the entire model/backend/topology cross-product. The original three-update/resume jobs predate the checkpoint-counter ordering correction; the twenty-update pair uses the final implementation and verifies the corrected saved counters.
Correctness and integration results
[9,5],[9,0],[0,7],[3,1]; maximum parameter-gradient discrepancy 0.01074 within the 0.015 absolute / 0.05 relative boundgit diff --checkpassesFixture correction: Initial tiny Qwen3-MoE probes left stacked expert parameters uninitialized. Those objective/offload/performance results were rejected and rerun with explicit nonzero expert initialization and production FP32 routers. The results above use the corrected runs. The padded initialized-MoE reference also failed the initial 0.015 absolute gradient bound (max 0.024353), which motivated the documented 0.025 absolute bound for that regression; dense and exact collective bounds were unchanged.
torch.compileand CUDA Graph behaviortorch.compile(fullgraph=True, dynamic=True): seven graph variants, zero graph breaks, exact forward/backward checks.fullgraph=Trueforward and backward with checkpointing. Zero/unit shard specialization is explicit, rather than a newly introduced eager fallback.fullgraph=False, as configured in the supplied recipe. Compile enablement and successful updates do not imply the entire application is one compiled graph.fullgraph=Falsepaths were exercised and their limitations retained.mode="reduce-overhead"captures/replays forward and backward at isolated T33/4097/8193/32765: two CUDA Graph captures, one Dynamo graph, zero breaks per size.Performance measurements
Controlled comparisons
The dense and captured-row MoE comparisons used a separate four-H200 allocation, identical real rows/masks within each comparison, full checkpointing, AdamW, five warmups and 30 measured updates (three windows of ten). Reported latency uses the slowest rank per iteration. Startup/warmup is separate from steady state.
Dense model: four layers, hidden size 1024, FFN 3072, vocabulary 4096, Q16/KV4/head64, FA3, strict compile; rows 8193 and 4097. One lane deliberately holds real data and the other is copied/inactive. Maximum five-warmup duration was 31.24 s padded versus 27.96 s unpadded; this includes initialization/compilation and is not an isolated compiler-time measurement.
MoE replay: four layers, hidden size 1024, eight experts/top-2, expert FFN 1024, vocabulary covering captured IDs, initialized experts and FP32 routers. Accepted rollout rows contain 7173/8203 tokens and 7052/8082 trained positions. Timing uses a masked logprob objective on a small model and a deliberately underfilled two-round schedule, not the original 30B weights, the full RL objective, or the packer's optimal schedule. Corrected measurements ran unpadded before padded; maximum warmup duration was 12.86 versus 12.14 s. Active-rank memory is essentially unchanged.
CP-only, ring, communication and packing
[7176,7176,7176,7176]→[7173,0,0,0]. This measures local preparation/serialization, not network latency.Real RL observations, not controlled speedups
Rollout admission and checkpoint writes contribute substantially to wall time. Different sampled rows and checkpoint behavior prevent interpreting this table as an end-to-end performance improvement.
Twenty-step RL follow-up — measured updates 11–20
Both fresh RL runs completed 20 optimizer updates, final weight broadcasts and final checkpoints from commit
5abeb22c1: padded job 1815 on node 011 and unpadded job 1814 on node 053. Each used an exclusive eight-H200 node with four trainer GPUs and four inference GPUs. The nodes have matching H200/driver and Xeon Platinum 8468 CPU specifications; separate-node variability remains a limitation.The workload is Qwen/Qwen3-30B-A3B-Thinking-2507, source
math/ taskseti3_math, CP4 Ulysses / EP4, FA3, 32K context, completion cap 31000, batch 8/group 4 and inflight 8. Both use the originaltorch.compile(fullgraph=False, mode=None), full activation checkpointing/offload, native full CPU AdamW offload, lr1e-6, and unchanged numerical settings. Both start from pretrained weights. Inference uses TP4/DP1 and seed 0; filesystem rollout transport retains exact packed batches. A final checkpoint after update 20 keeps checkpoint I/O outside the measured training loop. Runtime versions: PyTorch2.13.0+cu130, Triton3.7.1, FA33.0.0, vLLM0.30.0, Transformers5.6.2.Warmup protocol, fixed before launch: updates 1–5 warmup, 6–10 settling diagnostics, 11–20 primary measurement. Each trainer reports 48 compiled layers. Every trainer rank logged one shape recompilation during update 1; none occurred in updates 2–20. NCCL
INIT COMPLETErecords stayed at 13 per run across all trainer ranks from startup through update 20. INIT-only diagnostics avoid per-collective logging overhead. Initial jobs using intrusive NCCL TUNING logging were cancelled before their first optimizer update and contribute no measurements. This checks startup/recompilation behavior; it does not isolate individual collective kernel latency.Primary window: ten measured updates per run
Compute throughput is sum(original input tokens) / sum(
time/forward_backward), not an average of per-step rates. Original tokens are counted from retained batches using nonemptyenv_names; the baseline's 102 appended tokens are excluded.time/forward_backwardincludes forward/backward, optimizer/offload and scheduler work. Active time is total step time minus batch wait and checkpoint time, so it also includes broadcasts, loading and metric overhead. Total RL step time includes rollout waiting. Final checkpoint time is outside all three primary-window denominators. The broadcast timer includes the subsequent CUDA synchronization, so the compute-phase timer alone is not a synchronized end-to-end update measurement.Late-window stability
Neither run crosses the predeclared 10% drift flag, and neither has late recompilation or communicator initialization. Per-update latency and throughput still vary with packed-row count and row length. Recorded trainer loss, entropy, mismatch-KL and optimizer statistics remain finite. The earlier three-update 11–16% trainer-throughput deficit did not persist in this warmed-up window. The longer run establishes near-equal observed trainer throughput for this CP4/EP4 workload; it does not establish a causal speedup or regression, because the runs use independent rollouts, different policy trajectories and separate nodes. The unpadded run's lower end-to-end RL throughput must also be retained in the result; longer rollout waits and admission differences prevent attributing it to padding removal.
This topology still has one data lane, so it does not exercise removal of duplicated inactive-DP rows. Its direct CP token savings are tiny. Controlled DP2/CP2 benchmark benefits above and this CP4/EP4 result answer different workload questions.
Completion and checkpoint checks: both SLURM jobs ended
COMPLETED, exit0:0, within their original allocations (2:37:20 padded; 2:33:05 unpadded). Final checkpoint saves took 4m53s and 4m34s respectively, after the measured loop. Loading only their progress fields through distributed-checkpoint I/O verifiedstep=20,total_samples=160, andtotal_tokens=3866020padded /4036622unpadded, exactly matching all 20 trainer updates. This verifies the corrected saved counters; a full model/optimizer resume from these new checkpoints was not run.The final runtime audit found no active-run trainer, orchestrator, inference or environment errors. Both inference servers logged
EngineDeadErrorduring SIGTERM cleanup after training/checkpoint completion; the launcher then exited successfully. Those shutdown messages are retained in the audit and are not presented as training failures or silently omitted.The full curves, exact per-step CSV, window summaries, runtime audit and checkpoint-counter checks are retained locally as
steady20-performance.{png,pdf},steady20-steps.csv,steady20-summary.json,steady20-runtime-audit.jsonandsteady20-checkpoint-counters.jsonunderoutputs/remove-pad-validation/. They are not bundled into this PR. The deployment overlays and chronological progress log remain private.Remaining gaps and limits
Additional coverage limits: no end-to-end SFT run; no multi-node benchmark; no convergence/quality conclusion from these short RL validation runs; no claim that every architecture/topology/backend combination is certified. The draft retains these gaps openly and adds no model-family restriction to bypass them.
Reproduction and artifact provenance
The checked-in distributed harness is
tests/unit/train/test_cp_collectives.py. Obtain an interactive four-GPU allocation withsrun, then run from the checkout:-k cp_exchange_and_gather_gradientsselects the small exact collective subset. The command above describes the full persisted matrix; executed development runs used bounded subsets and additional diagnostics, as described above.Implementation commit:
5abeb22c1a7075da1e402d25273dff565aa87d32. Implementation and experiments started from88f5e6def15507b42cb520f3a7bbc57f500c0c1e. The final staged files were checked against the retained validation manifest. The original validation allocations, three-update RL jobs and twenty-update follow-up are complete; both follow-up allocations were released normally.Raw diagnostic scripts, per-rank logs, benchmark JSON, Chrome traces and captured rollout rows remain in the author's local
outputs/remove-pad-validation/; real runs are inoutputs/remove-pad-runs/. Deployment-specific TOML overlays and the chronological research log remain outside the PR. These local artifacts are not bundled or remotely downloadable from this PR; the checked-in tests and the detailed results above are the published evidence. Key artifact families includecore-final-ranks,metrics-calibrated-ranks,optimizer-state-ranks,init-offload-ranks,persisted-final-ranks,deepseek-{attention,variability,torch-norm}-ranks,benchmark-dp-*,benchmark-replay-init-*,benchmark-ring.json, andcudagraph-changes.json.