2112 字
约 7 分钟
6
S23-J22 · 请求执行、批处理与 KV 分配

适用读者:要调 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. 自测

  1. prefill 0.1 s、排队 0.05 s、26.5 ms/token,输出 128 token 的总耗时多少?
  2. D_w=14.1 GiB、2K 上下文 0.28 GiB,batch=32 时每 token 读取多少?
  3. 4 条请求长度 9/13/5/15、上限 16、p=4,分页后未用容量多少?
  4. 100 套 LoRA adapter 与 100 条 8K 上下文,哪个先撑爆?
答案
  1. TTFT = 0.15 s;127 × 26.5 ms = 3.37 s;合计 3.52 s。
  2. 14.1/32 = 0.44 + 0.28 = 0.72 GiB。
  3. 12+16+8+16 = 52,已用 42 → 未用 10 token。
  4. KV:112.5 GiB,远大于 adapter 的 1.43 GiB。

10. 下一篇

S23-J23 · 压缩、卸载与推测解码 —— 权重量化后的内存账、KV 压缩的读取成本、卸载与预取,以及推测解码的接受率与加速比。

S23-J22 · 请求执行、批处理与 KV 分配
https://www.clxhxhhr.top/posts/5549/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。