Repository navigation
Internal ensembling in Aurora models #199
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
Merged
Wessel (wesselb)
merged 24 commits into
microsoft:main
from
Slowika:agaslowik/ensemble-members-internal-to-aurora
Oct 14, 2026
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
b5d8773
Add support for internal ensembling in Aurora models. Add tests.
Slowika c1a9cad
Address formatting complaint.
Slowika a4c12de
Fix test.
Slowika f0fa368
Split forward()/rollout() from forward_ensemble()/rollout_ensemble() …
Slowika aa60f79
Address wesselb feedback.
Slowika 225ef3a
Update `rollout_ensemble` docstring.
Slowika ecce6df
Remove stale `try/except` block.
Slowika ff7db21
Add input validation to `rollout_ensemble`.
Slowika 4312687
Update `models.md` doc.
Slowika f7a5783
Update test to demonstrate an exception is raised in `rollout_ensemble`.
Slowika 4d449f0
Address CI failures in test_rollout_ensemble_num_ensemble_members_one…
Slowika 6297886
Docs and API.
Slowika a584479
Simplify tests.
Slowika 59b755a
Add divisibility check and docstring update.
Slowika 91102b3
Clean up docstring (reference to `split_batch`)
Slowika e45927e
Update docs/models.md with an anchor for the Internal Ensembling section
Slowika a549825
Update docs/models.md
Slowika 6a46de1
Simplify test_ensemble.py imports
Slowika cfd3a98
Add divisibility check test
Slowika 9d62e6c
Update comments in test_ensemble
Slowika bd7f3bb
Simplify rollout_ensemble test of raising
Slowika a23e908
Merge branch 'main' into agaslowik/ensemble-members-internal-to-aurora
Slowika 64d2e57
Update aurora/rollout.py
Slowika f5cb3ec
Update docs/models.md
Slowika File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| """Copyright (c) Microsoft Corporation. Licensed under the MIT license. | ||
|
|
||
| Tests for ensemble rollout and tiling utility functions. | ||
| """ | ||
|
|
||
| import itertools | ||
|
|
||
| import pytest | ||
| import torch | ||
|
|
||
| from ._helpers import _OUTPUT_ONLY_SURF, _SURF_VARS, BATCH, _make_batch, _make_small_v1p5 | ||
| from aurora import Aurora, rollout_ensemble, split_batch, tile_batch | ||
| from aurora.model.film import AdaptiveLayerNorm | ||
|
|
||
|
|
||
| def _unzero_adaptive_layer_norms(model: Aurora, std: float = 0.1) -> None: | ||
| """Nudge every `AdaptiveLayerNorm`'s modulation away from its zero initialisation. | ||
|
|
||
| A freshly constructed model is exactly insensitive to its conditioning signal, and hence to | ||
| the injected noise, because `AdaptiveLayerNorm.ln_modulation` is zero-initialised. | ||
| """ | ||
| for m in model.modules(): | ||
| if isinstance(m, AdaptiveLayerNorm): | ||
| with torch.no_grad(): | ||
| m.ln_modulation[-1].weight.normal_(std=std) | ||
| m.ln_modulation[-1].bias.normal_(std=std) | ||
|
|
||
|
|
||
| def test_tile_and_split_batch_roundtrip(): | ||
| b, n = 2, 3 | ||
| batch = _make_batch(batch_size=b) | ||
|
|
||
| tiled = tile_batch(batch, n) | ||
| assert len(tiled.metadata.time) == n * b | ||
| for v in (*tiled.surf_vars.values(), *tiled.atmos_vars.values()): | ||
| assert v.shape[0] == n * b | ||
|
|
||
| members = split_batch(tiled, n) | ||
| assert len(members) == n | ||
| for member in members: | ||
| assert member.metadata.time == batch.metadata.time | ||
| for k, v in member.surf_vars.items(): | ||
| torch.testing.assert_close(v, batch.surf_vars[k]) | ||
| for k, v in member.atmos_vars.items(): | ||
| torch.testing.assert_close(v, batch.atmos_vars[k]) | ||
|
Slowika marked this conversation as resolved.
|
||
|
|
||
| # `batch` has batch size `b`, which is not divisible by `n`. | ||
| with pytest.raises(ValueError): | ||
| split_batch(batch, n) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("stochastic", [True, False]) | ||
| def test_forward_ensemble(stochastic: bool): | ||
| torch.manual_seed(0) | ||
| model = _make_small_v1p5(stochastic=stochastic) | ||
| _unzero_adaptive_layer_norms(model) # Otherwise, the noise has no effect on the output. | ||
| model.eval() | ||
| surf_vars = tuple(v for v in _SURF_VARS if v not in _OUTPUT_ONLY_SURF) | ||
| batch = _make_batch(surf_vars=surf_vars) | ||
| n = 3 | ||
| batch = tile_batch(batch, n) | ||
| b = next(iter(batch.surf_vars.values())).shape[0] | ||
|
|
||
| with torch.inference_mode(): | ||
| pred = model.forward(batch, lead_times=torch.full((b,), 6.0)) | ||
| members = split_batch(pred, n) | ||
|
|
||
| if stochastic: | ||
| # Every member receives independent noise, so members must differ. | ||
| for member1, member2 in itertools.combinations(members, 2): | ||
| assert (member1.surf_vars["2t"] - member2.surf_vars["2t"]).abs().mean() > 1e-2 | ||
| else: | ||
| # Check that all are equal. | ||
| for m in range(1, n): | ||
| torch.testing.assert_close( | ||
| members[0].surf_vars["2t"], members[m].surf_vars["2t"], atol=1e-3, rtol=1e-3 | ||
| ) | ||
|
|
||
|
|
||
| def test_rollout_ensemble(): | ||
| num_ensemble_members = 3 | ||
| torch.manual_seed(0) | ||
| model = _make_small_v1p5(stochastic=True) | ||
| _unzero_adaptive_layer_norms(model) # Otherwise, the noise has no effect on the output. | ||
| model.eval() | ||
| batch = _make_batch(surf_vars=tuple(v for v in _SURF_VARS if v not in _OUTPUT_ONLY_SURF)) | ||
| steps = 2 | ||
|
|
||
| with torch.inference_mode(): | ||
| preds = list(rollout_ensemble(model, batch, steps, num_ensemble_members)) | ||
|
|
||
| assert len(preds) == steps | ||
| for members in preds: | ||
| assert len(members) == num_ensemble_members | ||
| for member in members: | ||
| for v in member.surf_vars.values(): | ||
| assert v.shape[0] == BATCH | ||
| # Every member receives independent noise, so members must differ. | ||
| for member1, member2 in itertools.combinations(members, 2): | ||
| assert (member1.surf_vars["2t"] - member2.surf_vars["2t"]).abs().mean() > 1e-2 | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("stochastic, num_ensemble_members", [(True, 0), (False, 3)]) | ||
| def test_rollout_ensemble_raises(stochastic: bool, num_ensemble_members: int): | ||
| model = _make_small_v1p5(stochastic=stochastic) | ||
| with pytest.raises(ValueError): | ||
| next(rollout_ensemble(model, _make_batch(), 2, num_ensemble_members)) | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.