适用读者:要调 vLLM/TGI 类引擎参数、要解释"batch 加大为什么变慢"、要设计多轮对话容量的人。
前置:S23-J09、J13、J18(KV、roofline、并行)。
覆盖旧文:A-4341 §8.1–§8.3(请求执行 + 批处理与调度 + KV 分配复用释放,465 点)→ 465 点。
扩展点:E-S23-075(TTFT = 排队 + prefill:0.196 s,之后 26.5 ms/token)、E-S23-076(每 token 读取D_w/b + L·k:14.38 → 0.50 GiB)、E-S23-077(分页把未用容量 22 → 10 token,浪费上限p−1)、E-S23-078(LoRA 两笔账:adapter 1.43 GiB vs KV 112.5 GiB)。
上一篇:S23-J21 · 数据交接、拥塞与任务完成
下一篇:S23-J23 · 压缩、卸载与推测解码。
1. 一句话结论
batch 提高的是"权重分摊",不减少"KV 读取",所以它有个下限。 Qwen3-8B、2K 上下文:每输出 token 的读取量从 b=1 的 14.38 GiB 降到 b=64 的 0.50 GiB(1/29);但其中 KV 项始终是 0.28125 GiB——再加大 batch 也降不下去。
出处:AI Infra 教材 §8.1–§8.3(
id 4341),实测平台 RTX PRO 6000(实验 8-1)。
每 token 读取 = D_w/b(权重分摊) + L·k(本请求 KV)
2. 一个请求的一生(E-S23-075)
到达 → 等待队列 → 调度器入 batch、分配 KV → prefill(并行处理全部输入)
→ 生成第 1 个 token(TTFT) → decode:每步 1 个 token,直到结束
实测(2K 输入,单请求):
| 段 | 耗时 |
|---|---|
| prefill + 首 token | 0.096 s |
| 排队(假设) | 0.1 s |
| TTFT | 0.196 s |
| decode 间隔 | 26.5 ms/token |
| 256 输出 token | 255 × 26.5 ms ≈ 6.76 s |
判据:TTFT 与"每 token 间隔"是两个独立指标,优化手段不同:
- TTFT ← 排队 + prefill(受 chunking、队列策略影响);
- 间隔 ← decode 每步读取(受 batch、量化、KV 影响)。
术语对齐:batch 指"同一次执行里一起算的 token";调度迭代指"调度器选一轮工作"的周期。 一条请求跨很多迭代,每轮的同伴可能不同。
3. 批处理的数学(E-S23-076)
I_weight ≈ 2b / s_w (BF16 s_w=2;b 行共用一次权重读取)
D(b,L) = D_w + b·L·k (整批读取总量)
d(b,L) = D_w/b + L·k (平均每输出 token)
Qwen3-8B:D_w = 14.1 GiB,2K 上下文 L·k = 0.28125 GiB。
| batch | 权重分摊 | KV | 每 token 合计 |
|---|---|---|---|
| 1 | 14.1 GiB | 0.28 GiB | 14.38 GiB |
| 16 | 0.88 GiB | 0.28 GiB | 1.16 GiB |
| 64 | 0.22 GiB | 0.28 GiB | 0.50 GiB |
判据:b=16 时权重项已降到 KV 项的 3 倍;b=64 时 KV 反而更大。 继续加 batch 的收益趋零,而 KV 容量与读取线性上涨——这就是"吞吐有个拐点"的算术来源。
3.1 LoRA 的两笔账(E-S23-078)
| 项 | 数值 | 说明 |
|---|---|---|
| 一套 adapter(Q/V,rank 16,BF16) | 14.6 MiB | 100 套 = 1.43 GiB |
| 基础矩阵权重 | 14.1 GiB | 全批共用 |
| 100 条互不共享 8K 上下文 KV | 112.5 GiB | 决定并发 |
判据:"能装多少 adapter"由小矩阵决定,"能服务多少请求"仍由 KV 决定。 前者宽裕 ≠ 后者宽裕。
批内代价:不同 adapter 的请求要按 adapter 分组,每组行数变少 → 那部分的权重复用变差。"放几种 adapter"和"放多少请求"是两个独立决策。
4. 连续批处理与长 prefill 干扰(§8.2.2–8.2.4)
| 方式 | 行为 | 问题 |
|---|---|---|
| 固定 batch | 等最慢一条结束再换批 | 短请求等长请求,利用率低 |
| 连续批处理 | 每条结束即换新请求,逐迭代重排 | 需要每轮重算 batch 组成与形状 |
| 长 prefill + decode 混跑 | 同一迭代里既有 prefill 又有 decode | 长 prefill 卡住所有 decode |
| 分块 prefill(chunked) | 把长 prefill 切成多块,与 decode 交替 | 多几次迭代启动,TTFT 略增 |
判据:长 prefill 的干扰是"一个迭代里最慢的那件事决定全部"。 1024 token 的 prefill 塞进 decode 迭代,会让所有正在生成的请求多等一整轮。
token 预算:每轮能算多少 token 受"KV 容量 + 计算时间 + 显存带宽"三者共同约束;动态形状让每轮的 M 变化,于是又有 J17 的分桶/特化问题。
5. KV 分页:把碎片压到 p−1(E-S23-077)
逻辑块(按 token 位置编号)→ 块表 → 物理块(实际内存)
需要块数 = ⌈L/p⌉;最后一块的浪费 ≤ p−1 个 token 的 KV
算例:A/B/C/D 长度 9/13/5/15,每条最大 16 token。
| 方案 | 分配容量(token 数) | 未用 |
|---|---|---|
| 整段预留 | 4×16 = 64 | 22 |
分页 p=4 |
12+16+8+16 = 52 | 10 |
实际保存 42 token 的 KV → 未用从 22 降到 10
判据:块越小浪费越少,但块表项和分配次数越多。 这是"内存碎片 vs 元数据开销"的经典权衡——PagedAttention(vLLM,2023) 把 OS 分页思路搬进 KV;FlashAttention 解决的是算子内部的访存组织,两者作用的层次不同。
5.1 前缀共享与写时复制
| 场景 | 机制 | 省什么 |
|---|---|---|
| 同一 system prompt / 多轮对话 | 前缀块共享(引用计数) | 一份 KV 给多请求 |
| 分支(beam search、多轮改写) | 写时复制(COW) | 分叉处才复制 |
| 智能体(长工具输出) | 前缀复用 + 准入策略 | 避免重复 prefill |
5.2 准入 / 淘汰 / 换出 / 重计算
容量不够时的四种处理:
换出(swap 到主机内存) → 重计算(丢掉再算) → 淘汰(丢最冷) → 拒绝(排队/降级)
判据:"换出"省的是算力、"重计算"省的是内存,两者都是拿时间换容量。 选哪个取决于 TTFT 预算 与 prefill 成本——长上下文下重计算极贵,宁可换出或直接限流。
6. 可复算(实测)
$ cd /root/博客运营官/blog-v2 && python3 tools/s23_book_calc.py | tail -9
TTFT: prefill 0.096 s + 排队 0.1 s = 0.196 s;decode 26.5 ms/token → 256 token 约 6.76 s
批处理(Qwen3-8B,D_w=14.1 GiB,2K KV=0.28125 GiB)
b=1 : 14.10 + 0.28125 = 14.38 GiB/token
b=16 : 0.88 + 0.28125 = 1.16 GiB/token
b=32 : 0.44 + 0.28125 = 0.72 GiB/token
b=64 : 0.22 + 0.28125 = 0.50 GiB/token
1/29 关系: 14.38 / 0.50 = 29×
分页: 整段 64 token / 未用 22 → p=4 分配 52 / 未用 10(每请求上限 p−1=3,4 条 ≤ 12)
LoRA: 100 套 adapter 1.43 GiB vs 100 条 8K KV 112.5 GiB
7. 前提与边界
| 结论 | 成立条件 | 不成立时 |
|---|---|---|
d = D_w/b + L·k |
权重每轮读一次、KV 不共享 | 权重分片/量化 → 改 D_w |
| batch 越大越好 | KV 容量够 | KV 撑满 → 只能等待/换出 |
| 分页减少浪费 | 变长请求、块表可寻址 | 等长请求 → 整段预留更简单 |
| 前缀共享有效 | 前缀完全一致 | 前缀有差异 → 只能部分命中 |
8. 常见误区
| 误区 | 纠正 |
|---|---|
| "batch 翻倍就快一倍" | 权重项减半,KV 项不变,收益递减 |
| "TTFT 由 prefill 决定" | 还有排队;长 prefill 分块会再加迭代开销 |
| "分页块越小越好" | 块表项与分配次数上升 |
| "adapter 装得下就能多服务请求" | 并发由 KV 决定 |
| "换出一定比重计算好" | 长上下文重算极贵,短上下文重算更省 |
9. 自测
- prefill 0.1 s、排队 0.05 s、26.5 ms/token,输出 128 token 的总耗时多少?
D_w=14.1 GiB、2K 上下文 0.28 GiB,batch=32 时每 token 读取多少?- 4 条请求长度 9/13/5/15、上限 16、
p=4,分页后未用容量多少? - 100 套 LoRA adapter 与 100 条 8K 上下文,哪个先撑爆?
答案
TTFT = 0.15 s;127 × 26.5 ms = 3.37 s;合计 3.52 s。14.1/32 = 0.44+ 0.28 = 0.72 GiB。12+16+8+16 = 52,已用 42 → 未用 10 token。- KV:112.5 GiB,远大于 adapter 的 1.43 GiB。
10. 下一篇
S23-J23 · 压缩、卸载与推测解码 —— 权重量化后的内存账、KV 压缩的读取成本、卸载与预取,以及推测解码的接受率与加速比。