跳到正文

量化与吞吐调优 ​

一句话:量化用精度换显存和带宽,从而在同一张卡上跑更大模型或更多并发;调参则是在延迟与吞吐之间选折中,二者都必须用真实负载评测,不能只看“模型能启动”。

为什么需要 ​

推理成本有两个硬约束:显存装不下,或者装得下但并发上不去。量化直接缓解前者,调参缓解后者。常见误区是先调一堆参数,最后发现模型权重就占了八成显存;或者为了省显存上了激进量化,业务指标掉得比成本省得多。

正确顺序是:先算显存账,决定是否需要量化;再测量化后的质量;最后才调引擎参数。顺序反了,调参是在一个错误的基础上做局部优化。

这张图的顺序是排障顺序:先判断是权重装不下还是 KV 不够,前者才考虑量化或多卡,后者优先降并发或加显存;两者都够才轮到调参。量化之后必须过业务评测,不达标就换方案,不要继续调参掩盖质量损失。

核心机制 ​

量化到底省了什么 ​

精度每参数字节13B 权重相对 FP16
FP16 / BF162约 26 GB1×
FP81约 13 GB1/2
INT81约 13 GB1/2
INT40.5约 6.5 GB1/4

权重省下来的显存可以直接变成 KV 容量,提升并发;同时 decode 每步要读权重,字节数减少也降低带宽压力,TPOT 通常改善。但量化不是无损压缩:激活、注意力和长尾输入可能放大误差,必须用业务评测集验证。

常见方案 ​

方案粒度特点注意
FP8权重 / 激活新卡支持好,精度损失小依赖硬件与引擎支持
INT8权重 / 激活成熟,通用性好校准数据要贴近真实分布
GPTQ权重量化离线量化,生态广不同任务精度差异大
AWQ权重量化保护显著通道,小模型表现好仍需业务评测
KV cache 量化KV直接提升并发长上下文精度风险更高

选择顺序:硬件支持 FP8 时优先 FP8;否则 INT8 / AWQ;INT4 只在显存极度受限且业务能接受精度损失时使用。不要仅凭困惑度(perplexity)选方案,生成质量、指令遵循和工具调用参数准确性都要测。

量化后的显存账 ​

text
总显存 = 权重 + KV + 激活峰值 + 固定开销

量化的收益主要在第一项。举例:80 GB 卡,13B FP16 权重 26 GB,KV 可用约 48 GB;换成 FP8 后权重 13 GB,KV 可用约 61 GB,并发上限提升约四分之一。如果模型是 70B,FP16 需要多卡,INT4 可能单机装下,这时量化决定的是“能不能上线”,不只是“快不快”。

一个具体的三步账:14B + 32 GB 卡 ​

参数表上的数字放到卡上,经常是“能加载、不能服务”。以 14B 模型、32 GB 卡为例,三步算完就知道该不该量化:

  1. FP16 权重约 28 GB(14B × 2 B)。扣掉 CUDA context、激活峰值和通信 buffer 约 2 GB,只剩 2 GB 给 KV,按 192 KiB/token 折算约 1 万 token:一条 8K 请求就占掉八成,第二路挤不进来。权重装下了,有效服务区间装不下。
  2. GPTQ-Int4 权重约 8.5 GB。名义值是 14B × 0.5 B = 7 GB,实际文件还要带分组 scale/zero,embedding 和输出层往往不量化,按 8.5 GB 估更接近真实占用。
  3. 32 − 8.5 − 2 ≈ 21.5 GB 留给 KV。按 192 KiB/token 折算约 11 万 token,平均 4K 序列约 26–28 路并发,8K 约 13–14 路,32K 长上下文只剩约 3 路。

这张账说明量化的收益不是“模型变小了”,而是把 KV 池从“几乎为零”变成“能排下几十路请求”;同时也说明第三步之后仍要按序列长度规划,同样是 21.5 GB,4K 和 32K 的并发差一个数量级。

前提:V100 没有 FP8 路径,实际可选项是 FP16 / INT8 / GPTQ / AWQ;固定开销那 2 GB 随卡型、驱动、引擎版本变化,账要按实测重算。

调参的四个核心旋钮 ​

参数控制什么调大调小
gpu_memory_utilizationKV 池上限并发高,OOM 风险高安全,浪费显存
max_num_batched_tokens每步 token 预算prefill 快,decode 可能被挤decode 稳,长 prompt 慢
max_num_seqs同时运行序列数并发高,尾延迟差排队多,TTFT 高
enable_prefix_caching前缀复用固定前缀场景省算力前缀多变时只增开销

gpu_memory_utilization 不是“KV 能用多少”,而是“KV 池的保险丝”。设得越接近 1,越容易被 CUDA context、通信 buffer 和激活峰值顶到 OOM。生产通常从 0.85 起步,每次加 0.05,用固定负载压 30 分钟,找到稳定上限后退回一档。

max_num_batched_tokens 是最容易被忽略的参数:它同时决定 prefill chunk 大小和 decode 预算。设得太大,长 prompt 会挤占 decode,TPOT 尾延迟变差;设得太小,prefill 步数多,TTFT 变长。

max_num_seqs 要和 KV 容量匹配。设得比实际能容纳的序列多,多出来的请求只是在等待队列里排队,TTFT 更高,不会提升吞吐。

场景化配方 ​

场景目标倾向
在线对话低 TTFT、稳 P99中等 batch,开分块 prefill,前缀缓存开
离线批处理总吞吐大 batch,大 token 预算,前缀缓存按需
代码补全极低延迟小 batch,短输出,限制并发
长文档摘要prefill 吞吐大 token 预算,按长度分池
多轮客服首字快、前缀复用前缀缓存开,会话粘性路由

配方只是起点。同一场景的 prompt 长度分布变了,最优参数就会变;用生产采样做压测,不要用几条固定 prompt。

评测:质量和性能一起看 ​

维度指标判据
质量任务成功率、指令遵循、工具参数准确率不低于基线的可接受区间
延迟P50 / P95 / P99 TTFT、TPOT满足 SLA
吞吐tokens/s、请求/s达到容量目标
成本每千 token 成本、每次成功成本低于预算
稳定性OOM、抢占、超时率长时间压测无异常

只看吞吐会忽略质量,只看质量会忽略成本。量化版本上线前至少跑一遍业务 Golden Set,并对比基线的失败类型;如果失败集中在长尾输入,说明量化对边界样本更敏感。

实战场景 ​

场景:14B 工单抽取模型 Int4 后困惑度只涨 0.5%,业务通过率掉 12 个点 ​

现象:14B 客服加工单抽取模型,FP16 基线困惑度 5.79、1,200 条 Golden Set 通过率 93.4%;换 GPTQ-Int4(group_size 128)后困惑度 5.82(+0.5%),业务通过率却只剩 81.2%,失败集中在金额、日期抽取和工具参数 JSON(经验值,需校准)。上线三天质检抽样才发现,其间 2.4 万次调用里约 4,500 次输出错误。

排查路径:

排除对象判定证据结论
采样参数漂移两侧 temperature=0、seed 固定,同一 prompt 复跑 3 次输出逐字相同排除
截断或 OOMfinish_reason 全为 stop,num_preemptions_total 为 0排除
模板被改网关记录的 prompt hash 与基线一致,git diff 无变化排除
量化误差集中在长尾Golden Set 分桶:常规问答 96.1%、数字抽取 74.0%、工具参数 JSON 68.5%;失败样本与基线的 logit 差集中在数字 token确认:均值掩盖了长尾

对症做法:把验收门从困惑度换成“分桶通过率 + 结构化输出合法率”,不达标就换方案。

python
GATE = {"ppl_delta": 0.05, "golden_pass": 0.90, "bucket_floor": 0.85, "json_valid": 0.99}
def accept(base, cand, buckets):
    if cand["ppl"] / base["ppl"] - 1 > GATE["ppl_delta"]: return "PPL 超限"
    if min(buckets.values()) < GATE["bucket_floor"]: return "分桶低于红线"
    if cand["json_valid"] < GATE["json_valid"]: return "结构化输出不合法"
    return "通过" if cand["golden_pass"] >= GATE["golden_pass"] else "整体通过率不足"

困惑度是所有 token 的平均交叉熵,被高频常见 token 拉平;金额、日期、JSON 键名是低频 token,量化误差在这里放大,均值看不出来。分桶把长尾从均值里拆出来,本例改用 W8A8 INT8(权重约 14 GB)后最低分桶回到 88.4%、整体 91.8%,显存占用仍低于 FP16 基线。

复盘要点:判据是“量化验收以最低分桶通过率和结构化输出合法率为准,困惑度只作准入粗筛”,红线是任一分桶低于基线 5 个点不得上线。更早的发现点是灰度期就按桶看通过率——本次是全量替换,没有灰度,指标口径再对也来不及。

场景:14B 上 32 GB 卡,三步账漏了权重占比,上线只有 1,184 token 的 KV ​

现象:14B 模型部署到 32 GB 卡,FP16 权重、gpu_memory_utilization=0.95,启动直接报 The model's max seq len (8192) is larger than the maximum number of tokens that can be stored in KV cache (1184);把 max_model_len 降到 1024 能起,但 max_num_seqs=8 时第二路就触发抢占,P99 TTFT 从 1.2 s 涨到 9 s。现场判断是“KV 参数没调好”。

排查路径:

排除对象判定证据结论
引擎或版本问题同镜像跑 7B FP16,KV 池 26 GB、max_model_len 32768 正常排除
max_model_len 太大8192 × 192 KiB = 1.5 GB,而报错说明池子只有 0.23 GB,任何 len > 1,184 都起不来排除:len 只是暴露了池子太小
权重实际占用加载后 nvidia-smi 显示 28.6 GB,与 14B × 2 B 吻合,占卡容量 89%确认:权重挤掉了 KV
固定开销30.4 GB 预算 − 28.6 GB 权重 − 0.23 GB 池 ≈ 1.6 GB,正好是 context 与激活峰值确认:余量不足

对症做法:按本章三步账重算,并把 gpu_memory_utilization 乘进第一步。

text
① 预算      32 × 0.90 = 28.8 GB(起点 0.85,每次 +0.05 压测)
② Int4 权重 14 × 0.5 = 7.0 GB,含分组 scale/zero 与未量化层按 8.5 GB 估
③ KV 池     28.8 − 8.5 − 2.0 = 18.3 GB ÷ 192 KiB/token ≈ 9.3 万 token
   4K → 22 路    8K → 11 路    32K → 2 路
   max_num_seqs = 22(对齐 4K 段;8K 请求由块数自然限流)  max_model_len = 8192

量化只改第一项,固定开销和“按序列长度折算并发”的规则不变:28.6 GB 已经吃掉 89% 的卡,gpu_memory_utilization 再怎么调也补不出 KV,只能把权重从 28.6 GB 压到 8.5 GB。第三步之后同一个 18.3 GB 在 4K 和 32K 下的并发差十倍,所以 max_num_seqs 必须按目标长度段定,不是按平均值定。

复盘要点:判据是“权重 ÷ 卡容量 > 0.75 就必须量化或多卡”,剩下 25% 要同时装 KV、激活峰值和 CUDA context,这条比任何调参都先决。启动日志里那句 maximum number of tokens that can be stored in KV cache 就是现成的容量结论,应写进部署验收清单——本次它出现后仍被当成参数问题排查了四小时。

场景:14B 服务放大显存上限与长度,并发从 23 掉到 9、首字翻三倍 ​

现象:14B 服务原来 gpu_memory_utilization=0.90、max_model_len=8192、max_num_batched_tokens=8192,稳定 23 路并发,P99 TTFT 1.1 s、TPOT 62 ms。为接长文档把显存上限提到 0.94、长度放到 32768,同时把 token 预算压到 4096 保护 TPOT,结果并发掉到 9 路、Waiting 积压 27,P99 TTFT 涨到 3.4 s,TPOT 反而升到 71 ms(经验值,需校准)。

排查路径:

排除对象判定证据结论
显存泄漏8 小时 nvidia-smi 曲线平稳,无单调上升排除
负载结构变了网关侧长度直方图:32K 档占 9%,主体仍是 4K排除:是单请求上限变了
长请求占满 KV日志 Running: 9, Waiting: 27,抢占 41 次/分钟,num_preemptions_total 斜率与 32K 请求数同步确认:块被长文吃干
prefill 预算trace 里 8K prompt 在 4096 预算下走 2 个 chunk,prefill 段占 TTFT 的 2.6 s确认:首字被 chunk 数拖慢

对症做法:短文本池回退参数,长文本单独分池,两个池各自按长度定 max_num_seqs。

yaml
# 短文本池(约占九成流量)
gpu_memory_utilization: 0.90
max_model_len: 8192
max_num_batched_tokens: 8192   # 一条 8K prompt 单步 prefill,TTFT 不再按 chunk 累加
max_num_seqs: 22               # 18.3 GB ÷ 192 KiB ÷ 4096 ≈ 22 路
# 长文本池(32K 独立副本)
max_model_len: 32768
max_num_seqs: 2                # 18.3 GB ÷ 6.4 GB/条 ≈ 2 路
max_num_batched_tokens: 16384

四个旋钮是乘在一起的:max_model_len 定单请求块上限,max_num_seqs 定同时占用者,两者相乘不能超过 KV 池——32768 × 192 KiB = 6.4 GB,两条长文就占到 12.8 GB,第三条挤不进来。max_num_batched_tokens 决定一条 prompt 走几个 prefill 步,TTFT 大致正比于 chunk 数,4096 让 8K 变成 2 步,恢复到 8192 后单步完成,TTFT 回到 1.2 s。0.94 把卡上仅剩的 1.9 GB 留给 CUDA context 与激活峰值,而这段实测要 2 GB,长压必然顶到 OOM。

复盘要点:判据是“一次只动一个旋钮,并同时看 running/waiting、抢占数、TTFT 三个数”,红线是 max_model_len × 单 token 块占 × 预期并发 > KV 池 的配置直接拒绝启动。更早的发现点是 Waiting 连续 5 分钟大于 Running:那说明瓶颈已从算力变成块容量,此时再调 token 预算只会让首字更慢。

最小上手 ​

用同一组负载做 A/B,不要跨时间比较:

text
基线:FP16,默认参数
实验:FP8 / INT8,固定 gpu_memory_utilization=0.85
负载:真实 prompt 采样,固定并发 1/8/32,各 10 分钟
记录:P99 TTFT、P99 TPOT、吞吐、显存峰值、质量通过率

判据:质量通过率下降在可接受范围内,P99 不退化,吞吐或并发有明显提升,才算量化有效。任何一项明显变差,都要回到方案选择,而不是继续调参掩盖。

生产动作 ​

  • 先算账再量化:明确是权重装不下,还是 KV 不够;两种问题的解法不同。
  • 校准数据真实:量化校准集要覆盖生产输入分布,避免长尾失真。
  • 保留回退:量化版本与基线版本同时可部署,出问题能切回。
  • 分池部署:不同量化精度、不同上下文长度的实例分开,避免互相挤占。
  • 监控 OOM 与抢占:显存峰值、空闲块、抢占次数、换出量一起看。
  • 版本固定:量化格式与引擎版本强相关,升级要重新验证兼容性。

边界 ​

  • 量化不能修复容量规划错误:KV 需求随并发和长度增长,量化只是把起点降低。
  • 精度损失不均匀:通用 benchmark 正常不代表业务长尾正常,必须用业务集评测。
  • 低精度不总是更快:某些硬件对 FP8 / INT4 的支持不完整,可能插入反量化开销。
  • KV 量化风险更高:长上下文下误差会累积,检索和引用类任务要重点验证。
  • 调参有交互:四个旋钮互相影响,一次只改一个,否则无法归因。

讲清楚 ​

量化用更少的字节存权重,省下的显存变成 KV 容量,同时降低 decode 的带宽压力;代价是精度损失,必须用业务评测集验证,不能只看困惑度。方案按硬件支持选 FP8、INT8、AWQ,INT4 只在极端受限时用。调参围绕四个旋钮:显存上限决定并发天花板,token 预算决定 prefill 与 decode 的分配,序列上限要和 KV 容量匹配,前缀缓存按场景开关。正确顺序是先算显存账、再选量化、再调参,最后用真实负载同时看质量、延迟、吞吐和成本。

适用:2026 年主流推理引擎的量化与调优实践;具体格式支持以引擎和硬件文档为准 | 初稿 2026-10

把 AI 工程讲到既懂也会用