Skip to content

Commit 481329d

Browse files
committed
fix(data): detect unsharded iterable overrides
1 parent cb3a713 commit 481329d

3 files changed

Lines changed: 43 additions & 16 deletions

File tree

monai/data/grid_dataset.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ class GridPatchDataset(IterableDataset):
218218
219219
"""
220220

221+
_shards_by_worker = True
222+
221223
def __init__(
222224
self,
223225
data: Iterable | Sequence,
@@ -404,6 +406,8 @@ class PatchDataset(IterableDataset):
404406
405407
"""
406408

409+
_shards_by_worker = True
410+
407411
def __init__(
408412
self, data: Sequence, patch_func: Callable, samples_per_image: int = 1, transform: Callable | None = None
409413
) -> None:

monai/data/iterable_dataset.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@
2525
pd, _ = optional_import("pandas")
2626

2727

28+
def _source_shards_by_worker(data: Iterable[Any]) -> bool:
29+
"""Return whether the source declares that its iterator partitions by worker."""
30+
source_type = type(data)
31+
if "__iter__" in source_type.__dict__:
32+
return bool(source_type.__dict__.get("_shards_by_worker", False))
33+
return bool(getattr(source_type, "_shards_by_worker", False))
34+
35+
2836
class IterableDataset(_TorchIterableDataset):
2937
"""
3038
A generic dataset for iterable data source and an optional callable data transform
@@ -40,6 +48,8 @@ class IterableDataset(_TorchIterableDataset):
4048
4149
"""
4250

51+
_shards_by_worker = True
52+
4353
def __init__(self, data: Iterable[Any], transform: Callable | None = None) -> None:
4454
"""
4555
Args:
@@ -77,9 +87,11 @@ class ShuffleBuffer(Randomizable, IterableDataset):
7787
epochs: number of epochs to iterate over the dataset, default to 1, -1 means infinite epochs.
7888
source_shards_by_worker: whether ``data`` already partitions its stream
7989
using ``torch.utils.data.get_worker_info``. ``None`` automatically
80-
recognizes MONAI ``IterableDataset`` sources, ``True`` avoids a
81-
second worker partition for any worker-aware source, and ``False``
82-
preserves the outer partition for unsharded iterable datasets.
90+
recognizes built-in MONAI sources that declare worker partitioning.
91+
A subclass that overrides iteration without declaring that capability
92+
is treated as unsharded. ``True`` avoids a second worker partition
93+
for any worker-aware source, and ``False`` preserves the outer
94+
partition for unsharded iterable datasets.
8395
8496
Note:
8597
Both ``monai.data.DataLoader`` and ``torch.utils.data.DataLoader`` do not seed this class (as a subclass of
@@ -102,6 +114,8 @@ def run():
102114
103115
"""
104116

117+
_shards_by_worker = True
118+
105119
def __init__(
106120
self,
107121
data,
@@ -121,14 +135,15 @@ def __init__(
121135
epochs: number of source iterations, where ``-1`` means infinite.
122136
source_shards_by_worker: whether ``data`` already partitions its
123137
stream using ``torch.utils.data.get_worker_info``. ``None``
124-
automatically recognizes MONAI ``IterableDataset`` sources.
138+
automatically recognizes built-in MONAI sources that declare
139+
worker partitioning.
125140
"""
126141
super().__init__(data=data, transform=transform)
127142
self.size = buffer_size
128143
self.seed = seed
129144
self.epochs = epochs
130145
self.source_shards_by_worker = (
131-
isinstance(data, IterableDataset) if source_shards_by_worker is None else source_shards_by_worker
146+
_source_shards_by_worker(data) if source_shards_by_worker is None else source_shards_by_worker
132147
)
133148
self._idx = 0
134149

@@ -232,6 +247,8 @@ class CSVIterableDataset(IterableDataset):
232247
233248
"""
234249

250+
_shards_by_worker = True
251+
235252
def __init__(
236253
self,
237254
src: str | Sequence[str] | Iterable | Sequence[Iterable],

tests/data/test_shuffle_buffer.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,24 @@ def test_worker_sharded_source_is_not_sharded_twice(self):
9090
self.assertEqual(len(outputs), 40)
9191
self.assertEqual(set(outputs), set(range(40, 80)))
9292

93-
def test_explicit_unsharded_source_keeps_outer_worker_partition(self):
94-
"""Verify explicit unsharded mode preserves outer worker partitioning."""
95-
outputs = []
96-
for worker_id in range(2):
97-
source = _UnshardedMonaiIterable(range(40))
98-
buffer = ShuffleBuffer(source, buffer_size=8, seed=7, source_shards_by_worker=False)
99-
worker_info = SimpleNamespace(num_workers=2, id=worker_id)
100-
with patch("monai.data.iterable_dataset.get_worker_info", return_value=worker_info):
101-
outputs.extend(buffer)
93+
def test_unsharded_monai_subclass_keeps_outer_worker_partition(self):
94+
"""Verify default and explicit unsharded modes preserve outer partitioning."""
95+
for source_shards_by_worker in (None, False):
96+
outputs = []
97+
for worker_id in range(2):
98+
source = _UnshardedMonaiIterable(range(40))
99+
buffer = ShuffleBuffer(
100+
source,
101+
buffer_size=8,
102+
seed=7,
103+
source_shards_by_worker=source_shards_by_worker,
104+
)
105+
worker_info = SimpleNamespace(num_workers=2, id=worker_id)
106+
with patch("monai.data.iterable_dataset.get_worker_info", return_value=worker_info):
107+
outputs.extend(buffer)
102108

103-
self.assertEqual(len(outputs), 40)
104-
self.assertEqual(set(outputs), set(range(40)))
109+
self.assertEqual(len(outputs), 40)
110+
self.assertEqual(set(outputs), set(range(40)))
105111

106112
def test_epochs(self):
107113
buffer = ShuffleBuffer([1, 2, 3, 4], seed=0, epochs=2)

0 commit comments

Comments
 (0)