test(e2e): add the DiffusionNFT pickscore e2e on stage-c-3-gpu-h200 - #157
Merged
Conversation
6 tasks
Rockdu
force-pushed
the
test/nft-e2e-ci
branch
from
August 12, 2026 01:07
3e7ce69 to
9144faf
Compare
6 tasks
Rockdu
force-pushed
the
test/nft-e2e-ci
branch
from
August 12, 2026 07:44
9144faf to
00eb322
Compare
Rockdu
marked this pull request as ready for review
August 12, 2026 09:16
This was referenced Aug 13, 2026
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.
Third of three. Base: #156.
What
tests/e2e/short/test_sd3_nft_pickscore_3xGPU.pyonstage-c-3-gpu-h200— the runner already had a spare third GPU the SD3 GRPO e2e does not use.scripts/run_diffusion_nft_sd3_pickscore.py—--deterministic-mode, matching the three other e2e-backed recipes.tests/ci/e2e_metrics_registry.py— a recorded NaN now matches itself.It runs the recipe's real configuration
PickScore reward,
flowgrpo_pickscore, 8 prompts × 8 samples = 64 per rollout, micro-batch 4, 2 colocated train/rollout GPUs + 1 dedicated reward GPU. The only knob cut is--num-rollout, 100 → 4.An earlier attempt used the recipe's
--smokepath and produced nothing worth comparing: 4 samples, all scoring 0 on OCR, zero reward variance, zero advantage, zero gradient. That is what--smokeis for — checking the pipeline runs — and not what an e2e should verify.The recorded standard
grad_normnft_pos_loss−nft_neg_lossnanWhy 4 rollouts.
--num-steps-per-rollout 1makes one rollout one optimizer step, and the fp16 grad scaler starts at 65536 against an fp16 ceiling of 65504, so step 1 overflows and is skipped. Until a step lands the weights do not move, the EMA reference stays identical to the policy, and NFT's two loss branches are algebraically one value — which is exactly what steps 1 and 2 show. They separate at step 3. Two rollouts would exercise none of the dual-branch loss, the thing this test exists to guard. Three is the minimum; four leaves a step of margin.Eight was the first attempt and cost 5 minutes of runner time per PR more than it needed: fixed cost is 283s (image, engines, SD3.5 + PickScore load, first rollout), each further rollout 74s.
The standard is reproducible, and that took #156
Recorded twice, independently, on this branch's
00eb322. The two runs are bit-identical across all 10 metric series × 4 steps.That is the end-to-end proof for #156. Before its seeding fix, two recordings of the same configuration on an identically-pinned stack diverged at the first training step:
train/nft_loss@1train/nft_t_mean@1The rollout side agreed even then — it was the train-pair construction that drew from an unseeded RNG. Finding that is why this PR sat unrecordable until #156 existed.
A recorded NaN could never match itself
_values_matchcompared with==, ormath.iscloseunder a tolerance. Neither matches NaN against NaN, so any standard containing one was permanently red.Not hypothetical: an fp16 run emits
grad_norm=nanon the step the grad scaler overflows its init scale, deterministically, andactor.pyemits the metric betweenunscale_andstep— so the value the scaler is about to discard is the one that gets logged. Droppinggrad_normfrom the metric list instead would have hidden a real property of fp16 NFT training.Now a NaN matches a NaN and nothing else: a run that stops overflowing still fails, and no tolerance lets a number pass against a recorded NaN. Both directions are asserted.
Metrics
The ones the GRPO e2e structurally cannot cover: NFT has no log-prob ratio, its loss is the two-branch x0-MSE, and
nft_t_meantracks the sigma grid the prepare hook feeds the DiT — the path #155 changes.train/nft_r_meanis in the list and is exactly 0.5 at every step, which is what a mean-zero GRPO advantage forces. It guards little; dropping it needs no re-record (the check iterates the test's list, not the standard's), so say the word.Stack
Checklist
pre-commit runpasses — on the touched files (black/ruff/isort/autoflake)run_suite.py --hw cuda --suite stage-c-3-gpu-h200 --list-onlylists it["h200","3gpu"]), and reproduced bit-for-bit on a second independent recordingpython3 train.py --helpstill parses — no flag changesThe recipe trains, not just starts
100 rollouts of the unmodified recipe (no
--num-rolloutcut, no--smoke) on a 4×H200 devbox, from this branch's code — wandb run. Job succeeded, 100/100 steps, no errors.rollout/reward/raw_meanrollout/reward/raw_stdtrain/nft_losstrain/grad_normnanPickScore reward rises 0.77321 → 0.84485 over the run, +9.3% (first-10 vs last-10 mean). Reward std stays in 0.04–0.08 throughout, so the signal is real spread and not a collapsed batch.
Two things in this long run confirm what the 4-step standard asserts:
grad_normisnanat step 1 only — the grad scaler overflows its 65536 init scale once, backs off, and never again. The standard records that same single NaN, which is why_values_matchhad to learn to match one.nft_pos_loss == nft_neg_lossexactly at steps 1, 2 and 5, and differs everywhere else (max |diff| 1.5e−04). Steps 1–2 are the scaler warmup the standard captures; step 5 is a later skipped step. This is the dual-branch behaviour the e2e guards, and it confirms a 2-rollout test would have seen none of it.