Bug Fix: sanitise non-finite gradients in PPO.before_step (#2226) - #2229
Bug Fix: sanitise non-finite gradients in PPO.before_step (#2226)#2229dparikh79 wants to merge 1 commit into
Conversation
torch.nn.utils.clip_grad_norm_ is NaN-unsafe: a single NaN in any gradient produces a NaN total_norm and a NaN clip_coef, silently leaving NaN gradients in place. optimizer.step() then writes NaN into every parameter, after which the entire model is dead. Training continues with NaN losses and NaN actions until something like torch.multinomial finally raises, by which point recent checkpoints are unusable. In DD-PPO this is particularly bad because DDP all-reduce averages NaN with finite into NaN: a single bad mini-batch on one worker corrupts every worker's model in the next step. Replacing non-finite gradient elements with zero before clip_grad_norm_ runs turns a single bad mini-batch into a no-op update instead of a permanent weight corruption. torch.nan_to_num_ of a finite tensor is identity, so clean training paths produce bitwise-identical weights and there is no behaviour change in the common case. Credit to @alunxu for the rigorous bug analysis, root-cause walk, and minimal repro in facebookresearch#2226. Fixes facebookresearch#2226
|
Hi @dparikh79! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
@facebook-github-bot recheck cla |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
Summary
Fixes #2226.
torch.nn.utils.clip_grad_norm_is NaN-unsafe: a single NaN in any parameter's gradient produces a NaNtotal_norm, a NaNclip_coef, and silently leaves NaN gradients in place.optimizer.step()then writes NaN into every parameter. The training loop keeps running with a dead model until something liketorch.multinomialfinally raises on a NaN probability tensor, by which point recent checkpoints contain NaN parameters.In DD-PPO this is especially bad: DDP all-reduce averages NaN with finite into NaN, so a single bad mini-batch on one worker corrupts every worker's model on the next step. @alunxu's issue write-up walks through a reproduction on a ~180M-frame DD-PPO PointGoal navigation run where this happened in production.
Change
habitat-baselines/habitat_baselines/rl/ppo/ppo.py,PPO.before_step: beforeclip_grad_norm_runs, sanitise non-finite elements in every parameter gradient withtorch.nan_to_num_(p.grad, nan=0.0, posinf=0.0, neginf=0.0). This turns a NaN mini-batch into a no-op update instead of a permanent weight corruption.The fix runs
nan_to_num_only whentorch.isfinite(p.grad).all()is False, so:nan_to_num_of a finite tensor is identity, and we skip the call entirely. Bitwise-identical weights, no measurable perf hit.clip_grad_norm_returns a finite number,optimizer.step()updates withlr * 0 = 0, model weights unchanged.The fix matches @alunxu's exact proposal in the issue.
Minimal reproduction (no habitat needed)
Borrowed from @alunxu's #2226 write-up; demonstrates that today's
clip_grad_norm_silently leaves NaN gradients andoptimizer.step()corrupts weights:Applying the sanitise step before
clip_grad_norm_flips bothgrads finiteandweights finitetoTrue.Test plan
ppo.py:347-371to confirm the patched function is the one DD-PPO actually calls via thebefore_stephook.clip_grad_norm_is the only path that mutatesp.gradbeforeoptimizer.step()in this code path.test_ddppo_reduce.py,test_pointnav_resnet_policy.py) require the full habitat env + scene data downloads + distributed setup. Adding an isolated unit test for the NaN-sanitise behaviour without that infrastructure would either require extracting the sanitise block into a helper (larger surface) or building a minimal PPO instance (heavy). Happy to add one in a follow-up if a maintainer prefers a particular shape.CLA
Will need to sign the Meta CLA on first PR; will do so when the bot prompts.
Acknowledgements
AI Assistance Disclosure
Implementation drafted with Claude assistance against @alunxu's proposed fix. I reviewed every changed line, traced the call path through
before_step, and confirmed that the conditionalif not torch.isfinite(p.grad).all()guard makes the change a no-op on clean training paths. I am the human contributor accountable for this PR.