☰
大模型推理调优实战:从指标拆解到关键参数配置
2026/10/20 13:33:50 网站建设 项目流程

做推理服务的人,一定经历过这种局面:模型在测试环境怎么压都稳,一上生产并发稍微上来就超时;或者本地单条请求响应快得飞起,线上看着GPU利用率不到一半,吞吐却死活上不去。更恼火的是,网上到处是零散的经验贴,看完还是一头雾水。这篇文章把大模型推理调优的思路串成一条主线:先弄懂衡量服务的指标和模型执行时的性能逻辑,再按故障分类排查,最后把关键调优参数一个个讲透。适合正在负责推理服务上线、优化线上延迟和吞吐的工程师,也适合想搞明白推理瓶颈到底在哪的算法同学。

1. 先搞清楚在优化什么:推理核心指标拆解

1.1 延迟类指标:TTFT和TPOT必须分开看

大模型推理的延迟和普通接口不一样,它不是一锤子买卖。用户发一个请求,服务端要先把整个prompt喂给模型算一遍,然后一个token一个token地往外吐。于是延迟被拆成两个关键数字。

TTFT(Time To First Token),首token延迟。从请求发送到收到第一个字的间隔,直观对应“用户感觉服务有没有响应”。TTFT超过两秒,体验就会明显变差。TTFT主要由prefill阶段决定,也就是模型把整段输入并行计算一遍的过程。

TPOT(Time Per Output Token),每个输出token的耗时,对应输出阶段的流畅度。如果TPOT超过100ms,用户会觉得打字速度慢得让人难受。实测想达到“顺畅阅读”的感觉,TPOT一般要压到50ms以内,也就是每秒至少能吐20个token。

除了这两个,还有一个端到端延迟(E2E Latency),指整条生成链路从请求进来到完整回答落地的总耗时。业务侧超时判断基本看它,但定位问题时要把E2E拆回TTFT加上TPOT乘以生成长度。

这里有个容易踩的坑:用E2E延迟对比不同配置,结果会被生成长度干扰。两个配置一个输出200字、一个输出800字,E2E自然差一大截,但这不代表服务性能差。正确做法是分开记录TTFT和TPOT,并用固定长度输出做基准。

1.2 吞吐类指标:QPS与token/s不是一回事

吞吐指标经常被混着报,但意义完全不同。QPS是每秒完成的请求数,适合衡量短输出、高并发的交互场景。token/s是每秒生成的token总数,适合衡量长文本、流式输出的生产场景。还有一个每车吞吐,比如每秒每GPU能生成的token数,用来横向对比机器规模。

服务端吞吐有个关键特征:总token/s约等于单请求TPOT的倒数乘以并发批大小。在prefill能力足够的前提下,并发数翻倍通常会让总吞吐明显上升,这一点后面调优会反复用到。

1.3 为什么延迟与吞吐必须放在同一张表里权衡

单独看任何一边都是耍流氓。把batch size拉得很大,吞吐可以冲得很高,但每个请求排队和计算的时间全被拉长,TTFT和TPOT一起恶化;反过来把并发压到1,单请求延迟漂亮,但GPU算力大部分时间在空转。

实际项目里,一个合理的做法是给三个指标设阈值:TTFT不超过2秒、TPOT不超过60ms、总吞吐达到预期业务量,然后再去调参。所有调优动作,本质是在这三个约束里找平衡点,而不是追求单一数字。

2. 推理执行模型与显存分配逻辑

2.1 Prefill和Decode是完全不同的两类计算

模型生成一段回答,底层是两段截然不同的计算阶段。Prefill阶段,模型拿到整个输入序列,把所有token并行计算,一个请求可以迅速算出第一轮所有Key和Value。它的特点是计算密集、时间紧凑。Decode阶段,模型每次只计算一个token,然后把这个token追加到序列末尾,通过缓存技术避免重算历史,逐字生成。它的特点是访存密集、串行执行。

同样是生成100个token,prefill可能只占零点几秒,decode却要几十毫秒乘一百次。这也是为什么线上很多服务平均延迟看着还行,输出阶段一样长到让人等得不耐烦。

注意:很多优化手段都围绕“两段式”展开。比如连续批处理,就是想办法把请求的prefill和decode交错到一起跑;前缀缓存目的则是砍掉重复prefill的时间。

2.2 KV Cache到底吃掉多少显存:一次手算

KV Cache是解码阶段的核心机制。每次生成一个token,模型都要算新的Key和Value向量,并把历史上所有token的Key和Value缓存下来,避免每次都从头重算。KV Cache的大小随序列长度线性增长,随并发序列数线性增长,这就是长上下文和大并发吃掉显存的根本原因。

可以快速估算:假设某个70B参数模型有80层、每组8个KV头、head维度128,用BF16存储,那么每个token每层需要 2×8×128×2字节=4KB,80层就是320KB。一个用户如果带了8000个token的上下文,这一个请求就占掉约2.5GB;10个并发用户就是25GB。算完这个数字再去看显存,大部分OOM的根因就一目了然了。

多卡并行时,权重按张量并行切分到各卡,KV Cache也按层切分,但预算思路不变:每张卡都要同时容纳权重切片、KV Cache、激活和运行时buffer。

2.3 完整显存账单:权重、KV、激活、运行时buffer

很多刚开始调优的同学下意识以为显存就是模型权重占用的空间,只盯着权重文件大小,结果一上线就爆。实际显存账单有四块:

  • 模型权重:BF16下大约每10亿参数占2GB,70B就是140GB左右,这是基础盘。
  • KV Cache:和上下文长度、并发数正相关,上面算过,随序列总长增长很快。
  • 激活值:prefill阶段中间计算结果瞬时占用很高,长prompt尤其明显。
  • 框架运行时缓存:CUDA context、显存碎片、临时buffer,通常占几GB到十几GB。

所以算显存预算时,这四样都要留空间。我的习惯是先大致算一遍,再额外留10%到15%作为安全余量,绝不在生产环境里把显存利用参数顶到极限。

2.4 为什么OOM容易出现在解码阶段

把整个过程拉长看,prefill阶段虽然激活值高,但KV Cache还没累积起来;decode阶段KV Cache随产出token数量一路涨,叠加并发序列越来越多,很容易在生成到一半时撞上显存上限。所以线上看到“单个请求没问题,并发一多就报显存错误”,先往KV Cache和并发序列数上想,往往不需要动权重。

3. 故障排查方法论:先量化,再定位,后修改

3.1 排障第一步:把性能指标完整接出来

排查性能问题最容易犯的错,就是还没量化就开始猜。我见过有人因为一次线上偶发超时,立刻把量化等级从FP16换成INT4,结果问题完全没解决,反而多了精度损失。正确流程是先拿到证据,再改配置。

先搭一套轻量级的可观测体系:把TTFT、TPOT、prefill时长、decode时长、排队等待时长、显存占用峰值、GPU利用率全部记录成直方图指标。没有这些前置数据,后续所有判断都靠猜。

给一个最基础的实时监控命令示例:

# 实时查看GPU利用率和显存变化 nvidia-smi dmon -s pucmet -d 1 -c 60

也可以把指标输出到时序数据库,等故障波峰过去后拉出对应时间段的曲线。这一步通常能筛掉八成的问题。

3.2 常见故障症状与第一怀疑点对照

抛一张典型的故障现象定位表:

现象第一怀疑点定位方法
单请求正常,并发后OOMKV Cache容量不足看并发数、输入长度、显存曲线
TTFT异常高prefill被长输入或重复前缀拖慢看prompt长度分布、prefill耗时
TPOT整体慢模型过大、单卡算力不足对比不同量化、换更大GPU
TPOT波动大并发波动、排队挤压看排队时长和in-flight请求数
输出质量诡异采样参数被改、量化损失复测固定样例、检查采样配置

3.3 一次典型TTFT恶化的完整推演

举一个复合例子。某个业务上线了问答服务,现象是白天高峰期TTFT逐步升到三秒以上,TPOT却保持正常。第一次看到这个组合,可以先把“TPOT正常”排掉,说明单次decode算力没退化,重点转向prefill和排队。

看指标,发现prefill耗时确实涨了,但瓶颈不在单条请求,而在于高峰期并发请求的大batch把一堆prefill任务堆在一起执行,每条都被拖慢。再往下查,发现大量重复的固定system prompt,每条请求都白算一遍相同的几千token。

对症下药就三步:给固定前缀做缓存,砍掉大量重复prefill;把超长prompt截断或做长度限制;调度上让prefill请求优先处理。改完之后TTFT从三秒降到六百毫秒。这个例子想说明的是,排查不是靠经验玄学,而是靠“指标分层—定位瓶颈—针对性改动”的顺序。

4. 性能调优的关键参数与实操配置

4.1 连续批处理与并发上限的关系

传统批处理方式是等一批请求凑齐了再推理,批大小固定,天然导致排队延迟。主流推理框架基本都改用了连续批处理:一个请求decode结束就立刻移出,新请求的prefill可以插到当前batch的空隙里,GPU始终在密集计算。

调这个能力最直接的参数是并发序列上限,也叫max_num_seqs或max_batch_size。调大它,吞吐会涨,但每个请求分到的算力变少,TPOT变差;调小,延迟稳定,但吞吐上限低。一个可操作的策略是:从单请求基准出发,逐步倍增上限,每次记录TTFT、TPOT和总吞吐,直到TPOT越过阈值再退回上一档。

参考配置片段:

{ "max_num_seqs": 16, "gpu_memory_utilization": 0.85, "max_model_len": 4096, "enable_prefix_caching": true }

max_num_seqs从16试到64,吞吐和TPOT的变化通常会呈一条清晰的曲线,选中间最稳的点。

4.2 显存利用率与KV Cache预算的配置逻辑

被问得最多的就是:显存利用率参数到底设多少合适。设低了,KV Cache容量不够,高并发长上下文直接OOM;设高了,权重和激活空间被压缩,同样可能OOM。

我的经验是分两步走。第一步,先按权重大小、KV Cache估算值、激活峰值算出总预算,看显存还剩多少。第二步,把显存利用率参数从0.8起步,跑一组极限压力测试,尽量长文本、尽量多并发,然后逐步递增到0.9左右。如果压测中出现KV Cache不足的报错,优先降低并发上限,而不是继续压榨显存比例。

注意:显存利用率是“给框架分配显存的上限”,不是“KV Cache独占比例”。调到0.99看起来能最大化缓存,实际上留给其他模块的余量几乎为零,生产环境非常容易被临时峰值击穿。

4.3 量化选型的分场景思路

量化减少显存占用、提高计算效率,同时也在损失精度。选型时要区分两类场景。

对延迟敏感且用户能接受轻微输出变化的在线助手类场景,INT8是一个稳妥的中间选项:显存降低约一半,推理效率提升,精度损失通常辨析不出来。对显存极其紧张、模型又很大的场景,INT4级别的低比特量化能把大模型压进更小显存,但要注意有的层对量化特别敏感,输出质量和稳定性都可能波动。

有没有更优解?有。可以只对部分敏感层做高精度保留,用混合精度方案。这类方案配置复杂,除非已经明确精度问题由量化触发,否则不建议一上来就搞。

选型的判断标准也很直接:换完之后,固定跑同一组线上代表样本,对比关键字段是否一致、生成分布是否偏移。只盯benchmark数字不看业务结果,是选型中最容易犯的错。

4.4 前缀缓存:最容易被忽略的加速手段

很多应用的prompt由固定模板和用户输入拼接而成。如果是同一个固定前缀,模型在prefill阶段会在一模一样的位置算出一模一样的KV Cache。前缀缓存就是让这些固定部分只算一次,后续请求直接复用。

它对多轮对话场景尤其有效,因为历史对话内容会不断拼接进prompt,重复计算量非常大。开启后固定前缀命中时,TTFT往往能下降一个量级。

代价是前缀缓存需要额外显存存放已缓存的KV,且不同输入的前缀不一致时命中率低。线上设计系统提示词时,把固定部分放在最前面、内容改动尽量少,能大幅提高命中率。

4.5 投机解码的收益与适用条件

投机解码的思路是:先用一个小模型快速猜出接下来几个token,再让大模型一次验证多个token。如果猜得对,一次decode能“白赚”几个token的产出。

这个技术对TPOT的优化效果非常明显,但稳定依赖两个条件:草稿模型足够快,且猜中的概率足够高。实测下来,在代码生成、结构化JSON输出、固定格式文本这类低熵场景,接受率很高,收益很大;但在开放式创意写作、随机性很强的对话里,收益就没那么明显,有时还会因为猜测失败浪费算力。

所以不要看别人说投机解码好就无脑开,先拿自己的线上prompt做一轮小规模A/B测试,再决定是否全局开启。

5. 线上踩坑实录与问题速查

5.1 问题、根因、解法一览表

把典型线上问题浓缩成一张速查表:

问题现象大概率根因解决方向
并发一高就OOMKV Cache预算不足或并发上限过大调低并发上限、限制输入长度、优化KV Cache预留
TTFT高企固定前缀反复计算 / prefill与decode争抢算力开启前缀缓存、调度优先prefill、限制超长prompt
TPOT波动明显请求大量到达导致批大小抖动设置稳定并发上限、启用平滑排队、压测确定容量
输出内容突然变差量化损失 / 采样参数被改动回滚参数、对比基准样本、局部高精度量化
生成停不下来缺少长度兜底或终止条件失效服务端设max_tokens、增加终止符检测

5.2 三个反复出现的坑

第一个坑:显存利用率参数拍脑袋填0.95。结果权重加载时直接报显存不足,或者一压测就崩。正确做法是先算清权重和激活的底线空间,再往KV Cache方向去加,而不是一上来就顶满。

第二个坑:只看平均延迟,不看p95和p99。某个配置平均延迟甚至更低了,但p99翻了一倍,这在线上等于把一部分用户直接推出门外。任何调优结论都必须同时看尾延迟,尤其服务端会同时服务多种类型请求时。

第三个坑:只顾GPU算力,忘了数据预处理和tokenizer开销。大并发时系统会先把大量prompt转成token、做长度校验、拼装prompt模板,这些CPU进程如果不做分离或限流,会成为隐蔽的排队瓶颈。我调过的某次“GPU利用率只有30%”的问题,根因就是CPU侧tokenizer线程数不够。

5.3 攻坚排障时我常用的老办法

如果配置已经调到看起来都合理的状态,但性能还不满足要求,我会回到一个老做法:构造一条明确的复现请求,固定它的输入和输出长度,在不同配置下反复跑。真到排障攻坚期,问题几乎都能在这样“可变条件最少”的实验里暴露出来,比东看一眼西猜一下高效得多。

这套方法对算法和工程同学都适用,核心就是把变量锁住,一次只动一个。

做推理调优这么久,我最大的感受是:大部分性能和故障问题都不是玄学,而是指标没拆清、资源没算透、变更没有对照导致的。每次上线前固定跑一遍同一批基准样本,把TTFT、TPOT和p95保存下来,任何改动都拿它做对照。这套流程看着笨,但真到线上出问题的时候,它能帮你省下整整一晚上的排查时间。之后再遇到推理卡顿,先对照速查表过一遍,再回到参数区手动调一轮,你会发现很多问题都没那么邪乎。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询