Note about modification to PyTorch’s default dataloader¶
This section describes modifications to PyTorch’s default DataLoader used in SkiNet to prevent spawning new processes at the beginning of each epoch.
A Jupyter notebook with examples is available at:
SkiNet/ML/dataloaders/examples/RepeatDataloaders.ipynb
Motivation¶
Spawning new worker processes at the beginning of each epoch in PyTorch’s DataLoader
(when num_workers > 0 and persistent_workers=False) causes:
Overhead of initialising the dataset in each worker (by deserialising from the main process)
Memory overhead as each worker requires its own memory space
Any worker-cached state is lost when workers are shut down
Copy-on-write behaviour during fork increases memory if the dataset contains Python lists or dicts
Potential deadlocks and race conditions with shared resources
Dataloaders and subprocesses¶
When iterating over a DataLoader:
Dataset initialisation runs in the main process (
Dataset.__init__).DataLoader initialisation runs in the main process (
DataLoader.__init__).Prefetching & Queues: At the start of epoch 0, PyTorch spawns
num_workersseparate subprocesses. Dataset indices are put in worker input queues (up toprefetch_factor × num_workersindices ahead). Each worker calls__getitem__asynchronously; the main process collects results.
At the start of each epoch, for batch in loader calls iter(loader), which calls __iter__():
If
persistent_workers=Trueandnum_workers > 0and no iterator exists yet,_get_iterator()creates a new iterator.If an iterator exists (epoch > 0), its state is reset via
_reset()— workers are reused.If
persistent_workers=Falseandnum_workers > 0,_get_iterator()is called every epoch, creating new workers each time.
When num_workers > 0, _get_iterator() returns _MultiProcessingDataLoaderIter(self). New workers
are created in _MultiProcessingDataLoaderIter.__init__(). Workers are normally shut down when the
sampler is exhausted at epoch end.
How SkiNet prevents new worker spawning: RepeatDataLoader¶
SkiNet.ML.dataloaders.dataloaders.RepeatDataLoader subclasses torch.utils.data.DataLoader and
relies on a single mechanism — persistent workers. This replaces an earlier
_RepeatSampler/iterator-override implementation (the previous infinite-sampler approach is gone).
In RepeatDataLoader.__init__:
persistent_workersdefaults tonum_workers > 0(set only if the caller did not pass it). Withnum_workers=0the flag is invalid and PyTorch raises, so it is leftFalse. Persistent workers survive across epochs: PyTorch’s_MultiProcessingDataLoaderIteris created once and_reset()between epochs instead of being torn down and respawned.worker_init_fndefaults todefault_worker_init_fn, which setscv2.setNumThreads(0)and seedsnumpy/randomper worker fromtorch.initial_seed() % 2**32for reproducibility.collate_fndefaults tocollate_preserving_specs(see below).The dataset must be sized (
__len__), elseTypeErroris raised.max_num_to_repeatis accepted for backward compatibility but ignored — Lightning’sTrainercontrols epoch iteration viamax_epochs.
Usage with ISIC 2017¶
create_segmentation_dataloaders(main_config) is the segmentation entry point: it calls
create_segmentation_datasets_from_config then create_dataloaders_from_datasets, which builds the
RepeatDataLoader instances for the train, val, and test splits:
from SkiNet.ML.configs.load_config_from_yaml import load_config_from_yaml
from SkiNet.ML.dataloaders.create_dataloaders import create_dataloaders_from_datasets
from SkiNet.ML.datasets.dataset_factory import create_segmentation_datasets_from_config
cfg = load_config_from_yaml("main_config.yaml")
datasets = create_segmentation_datasets_from_config(cfg)
loaders = create_dataloaders_from_datasets(datasets, cfg.trainconfig)
# loaders.train, loaders.val, loaders.test are RepeatDataLoader instances
Key dataloader settings from TrainConfig that affect worker behaviour:
Field |
Effect |
|---|---|
|
Auto-set to |
|
Auto-set |
|
Batches pre-loaded per worker; |
|
Eliminates disk I/O in workers when |
Custom collate function¶
RepeatDataLoader uses collate_preserving_specs as its collate_fn. This preserves the
specs field (sample metadata) as a Python list rather than attempting to stack it into a tensor,
since metadata values are heterogeneous strings.