[ci] layer-0 grad-norm regression for per-method training tests (5a-ii) - #1396
Conversation
Phase 2 / 5a-ii: layers a device-keyed grad-norm check on top of the finite/non-zero grad assertions from 5a-i. After single_train_step + backward, block-0's trainable grad L2 norm is compared against a per-GPU reference in grad_norm_refs.json (in-repo). - grad_norm_regression.py: capture + device-key + compare/record helper. FASTVIDEO_GRADNORM_UPDATE=1 records the reference for the current GPU; unseeded/unsupported GPUs skip rather than fail. - Seeded references for GB200 (local dev) and L40S (the CI runner). The norm is stable to 4 decimals across runs; rtol=0.10. The cross-GPU spread on the causal/dfsft test (~9%) is why references are device-keyed. - pr_test.py: seed_grad_norm_references Modal entrypoint (L40S, UPDATE=1) to record/refresh a runner's reference.
Merge ProtectionsYour pull request matches the following merge protections and will not be merged until they are valid. 🟢 PR merge requirementsWonderful, this rule succeeded.
|
There was a problem hiding this comment.
Code Review
This pull request introduces device-keyed gradient norm regression checks for training smoke tests, allowing validation of layer-0 trainable gradients against reference values pinned per GPU architecture. The feedback suggests several improvements to the implementation: making CUDA device name matching case-insensitive for better robustness, optimizing gradient norm calculation by accumulating squared sums on the GPU to avoid multiple CPU-GPU synchronizations, and explicitly specifying UTF-8 encoding when reading and writing the reference JSON file.
Davids048
left a comment
There was a problem hiding this comment.
Most looks good.
Just clearer documentation on the tested configs
- Document the two seeding paths in grad_norm_regression.py's module docstring (CI / L40S via Modal vs local / non-L40S via pytest + FASTVIDEO_GRADNORM_UPDATE=1) and clarify that seed_grad_norm_references in pr_test.py is L40S-only — answers Davids048's question about how the GB200 values were obtained. - Drop unused H100/H200/A100 mappings from _DEVICE_MAPPINGS; only devices with seeded references in grad_norm_refs.json remain. - Make resolve_device_key's substring match case-insensitive. - Accumulate layer0_grad_norm's squared sums on the GPU and sync once at the end instead of one .item() per parameter. - Specify encoding="utf-8" on _REFS_PATH read/write for cross-platform safety.
|
Pushed
@gemini-code-assist (nits)
Re-requesting review when you have a moment. |
There was a problem hiding this comment.
Code Review
This pull request introduces a device-keyed grad-norm regression check for training smoke tests. It adds a regression harness that compares the L2 norm of transformer block 0's trainable gradients against reference values stored in a JSON file (grad_norm_refs.json), supporting both local and CI environments (such as L40S and GB200 GPUs). The check is integrated into the Wan causal and finetuning tests, and a Modal helper function is added to seed references. Feedback on the changes suggests using a file lock when updating the reference JSON file to prevent race conditions during parallel test execution.
| refs = _load_refs() | ||
| refs.setdefault(test_name, {})[device_key] = round(norm, 4) | ||
| _save_refs(refs) |
There was a problem hiding this comment.
When running tests in parallel (e.g., using pytest -n with pytest-xdist) with FASTVIDEO_GRADNORM_UPDATE=1 enabled, concurrent processes will attempt to read, modify, and write to the same grad_norm_refs.json file. This creates a race condition where one process can overwrite the updates made by another, or read a partially written file. \n\nTo prevent this, we can use a file lock to make the read-modify-write operation atomic. Since filelock is a standard dependency of packages like torch and huggingface_hub, it is guaranteed to be available in the environment.
from filelock import FileLock\n with FileLock(_REFS_PATH.with_suffix(\".lock\")):\n refs = _load_refs()\n refs.setdefault(test_name, {})[device_key] = round(norm, 4)\n _save_refs(refs)|
/merge |
Thanks for reviewing! |
Summary
Phase 2 / PR 5a-ii of the
fastvideo.trainCI plan. Layers a device-keyed layer-0 grad-norm regression on top of the per-method smoke tests landed in 5a-i (#1343).5a-i asserts the loss is finite and block-0 grads are finite + non-zero. This PR adds a numeric fingerprint: after
single_train_step+backward, the L2 norm of transformer block-0's trainable grads is compared against a per-GPU reference withinrtol.Changes
fastvideo/tests/train/methods/grad_norm_regression.py— helper:layer0_grad_norm(transformer)— global L2 norm of block-0 trainable grads.resolve_device_key()— maps the GPU name to a reference key (L40S / GB200 / H100 / …).check_grad_norm_regression(test_name, transformer, rtol=0.10)— asserts withinrtol; skips (never hard-fails) on an unsupported or not-yet-seeded GPU; withFASTVIDEO_GRADNORM_UPDATE=1records/refreshes the reference instead.test_wan_finetune.py/test_wan_causal_dfsft.py— callcheck_grad_norm_regression(...)after the existing finite/non-zero assertions.fastvideo/tests/train/methods/grad_norm_refs.json— in-repo references, seeded for GB200 (local dev) and L40S (the CI runner).fastvideo/tests/modal/pr_test.py—seed_grad_norm_referencesModal entrypoint (L40S,FASTVIDEO_GRADNORM_UPDATE=1) to record/refresh a runner's reference.Determinism & why device-keyed
method.on_train_start()seeds the global RNG + the method'scuda_generator(training.data.seed=42in the fixture) and the syntheticraw_batchis built after that call, so the grad norm is reproducible — identical to 4 decimals across local runs. Inputs are synthetic (torch.randntext-embed + VAE latent, all-ones mask); model weights are the realWan-AI/Wan2.1-T2V-1.3B-Diffuserscheckpoint. The check is a forward/backward wiring + scale fingerprint, not a real-data quality test.References are device-keyed because the norm differs across GPU architectures (bf16 accumulation, kernel choice). The causal/dfsft test spreads ~9% between GB200 (2.9781) and L40S (3.2562) — a single golden value couldn't cover both;
rtol=0.10.Grad-alignment coverage after this PR
WanModel)WanCausalModel)i.e. 2 (model, method) cells:
WanModel/FineTuneMethodandWanCausalModel/DiffusionForcingSFTMethod.dmd2/self_forcing/kdand other models (Hunyuan / Cosmos / LongCat / MatrixGame2) are out of scope here — see plan below.Test plan
pytest fastvideo/tests/train/methods -v→ 2 passed (asserting against GB200 references)modal run fastvideo/tests/modal/pr_test.py::seed_grad_norm_references/test train-framework— full GPU CI run on L40S (now asserts the grad-norm, no longer skips)Plan context
dmd2/self_forcing/kd(needs tiny teacher / fake-score fixtures)