想象你是一家快递站的站长。你手头有一辆货车、一个司机,却要面对接连不断的包裹。如果每个包裹都单独派一辆车,司机一天要跑几十趟,油费翻倍,包裹还在路上堵着。聪明的做法是:把顺路的包裹装进同一辆车,一趟送一片。批量推理(Batching),正是大模型世界里的“凑车发车”——把多个用户请求拼成一个批次,让 GPU 一次算完,吞吐量翻着倍地涨。 当然,凑车也有学问:等多久发车?装多少货出发?装太满,有些急件等不了;发太勤,车厢空着浪费。这些权衡,对应着动态批处理、静态批处理、batch size 调优等一整套工程手段。本文会用快递站的故事,带你从零理解批量推理的原理、策略选择和落地经验,让你在实际部署大模型服务时,心里有杆秤。

岗位关联

掌握批量推理,等于拿到了大模型服务吞吐量翻倍的钥匙——这是推理优化工程师最核心的武器之一,直接决定线上服务的成本与用户体验。

目录
  1. 可视化演示
  2. 批量推理原理
  3. 动态批处理
  4. 静态批处理
  5. 批大小设置
  6. 吞吐延迟权衡
  7. 适用场景分析
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

动态演示批量推理提升服务吞吐量的核心机制,通过概览/流程/对比/趋势四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

批量推理提升服务吞吐量:批量推理 批量推理基本原理与GPU利用率 静态批处理与动态批处理对比 核心机制可视化 批量推理提升服务吞吐量整体架构与关键组件

批量推理原理

经营快递站的第一课,就是别让货车空着跑。假设你手头有 10 个包裹要送到同一个片区,最笨的办法是派 10 趟车;聪明人会把它们装进一辆车,一趟送完。大模型推理服务面对的处境一模一样:用户请求像包裹一样不断涌来,如果每个请求都单独触发一次模型计算,GPU 这座“货车”大部分时间都在空转。批量推理(Batching)的核心思想,就是把这些散装请求“装车”——拼成一个批次,让模型一次前向计算同时处理多个请求。

为什么模型能一次处理多个请求?这要从深度学习的计算方式说起。大模型的前向传播,本质上是一连串矩阵乘法。假设输入是一句话,它会被编码成一个形状为 [1, seq_len] 的矩阵;如果用批量推理,输入变成 [B, seq_len],其中 B 就是这次拼接的请求数。GPU 的矩阵运算单元对第 0 维(batch 维)天然并行——计算 B 个序列的耗时,只比计算 1 个序列多一点。这就好比货车拉 10 个包裹和拉 1 个包裹,油费差别不大,但送达数量差了 10 倍。

用吞吐量来衡量这种收益。吞吐量的定义是单位时间处理的请求数。假设每批处理耗时是 t_batch,批大小是 B,那么模型平均每个单位时间能处理 B / t_batch 个请求。对比串行处理的 1 / t_single,只要 t_batch 不比单请求耗时大太多,吞吐量就近似随 B 线性增长。在实际部署中,B=16 时吞吐量常常能提升 5~10 倍。

所以批量推理的本质,就是提高货车的“满载率”。GPU 这台昂贵的货车,一旦满载运行,单位成本骤降、吞吐翻倍。理解了这一点,接下来要解决的,就是“什么时候发车、装多少货”这两个现实问题。

Throughput=B×MT=Btbatch\text{Throughput} = \frac{B \times M}{T} = \frac{B}{t_{\text{batch}}}
吞吐量 = 批大小 ÷ 每批耗时。批越大、每批算得越快,单位时间能处理的请求就越多。
  1. 总请求数 N每批处理 B 个请求,一共 M 批,总请求数等于两者相乘
    N=B×MN = B \times M
  2. 总耗时 T每批耗时 t_batch,共 M 批,总时间就是 M × t_batch
    T=M×tbatchT = M \times t_{\text{batch}}
  3. 吞吐量化简把 N/T 代入,分子分母的 M 约掉,得到最简形式
    Throughput=B×MM×tbatch=Btbatch\text{Throughput} = \frac{B \times M}{M \times t_{\text{batch}}} = \frac{B}{t_{\text{batch}}}

动态批处理

快递站随时都有包裹进来,但不可能每来一件就发一趟车。经验丰富的调度员会盯着门口的包裹堆:攒得差不多了就喊司机发车;如果某个包裹等得太久,就算没攒够,也会先送这一趟。动态批处理(Dynamic Batching)就是这样一个“智能调度员”。

在线服务的请求到达时间完全随机:有时一秒来 100 个,有时 10 秒才来 1 个。动态批处理的思路是:维护一个请求队列,新请求先入队等待;当队列中的请求数达到预设阈值 N(例如 16),或者最早请求的等待时间超过阈值 T(例如 100ms),就触发一次模型计算,把队列里所有请求打包处理。两个条件用“或”连接——流量大时靠数量触发,流量小时靠时间兜底。

一个关键参数是触发阈值的选择。从排队论角度看,攒够 N 个请求的平均等待时间约为 N/(2λ),其中 λ 是请求到达速率。如果 λ 大(并发高),很快就能攒够一批,等待时间短;如果 λ 小,就得依靠时间阈值 T 来兜底,避免请求饿死。这也是为什么动态批处理特别适合流量波动大的在线场景——它让批大小自适应流量,忙时大、闲时小。

调度员不会死等,也不会乱发。动态批处理用“数量 + 超时”两个旋钮,让货车在满载和时效之间自动找平衡。实时聊天、AI 客服这类流量忽高忽低的服务,靠它把空闲 GPU 利用起来,是最常见的落地方式。

Wq≈N2λW_q \approx \frac{N}{2\lambda}
攒满一批的平均等待时间,约等于目标批大小除以两倍到达速率。到得快,等得短;到得慢,全靠超时兜底。
  1. 平均等待时间 W_q指请求从进入队列到攒满一批、被送入模型的平均时长
    WqW_q
  2. 目标批大小 N攒够 N 个请求就触发一次批处理,N 越大,需要等的请求越多
    NN
  3. 到达速率 λ 的影响λ 是每秒新到达的请求数,除以 2 表示请求是陆续到达的,平均等待约为 N/(2λ)
    N2λ\frac{N}{2\lambda}

静态批处理

和随机应变的调度员相反,有些物流公司走的是“班车制”:早上 8 点、下午 2 点、晚上 6 点,不管有没有货,准时发车。货多就多装几车,货少就空着跑一段。静态批处理(Static Batching)就是这么“固执”——批大小在启动前就定死了。

静态批处理预先指定 batch size = B,所有请求在进入服务前先被收集成固定大小的批次,再依次送入模型。它不关心请求什么时候到、到了多少,只是机械地按 B 个一组执行。这种“固执”在特定场景反而成了优点:批次形状固定,模型推理路径完全确定。

固定批大小带来两个实际好处。一是可以配合 CUDA Graph 等编译优化,把整个批处理的计算图提前编译成 GPU 指令序列,运行时省去大量内核启动开销,单批耗时更短、更稳定。二是吞吐量可预期:给定 N 个请求,总时间就是 ⌈N/B⌉ 乘以每批耗时,方便做容量规划。代价是流量波动时,要么批次不满浪费算力,要么请求排队时间随机,所以它更适合离线、批量的任务形态。

班车制的好处是规律和可控:什么时间发车、成本多少,早就写在表上。离线数据标注、批量评测、夜间数据回放,这类不差时间的任务,静态批处理能最大化吞吐、把 GPU 榨干。

Ttotal=⌈NB⌉×tbatchT_{\text{total}} = \left\lceil \frac{N}{B} \right\rceil \times t_{\text{batch}}
总耗时 = 批次数 × 每批耗时。批次数用向上取整,保证最后一个不完整的批次也要计算在内。
  1. 批次数⌈N/B⌉ 表示 N 个请求按每批 B 个分组,有余数也单独成一批,向上取整保证算满
    ⌈NB⌉\left\lceil \frac{N}{B} \right\rceil
  2. 总耗时批次数乘以每批固定耗时 t_batch,由于每批耗时稳定,总时间完全可以预先估算
    Ttotal=⌈NB⌉×tbatchT_{\text{total}} = \left\lceil \frac{N}{B} \right\rceil \times t_{\text{batch}}

批大小设置

货车装货不是越多越好。装到顶棚,引擎爬不动坡;装得太少,路费摊到每个包裹上又太贵。老司机跑几趟就知道这辆车的“经济载重”。batch size 也一样,太小并行度不足,太大则显存爆掉或延迟失控。

batch size 的上限由硬件资源决定。每个请求在推理时会占用显存,主要来自两部分:中间激活值(activations)和 KV Cache。一张 A100 有 80GB 显存,如果每个序列平均占 2GB,那么 batch size 最大只能开到约 40。下限则由吞吐目标决定——批太小,GPU 计算单元喂不饱,吞吐上不去。

工程上的标准做法是“压测扫描”:从 batch size = 1 开始,按 2 的幂次倍增(2、4、8、16、32……),记录每个取值下的吞吐量和 p95 延迟。通常你会看到吞吐量先快速爬升,到达某个拐点后增速放缓甚至下降——这个拐点附近就是最佳工作区。再结合延迟预算(例如要求 p95 < 2 秒),在预算内选最大的 batch size,就是最终参数。

找到“经济载重”不是拍脑袋,而是用公式定上限、用压测找拐点。batch size 这个数字,直接决定了你的货车是满载飞奔,还是超载趴窝。

Bmax⁡=⌊显存可用显存每序列⌋B_{\max} = \left\lfloor \frac{\text{显存}_{\text{可用}}}{\text{显存}_{\text{每序列}}} \right\rfloor
最大批大小 = 可用显存 ÷ 单序列显存,向下取整。这是硬性上限,接近它之前延迟往往已经先爆表。
  1. 可用显存GPU 中真正能用于模型推理的显存,要减去模型权重、CUDA 上下文等固定开销
    显存可用\text{显存}_{\text{可用}}
  2. 单序列显存处理一个请求所占用的显存,包括每一层的激活值和 KV Cache
    显存每序列\text{显存}_{\text{每序列}}
  3. 向下取整结果必须向下取整,保证实际 batch 的显存占用不超过总量,否则直接 OOM
    ⌊⋅⌋\left\lfloor \cdot \right\rfloor

吞吐延迟权衡

货车司机都懂一个矛盾:等满货再走,单车成本最低,但急件可能等上半天;立刻发车,时效最好,但空驶率飙升。吞吐量和延迟,就是物流世界里这对永恒的矛盾。

在批量推理里,二者的矛盾体现在:batch size 越大,GPU 利用率越高、吞吐量越大,但请求在队列里的等待时间和单批计算时间都在增加,端到端延迟随之上升。一个请求的完整生命周期包括:排队等待 W_q、本批计算 t_batch、网络传输等。其中 W_q 随批大小和队列长度增长,t_batch 也随批大小增长(尽管是亚线性的)。

实际工作中,我们很少追求“最大吞吐”,而是在满足延迟约束(SLO)的前提下最大化吞吐。SLO 通常以分位数定义,例如“99% 的请求延迟低于 2 秒”。这意味着你可以容忍少数慢请求,只要绝大多数达标。优化目标写成一个带约束的优化问题:让吞吐量最大,同时保证 p95 延迟不超过红线。调 batch size、调触发阈值,本质都是在解这个优化问题。

就像快递公司会承诺“同城当日达”,用时效红线换成本效率。批量推理的调优,就是在延迟红线和吞吐收益之间走出来的一条钢丝。记住:优化的目标永远不是某个单一指标,而是“在约束内做到最好”。

max⁡ Throughput(B)s.t.Lp95(B)≤SLO\max \ \text{Throughput}(B) \quad \text{s.t.} \quad L_{p95}(B) \le \text{SLO}
优化的目标是在延迟红线之内最大化吞吐。延迟是约束,吞吐才是要追求的目标。
  1. 目标函数吞吐量是批大小 B 的函数,要让它在可行域内取最大值
    max⁡ Throughput(B)\max \ \text{Throughput}(B)
  2. 延迟约束p95 端到端延迟必须小于等于服务等级目标 SLO,这是不可逾越的红线
    Lp95(B)≤SLOL_{p95}(B) \le \text{SLO}
  3. 最优批大小在满足约束的所有 B 中,取使吞吐量最大的那个值,记为 B*
    B∗=arg⁡max⁡B Throughput(B)B^* = \arg\max_{B} \ \text{Throughput}(B)

适用场景分析

不同的货物,有不同的物流策略。生鲜冷链走专车、当日达;普通快递走班车、三四天到货。聪明的快递站不会用一套方案服务所有客户。批量推理也是一样,得看业务到底在不在乎那几秒钟。

先看在线业务:聊天机器人、实时问答、AI 搜索。这类服务对延迟极其敏感,用户按一下回车就要看到回复。适合动态批处理 + 小 batch + 短超时(比如 batch 上限 8、超时 50ms),既保证响应速度,又能在流量峰值时吃到批量收益。

再看离线业务:数据标注生成、批量评测、内容审核。这些任务不关心单个请求等多久,只关心总体成本——适合静态批处理 + 大 batch(32 甚至 64),把吞吐量顶到极限。还有一种混合场景:同一个服务里既有实时交互请求,又有后台批量任务。可以用优先级队列把实时请求插队到批头,或用两个推理实例分别承载两种流量,避免互相干扰。

用对场景,批量推理是提效利器;用错场景,可能适得其反。判断方法很简单:问一句“用户等得起吗?”等不起,走动态;等得起,上静态,把每一分 GPU 算力都换成钱。

策略=argmax策略∈{动态,静态} 吞吐量延迟α\text{策略} = \text{argmax}_{\text{策略} \in \{\text{动态}, \text{静态}\}} \ \frac{\text{吞吐量}}{\text{延迟}^{\alpha}}
延迟敏感度 α 决定了天平倾向哪边:实时业务 α 大,延迟权重高,选动态;离线业务 α 小,吞吐权重高,选静态。
  1. 延迟敏感度 αα 是超参数,实时业务设大值(如 2),离线业务设小值(趋近 0)
    α\alpha
  2. 延迟惩罚项延迟的 α 次方,α 越大,延迟高的策略被惩罚得越狠
    延迟α\text{延迟}^{\alpha}
  3. 策略选择遍历可选策略,选“吞吐量/延迟罚项”最大的那个,即综合收益最高
    argmax策略\text{argmax}_{\text{策略}}

AI 应用场景

在线对话机器人的并发推理

阶段:模型部署与推理优化

解决的问题:聊天机器人服务每秒钟要响应几十个用户请求,若每个请求单独推理,GPU 利用率不到 20%,机器成本居高不下,高峰期还容易排队超时。通过动态批处理把并发请求打包推理,吞吐量提升 5 倍以上,单机可以扛住原来 5 台机器才能扛住的流量。

具体案例:某电商平台的 AI 客服接入大模型后,通过 vLLM 的 continuous batching 机制,请求到达率从 50 req/s 提升到 300 req/s,单 GPU 利用率从 15% 提升到 75%,采购成本降低 40%。

离线数据集批量生成

阶段:数据准备与生成

解决的问题:训练数据需要大规模合成标注,传统方式一条条生成又慢又贵。静态批处理 + 大 batch 让 GPU 满载运行,把整批数据一次生成多份。

具体案例:数据团队要用大模型生成 100 万条指令微调数据。采用静态批处理、batch size = 64,在 8 卡 A100 上对比串行生成提速约 20 倍,总耗时从 12 天压缩到 14 小时。

推荐系统向量批量计算

阶段:特征工程与向量化

解决的问题:推荐系统需要把海量物品实时编码成向量。动态批处理让不同长度的文本共用一次前向传播,在不影响召回延迟的前提下,显著提升向量生产的吞吐。

具体案例:某短视频平台用开源推理框架部署文本编码模型,通过动态批处理将向量生成服务的 QPS 从 2000 提升到 12000,p99 延迟保持在 150ms 以内。

Python 演示

python43 行
import math  def simulate_serial(requests, per_request_time):    """串行处理:一个请求算完再算下一个"""    total = 0.0    for _ in range(requests):        total += per_request_time    return total  def simulate_batch(requests, batch_size):    """批量处理:一批请求一次前向传播"""    num_batches = math.ceil(requests / batch_size)    total = 0.0    for _ in range(num_batches):        # 每批耗时随 batch_size 亚线性增长(并行带来的收益)        batch_time = 0.03 + 0.05 * math.log2(batch_size + 1)        total += batch_time    return total  def main():    total = 200    per_req = 0.05     serial_time = simulate_serial(total, per_req)    print("=== 串行处理 ===")    print(f"总耗时: {serial_time:.3f}s")    print(f"吞吐量: {total / serial_time:.1f} req/s")    print()     print("=== 批量处理 ===")    for bs in [1, 2, 4, 8, 16, 32, 64]:        batch_time = simulate_batch(total, bs)        throughput = total / batch_time        avg_latency = batch_time / math.ceil(total / bs)        print(f"batch={bs:>3} 总耗时={batch_time:>7.3f}s "              f"吞吐={throughput:>7.1f} req/s 平均延迟={avg_latency:.3f}s")  if __name__ == "__main__":    main()
目的:用模拟数据对比串行处理与不同 batch size 下批量处理的吞吐量和延迟,直观展示“吞吐升、延迟也升”的权衡关系。
  1. num_batches = math.ceil(requests / batch_size)向上取整算出要跑几批,最后一个不满批也算一次。
  2. batch_time = 0.03 + 0.05 * math.log2(batch_size + 1)用对数函数模拟批处理耗时亚线性增长:批越大,每请求均摊成本越低。
  3. throughput = total / batch_time总请求数除以总耗时,得到每秒吞吐量,这是衡量收益的核心指标。
  4. avg_latency = batch_time / math.ceil(total / bs)把每批耗时摊到每个请求上,近似估算单请求平均延迟。

常见误区

误区显存装得下就一直往上调,反正大 batch 吞吐高。我之前把 batch size 从 8 调到 64,结果服务直接 OOM 崩溃。

正解batch size 不是越高越好。首先显存按比例增长,超了就 OOM;其次单批耗时也会变长,排队等待随之恶化。而且很多框架在 batch 过大后,吞吐增长会进入平台期甚至回落。正确做法是用延迟预算卡上限,再做压测找拐点,而不是盲目拉满。

为何常见:因为“吞吐量”这个指标太显眼,容易让人误以为它单调递增。实际上吞吐曲线有拐点,过了拐点收益递减甚至为负。

误区动态批处理灵活、自适应流量,静态批处理太死板,肯定选动态。

正解静态批处理也有独门优势:批大小固定,输入形状整齐,可以配合 CUDA Graph 把计算图预编译好,启动开销极低,单批耗时稳定。离线批量场景里,静态批处理能碾压动态。选型要看场景,而不是看名字新不新。

为何常见:因为动态批处理听起来“智能”,容易被默认优先。但“智能”带来的灵活性,是用放弃编译优化和复杂度换来的。

误区在线服务对延迟敏感,用批量推理会让人等太久,所以只能放在离线批处理里用。

正解动态批处理就是为在线场景设计的。它通过数量阈值 + 超时阈值双条件控制,流量大时马上凑够一批,流量小时超时兜底,把单请求的排队等待控制在几十毫秒以内。配合 p95 延迟监控,完全可以在线使用,且收益非常明显。

为何常见:初学者容易把“批量推理”和“大 batch”划等号,忽略了超时机制对延迟的保护作用。

误区批量推理让吞吐量翻倍,那用户响应也会变快吧?

正解恰恰相反,批量推理通常会让单请求延迟小幅上升——请求要等同一批的伙伴凑齐,计算时间也变长。它的价值在于同样的资源能服务更多请求,摊薄单位成本。用户可感知的延迟由排队、计算、网络共同决定,吞吐和延迟是两回事,二者需要权衡而非等同。

为何常见:因为日常语境里“服务变快了”常被混为一谈,但工程上 throughput 和 latency 是两条指标,优化一个常常牺牲另一个。

面试题

Q1. 批量推理为什么能提升吞吐量?

单选题 · easy

  • A.模型参数量变少
  • B.并行计算多个请求← 正确答案
  • C.输入数据被压缩
  • D.单请求延迟降低

答案:B。批量推理把多个请求拼成批次,GPU 的矩阵运算单元对 batch 维天然并行,一次前向计算处理多个请求,从而吞吐量大幅提升。它不改变模型参数量也不压缩输入;延迟通常不降反升,因此 B 正确。

Q2. 动态批处理的典型触发条件有哪些?

多选题 · mid

  • A.请求数凑满预设阈值← 正确答案
  • B.队列等待超过时限← 正确答案
  • C.到达流量出现波动
  • D.任一条件满足即可← 正确答案

答案:ABD。动态批处理由“数量阈值”和“超时阈值”共同控制,两个条件用“或”连接,任一满足即触发。流量波动是背景现象,不是触发条件本身,所以 C 错;A、B、D 描述正确。

Q3. 相比静态批处理,动态批处理的优势有哪些?

多选题 · mid

  • A.更好适配流量波动← 正确答案
  • B.单批耗时更加稳定
  • C.请求等待时间更短← 正确答案
  • D.编译优化更加彻底

答案:AC。动态批处理根据实时到达的请求动态凑批,能适配流量波动,并通过及时触发避免长排队,所以 A、C 对。单批耗时稳定和编译优化彻底恰好是静态批处理的优势,B、D 张冠李戴。

Q4. batch size 增大时,GPU 显存占用如何变化?

单选题 · mid

  • A.线性增加← 正确答案
  • B.对数增加
  • C.保持不变
  • D.先增后减

答案:A。每个序列的激活值和 KV Cache 都要占显存,B 个序列就是单序列的 B 倍,因此显存占用随 batch size 线性增长。这也是 batch size 存在硬上限的原因。

Q5. 下面哪个场景最适合用静态批处理?

单选题 · easy

  • A.在线即时聊天
  • B.离线批量数据标注← 正确答案
  • C.实时语音翻译
  • D.自动驾驶感知

答案:B。离线批量数据标注对延迟不敏感、请求量大且规律,静态批处理的大 batch + 编译优化能最大化吞吐。其余三项是实时交互场景,需要动态批处理控制延迟。

Q6. 关于批量推理中吞吐量与延迟的关系,正确的说法是?

多选题 · hard

  • A.批越大吞吐越高← 正确答案
  • B.批越大延迟越大← 正确答案
  • C.二者不存在关联
  • D.需要权衡取舍← 正确答案

答案:ABD。批越大,GPU 并行利用率越高,吞吐通常上升;但请求排队等待和单批计算时间都变长,延迟也上升。二者存在强关联且方向相反,需要在 SLO 约束下权衡,因此 A、B、D 正确,C 错。

Q7. 批大小设置的上限主要受什么约束?

单选题 · easy

  • A.显存容量← 正确答案
  • B.CPU 核数
  • C.网络带宽
  • D.磁盘空间

答案:A。每个请求的激活值和 KV Cache 都占显存,批越大显存占用线性增长,超过显存直接 OOM。所以显存容量是 batch size 的硬性约束。

Q8. 动态批处理中队列等待时间过长会带来什么直接后果?

单选题 · mid

  • A.吞吐量必然上升
  • B.端到端延迟恶化← 正确答案
  • C.显存占用减少
  • D.GPU 利用率下降

答案:B。请求在队列里等得越久,用户感知的端到端延迟越大,可能突破 SLO。此时应缩短超时阈值或降低触发数量。吞吐量和显存不会因此必然改善。

Q9. 设置 batch size 时,正确的工程做法有哪些?

多选题 · hard

  • A.直接取显存最大值
  • B.用延迟预算约束上限← 正确答案
  • C.压测扫描不同取值← 正确答案
  • D.固定为某个经验值

答案:BC。正确做法是先确定延迟预算(SLO),再通过压测扫描不同 batch size 的吞吐和延迟曲线,找拐点选最优。直接取显存最大值会延迟爆表,固定经验值则没有依据,A、D 错。

Q10. 在吞吐量公式 Throughput = B / t_batch 中,t_batch 表示什么?

单选题 · easy

  • A.单个请求耗时
  • B.每批处理耗时← 正确答案
  • C.队列等待时间
  • D.网络传输时间

答案:B。t_batch 是模型处理一批请求的总耗时,批大小 B 除以单批耗时得到平均吞吐。它包含的是计算时间而不含排队等待和网络传输。