Fix silent stat corruption in distributed standardization-stats depadding - #748
Open
nikhil3495 wants to merge 2 commits into
Open
nikhil3495 wants to merge 2 commits into
nikhil3495 wants to merge 2 commits into
Conversation
sadamov
self-requested a review
September 14, 2026 14:19
sadamov
requested changes
Sep 14, 2026
Collaborator
There was a problem hiding this comment.
Nice catch, reproduced with a real DataLoader (101/4/8 loses samples 95 and 99 and counts sample 100 three times). Suggestions below, please rebase first, the CHANGELOG conflicts with #743.
Outside the diff so not suggestible: get_original_indices, original_indices and padded_indices have no callers left after this PR (__getitem__ can return self.base_dataset[min(idx, self.total_samples - 1)]), I would drop them and test_original_indices too.
Below I am mostly talking about shorter comments and docstrings.
…ding PaddedWeatherDataset pads WeatherDataset so its length divides evenly across --distributed ranks. compute_standardization_stats.main() then "depadded" after the multi-rank gather by selecting gathered[:total_samples] (equivalently gathered[i] for i in original_indices) -- which assumes the padded rows land at the tail of the gathered, rank-major-concatenated tensor. They don't: DistributedSampler(shuffle=False) stripes dataset indices across ranks (rank r gets r, r+world_size, r+2*world_size, ...), so whenever total_samples % world_size != 0, the padded rows end up scattered across several ranks' tails, not the global tail. The prefix-selection then silently keeps a few padded (duplicated last-sample) rows while dropping an equal number of real ones, corrupting the saved parameter_mean/std.pt and diff_mean/std.pt tensors. Confirmed with a standalone simulation using this repo's actual DistributedSampler: for total_samples=101, world_size=4, batch_size=8, the old logic swapped real samples 95 and 99 for padded samples 101 and 102. Fix real vs. padded identification locally, per rank, before the gather: add real_sample_mask_per_batch(), which derives -- from pure index arithmetic on the sampler's (already deterministic) iteration order -- which rows of each minibatch are real. Filter both the parameter-stats and diff-stats accumulators by this mask as soon as each batch is computed, so the post-gather concatenation already contains exactly the real samples regardless of order (order doesn't matter for a mean/std reduction). This also lets the now-unnecessary get_original_indices / n_original_windows positional bookkeeping in main() be dropped. Not a duplicate of mllam#409/mllam#412/mllam#413 (closed via mllam#411): those fixed a shape/IndexError bug in the same file's gather logic, not this sample-identity mismatch, and the tests mllam#411 added only check that slicing preserves shape -- they never simulate an actual multi-rank DistributedSampler, which is why this gap went uncaught. Add tests/test_compute_standardization_stats.py::TestRealSampleMaskPerBatch reproducing the bug scenario (via the real DistributedSampler) and verifying the mask-based fix recovers the exact original index set. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLydvPZKD3RL4mcgAETmNG
- Turn real_sample_mask_per_batch into PaddedWeatherDataset.real_sample_masks - Drop now-dead original_indices/padded_indices/get_original_indices - Fix inaccurate comments flagged in review (real vs. padded rows) - Rewrite Bug 3 tests to exercise a real DataLoader instead of re-implementing its batching logic - Shrink CHANGELOG entry to one line matching existing style Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WxwetMbSNLD4ibdisG2MmH
nikhil3495
force-pushed
the
fix/distributed-standardization-stats-depadding
branch
from
September 15, 2026 12:14
385e035 to
df280e7
Compare
Contributor
Author
|
Thanks for the detailed review! I've addressed all points:
All pushed. Let me know if anything else needs adjusting! |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Describe your changes
compute_standardization_stats.py --distributedpadsWeatherDataset(viaPaddedWeatherDataset) so its length divides evenly across ranks, then after the multi-rank gather "depads" by takinggathered[:total_samples](equivalentlygathered[i] for i in original_indices]). That assumes padded rows land at the tail of the gathered, rank-major-concatenated tensor.They don't:
DistributedSampler(shuffle=False)stripes dataset indices across ranks (rankrgetsr, r+world_size, r+2*world_size, ...), so whenevertotal_samples % world_size != 0, the padded rows are scattered across several ranks' local tails rather than one global tail. The prefix-selection then silently keeps a few padded (duplicated last-sample) rows while dropping an equal number of real ones — corrupting the savedparameter_mean/std.ptanddiff_mean/std.pttensors used to standardize the whole dataset.I confirmed this with a standalone simulation using this repo's actual
DistributedSampler: fortotal_samples=101, world_size=4, batch_size=8, the old logic swaps real samples95and99for padded samples101and102.Fix: identify real vs. padded rows locally, per rank, before the gather, instead of trying to reconstruct sample identity after it.
real_sample_mask_per_batch()derives — from pure index arithmetic on the sampler's already-deterministic iteration order — which rows of each minibatch are real, with no dependency on gather order. Both the parameter-stats and diff-stats accumulators are filtered by this mask as soon as each batch is computed, so the post-gather concatenation already contains exactly the real samples, in whatever order (order doesn't matter for a mean/std reduction). This also removes the need for the previousget_original_indices()/n_original_windowspositional bookkeeping inmain().Not a duplicate of #409/#412/#413 (all closed via #411): those fixed a shape/
IndexErrorbug in the same file's gather logic, not this sample-identity mismatch. The tests #411 added only check that slicing preserves shape/values on synthetic tensors — they never simulate an actual multi-rankDistributedSampler, which is why this gap went uncaught.No new dependencies required.
Issue Link
closes #749
Type of change
Checklist before requesting a review
pullwith--rebaseoption if possible).Checklist for reviewers
Each PR comes with its own improvements and flaws. The reviewer should check the following:
Author checklist after completed review
Checklist for assignee
Generated with Claude Code