Skip to content

Fix gather_for_metrics remainder for sized IterableDataset with dispatch_batches=False - #4416

Open
deep1blue wants to merge 1 commit into
huggingface:mainfrom
deep1blue:fix-sized-iterable-dataset-shard-remainder
Open

deep1blue wants to merge 1 commit into
huggingface:mainfrom
deep1blue:fix-sized-iterable-dataset-shard-remainder

Conversation

@deep1blue

@deep1blue deep1blue commented Oct 18, 2026 •

Copy link
Copy Markdown

What does this PR do?

Fixes #4359.

When dispatch_batches=False is used with a sized IterableDataset across multiple processes (num_processes > 1), prepare_data_loader wraps the dataset in IterableDatasetShard and returns a DataLoaderShard.

Previously, two metadata mismatches caused DataLoaderStateMixin.begin() to compute self.remainder = 0 instead of the true remainder at the end of the dataset:

  1. IterableDatasetShard.__len__ returns the padded per-rank shard length (math.ceil(len(dataset) / (batch_size * num_processes)) * batch_size), which is always a multiple of batch_size. Because IterableDatasetShard did not define total_dataset_length, DataLoaderStateMixin.begin() fell back to len(self.dataset) (the padded shard length) instead of len(self.dataset.dataset) (the unpadded dataset length).
  2. DataLoaderShard.total_batch_size read num_processes and split_batches from self.batch_sampler. For an IterableDataset, PyTorch's default _InfiniteConstantSampler / BatchSampler is not wrapped in BatchSamplerShard, so getattr(batch_sampler, "num_processes", 1) evaluated to 1 and total_batch_size returned the per-rank batch_size instead of batch_size * num_processes (when split_batches=False).

Consequently, self.remainder = length % self.total_batch_size evaluated to 0, and Accelerator.gather_for_metrics() did not truncate the padded duplicate samples on the final batch.

Changes

  • Add total_dataset_length property on IterableDatasetShard returning len(self.dataset) (which still raises TypeError/NotImplementedError for unsized IterableDatasets and is safely ignored by with suppress(Exception): in DataLoaderStateMixin.begin()).
  • Update DataLoaderShard.total_batch_size and DataLoaderShard.total_dataset_length to handle IterableDatasetShard.
  • Add unit tests in tests/test_data_loader.py covering both split_batches=False and split_batches=True.

Credits & Acknowledgments

When prepare_data_loader wraps a sized IterableDataset in IterableDatasetShard
(dispatch_batches=False), DataLoaderStateMixin.begin() previously fell back to
len(self.dataset), which returns the padded per-rank shard length rather than
the underlying dataset length. In addition, DataLoaderShard.total_batch_size
inspected the default batch_sampler (where num_processes is not set) instead of
the IterableDatasetShard configuration, returning the per-process batch_size
rather than the global total_batch_size when split_batches=False.

As a result, dataloader.remainder evaluated to 0 whenever the padded per-rank
length was divisible by batch_size, causing Accelerator.gather_for_metrics() to
retain padded duplicate samples at the end of evaluation.

Expose IterableDatasetShard.total_dataset_length and teach DataLoaderShard's
total_batch_size and total_dataset_length properties to read from
IterableDatasetShard so remainder is computed against the true dataset length
and global batch size.

Fixes huggingface#4359

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Parth Vadera <parthvadera@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gather_for_metrics keeps padding duplicates for a sized IterableDataset with dispatch_batches=False

1 participant