Navigation
πŸ’Ύ Data & StorageΒ· 10 min read

PyTorch DataLoader

num_workers, prefetching, pin_memory, multiprocessing overhead β€” understanding the default data loading path and its limits at scale.

dataloaderpytorchnum-workersprefetchpin-memorybottleneck

The PyTorch DataLoader is the default data feeding mechanism. Understanding its internals β€” worker processes, memory pinning, prefetching β€” is essential for diagnosing training bottlenecks.


1. How DataLoader Works

DataLoader Pipeline

Main process (GPU training)
  ↓ requests batch
DataLoader
  β”œβ”€β”€ Worker 0: read data β†’ transform β†’ collate
  β”œβ”€β”€ Worker 1: read data β†’ transform β†’ collate
  β”œβ”€β”€ ...
  └── Worker N: read data β†’ transform β†’ collate
  ↓ prefetched batch
Pin memory (optional: CPU β†’ pinned CPU)
  ↓
GPU transfer (cudaMemcpyAsync)
  ↓
Training step

Each worker is a separate OS process (not thread β€” Python GIL forces multiprocessing). Workers read data from disk/network, apply transforms, and send batches to the main process via shared memory.


2. Key Parameters

ParameterDefaultWhat it doesTuning
num_workers0 (main process)Number of parallel data loading processes4-16 per GPU (test!)
prefetch_factor2Batches prefetched per worker2-4 (more hides latency)
pin_memoryFalseCopy data to CUDA-pinned memoryAlways True for GPU training
persistent_workersFalseKeep workers alive between epochsTrue (avoids respawn cost)
drop_lastFalseDrop incomplete last batchTrue for distributed training

num_workers Tuning

# Too few workers: GPU starved (data loading slower than training)
# Too many workers: CPU/memory contention, diminishing returns
# Sweet spot: typically 4-8 per GPU

# Empirical test:
for nw in [0, 2, 4, 8, 16, 32]:
    loader = DataLoader(dataset, num_workers=nw, pin_memory=True)
    throughput = measure_throughput(loader)
    print(f"num_workers={nw}: {throughput:.1f} samples/sec")

Rule of thumb: Start with num_workers = 4 Γ— num_gpus. Increase until throughput plateaus or CPU becomes saturated.

pin_memory Explained

Normal (pageable) memory β†’ GPU transfer: kernel must copy to pinned memory first, then DMA to GPU (2 copies).

Pinned memory β†’ GPU transfer: DMA directly to GPU (1 copy, 2Γ— faster).

# Always use for GPU training
loader = DataLoader(dataset, pin_memory=True, num_workers=8)

Cost: Pinned memory is non-swappable β€” too much pins can reduce available system RAM.


3. Common Bottlenecks

SymptomCauseFix
GPU utilization < 80%Data loading too slowIncrease num_workers, use pin_memory
CPU at 100%Workers saturatedPre-process data offline, simpler transforms
High RAM usageToo many workers + large batchesReduce num_workers or batch size
Workers keep restartingpersistent_workers=FalseSet persistent_workers=True
Slow first epochWorkers initializingUse persistent workers + warmup
I/O wait dominantDisk/network too slowMove to faster storage (NVMe, streaming)

Diagnosing the Bottleneck

import torch.profiler

with torch.profiler.profile(
    activities=[torch.profiler.ProfilerActivity.CPU,
                torch.profiler.ProfilerActivity.CUDA],
) as prof:
    for batch in loader:
        train_step(batch)

# Look for: large gaps between CUDA kernels = data loading bottleneck
# Look for: DataLoader.__next__ dominating CPU time = I/O bottleneck

4. Limits of DataLoader

LimitationImpactSolution
Python multiprocessingIPC overhead, serialization costUse C++ data pipelines (DALI)
Local storage assumptionDoesn’t natively stream from cloudUse StreamingDataset, WebDataset
No cross-node coordinationEach node reads independentlyStreaming datasets with sharding
Memory copiesShared memory β†’ main process β†’ pinned β†’ GPUDALI: direct GPU loading
Single-machine designNo built-in distributed data shardingDistributedSampler or streaming

For streaming from cloud storage, see Streaming Datasets. For GPU-native loading, see GPU-Accelerated Preprocessing.


Self-Check

  • Draw the DataLoader pipeline (workers β†’ shared memory β†’ pin β†’ GPU)
  • Tune num_workers for a given GPU count and measure throughput
  • Explain why pin_memory=True speeds up CPUβ†’GPU transfer
  • Diagnose a data loading bottleneck from profiler output
  • Identify when DataLoader isn’t enough and alternatives are needed

References