validation rollout sliced - #770
Conversation
|
@codex review |
|
Thanks @amogh-gulati! One high-level question: why is this separate from the existing Also looks like you have some (real) CI failures now that I've finally fixed the spurious ones. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a182db0acc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| def should_log_validation_images(epoch: int, frequency: int) -> bool: | ||
| """Return whether to log validation images for a 1-based training epoch.""" | ||
| def should_run_on_epoch_freq(epoch: int, frequency: int) -> bool: |
There was a problem hiding this comment.
Restore should_log_validation_images
The old should_log_validation_images helper was removed by this rename, but tests/test_trainer.py still imports and exercises it. Any run that collects that module now fails with ImportError before the tests execute, so this breaks the existing test suite; keep a wrapper/export or update those callers in the same commit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Is this still a valid concern? I agree with codex.
| if self.distributed is not None: | ||
| torch.distributed.barrier() |
There was a problem hiding this comment.
Avoid blocking nonzero ranks before long rollout
When rollout validation is enabled under NCCL/DDP for the intended long horizons, non-main ranks enter this barrier before rank 0 runs the entire validation rollout, so they can sit inside a distributed collective for the full rollout duration. Since init_distributed_mode does not configure a longer process-group timeout, quarter-degree 90/360-day rollouts that exceed the default timeout can abort otherwise healthy training; configure a timeout that covers this path or avoid starting the collective until the long rank-0 work is done.
Useful? React with 👍 / 👎.
The epoch argument to should_run_rollout_validation was previously ignored, so rollout validation ran every epoch regardless of config.
Uses a lazily created gloo process group with a 12h timeout so the default NCCL watchdog cannot abort healthy training during long rollouts.
The old should_log_validation_images name was still imported by test_trainer.py, failing CI at collection.
alxmrs
left a comment
There was a problem hiding this comment.
Review part 1/N. Will return to this when I can.
| from typing import Any, NamedTuple | ||
|
|
||
| import dask | ||
| import numpy as np |
There was a problem hiding this comment.
I'm surprised we didn't have numpy before!
|
|
||
| def should_log_validation_images(epoch: int, frequency: int) -> bool: | ||
| """Return whether to log validation images for a 1-based training epoch.""" | ||
| def should_run_on_epoch_freq(epoch: int, frequency: int) -> bool: |
There was a problem hiding this comment.
Is this still a valid concern? I agree with codex.
| return requested_steps | ||
|
|
||
|
|
||
| class RolloutValidationSpec(NamedTuple): |
There was a problem hiding this comment.
🐑 I prefer using data classes over named tuples. Though, if tuples make this cleaner, that's ok too (I could lack context).
There was a problem hiding this comment.
If this is a public contract, it would be nice to add a docstring here to explain what this is for and what it does.
| return float(delta) | ||
|
|
||
|
|
||
| def resolve_rollout_validation_day_spec( |
There was a problem hiding this comment.
I think these new methods and classes should live in util somewhere to not clutter train.
| self.rollout_validation_steps = cfg.rollout_validation_steps | ||
| self.rollout_validation_days = cfg.rollout_validation_days | ||
| self.rollout_validation_steps_forward = cfg.rollout_validation_steps_forward | ||
| self.rollout_validation_freq = cfg.rollout_validation_freq | ||
| self._rollout_validation_pg: torch.distributed.ProcessGroup | None = None |
There was a problem hiding this comment.
I think we should pull these up into a single rollout validation config object, not as top lvl train settings.
There was a problem hiding this comment.
feel free to pull in related, existing vars from the train config into here.
| if self.should_run_rollout_validation(epoch): | ||
| rollout_val_stats = self.validate_rollout_one_epoch(epoch) | ||
| end_epoch_rollout_val_time = time.perf_counter() | ||
| else: | ||
| rollout_val_stats = {} | ||
| end_epoch_rollout_val_time = None |
There was a problem hiding this comment.
What is the train time cost of this operation (approximately)?
| logger.info(f"Aggregating validation logs") | ||
| return val_aggregator.get_logs(label="val") | ||
|
|
||
| def should_run_rollout_validation(self, epoch: int) -> bool: |
There was a problem hiding this comment.
If we have a rollout validation config object, then this could just be a null check (does the object exist?) at the call site -- this function would go away.
There was a problem hiding this comment.
To be clear, the config file would have a config object that builds a new data class or ADT. The ADT could live in util and would be built at the top of train.py.
| 0, available_steps | ||
| ).values | ||
| specs = [ | ||
| resolve_rollout_validation_day_spec( |
There was a problem hiding this comment.
Maybe this method could live as a static build method on the RolloutValidationSpec data class?
| torch.distributed.barrier(group=group) | ||
| yield False | ||
|
|
||
| def validate_rollout_one_epoch(self, epoch): |
There was a problem hiding this comment.
I'm torn if a lot of the contents of this method should live in the stepper (like validate_one_epoch) or not. That would follow convention, but I'm not sure what the right convention is. The way this is done has some advantages.
alxmrs
left a comment
There was a problem hiding this comment.
2/2. Looks pretty good so far! My main note of feedback is on how we package this new process.
| "a value of 10 logs on epochs 1, 11, 21, ..." | ||
| ), | ||
| ) | ||
| rollout_validation_steps: int = Field( |
There was a problem hiding this comment.
See comment in train.py about packaging these new options differently.
| return ValBatchOutput(loss, loss_per_channel, input_data, label, outs, batch.ctx) | ||
|
|
||
|
|
||
| def _get_rollout_step_chunks( |
There was a problem hiding this comment.
I think this function could be simpler if we used batched:
https://docs.python.org/3/library/itertools.html#itertools.batched
| hist: int, | ||
| area_weights: torch.Tensor, | ||
| wet: torch.Tensor, | ||
| num_prognostic_channels: int, | ||
| normalize: Normalize, | ||
| tensor_map: TensorMap, | ||
| distributed_reduce: bool = True, |
There was a problem hiding this comment.
🐑 I could be wrong, but could we use the DatasetSpec here instead? Many of the arguments we need are there.
Do we need to include TensorMap and Normalize? We're generally trying to move away from including those multitions.
| if len(data.target) == 0: | ||
| raise ValueError("No target values in data") | ||
|
|
||
| _, target_unnorm = get_aggregator_dicts( |
There was a problem hiding this comment.
🐑 optional: We may be able to create a replacement to this function and the one below to use "modern" normalization code that manages state better so we could avoid adding the multitons.
|
Hey @fomo-bot, will you address the concerns that I've raised in my review of Amogh's code? Where there is ambiguity, please let me know your thinking and I'll help resolve it. Feel free to push back on my suggestions where that is appropriate, given your reading of the code in the branch. Thank you! |
This PR adds long-horizon autoregressive rollout validation to the training loop, complementing the existing single-step validation.
Previously, validation only checked one-step-ahead prediction error each epoch. This change allows training to periodically roll the model forward autoregressively over the validation period and log rollout RMSE at configured horizons, such as 90 days and 360 days.
The implementation supports multiple rollout horizons in a single rollout. For example, with
rollout_validation_days: [360, 90], the code rolls out once to the maximum horizon, records the 90-day metrics at the intermediate cutoff, and then continues to 360 days. This avoids launching a separate 90-day rollout.Metrics are logged separately by horizon, variable, depth level, and depth band using raw, un-normalized fields. Rollout validation currently runs only on rank 0, with other ranks waiting at a barrier, since the validation window is not sharded across workers. The rollout is processed in bounded chunks to avoid materializing the full forecast horizon’s targets at once.
This is currently wired up for the standard single-scale training schedule and is a no-op for FOMO’s multi-scale schedule.
Added rollout validation config options:
rollout_validation_daysrollout_validation_stepsrollout_validation_freqrollout_validation_steps_forwardConfig
In
configs/samudra_om4_v2/train.yaml, rollout validation is enabled with:test run here - https://wandb.ai/ocean_emulators/default/runs/e7r70ugc?nw=nwuseramoghgulati
metrics present in rollout_val for 90 days and 360 days