Repository navigation
eval: deterministic validation (seeded prompts + flow sampling); log-only grad-norm MAD #644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ryanco/eval-loaders
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,4 +17,11 @@ check_val_every_n_epoch: 100 | |
|
|
||
| # set True to to ensure deterministic results | ||
| # makes training slower but gives more reproducibility than just setting seeds | ||
| deterministic: False | ||
| deterministic: False | ||
|
|
||
| # Fixed gradient clipping, applied by Lightning in configure_gradient_clipping. | ||
| # The MAD spike detector in ModelWrapper.on_after_backward is log-only and never | ||
| # touches gradients; this is the only thing that clips. null = no clipping (the | ||
| # value is picked from the grad-norm curve of a log-only baseline run). | ||
| gradient_clip_val: null | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. medium (scope): The grad-clipping change has nothing to do with deterministic eval, and it changes training defaults. enable_grad_norm defaults to True, so HPT runs used to clip MAD-flagged spikes to the median. With |
||
| gradient_clip_algorithm: norm | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -184,38 +184,23 @@ def on_after_backward(self): | |
| info["policy_grad_norms_mad_threshold"] = threshold | ||
| grad_norm_flagged = grad_norm_val > threshold | ||
| info["policy_grad_norms_mad_flag"] = float(grad_norm_flagged) | ||
| if grad_norm_flagged: | ||
| torch.nn.utils.clip_grad_norm_(self.parameters(), max_norm=median) | ||
| if self.trainer.is_global_zero: | ||
| print( | ||
| "[GRAD_NORM_SPIKE] " | ||
| f"step={self.global_step} " | ||
| f"grad_norm={grad_norm_val:.4f} " | ||
| f"median={median:.4f} " | ||
| f"mad={mad:.4f} " | ||
| f"threshold={threshold:.4f}", | ||
| flush=True, | ||
| ) | ||
|
|
||
| if not grad_norm_flagged: | ||
| self.grad_norm_history.append(grad_norm_val) | ||
| if grad_norm_flagged and self.trainer.is_global_zero: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. low (behavior-change): With the MAD clip turned log-only and |
||
| print( | ||
| "[GRAD_NORM_SPIKE] " | ||
| f"step={self.global_step} " | ||
| f"grad_norm={grad_norm_val:.4f} " | ||
| f"median={median:.4f} " | ||
| f"mad={mad:.4f} " | ||
| f"threshold={threshold:.4f}", | ||
| flush=True, | ||
| ) | ||
|
|
||
| # Log-only detector: every step enters the rolling window; clipping is | ||
| # trainer.gradient_clip_val. | ||
| self.grad_norm_history.append(grad_norm_val) | ||
| for k, v in info.items(): | ||
| self.log("Train/" + k, v, on_step=False, on_epoch=True, sync_dist=True) | ||
|
|
||
| def on_before_optimizer_step(self, optimizer): | ||
| if not self.enable_grad_norm: | ||
| return | ||
| grad_norm = torch.nn.utils.clip_grad_norm_( | ||
| self.parameters(), max_norm=float("inf") | ||
| ) | ||
| self.log( | ||
| "Train/policy_grad_norms_clipped", | ||
| float(grad_norm), | ||
| on_step=False, | ||
| on_epoch=True, | ||
| sync_dist=True, | ||
| ) | ||
|
|
||
| def _val_heads(self) -> dict: | ||
| return { | ||
| "valid": self.evaluator, | ||
|
|
@@ -224,6 +209,14 @@ def _val_heads(self) -> dict: | |
| } | ||
|
|
||
| def on_validation_start(self): | ||
| # Deterministic eval: rewind the algo's per-batch RNG step so this | ||
| # pass replays the same prompts and sampling noise as any other pass | ||
| # over the same batches (egomimic/algo/hpt.py). Algos without the hook | ||
| # (e.g. PI) are unaffected. | ||
| reset_eval_rng = getattr(self.model, "reset_eval_step", None) | ||
| if callable(reset_eval_rng): | ||
| reset_eval_rng() | ||
|
|
||
| heads = [h for h in self._val_heads().values() if h is not None] | ||
| if not heads: | ||
| return | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit (simplification): The RANK/LOCAL_RANK/SLURM_PROCID fallback only runs when no process group exists, which under Lightning means only in tests (test_eval_determinism monkeypatches RANK). Checking LOCAL_RANK before SLURM_PROCID would also give two nodes the same seed if the fallback ever ran. Return
get_rank()when initialized, else 0, and have the test monkeypatch_eval_rank.