fix(nft): fix deterministic mode and add the SD3.5 DiffusionNFT e2e - #87
Open
Rockdu wants to merge 4 commits into
Open
fix(nft): fix deterministic mode and add the SD3.5 DiffusionNFT e2e#87Rockdu wants to merge 4 commits into
Rockdu wants to merge 4 commits into
Conversation
Rockdu
force-pushed
the
ci/e2e-nft-sd3-pickscore
branch
from
August 1, 2026 07:33
2b9b6f8 to
773c686
Compare
Rockdu
marked this pull request as ready for review
August 1, 2026 07:55
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
RolloutManager.__init__, so DiffusionNFT's timestep shuffle reproduces across runs.scripts/run-diffusion-nft-sd3-pickscore.shunder--deterministic-mode, matching the other recorded recipes.tests/e2e/short/test_sd3_pickscore_nft_2xGPU.pyto the existingstage-c-3-gpu-h200suite (2 train GPUs colocated with 2 sglang rollout engines + 1 PickScore GPU,NUM_ROLLOUT=2), plus its recorded standard.Why
The SD3.5 DiffusionNFT recipe was not bitwise reproducible, so no e2e standard could be recorded for it. Two runs of 3 rollouts under
--deterministic-modeagreed on only 7 of 29 metric series.miles/ray/data_conversion_hub/nft.py:79shuffles each sample's timesteps withtorch.randperm(...)and no generator, i.e. from the global RNG. That code runs inside the@ray.remote class RolloutManager, which never seeded torch — only the FSDP train actor does (actor.py:72). So the permutation came from default entropy and differed every run.--deterministic-modecannot cover this:_enable_deterministic_trainingonly sets the cuDNN/use_deterministic_algorithmsknobs (it does no seeding at all) and is only invoked in the train actor.Seeding is preferable to passing
--no-diffusion-nft-shuffle-timesteps: the RNG still advances per rollout, so shuffling keeps varying across rollouts as intended while reproducing across runs.This does not invalidate the existing standards
The obvious risk with seeding a shared actor is that already-recorded e2e standards start drifting and need re-recording. They do not, on three independent grounds:
nft.py:79is the only unguarded global-torch-RNG draw in the whole package —grep -rE "torch\.(rand|randn|randint|randperm|normal)\(" miles/ | grep -v "generator="returns that one line.step_strategy_hub.pyuses explicit local generators (np.random.default_rng(seed),torch.Generator().manual_seed(...)), and the remainingrandom/np.randomcall sites (data order, port picking, dp_rank) are untouched bytorch.manual_seed.loss_type == "nft"(arguments.py:1476), so Flow-GRPO recipes never execute it, andflow_grpo.pyitself makes no RNG draws at all.tests/e2e/short/test_sd3_ocr_grpo_2xGPU.pywas run on this branch and passed bit-for-bit against its committed standard —[e2e-metrics] PASSED: 8 metric series match test_sd3_ocr_grpo_2xGPU.json.Two things for the reviewer to note
train/grad_normis deliberately absent from the metric list. fp16 forward enablesShardedGradScaler, whose initial scale (65536) overflows on NFT's much larger loss (~15 vs Flow-GRPO's tiny policy loss), so step 1 logsnan, the optimizer step is skipped and the scale halves. Since the verifier compares withgot == wantandnan == nanisFalse, including it would fail on every run — and a tolerance would not help, asmath.isclose(nan, nan)is alsoFalse. The Flow-GRPO standards have clean floats at every step, which is why they can include it.DiffusionNFT is a dual-policy x0-MSE objective with no importance ratio, so the Flow-GRPO
log_prob_*series are never emitted bynft_loss_formulaand cannot be used here.nft_pos_lossvsnft_neg_lossis the NFT analogue of the log-prob drift check: it measures how far the trained policy has moved from the EMA reference.Validation
Determinism. h200, 3 GPUs,
NUM_ROLLOUT=3,--deterministic-mode, full metrics JSONL compared with exact float equality (perf/*wall-clock keys excluded).The pre-fix divergence signature, which the fix removes:
rollout/reward/raw_mean@0rollout/reward/raw_mean@1rollout/reward/raw_mean@2train/nft_loss@1train/nft_t_mean@1nft_t_meandiffering at ~1e-9 withnft_num_timestepsidentical is the fingerprint of reordering rather than different values. Rollouts 0 and 1 stayed bit-identical because step 1's update is skipped by the fp16 scaler, so the weights were unchanged going into rollout 1; divergence only surfaced at rollout 2.Determinism on the CI runner. The standard was recorded three times on
["h200", "3gpu"]— the same runner class the test verifies on — and all three agree on all 12 series, including across the final code edit (2b9b6f8→773c686).Curve health. Same recipe at
NUM_ROLLOUT=100:rollout/reward/raw_meanReward buckets rise then plateau (
0.770 0.812 0.826 0.819 0.835 0.839 0.837 0.847 0.850 0.844); the median rises 0.767 → 0.850. The largest single-rollout drop is -0.0714 at idx 32 (nft_lossspiking 17.2 → 23.4) and it recovers on the very next rollout to 0.828 / 16.9; all five worst drops are single-rollout and recover, with no sustained decline. Thenft_pos_loss/nft_neg_lossgap grows from ~1e-5 to ~5e-5 over the run — a trend rather than flat, but 0.014% of the branch loss, smooth, and expected as the policy drifts from the lagging EMA reference (--ema-decay 0.001).Cost. 3 rollouts end-to-end in 7m27s, of which ~5.5 min is engine/model load.
est_time=600forNUM_ROLLOUT=2, takingstage-c-3-gpu-h200to 1800s total against the 60-minute job timeout.Files
miles/ray/rollout.pyRolloutManager.__init__(the fix).scripts/run-diffusion-nft-sd3-pickscore.sh--deterministic-mode.tests/e2e/short/test_sd3_pickscore_nft_2xGPU.pystage-c-3-gpu-h200.tests/ci/fixtures/e2e_standards/test_sd3_pickscore_nft_2xGPU.jsontests/fast/backends/fsdp_utils/test_loss_hub_nft.pystage-a-cpu.Checklist
pre-commit run --all-filespasses — run on the touched files only (pre-commit run --files ...), all hooks passpytest -xis green — scope:tests/fast/backends/fsdp_utils/test_loss_hub_nft.py, 7 passed; the full fast suite was not run locallypython3 train.py --helpstill parses — no new flags; the recipe uses the existing--deterministic-mode