Num_workers和Batch_Size
2026/7/23大约 4 分钟
在大模型和深度学习(如 PyTorch)的数据准备与训练流水线中,num_workers 和 bs(Batch Size)共同决定了“数据从硬盘加载到 GPU 显存中,并喂给模型吃”的整条流水线的速度。
这两个参数一个是“运货卡车的数量”,一个是“每辆卡车装载的货物箱数”。
一、 两个核心指标的物理定义
1. bs (Batch Size / 批大小)
- 物理含义:模型在每一个训练步(Step)中,一次性吃进去并处理的样本数量。
- 物理比喻:“大货车每趟拉的集装箱数量”。
bs=256意味着大货车每跑一趟,车上都整整齐齐装了 256 箱货(样本),少一个都不行。
2. num_workers (数据加载线程/进程数)
- 物理含义:在 PyTorch 的
DataLoader中,用来从硬盘读取数据、进行数据预处理(如裁剪、旋转、正则化),并拼装成 Batch 喂给 GPU 的子进程数量。 - 物理比喻:“在仓库里负责搬运、打包和装车的小工(Worker)数量”。
二、 它们是如何在后台协同工作的?(物理流)
在训练过程中,CPU 负责“数据准备”,GPU 负责“核心计算”。它们构成了一个高频流转的生产线:
【 硬盘 (仓库) 】
│
▼ (num_workers 决定有多少个搬运工在这里干活)
[ 小工 1 ] ──┐
[ 小工 2 ] ──┼─> 【 CPU 内存 (装配车间) 】 ───> 拼装成一个 Batch (容量由 bs 决定)
[ 小工 3 ] ──┘
│
▼ (数据通过 PCIe 通道拉到显存)
【 GPU 显存 (生产线) 】 ───> 模型进行 Step 训练
num_workers=0(默认):
只有一个光杆司令(主进程)在干活。 主进程得自己去硬盘里读数据、做预处理、打包成bs大小的包,然后亲自送到 GPU 手里。送完之后,GPU 在计算,主进程只能干等;GPU 算完了,主进程再去仓库搬货。
- 致命后果:GPU 长期处于“饥饿”状态,显力利用率极低(极低的 Samples/s),Step/s 极其缓慢。
num_workers=4(多小工并行):
主进程当了包工头。他手下有 4 个小工(4 个独立子进程)。
- 小工们在后台提前从硬盘里读数据、做预处理,并源源不断地在内存里拼装好下一个
bs(比如 256 个样本)的货。 - GPU 一算完当前这步,主进程立刻把后台已经装配好的新 Batch 扔给 GPU。
- 物理效果:GPU 零等待,全程满载燃脂,吞吐量暴涨。
三、 工业界调优:这两个参数该怎么配?
在实际 MLOps 生产中,如果这两个参数配得不合理,系统不是卡死就是崩溃:
1. num_workers 绝对不是越大越好
- 物理代价:每一个 worker 都是一个独立的 Python 子进程,它们会物理复制一份数据加载代码并常驻在你的系统内存(RAM)里。
- 崩溃报错:如果你把
num_workers设得太大(比如在 16 核 CPU 上强行设了 32),你会遇到极其恶心的Shared memory error(共享内存耗尽),或者因为 CPU 频繁在不同进程间切换(上下文切换损耗),导致DataLoader越来越卡,甚至系统直接内存溢出(OOM)崩掉。 - 黄金公式:
- 通用推荐:$\text{num_workers} = \text{当前宿主机 CPU 的物理核心数} \times 1 \text{ 或 } 2$。
- 如果训练集是存在极快 SSD 上的小图片,可以配高一点(如 4 或 8);如果是很大的 3D 医疗影像或重型视频,应该调低,防止撑爆物理内存。
2. bs 与 num_workers 的动态配比关系
- 如果你调大了
bs(如 256 $\rightarrow$ 512): - 因为每一个 Batch 的体积变大了一倍,后台装配车间的工作量变重了。
- 如果你保持
num_workers不变,小工们装配一个 Batch 的时间变长,可能会跟不上 GPU 的计算速度,GPU 重新开始挨饿。 - 应对手段:调大
bs的同时,通常需要**适当调大num_workers**,给后台增加人手,保证装配线的供货速度。
💡 架构师调优顺口溜:
bs决定 GPU 肚子有多大:调大它能让显卡干活更饱满,但别撑爆显存(VRAM)。
num_workers决定 CPU 送货有多快:调大它能消除 GPU 挨饿的气泡,但别撑爆物理内存(RAM)。
