批次(Batch)概念解析
说实话,刚接触深度学习推理那会儿,我对「Batch」的理解特别肤浅。不就是一次多塞几张图进去嘛,有什么好琢磨的?后来在实际项目中栽了跟头,才真正体会到——Batch 是吞吐量和延迟之间的天平,调好了是火箭,调歪了是炸弹。
那咱们今天就把 Batch 掰开了聊。我会结合 MNN 的实际用法,把批处理是怎么一回事、MNN 怎么支持、以及你实操时要注意哪些坑,一次说透。
什么是 Batch?
Batch 就是“批”。你给模型一次送进去的样本数量就叫 batch size。比如一次送 1 张图,batch=1;一次送 32 张,batch=32。
为什么不用单张依次跑?因为现代硬件(GPU、NPU、DSP)擅长并行计算。模型里大部分运算是矩阵乘法。你可以想象成:一张图是一个向量,多张图叠成一个矩阵。一次算一个矩阵,比一次算一个向量再循环 32 次,要快得多。
举个例子。我曾在某安防项目中批量处理 1080p 视频帧。单帧推理耗时 12ms,但视频流每秒 25 帧,单卡只能处理约 80 路。后来我把 batch size 调到 4,单帧耗时只涨到 16ms,但吞吐量翻了两倍多。这就是 Batch 的魔力。
Batch 在推理中的两个关键指标
| 指标 | 定义 | Batch 越大 |
|---|---|---|
| 延迟 | 处理一个样本的时间 | 通常略有增加(因为要等所有样本算完) |
| 吞吐量 | 单位时间处理的样本总数 | 大幅提升(硬件利用率高) |
你肯定发现了——延迟和吞吐量存在 trade-off。有些场景(比如无人驾驶)对延迟极度敏感,batch size 必须小;有些场景(比如离线批量转码)则尽量拉大 batch 吃满显存。我建议你自己拿目标设备跑一轮微基准测试,别只看理论值。
MNN 对 Batch 的支持方式
MNN 的批处理分为两类:静态批处理和动态批处理。
- 静态批处理:模型在转换时固定 batch size。所有张量形状都按固定 batch 分配内存。推理时输入的 batch 必须与模型定义一致。
- 动态批处理:模型训练或转换时用动态 shape。MNN 运行时根据实际输入自动调整中间张量大小。更灵活,但会有额外的内存分配开销。
我记得刚用 MNN 搞一个目标检测服务,模型是从 PyTorch 转的 ONNX,ONNX 默认 batch=1。我天真地把输入 batch 改成 8,结果 MNN 创建会话时报错——形状不匹配。后来查文档才发现,MNN 默认按转换时的输入 shape 固化推理图。如果你要用动态 batch,必须在转换时指定可变维度。
resizeTensor 重新指定尺寸。但要提前评估内存申请次数,别在实时路径里频繁 resize。MNN 设置静态 Batch 的示例
假设你要转换一个 batch=4 的模型:
# 用 MNNConvert 工具,在转换脚本里指定输入形状
MNNConvert -f ONNX --modelFile model.onnx --MNNModel model.mnn \
--inputNames input --inputShapes 4,3,224,224
# 或者用 Python API 转换时控制
from MNN.tools import MNNConvert
MNNConvert.convert(
model_file="model.onnx",
framework="ONNX",
MNN_model="model.mnn",
input_names=["input"],
input_shapes={"input": (4, 3, 224, 224)}
)
动态 Batch 配置(推荐用于生产)
# 转换时把 batch 维度设为 -1
MNNConvert -f ONNX --modelFile model.onnx --MNNModel model.mnn \
--inputNames input --inputShapes -1,3,224,224
# C++ 推理时动态设定 batch
MNN::Interpreter* interp = MNN::Interpreter::createFromFile("model.mnn");
MNN::Session* session = interp->createSession();
MNN::Tensor* input = interp->getSessionInput(session, "input");
// 假设当前要 batch=8
std::vector<int> shape = {8, 3, 224, 224};
input->resize(shape);
interp->resizeSession(session);
// 填充数据并运行
interp->runSession(session);
Batch 在 MNN 运行时的影响
MNN 的算子内部做了优化:当 batch > 1 时,会把矩阵乘法拆分到多个线程或 SIMD 向量上。比如 Conv2D 算子,batch 维度会参与 im2col 的张量重组。实际效果上,batch=2 比 batch=1 跑两次快 1.6~1.8 倍。到了 batch=8 以上,提速会慢慢饱和,因为内存带宽成了瓶颈。
我个人习惯在部署前先做一轮 batch sweep:从 1 到 64(显存上限),测延迟和吞吐,画个曲线找到最佳点。
一张图看懂 Batch 在推理流程中的位置
下面这张图展示了数据从输入到模型处理再到输出的全过程。batch 维度的作用贯穿始终:
从上图能清晰看到:batch 维度在输入时确定,经过预处理后直接喂给 MNN 模型。模型内部所有算子的计算张量都多了一个 N 维度。输出时,每个样本的结果被按序存放。
常用 Batch 实验命令
我习惯用 MNN 自带的 benchmark 工具快速摸清设备的上限:
# 跑单 batch 延迟
./benchmark.out model.mnn 1 20
# 测不同 batch 的吞吐
./benchmark.out model.mnn 4 20
./benchmark.out model.mnn 8 20
./benchmark.out model.mnn 16 20
注意,后面的数字 20 是预热后测试 20 次取均值。我一般至少跑三轮,取中位数。
避坑经验总结
- 不要盲目迷信大 batch:显存/内存有限,新一批数据的拷贝开销也会涨。
- 动态 batch 的 resize 成本:我见过有人每帧都 resize,结果速度反而比固定 batch=1 还慢。建议做 session 池。
- MNN 多线程配合 batch:如果你开了 4 线程,batch=4 时每个线程处理一个样本,会有接近线性的加速。但线程数超过 CPU 核数时,效果打折。
- 量化模型对 batch 更敏感:量化后计算密度低,batch 带来的收益更小。优先测延迟,再考虑提 batch。