27届大模型面试准备(五十四):大模型推理成本与吞吐调优实战——从连续批处理到 PD 分离

27届大模型面试准备(五十四):大模型推理成本与吞吐调优实战------从连续批处理到 PD 分离

引言:为什么"能跑"和"跑得划算"是两回事

A25 讲过推理服务化的基础组件(PagedAttention、连续批处理、投机解码),A30 讲过全栈性能优化。但面试官真正想听的不是名词罗列,而是"给定一块卡、一个 SLA、一个成本上限,你怎么把吞吐和延迟同时压到最优"。本篇是工程实战深化的第二讲,把推理成本拆成"算力、显存、带宽"三本账,给出可落地的调优杠杆,并重点讲清近年最关键的架构演进------Prefill 与 Decode 分离(PD 分离)。

读完本篇,你应当能对着白板画出"请求从进来到出答案"的成本曲线,并指出每一个拐点对应哪个调优开关。

复制代码
单请求延迟构成(自回归解码)
  Latency = Prefill_time + Decode_time
  Prefill_time ≈ (prompt_len + img_tokens) × 算力 / 并行度
  Decode_time  ≈ output_len × (每步耗时)
  每步耗时 ≈ max(算力瓶颈, 显存带宽瓶颈)   # 解码是 memory-bound

一句话点题:prefill 是 compute-bound(吃算力),decode 是 memory-bound(吃显存带宽)。这两个阶段的天性不同,决定了它们不该被同一个调度器用同一种方式对待------这正是 PD 分离的根本动机。

第一节 三本账:算力、显存、带宽

1.1 算力账(FLOPs)

一次前向的算力消耗与 token 数、模型参数量近似成正比。prefill 阶段一次处理整个 prompt,算力集中爆发;decode 阶段每步只算一个新 token,但每个 token 都要读一遍全部权重,算力反而不是瓶颈。

1.2 显存账(KV Cache)

KV Cache 是显存的主要占用者。粗略公式:

复制代码
KV_bytes ≈ batch × seq_len × 2(K,V) × n_layers × d_model × 2(bytes,FP16)

举例:13B 模型、d_model=5120、40 层、FP16,单条 4K 长度序列的 KV 约:

2 × 4096 × 2 × 40 × 5120 × 2 ≈ 5.4 GB。这意味着一块 24GB 的卡,光 KV 就吃掉近四分之一,剩下要装权重和激活。所以"能并发多少请求"几乎由 KV Cache 容量决定。

1.3 带宽账(Decode 的命门)

decode 每生成一个 token,都要把全部权重从显存读到计算单元一次。这个"权重大小 ÷ 显存带宽"决定了每步的理论下界耗时。因此:

  • 量化(W8A8、W4)直接缩小权重体积 → 每步读的字节更少 → decode 更快;

  • 更大的 batch 把"读权重"摊到更多 token 上 → 算力利用率更高(batch 内并行 decode)。

    成本杠杆对照
    账本 主要优化杠杆 作用阶段
    算力 prefix cache 命中、投机解码 prefill / decode
    显存 PagedAttention、KV 量化、prefix 复用 全程(决定并发数)
    带宽 weight 量化、大 batch、PD 分离 decode(主)

第二节 连续批处理:把"等待"变成"吞吐"

2.1 静态批处理的浪费

早期推理把请求凑成一个固定 batch,等最慢的请求生成完才释放整批。结果:一个长请求拖住整批,短请求空等,GPU 利用率低得可怜。这就是"静态批处理"的 vacancy 问题。

2.2 连续批处理(continuous batching)

核心思想:不按"请求"批,而按"step 里的 token"批。每解码一步,调度器把所有还在生成的请求的当前 token 聚成一个动态 batch;有请求结束了,立刻塞进新请求,不留空位。这样 GPU 始终满载,吞吐可提升数倍。

复制代码
静态批处理:  [ReqA ReqB ReqC ReqD] 全部生成完才进下一批
             ReqA 早结束 -> 空等 -> 利用率低

连续批处理:  step1: [A_t B_t C_t D_t]
             step2: [A_t B_t C_t D_t]   (D 结束了, 但 slot 立刻被新请求 E 占)
             step3: [A_t B_t C_t E_t]

实现关键在 PagedAttention(A25 详述):KV Cache 被切成固定大小的 block,像虚拟内存一样按需分配、可按请求拼接,从而支持"变长、可动态增删"的批。没有它,连续批处理的碎片问题会拖垮显存。

第三节 PD 分离:把 prefill 和 decode 拆成两个工厂

3.1 为什么要分离

prefill 是算力密集型、可大 batch 并行;decode 是带宽密集型、必须低延迟响应。把它们塞在同一块卡上,会出现"prefill 的大 batch 把 decode 的延迟抖上去"的互相干扰。PD 分离(Prefill-Decode Disaggregation)把两类实例分开部署:

  • P 实例(prefill):专吃算力,大 batch 处理 prompt + 图像 token,算完把 KV Cache 传给 D 实例;

  • D 实例(decode):专吃带宽,小 batch 低延迟自回归生成,从 P 实例接收已算好的 KV 起步。

    传统合设(混部):
    [Req] -> [同卡: prefill + decode 抢资源] -> 延迟互相干扰

    PD 分离:
    [Req] -> [P 实例: 大batch prefill] --KV 传输--> [D 实例: 低延迟 decode] -> answer

3.2 KV 传输是分离的代价

PD 分离引入了"KV 在 P、D 之间传输"的新开销。若 P、D 不在同一节点,要走网络(NVLink/IB/RDMA),KV 体积可能高达数 GB(长上下文)。工程上常用"KV 传输压缩 + 流水线 overlap"来掩盖:P 实例算完一段 KV 就流式发给 D,D 边收边开始 decode,不等全量到齐。

3.3 分离带来的调度自由度

分离后,P 和 D 可以独立扩缩容:对话类流量 decode 占比高,就多扩 D;摘要类流量 prefill 占比高,就多扩 P。还能做"prefill 队列削峰"------把突发的大 prompt 在 P 集群排队消化,不影响在线 decode 的尾延迟。这正是大规模服务里 PD 分离越来越成为标配的原因。

维度 合设(混部) PD 分离
资源干扰 强(prefill 抖 decode) 弱(各管各的)
扩缩容灵活性 低(绑定) 高(P/D 独立)
尾延迟稳定性
系统复杂度 中(多 KV 传输)
适用规模 中小流量 大流量/高低峰差异大

第四节 量化对成本的杠杆:不止是"省显存"

4.1 W8A8 与 W4 的取舍

权重量化(A19 详述 GPTQ/AWQ)在 serving 里最直接的好处是"每步读的字节变少"。W8A8 几乎无损、decode 提速明显;W4(如 GPTQ/FP4)提速更猛但可能掉点,需要校准。对成本敏感、且任务容错度高的场景(摘要、分类、草稿生成),W4 很划算;对精度敏感的生成任务,优先 W8A8。

4.2 KV Cache 量化常被忽视

KV Cache 量化(如 INT8/FP8 存 KV)能直接放大"可并发序列数",因为它压的是显存账里最大的一块。很多团队只量化权重忘了量化 KV,等于只做了一半。在长上下文场景,KV 量化带来的并发提升往往比权重量化更显著。

复制代码
# KV Cache 量化示意(存储时用低精度,计算时反量化)
class KVCacheQuant:
    def __init__(self, bits=8):
        self.scale = None
    def quantize(self, kv_fp16):
        # 按 token 维度做 per-token 对称量化
        amax = kv_fp16.abs().amax(dim=-1, keepdim=True)
        scale = amax / (2**(self.bits-1) - 1)
        kv_q = (kv_fp16 / scale).round().clamp(-127, 127).to(torch.int8)
        return kv_q, scale                      # 存 int8, 省一半显存
    def dequantize(self, kv_q, scale):
        return kv_q.float() * scale             # 计算前还原

第五节 容量压测与 SLA 设计:调优要打到靶上

5.1 先定义"好"的标准

调优前必须先定 SLA:TTFT(首 token 延迟,对应 prefill)、TPOT(每输出 token 延迟,对应 decode)、端到端延迟、以及可接受的 P99。没有 SLA 的压测是盲目的。

5.2 阶梯加压找拐点

从单并发开始逐步加压,记录吞吐与 P99 延迟曲线,找到"延迟开始陡增"的并发数------那就是该配置的真实容量。超过拐点后,应当触发限流或排队,而不是硬扛(硬扛只会让所有请求都慢)。

加压阶段 观察到的现象 对应调优动作
低并发 延迟低、GPU 利用率不足 提高 max_batch,榨算力
中并发 吞吐上升、延迟平稳 健康区,维持
拐点附近 P99 陡增 限流/排队,或加 D 实例
过载 OOM / 超时雪崩 KV 量化、PD 分离、降级

5.3 成本换算成钱

最终要把"每千次请求成本"算出来:显存占用决定需要的卡数,吞吐决定单位时间能服务的请求数,二者相除即单请求成本。面试官常问"怎么把推理成本降一半",标准答案不是某个银弹,而是"量化压权重+KV量化压显存+PD分离稳延迟+连续批处理提利用率"的组合拳,并量化每一步的收益。

第六节 一次真实调优复盘(把前面所有杠杆串起来)

光讲杠杆容易,面试里真正加分的是"给你一个现场,你怎么排"。下面用一段工程复盘把全篇串起来,你可以直接当作案例讲。

背景:某在线问答服务,单卡 A100 80G 部署 13B 模型,SLA 要求 TTFT<P2s、TPOT<P80ms,目标把单请求成本压到原来的 60%。初始配置是"合设 + FP16 + 静态批处理",实测单卡 QPS 仅 2.1,且 P99 TPOT 经常突破 200ms。

第一步,定位瓶颈。打 tracing 发现 decode 阶段每步耗时稳定在 ~70ms,且 GPU 显存带宽利用率接近打满、算力利用率仅 35%。结论:decode 是 memory-bound,且静态批处理让 GPU 大量时间空等------两本账同时出问题。

第二步,上连续批处理 + PagedAttention。把静态批改成连续批,max_batch 调到 32。结果 QPS 直接到 5.8,TPOT P99 降到 95ms。这一步几乎零精度代价,收益最大。

第三步,权重 W8A8 量化。decode 仍是带宽瓶颈,量化把每步读的权重减半,TPOT P99 降到 62ms,QPS 到 7.4。精度评测掉分在 0.5% 以内,业务可接受。

第四步,KV Cache INT8 量化。长上下文请求占比高,KV 是显存大头。量化 KV 后,单卡可并发序列数从 18 提到 34,QPS 到 9.1,且 OOM 不再发生。

第五步,PD 分离削峰。上线后观测到"摘要类长 prompt"会周期性把在线对话的 TTFT 抖高。把 prefill 拆到独立 P 集群、KV 走 NVLink 流式传输,在线对话 TTFT P99 从 1.8s 稳到 1.2s,且不再受批量摘要任务干扰。

最终:QPS 从 2.1 提升到 9.1(约 4.3 倍),单请求成本降到约 23%(远低于 60% 目标),全部 SLA 达标。复盘结论:调优不是"都上了就好",而是按"瓶颈账→杠杆→验证"的顺序逐层打,每一步都有可观测的收益归属。

复制代码
调优路径(实际生效顺序)
静态批+FP16  ──①连续批+PagedAttention──> QPS 2.1→5.8, TPOT 200→95ms
             ──②W8A8 权重量化──────────> TPOT 95→62ms, QPS→7.4
             ──③KV INT8 量化───────────> 并发 18→34, QPS→9.1, 无OOM
             ──④PD 分离削峰───────────> TTFT P99 1.8→1.2s, 稳定

这个案例的面试价值在于:它展示了"先量后调、按账归因、逐步验证"的工程方法论,而不是堆名词。面试官听到"我先打 tracing 看是算力还是带宽瓶颈,再决定上哪招",印象会明显不同。

第七节 排障:延迟突增的三条主线

其一,TTFT 变长:先看 prefill 是否被大图/长 prompt 拖住,检查 prefix cache 命中率;若命中率低,多半是 system prompt 被改或缓存键漂移。

其二,TPOT 变长(生成变慢):decode 是带宽瓶颈,检查是否 batch 过大导致每步排队、或权重量化没生效;也可看是否误用了高精度。

其三,吞吐上不去但 GPU 不满:典型"等数据"------KV 传输(PD 分离)或 RPC 序列化成了瓶颈,或连续批处理的调度有锁竞争。定位要靠端到端 tracing,而不是猜。

第八节 五个常见调优误区

误区一:只量化权重忘了 KV。很多团队把 W8A8 当作"降本全部",但长上下文场景下 KV Cache 才是显存主占,不量化 KV 等于只做了一半,并发上不去、成本降不下来。

误区二:把连续批处理当成银弹。连续批能提吞吐,但它不解决"prefill 抖 decode"的延迟干扰,也不解决带宽瓶颈。重载场景里光有连续批、不配合 PD 分离和量化,尾延迟照样崩。

误区三:盲目堆 max_batch。batch 不是越大越好。超过拐点后,每步排队时间超过并行收益,TPOT 反而上升,且显存压力陡增易 OOM。正确做法是用阶梯压测找到拐点,再留 20% 余量。

误区四:用平均延迟代替 P99 定 SLA。平均好看、长尾崩溃是 serving 最常见的"假健康"。面试官最爱追问"你的 P99 是多少",务必把尾延迟当一等公民。

误区五:调优不量化收益。每上一招就该记录 QPS、TPOT P99、成本的变化,否则无法判断"这招值不值"。复盘里那种"逐层打靶点"的思路,比一次性全上更显专业。

面试速答(本篇可直接背的 3 句)

  1. prefill 是 compute-bound、decode 是 memory-bound,二者天性不同,这是 PD 分离和所有批处理优化的根本出发点。
  2. 连续批处理按"每步 token"而非"整个请求"组批,配合 PagedAttention 消除碎片,是吞吐翻倍的基石。
  3. 成本三本账:算力(prefix/投机)、显存(PagedAttention/KV量化/复用)、带宽(权重量化/大batch/PD分离);调优要按 SLA 的 TTFT/TPOT 拐点打靶,不是盲目堆配置。

高频追问清单

  • 为什么 decode 阶段量化权重能加速,而 prefill 阶段主要看算力?请用带宽瓶颈解释。
  • PD 分离后,KV 在 P、D 之间怎么传?同节点和跨节点分别用什么?
  • 连续批处理下,一个超长请求会不会饿死短请求?怎么做公平调度?
  • KV Cache 量化掉点比权重量化更敏感还是更不敏感?为什么?
  • 如果 SLA 要求 P99 TPOT < 50ms,你会先调哪个参数?给出排查顺序。
  • 混合精度(部分层 FP8、部分 FP16)在 serving 里怎么落地?风险在哪?
相关推荐
tachibana24 小时前
把RAGAS跑起来
数据库·人工智能·ai·架构·大模型·llm·rag
thesky1234564 小时前
27届大模型面试准备(五十):大模型训练稳定性与容错工程——从 loss spike 到弹性训练
大模型·nan·混合精度·训练稳定性·容错训练·loss spike·bf16
ReleaseU5 小时前
Claude Code 两天三版本、Cursor 把仓库搬进编辑器:AI 编程工具在卷什么?
人工智能·大模型
tachibana25 小时前
初识智能体
人工智能·ai·大模型·llm·agent
艾莉丝努力练剑6 小时前
【AI大模型接入SDK】项目的数据结构设计
数据结构·人工智能·大模型·sdk·文件系统·岗位
uncle_ll15 小时前
大模型量化落地全解:原理、实操、效果对比与评估调优指南
大模型·llm·模型评估·量化·ptq
Web3&Basketball19 小时前
vLLM部署开源大模型实战:显存、命令与成本核算
人工智能·深度学习·大模型·ai技术·vllm
tachibana21 天前
性能指标的口径选择
数据库·人工智能·架构·大模型·llm
ReleaseU1 天前
Claude Code 翻倍、Cursor 推 Origin、Copilot 跌到 21%——AI 编程工具市场正在重新洗牌
人工智能·大模型