☰
大模型推理优化实战:量化、推测解码与PagedAttention的工程取舍
2026/10/9 22:37:55 网站建设 项目流程

1. 推理优化到底在优化什么:从一次线上延迟抖动说起

模型层推理优化这件事,很多人第一反应是“上量化”“换推理引擎”“加缓存”,但真到线上环境里,你会发现延迟抖动往往不是单一因素造成的。我印象很深的一次排查:同一个模型、同一批请求,白天 P99 延迟稳定在 800ms 左右,到了晚上高峰期突然飙到 3s 以上,GPU 利用率却只有 60% 出头。当时第一反应是算力不够,准备扩容,但仔细看监控才发现,显存占用已经接近上限,batch size 被迫压得很小,GPU 大部分时间在等内存搬运,而不是在算。

这就是推理优化最核心的矛盾:算力、显存、延迟、吞吐这四个指标互相拉扯。你增大 batch 能提升吞吐,但显存不够就会 OOM;你量化权重能省显存,但可能引入精度损失;你用更激进的并行策略能降延迟,但通信开销又会吃掉收益。所以做推理优化,第一步不是急着选工具,而是先搞清楚当前系统的瓶颈到底在哪。

这一章我们聚焦模型层的推理优化,主要覆盖四条技术路线:量化、推测解码、PagedAttention 以及算子与调度层面的优化。这四块不是孤立的,实际工程里往往是组合使用。比如一个典型的部署方案可能是:权重用 INT8 量化 + KV Cache 用 PagedAttention 管理 + 投机采样加速解码 + 算子融合减少 kernel launch 开销。每一块解决不同层面的问题,叠加起来才能把推理成本压到可接受的范围。

适合读这一章的人,我大致分三类:一是刚接触模型部署、想知道推理优化有哪些抓手的新手;二是已经在做推理服务、但遇到性能瓶颈不知道怎么下手的工程师;三是需要做技术选型、评估不同方案成本收益的架构同学。不管你是哪一类,我都建议先建立一个基本认知:推理优化的本质是在给定硬件约束下,找到延迟和吞吐的最优平衡点,所有技术手段都是为这个目标服务的。

下面我会按“先讲清楚每项技术解决什么问题、再讲怎么落地、最后讲踩过的坑”这个顺序展开。不会堆砌论文公式,而是尽量用工程视角把每个决策背后的取舍讲明白。

2. 量化:省显存和提速的第一抓手,但精度账要算清楚

2.1 量化到底在做什么,为什么它能同时省显存和提速

量化的本质,是把模型权重和激活值从高精度浮点数(比如 FP16、FP32)映射到低精度表示(比如 INT8、INT4)。举个直观的例子:FP16 每个参数占 2 字节,INT8 只占 1 字节,INT4 更是只占 0.5 字节。一个 70 亿参数的模型,FP16 下光权重就要 14GB 显存,INT8 降到 7GB,INT4 只要 3.5GB。显存省下来,就能塞更大的 batch,或者把模型塞进更小的卡里。

但量化不只是省显存。现代 GPU 上,INT8 矩阵乘法的理论吞吐通常是 FP16 的 2 倍左右,因为整数运算单元更密集、数据搬运量更小。实际加速比取决于你的 kernel 实现和硬件架构,通常在 1.3 到 1.8 倍之间。注意这里说的是“理论吞吐”,实际能不能吃到这个收益,还要看你的瓶颈是不是在计算上。如果瓶颈在内存带宽,量化省下的带宽反而可能带来更大收益。

量化的粒度也很关键。Per-tensor 量化是整个张量共用一个缩放因子,实现简单但精度损失大;Per-channel 量化是每个通道一个缩放因子,精度好很多;Group-wise 量化(比如每 128 个元素一组)在 INT4 场景下几乎是标配,因为 INT4 的动态范围太窄,不做分组精度会崩。我在实际项目里,INT8 一般用 per-channel 就够了,INT4 基本都会上 group-wise,group size 取 128 或 64。

2.2 PTQ 和 QAT 怎么选:大多数场景先做 PTQ 就够了

量化分两条路:训练后量化(PTQ)和量化感知训练(QAT)。PTQ 是拿训练好的模型直接量化,不需要重新训练,成本低、上手快;QAT 是在训练过程中模拟量化误差,让模型学会适应低精度,精度通常更好,但需要训练资源和数据。

我的经验是:INT8 PTQ 在绝大多数场景下精度损失可以忽略,尤其是用 per-channel 加校准集的情况下。校准集不需要很大,几百条代表性样本就够,但一定要覆盖真实分布的多样性。我见过有人拿训练集前 100 条做校准,结果线上遇到长文本就崩,因为校准集里全是短句,激活值的动态范围估计偏了。

INT4 就不一样了,PTQ 往往会有明显掉点,尤其是小模型或者对精度敏感的任务。这时候要么上 QAT,要么用更先进的 PTQ 方法(比如 GPTQ、AWQ 这类基于权重重要性的方法)。GPTQ 的思路是逐层量化,用 Hessian 矩阵指导哪些权重更重要、需要保留更高精度;AWQ 则是观察到激活值里有一小部分“显著通道”,对这些通道做保护。这两种方法在开源社区都有成熟实现,INT4 下能把精度损失压到 1% 以内。

提示:量化前一定要先跑一遍 baseline,把 FP16 下的精度指标记下来。量化后再跑同一套评测,对比掉点。没有 baseline 的量化就是盲人摸象。

2.3 量化落地的完整流程与实测数据

我拿一个实际项目举例,模型是 13B 级别的对话模型,硬件是单卡 24GB。FP16 下权重占 26GB,根本放不下,必须量化。流程大致是这样:

  1. 确定量化方案:权重 INT4 group-wise(group size 128),激活 INT8 per-token 动态量化。选这个组合是因为权重占大头,压到 INT4 收益最大;激活用 INT8 是因为动态量化不需要校准集,实现简单。
  2. 选择工具:用 GPTQ 做权重量化,推理时用支持 INT4 的 kernel。这里要注意,不是所有推理框架都支持 group-wise INT4,选型时要确认。
  3. 校准与评测:准备 512 条覆盖多轮对话、长文本、代码等场景的校准数据,量化后在自建评测集上对比。
  4. 精度对比:FP16 baseline 准确率 78.3%,INT4 量化后 77.1%,掉点 1.2 个百分点。这个幅度在可接受范围内,因为对话任务对轻微精度损失不敏感。
  5. 性能实测:显存占用从 26GB 降到 8.5GB,batch size 从 1 提到 8,吞吐提升约 5 倍,单请求延迟从 1.2s 降到 0.9s。

这里有个细节值得说:量化后首 token 延迟(TTFT)和解码延迟的变化趋势可能不一样。TTFT 主要受 prefill 阶段影响,这个阶段是计算密集型的,INT4 的加速比较明显;解码阶段是内存带宽密集型的,量化省带宽的收益更大。所以如果你的场景是长输入短输出,量化收益主要在 TTFT;如果是短输入长输出,收益主要在解码吞吐。

2.4 量化踩坑实录:那些文档里不会写的细节

第一个坑是量化格式和推理框架不匹配。我遇到过用 A 工具量化出来的模型,B 框架加载后精度暴跌,排查半天发现是缩放因子的排列顺序不一致。不同工具对 group-wise 量化的存储布局可能有差异,跨工具使用时一定要先做小规模验证。

第二个坑是动态量化的开销。激活用动态量化时,每次推理都要实时计算缩放因子,这个开销在小 batch 下可能抵消掉量化带来的收益。实测下来,batch size 小于 4 的时候,动态量化的额外开销能占到总延迟的 10% 以上。解决办法要么是改用静态量化(需要校准集),要么是增大 batch。

第三个坑是量化对某些层的破坏性特别大。比如 LayerNorm 层、embedding 层,这些层对精度敏感,量化后容易出问题。常见做法是这些层保持 FP16,只量化线性层。这个混合精度的策略在大多数框架里都支持,配置时留意一下。

第四个坑是INT4 的 kernel 支持不完整。有些框架宣称支持 INT4,但只支持特定 shape 或特定硬件。我踩过一次,模型在 A100 上跑得好好的,换到消费级卡上直接报错,因为那个 kernel 只针对特定架构优化过。选型时一定要在目标硬件上实测。

3. 推测解码:用一个小模型给大模型“打草稿”

3.1 推测解码的核心思想:为什么小模型能加速大模型

推测解码(Speculative Decoding)这个思路第一次看会觉得反直觉:用一个小模型去加速一个大模型?小模型本身也要算,怎么会更快?关键在于大模型解码是内存带宽瓶颈,不是计算瓶颈。解码阶段每生成一个 token,都要把整个模型的权重从显存读一遍,计算量却很小。也就是说,GPU 的算力大量闲置,时间都花在搬数据上了。

推测解码的做法是:先用一个小模型(draft model)快速生成 K 个候选 token,然后让大模型(target model)一次性验证这 K 个 token。验证是并行的,一次前向就能算完 K 个位置的概率。如果小模型猜得准,大模型一次前向就能确认多个 token,相当于把 K 次串行解码压缩成 1 次并行验证。小模型虽然也要算,但它小得多,开销可以忽略。

这里的关键指标是接受率(acceptance rate),也就是小模型猜的 token 里有多少被大模型认可。接受率越高,加速越明显。理论上,如果接受率是 α,平均每次能确认的 token 数是 (1-α^(K+1))/(1-α),加速比大致是这个数除以 (1 + K×cost_ratio),其中 cost_ratio 是小模型和大模型单次前向的开销比。

3.2 草稿模型怎么选:不是越小越好

选草稿模型有几个考量。第一是分布要接近,草稿模型和目标模型的能力差距不能太大,否则接受率会很低。实践中常用同系列的小模型,比如目标模型是 13B,草稿模型用 1B 或 3B 的同架构模型。第二是速度要够快,草稿模型本身不能太慢,否则验证的收益被草稿开销吃掉。第三是显存要放得下,草稿模型和目标模型要同时驻留显存。

我实测过几组搭配,数据大致是这样:目标模型 13B,草稿模型 1B,K=4 时接受率约 0.72,加速比 1.9 倍;草稿模型换成 3B,接受率提到 0.81,但草稿开销增加,加速比反而降到 1.6 倍。所以草稿模型不是越大越好,要找到接受率和开销的平衡点。

还有一种不需要额外草稿模型的方案,叫自推测解码(Self-Speculative Decoding),用目标模型自己的浅层或部分层做草稿。这种方案省显存,但接受率通常不如独立草稿模型。另外还有Medusa这类方案,在目标模型上加几个预测头,一次预测多个位置的 token,思路类似但实现不同。

3.3 推测解码的工程落地与参数调优

落地推测解码,核心是调好 K 值(每次猜多少个 token)。K 太小,加速有限;K 太大,草稿开销增加,而且后面的 token 接受率会下降。经验值是 K 取 4 到 8 之间,具体要看接受率曲线。我一般会先跑一组实验,测不同 K 下的实际加速比,选峰值点。

另一个参数是采样策略。推测解码要保证输出分布和原始模型一致,所以验证阶段不能简单取 argmax,而是要用一种叫“拒绝采样”的方法。具体来说,对每个候选 token,计算目标模型和草稿模型的概率比,以一定概率接受。这个实现细节如果搞错,输出分布就偏了,生成质量会下降。好在主流推理框架都封装好了,自己实现的话要仔细对照论文。

注意:推测解码在 batch size 较大时收益会下降,因为大 batch 下解码本身就不是纯内存带宽瓶颈了,计算占比上升,草稿模型的额外计算会拖后腿。所以推测解码更适合低并发、低延迟的场景。

3.4 推测解码的适用边界:什么时候不该用

推测解码不是万能的。第一,高并发场景收益有限,前面说了,大 batch 下瓶颈转移,草稿开销变成负担。第二,输出多样性高的任务接受率低,比如创意写作、开放式对话,草稿模型很难猜准。第三,显存紧张时放不下两个模型,这时候要么放弃,要么用自推测方案。

我踩过的一个坑是:在 batch size 动态变化的服务里,推测解码的收益波动很大。低峰期 batch=1,加速 1.8 倍;高峰期 batch=16,加速只有 1.1 倍,有时候甚至是负优化。后来我们的做法是做一个动态开关,根据当前 batch size 决定是否启用推测解码。这个策略在实际服务里效果不错。

4. PagedAttention:把 KV Cache 的显存浪费挤出来

4.1 KV Cache 为什么是显存大户

要理解 PagedAttention,先要理解 KV Cache 为什么重要。自回归解码时,每生成一个 token,都要用到之前所有 token 的 Key 和 Value。如果每次都重新算,计算量会随序列长度平方增长。所以标准做法是把历史 token 的 K、V 缓存下来,每次只算新 token 的 K、V,然后和缓存拼接。这就是 KV Cache。

问题在于,KV Cache 的大小和序列长度成正比。一个 13B 模型,如果 hidden size 是 5120,层数 40,那么每个 token 的 KV Cache 大约是 2 × 40 × 5120 × 2 字节 = 1.6MB(FP16)。序列长度 2048 时,单个请求的 KV Cache 就要 3.2GB。如果并发 10 个请求,就是 32GB,比模型权重还大。

传统做法是给每个请求预分配一块连续显存,大小按最大序列长度算。但实际序列长度往往远小于最大值,这就造成大量浪费。更麻烦的是,预分配的连续块会导致显存碎片,明明总空闲显存够,却因为没有连续的大块而分配失败。实测中,传统方案的显存利用率经常只有 20% 到 40%。

4.2 PagedAttention 的分页思路:像操作系统管理内存一样管理 KV Cache

PagedAttention 的核心思路借鉴了操作系统的虚拟内存分页。它把 KV Cache 切成固定大小的块(block),比如每块存 16 个 token 的 KV。每个请求的 KV Cache 不再要求连续,而是由一组 block 组成,通过一个块表(block table)来索引。这样带来几个好处:

第一,消除内部碎片。传统方案按最大长度预分配,实际用多少算多少,浪费严重。分页后按需分配 block,用多少给多少,最后一个 block 最多浪费 15 个 token 的空间。第二,消除外部碎片。block 是固定大小的,任何空闲 block 都能用,不会出现“总空间够但没有连续大块”的情况。第三,支持共享。多个请求如果有相同的前缀(比如相同的 system prompt),可以共享前缀部分的 block,只读不写,省显存。

实测数据很能说明问题:同样一张 24GB 卡,传统方案最多并发 8 个 2048 长度的请求,PagedAttention 能并发到 20 个以上,吞吐提升 2 到 3 倍。这个提升不是来自计算优化,纯粹是把浪费的显存利用起来了。

4.3 分页带来的调度新问题:block 管理和抢占

分页不是没有代价的。第一,block 表的维护有开销,每次分配、释放、查找都要操作元数据。不过这个开销相对推理本身可以忽略。第二,attention 计算要适配分页布局,不能再用简单的连续内存假设,需要专门的 kernel。好在主流框架都实现了,自己写的话要参考相关实现。

第三,也是最重要的,是抢占(preemption)策略。当显存不够时,需要把某些请求的 block 换出(swap out)到 CPU 内存,或者直接丢弃重算。换出到 CPU 再换回来,走 PCIe 带宽,延迟很高;丢弃重算则是把已生成的 token 重新跑一遍 prefill,计算开销大。两种策略各有适用场景:换出适合序列长、重算成本高的情况;重算适合序列短、换出开销大的情况。

我在实际调优中发现,抢占策略对 P99 延迟影响很大。如果策略太激进,频繁换出换入,P99 会明显恶化。建议根据业务特点调参:延迟敏感的场景,宁可多占显存也不要频繁抢占;吞吐优先的场景,可以激进一些,用抢占换更高的并发。

4.4 PagedAttention 的实测调优经验

几个实操要点。block size 的选择:太小则 block 表庞大、管理开销高;太大则内部碎片增加。常用值是 16,也有用 8 或 32 的,建议实测对比。前缀共享的收益:如果业务里大量请求共享 system prompt,开启前缀共享能省不少显存。我测过一个场景,system prompt 占 500 token,共享后显存节省约 15%。和量化的配合:KV Cache 也可以量化,INT8 的 KV Cache 能再省一半显存,但要注意精度影响,长序列下累积误差可能放大。

还有一个容易忽略的点:PagedAttention 和连续批处理(continuous batching)是绝配。连续批处理让不同请求在不同时间加入和退出 batch,传统 KV Cache 方案下这种动态性很难处理,分页后每个请求独立管理 block,加入退出都很自然。两者结合,才能把 GPU 利用率真正拉满。

5. 算子融合与调度:那些不起眼但收益稳定的优化

5.1 算子融合为什么能省时间

深度学习模型推理时,每一层往往由多个算子组成,比如 Linear + Bias + Activation。每个算子单独执行时,都要把数据从显存读到寄存器,算完再写回显存。算子融合就是把多个算子合并成一个 kernel,中间结果留在寄存器或共享内存里,不落显存。这样省的是内存带宽和 kernel launch 开销。

以常见的 LayerNorm 为例,它包含均值、方差、归一化、缩放、平移多个步骤。不融合的话,每个步骤都是一次显存读写;融合后一次读写搞定。实测中,LayerNorm 融合能省 30% 到 50% 的该层耗时。再比如 Attention 里的 QK^T、softmax、PV 三个步骤,融合成一个 FlashAttention kernel,显存读写从 O(N²) 降到 O(N),长序列下收益巨大。

5.2 连续批处理:让 GPU 不再空转

连续批处理(Continuous Batching)解决的是传统静态 batch 的浪费问题。静态 batch 下,一个 batch 里所有请求必须等最长的那个生成完才能一起返回,短请求早就结束了,但 GPU 还得为它保留计算资源。连续批处理则是每个请求独立管理,生成完就退出,新请求随时加入,GPU 始终在处理有效工作。

这个优化的收益在高并发、请求长度差异大的场景下特别明显。我测过一个混合负载:一半请求输出 50 token,一半输出 500 token。静态 batch 下 GPU 利用率只有 45%,连续批处理后提到 85% 以上,吞吐翻倍。实现上,连续批处理需要配合 PagedAttention 管理 KV Cache,还需要调度器支持动态 batch 组装。

5.3 调度层面的取舍:优先级、超时与降级

调度策略是推理服务里最容易被忽视、但对用户体验影响最大的一环。几个关键决策:优先级队列,VIP 请求优先处理,普通请求排队;超时控制,超过一定时间的请求直接返回部分结果或错误,避免拖垮整体;降级策略,高峰期自动切换到小模型或降低 max_tokens。

我踩过的坑是:早期没做超时控制,一个超长请求把整个 batch 卡住,导致后面所有请求 P99 爆炸。后来加了 per-request 超时,超时后强制结束并返回已生成内容,整体稳定性好了很多。另一个坑是优先级反转,低优先级请求先到但一直占着资源,高优先级请求反而排队。解决办法是用抢占式调度,高优先级请求来了可以打断低优先级。

6. 组合拳怎么打:一个真实部署方案的取舍过程

6.1 场景约束与目标拆解

说一个我实际做过的项目。业务是智能客服,模型 13B,硬件是 2 张 24GB 卡。约束条件:单请求延迟 P95 不超过 1.5s,峰值 QPS 50,显存要留出余量应对突发。目标是在这个约束下把成本压到最低。

先算账:13B 模型 FP16 权重 26GB,单卡放不下,必须量化或分卡。分卡的话通信开销大,延迟难保证,所以优先量化。INT8 权重 13GB,单卡能放下,但 KV Cache 还要占空间,并发上不去。INT4 权重 6.5GB,留出 17GB 给 KV Cache 和其他开销,并发空间大很多。所以权重定 INT4。

6.2 方案组合与参数确定

最终方案是:权重 INT4 group-wise + KV Cache INT8 + PagedAttention + 连续批处理。推测解码试过,但客服场景输出较短(平均 80 token),草稿模型收益不明显,反而增加复杂度,所以没上。

参数方面:block size 取 16,max batch size 根据显存动态调整,初始设 32。KV Cache 量化用 per-token 动态量化,实测精度损失在可接受范围。连续批处理的调度窗口设为 10ms,平衡延迟和吞吐。

6.3 实测结果与调优迭代

上线后实测:P95 延迟 1.1s,峰值 QPS 跑到 65,单卡就能扛住,另一张卡做冗余。显存占用稳定在 20GB 左右,留有余量。对比 FP16 单卡方案(QPS 只有 15 左右),成本降到原来的四分之一。

迭代过程中发现两个问题:一是 KV Cache 量化在超长序列(超过 1500 token)下精度下降明显,后来对超长请求做了特殊处理,KV Cache 保持 FP16;二是连续批处理的调度窗口在低峰期造成不必要的延迟,后来改成动态窗口,低峰期缩小到 2ms。

7. 我在这几条路线里踩过的坑和总结的经验

量化这块,最大的教训是不要迷信论文指标。论文里的精度对比往往在特定数据集上,你的业务数据分布可能完全不同。一定要用自己的评测集验证,而且要覆盖边界情况。另外,量化后的模型要重新做一轮压力测试,因为低精度下的数值稳定性可能不同,极端输入下更容易出问题。

推测解码,我的建议是先小规模验证再全量上线。接受率这个指标很依赖具体业务,别人的数据参考价值有限。而且推测解码对框架版本敏感,升级框架时要重新验证。我遇到过一次框架升级后接受率从 0.75 掉到 0.5,排查发现是新版本的验证逻辑改了。

PagedAttention,block size 和抢占策略要一起调。这两个参数互相影响,单独调一个往往找不到最优。建议做一个二维的参数扫描,虽然费时间,但能找到真正的甜点区。另外,前缀共享的收益和业务强相关,如果请求之间没有共享前缀,这个功能开了也没用。

算子融合和调度,收益稳定但容易被忽视。很多团队把精力都放在量化和新算法上,忽略了这些基础优化。实际上,一个配置良好的连续批处理加算子融合,能带来 1.5 到 2 倍的吞吐提升,而且没有精度风险。我的建议是先把这些基础打牢,再考虑激进的量化或推测解码。

最后说一个心态问题:推理优化没有银弹,所有方案都是取舍。量化省显存但可能掉精度,推测解码降延迟但增加复杂度,PagedAttention 提吞吐但引入调度开销。做决策时要把业务约束放在第一位,而不是追求某个指标的最优。我见过太多团队为了追求极致的吞吐,把延迟做到不可接受,最后用户流失,得不偿失。

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

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

立即咨询