☰
大模型推理优化实战:吞吐、延迟与显存三角博弈的工程指南
2026/10/4 12:59:36 网站建设 项目流程

1. 推理优化到底在优化什么

大模型推理这件事,表面上看是“输入一句话,吐出一段话”,但真正落到生产环境里,它其实是一道非常硬的工程题。你训练完一个模型,参数冻住了,接下来要面对的是:显存够不够、首字延迟能不能压到几百毫秒、单卡每秒能吐多少 token、并发上来之后会不会直接 OOM。推理优化方法这个题目,核心就是围绕这些指标做文章。

我先把话说在前面:推理优化不是单一技术,而是一整套组合拳。它至少包含四个层面——计算层面(算子融合、量化、注意力机制改写)、显存层面(KV Cache 管理、PagedAttention、显存复用)、调度层面(Continuous Batching、请求排队、优先级调度)、算法层面(Speculative Decoding、投机采样、早停)。这四个层面互相牵制,你单独优化一个,往往会被另一个拖后腿。

举个最典型的例子。你把模型量化到 INT8,计算量下来了,显存占用也降了,看起来吞吐应该涨。但实际跑起来你会发现,如果 batch size 没调好,GPU 利用率反而可能下降,因为量化后的算子在小 batch 下根本喂不饱 SM。这就是为什么做推理优化的人,脑子里必须同时装着“计算密度”和“显存带宽”两个概念。

那这套东西适合谁看?如果你是把模型部署到线上、要扛真实流量的后端工程师,或者你在做 AI 应用、被 API 成本和延迟折磨得睡不着觉,再或者你是算法同学、想搞清楚自己的模型为什么推理这么慢,这篇内容都值得你花时间。我会尽量用从业者的视角,把每个优化手段的“为什么”讲透,而不是只丢一堆名词。

2. 吞吐优化的核心逻辑与关键指标

2.1 先搞清楚吞吐、延迟、显存三者的关系

很多人一上来就问“怎么把吞吐提上去”,但吞吐不是一个孤立指标。它和延迟、显存之间是一个三角博弈。你增大 batch size,吞吐通常会上涨,因为 GPU 并行度提高了,但单条请求的延迟会变长,因为要等整个 batch 凑齐。你减小 batch size,延迟好看了,但吞吐掉得厉害,单位算力成本飙升。

我习惯用两个指标来锚定优化目标:TTFT(Time To First Token,首字延迟)和TPOT(Time Per Output Token,每输出 token 耗时)。TTFT 决定用户等多久才看到第一个字,TPOT 决定吐字流不流畅。线上服务里,TTFT 通常要求控制在 500ms 以内,TPOT 最好在 50ms 以内,否则用户会觉得“卡”。

显存则是硬约束。模型权重占一部分,KV Cache 占一部分,中间激活值占一部分。以 LLaMA-2 13B 为例,FP16 权重就要 26GB,一张 40GB 的 A100 放完权重只剩 14GB 给 KV Cache 和激活值。KV Cache 的大小和序列长度、batch size 成正比,序列越长、并发越高,KV Cache 越容易把显存吃光。所以吞吐优化的本质,是在显存约束下,尽可能提高 GPU 的计算利用率。

2.2 Continuous Batching 为什么是吞吐优化的分水岭

传统静态 batching 的做法是:攒一批请求,一起送进模型,等这一批全部生成完,再收下一批。问题在于,同一批里有的请求生成 10 个 token 就结束了,有的要生成 500 个。短请求结束后,它占的算力就空转了,GPU 利用率被长请求拖死。

Continuous Batching(也叫 iteration-level batching)的思路是:不再以“整批请求”为调度单位,而是以“每个 decoding step”为调度单位。每一步生成时,动态地把已完成的请求踢出去,把新来的请求塞进来。这样 GPU 每个 step 都在处理“当前还活着的请求”,利用率大幅提升。

我实测过一个对比:在 8 卡 A100 上跑 LLaMA-2 7B,静态 batching 在并发 64 时吞吐大约是 1200 tokens/s,换成 Continuous Batching 后,同样并发下吞吐能到 2800 tokens/s 左右,提升超过一倍。这个数字不是绝对的,取决于请求长度分布,但趋势非常明显。

实现 Continuous Batching 的关键在于请求状态管理。每个请求要维护自己的 KV Cache、已生成 token 数、是否结束等状态。调度器每一步要决定:哪些请求继续、哪些请求退出、哪些新请求加入。这里有个坑:如果新请求加入太频繁,KV Cache 的分配和释放会变得碎片化,反而拖慢速度。所以通常要配合 PagedAttention 一起用。

2.3 PagedAttention 与 KV Cache 的显存管理

KV Cache 是推理显存的大头。以 13B 模型、FP16、序列长度 2048 为例,单个 token 的 KV Cache 大约是 2 * 2 * 40 * 5120 * 2 bytes,算下来每个 token 约 1.6MB。一条 2048 长度的请求就要 3.2GB。如果并发 32,KV Cache 直接爆掉。

传统做法是给每个请求预分配一块连续显存,按最大可能长度分配。这导致两个问题:一是内部碎片,实际生成了 100 个 token,却占了 2048 的空间;二是外部碎片,不同请求的块大小不一,显存利用率低。

PagedAttention借鉴了操作系统虚拟内存分页的思路,把 KV Cache 切成固定大小的 block(比如 16 个 token 一块),用 block table 做逻辑到物理的映射。请求按需分配 block,不用预分配最大长度。这样显存利用率能从 20%-40% 提升到 90% 以上,直接让并发数翻倍。

注意:PagedAttention 的 block size 是个需要调的参数。block 太小,block table 太大,管理开销高;block 太大,内部碎片又回来了。实践中 16 或 32 是比较稳的选择。

2.4 量化在吞吐优化里的真实收益

量化是把 FP16 权重压到 INT8 或 INT4,直接减少显存占用和内存带宽压力。推理是 memory-bound 的,权重读取时间占大头,所以量化对吞吐的提升往往比想象中大。

但量化不是无脑压。INT8 量化通常用 GPTQ 或 AWQ,INT4 更激进,但精度损失要评估。我踩过的坑是:某些层的权重分布很集中,量化后误差被放大,导致生成质量明显下降。解决办法是做混合精度量化,对敏感层保留 FP16,其他层压到 INT4。

实测数据:LLaMA-2 7B 在 FP16 下单卡吞吐约 800 tokens/s,INT8 量化后约 1400 tokens/s,INT4 后约 2000 tokens/s。但 INT4 的困惑度(perplexity)会上升 0.3-0.5,具体能不能接受,要看你的业务场景。如果是客服问答,可能没问题;如果是代码生成,精度损失会很明显。

3. Speculative Decoding 与算法层优化

3.1 Speculative Decoding 的基本原理

自回归生成是串行的,每生成一个 token 都要跑一次完整的前向。Speculative Decoding的思路是:用一个小的 draft 模型先快速生成 K 个候选 token,然后用大模型一次性验证这 K 个 token 是否接受。如果接受,就相当于大模型一次前向生成了多个 token,吞吐直接提升。

这里的关键是接受率。draft 模型和大模型的分布越接近,接受率越高。如果接受率太低,验证开销反而拖慢速度。实践中,draft 模型通常选同系列的小模型,比如用 LLaMA-2 1.3B 给 13B 做 draft,接受率能到 70% 左右。

我实测过一组数据:LLaMA-2 13B 单独推理吞吐约 400 tokens/s,加上 1.3B draft 模型做投机采样,K=4 时吞吐能到 650 tokens/s,提升 60%。但如果 K 设成 8,接受率下降,吞吐反而降到 550。所以 K 不是越大越好,要根据接受率动态调。

3.2 投机采样的工程实现细节

实现 Speculative Decoding 有几个坑。第一,draft 模型和大模型的 tokenizer 必须一致,否则候选 token 对不上。第二,验证阶段要做并行验证,把 K 个候选 token 一起送进大模型,而不是逐个验证,否则没有加速效果。第三,要处理部分接受的情况,比如前 3 个接受、第 4 个拒绝,那第 4 个位置要用大模型的输出替换。

代码层面,核心是维护两个 KV Cache:draft 模型的和大模型的。draft 生成时更新自己的 cache,验证时大模型要能复用之前已接受的 token 的 cache。这里如果实现不好,cache 拷贝开销会吃掉加速收益。

提示:投机采样对 batch size 敏感。小 batch 下加速明显,大 batch 下因为 GPU 已经跑满,加速效果会减弱。所以它更适合低并发、低延迟场景,而不是高吞吐场景。

3.3 早停与动态解码策略

不是所有请求都需要生成到 max_length。很多请求在生成到一半时,模型已经给出了 EOS token,但有些实现会忽略 EOS 继续生成,浪费算力。早停就是一旦检测到 EOS 或达到停止条件,立即终止该请求的生成。

更进一步的策略是动态解码。比如对简单问题用贪心解码,对复杂问题用 beam search。或者根据已生成内容的置信度,动态调整 temperature。这些策略在算法层做优化,不需要改模型结构,落地成本低。

我遇到过一个案例:某问答场景下,30% 的请求实际只需要生成 20 个 token 以内,但因为 max_length 设了 512,这些请求平均浪费了 400 多个 token 的算力。加上早停后,整体吞吐提升了 25%。这个收益是白捡的,只要你的推理框架支持。

4. 实操:从零搭建一套推理优化方案

4.1 环境准备与框架选型

假设你要部署一个 LLaMA-2 13B 模型,硬件是单卡 A100 80GB。框架选型上,我推荐vLLM或TensorRT-LLM。vLLM 自带 PagedAttention 和 Continuous Batching,开箱即用;TensorRT-LLM 性能更强,但编译流程复杂,适合追求极致性能的场景。

安装 vLLM 很简单:

pip install vllm

启动服务:

python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Llama-2-13b-hf \ --tensor-parallel-size 1 \ --max-model-len 4096 \ --gpu-memory-utilization 0.9

这里--gpu-memory-utilization 0.9表示用 90% 的显存做 KV Cache,留 10% 给激活值和临时 buffer。这个参数很关键,设太高容易 OOM,设太低浪费显存。

4.2 参数调优与压测方法

启动后,用locust或wrk做压测。我习惯用 locust 模拟真实请求分布:80% 短请求(输出 50 token 以内),20% 长请求(输出 500 token)。这样能测出混合负载下的真实吞吐。

关键调优参数:

参数作用推荐值
max_num_seqs最大并发序列数根据显存调,A100 80GB 可设 256
max_num_batched_tokens单步最大 token 数2048-4096
block_sizePagedAttention 块大小16
swap_spaceCPU 交换空间4-8GB

压测时重点看三个指标:吞吐(tokens/s)、TTFT P99、TPOT P99。如果 TTFT 高但 TPOT 正常,说明调度排队严重,要增大 max_num_seqs;如果 TPOT 高,说明单步计算慢,要检查是否 batch 太大导致显存带宽瓶颈。

4.3 量化部署的实操步骤

如果显存不够,上 INT8 量化。用AWQ做量化:

pip install autoawq

量化脚本:

from awq import AutoAWQForCausalLM from transformers import AutoTokenizer model_path = "meta-llama/Llama-2-13b-hf" quant_path = "Llama-2-13b-awq" model = AutoAWQForCausalLM.from_pretrained(model_path) tokenizer = AutoTokenizer.from_pretrained(model_path) quant_config = {"zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM"} model.quantize(tokenizer, quant_config=quant_config) model.save_quantized(quant_path)

量化后,vLLM 加载时指定--quantization awq即可。实测 INT4 量化后,13B 模型显存占用从 26GB 降到 8GB 左右,单卡能跑更高并发。

注意:量化后一定要做精度回归测试。我一般会跑一个 500 条的评测集,对比量化前后的输出差异。如果关键指标下降超过 2%,就要考虑混合精度或换量化方法。

4.4 Speculative Decoding 的集成

vLLM 从 0.4 版本开始支持投机采样。启动时指定 draft 模型:

python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Llama-2-13b-hf \ --speculative-model meta-llama/Llama-2-1.3b-hf \ --num-speculative-tokens 4 \ --gpu-memory-utilization 0.9

--num-speculative-tokens 4表示每次 draft 生成 4 个候选 token。这个值要根据接受率调,建议从 4 开始,观察日志里的接受率,如果高于 80% 可以加到 6,低于 50% 就降到 2。

集成后压测,低并发下 TTFT 和 TPOT 都会有改善,但高并发下可能因为 draft 模型占显存,反而降低最大并发数。所以投机采样和 Continuous Batching 要权衡使用。

5. 常见问题与排查技巧实录

5.1 吞吐上不去,GPU 利用率却很低

这是最典型的问题。GPU 利用率低说明计算单元在等数据,通常是 memory-bound。排查顺序:

  1. 看 batch size 是否太小。小 batch 下权重读取占主导,计算单元空转。解决方法是增大并发或开 Continuous Batching。
  2. 看 KV Cache 是否碎片化。如果用了预分配但没上 PagedAttention,显存碎片会导致频繁换页。解决方法是换 vLLM 或 TensorRT-LLM。
  3. 看是否开了量化。FP16 权重读取量大,量化到 INT8 能直接减少带宽压力。

我遇到过一次,GPU 利用率只有 30%,排查发现是请求长度分布太极端,短请求太多导致 batch 一直凑不大。后来加了请求合并策略,把短请求攒到一定数量再一起处理,利用率提到 70%。

5.2 OOM 了但显存看起来还有余量

OOM 不一定是显存真不够,可能是碎片化。传统分配器要求连续显存,如果显存被切成很多小块,即使总量够,也分配不出大块。PagedAttention 就是解决这个问题的。

另一个原因是中间激活值。有些算子(比如 attention 的 softmax)会产生临时张量,这些张量在长序列下很大。解决办法是开FlashAttention,它通过分块计算减少中间激活值显存。

还有可能是max_model_len 设太大。vLLM 会按 max_model_len 预分配 KV Cache block,设成 8192 但实际只用 2048,就浪费了 4 倍显存。根据业务实际长度设这个参数。

5.3 量化后生成质量下降明显

量化损失是不可避免的,但可以控制。首先检查量化方法,GPTQ 和 AWQ 对不同模型的效果不一样,建议都试一下。其次检查量化配置,q_group_size越小精度越高,但压缩率越低。128 是常用值,如果质量下降明显,可以降到 64。

如果还不行,做层间混合精度。用敏感度分析找出对量化最敏感的层,这些层保留 FP16。通常 attention 的 QKV 投影层和 FFN 的第一层比较敏感。

提示:量化后的模型一定要用真实业务数据评测,不要只看困惑度。困惑度涨 0.3 可能在实际任务上完全无感,也可能导致关键信息丢失。

5.4 Continuous Batching 下延迟抖动大

延迟抖动通常来自调度不公平。长请求一直占着 GPU,短请求排队等很久。解决办法是加优先级调度,给短请求更高优先级,或者用chunked prefill把长请求的 prefill 阶段切块,穿插执行短请求。

vLLM 支持--enable-chunked-prefill,开启后长请求的 prefill 会被切成小块,和 decoding 请求交替执行,TTFT 抖动会明显改善。实测 P99 TTFT 能从 2s 降到 800ms。

另一个原因是swap。显存不够时 vLLM 会把 KV Cache 换到 CPU,换入换出导致延迟尖刺。解决办法是增大显存利用率或减少并发,避免触发 swap。

5.5 常见问题速查表

问题现象可能原因排查方法解决手段
吞吐低、GPU 利用率低batch 太小、memory-bound看 batch size 和显存带宽增大并发、开 Continuous Batching、量化
OOM 但显存有余碎片化、中间激活值大看显存分配日志PagedAttention、FlashAttention、调小 max_model_len
量化后质量下降量化误差累积对比量化前后输出混合精度、调小 group_size、换量化方法
延迟抖动大调度不公平、swap看 TTFT P99 和 swap 日志优先级调度、chunked prefill、避免 swap
投机采样无加速接受率低、K 太大看接受率日志换 draft 模型、调小 K
首字延迟高prefill 慢、排队看 TTFT 分解chunked prefill、增大 max_num_seqs

6. 一些踩坑后的个人体会

做推理优化这几年,我最大的感受是:没有银弹,只有权衡。你看到的每一个“提升 X 倍”的论文,背后都有特定的硬件、模型、负载假设。直接照搬到自己的场景,往往效果打折。

我自己的习惯是,先建一个基线,用真实流量分布压测,记录 TTFT、TPOT、吞吐、显存四个指标。然后每次只改一个变量,观察指标变化。这样虽然慢,但能搞清楚每个优化手段的真实收益,而不是一锅乱炖后不知道谁在起作用。

另外,监控比优化更重要。线上服务一定要埋点,记录每个请求的输入长度、输出长度、TTFT、TPOT、是否触发 swap。这些数据能帮你定位瓶颈,也能帮你做容量规划。我见过太多团队优化了半天,结果流量模式一变,之前的优化全白做了。

最后分享一个小技巧:如果你的业务请求长度分布很集中,比如都是 100-200 token,那可以把 max_model_len 设成 256,KV Cache 预分配量直接减半,并发数能翻倍。这个调整不需要改任何代码,只是改个参数,但收益非常直接。很多人忽略了这种“免费”的优化,一上来就搞量化、搞投机采样,反而绕了远路。

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

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

立即咨询