distributedsampler 必须显式设置 seed 并调用 set_epoch() 才能实现真正 shuffle,否则各进程打乱结果相同;dataloader 中 shuffle 必须设为 false 以避免双重 shuffle;验证/测试阶段应设 drop_last=false 并统一汇总指标。

为什么 DistributedSampler 不能直接用 shuffle=True 时忽略 seed
很多人在多卡训练中发现,即使设置了 shuffle=True,各进程的采样顺序却完全一致——这不是 bug,而是设计使然。默认情况下 DistributedSampler 不控制随机种子,每个进程独立调用 torch.randperm(),而 PyTorch 的全局 RNG 在各进程里初始状态相同,导致打乱结果雷同。后果是:每张卡看到的数据子集高度重叠,等效于没做数据划分。
解决办法是显式传入 seed 并配合 epoch 动态调整:
-
seed必须是固定整数(如42),确保所有进程基于同一随机源初始化 - 每次
train_loader迭代前,手动调用sampler.set_epoch(epoch),否则shuffle在多个 epoch 中不会变化 - 若用
torch.cuda.amp或梯度累积,set_epoch()仍需在每个 epoch 开始时调用,不能只调一次
如何正确构造 DistributedSampler 并接入 DataLoader
常见错误是把 DistributedSampler 当作普通 Sampler 直接传给 DataLoader,却忘了禁用 DataLoader 自带的 shuffle。这会导致双重 shuffle,破坏分布式划分逻辑。
标准写法如下:
# 假设已初始化 torch.distributed,rank=0~n-1,world_size=n
dataset = MyDataset(...)
sampler = torch.utils.data.DistributedSampler(
dataset,
num_replicas=world_size,
rank=rank,
shuffle=True,
seed=42, # 关键!必须显式指定
drop_last=True # 推荐设为 True,避免最后 batch size 不一致
)
loader = DataLoader(
dataset,
batch_size=32,
sampler=sampler,
shuffle=False, # ⚠️ 必须为 False,否则和 sampler 冲突
num_workers=4,
pin_memory=True
)
注意:drop_last=True 能防止某张卡最后一个 batch 样本数过少,引发 all-reduce 同步失败;若业务允许不丢样本,可设为 False,但需确保模型能处理变长 batch。
DistributedSampler 在验证/测试阶段要不要用?
验证和测试通常不需要梯度同步,但仍有两个关键考量点:
- 如果验证集大小不能被
world_size整除,且未设drop_last=False,部分进程会提前结束迭代,造成torch.distributed.all_gather()卡死 - 即使不卡死,各进程处理样本数不同,汇总指标(如 accuracy)时若简单取平均,会因权重不均引入偏差
推荐做法:
- 验证/测试用
DistributedSampler(drop_last=False),确保所有进程遍历全部数据(哪怕最后一轮 batch 不满) - 收集各进程预测结果后,用
all_gather拼成完整预测数组,再统一计算指标,而非各自算完再平均 - 若验证集很小(如
踩坑最多的三个细节
实际部署时,以下三点最容易被忽略,且调试困难:
-
torch.distributed.init_process_group()必须在创建DistributedSampler之前调用,否则rank和world_size获取为 0 和 1,退化为单机行为 - 使用
spawn方式启动多进程时,每个子进程要重新初始化cuda上下文(torch.cuda.set_device(rank)),否则DataLoader的pin_memory=True会报错 -
DistributedSampler的num_replicas必须严格等于当前world_size,不能硬编码为 GPU 数量——比如用 Slurm 分配了 2 节点 × 4 卡,world_size是 8,不是 4
最隐蔽的问题是:当 batch_size 设置过大,而某张卡分配到的样本数不足一个 batch,DataLoader 会静默跳过该 epoch 的剩余迭代,而不是报错。这种“看起来训完了但其实漏了数据”的情况,只能靠打印每个 epoch 实际 iter 数或监控样本计数来发现。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











