☰
AI推理性能优化实战:从显存瓶颈到服务端调优
2026/10/22 8:42:49 网站建设 项目流程

现在的 AI 项目大多靠上了模型才安心,可一旦到了线上,推理慢、排队久、显存爆,各种性能问题立刻原形毕露。很多团队训练时千般顺利,部署后却在性能瓶颈这关反复折腾。其实推理性能的优化和训练完全不是一回事,训练关注的是“多久收敛”,推理关心的是“单个请求多快返回、单位时间内能扛住多少并发”。这篇文章就围绕这两个目标展开,从硬件资源边界、框架开销、模型压缩到服务端设计,逐层拆解推理瓶颈是怎么形成的,以及每一层有哪些可以直接落地的优化手段。适合做算法落地、推理服务开发、模型加速的工程师参考,也适合刚接触模型部署的人建立一套清晰的排查思路。

1. 推理瓶颈为什么和训练瓶颈完全不同

1.1 两种阶段的耗时模型:预填充与自回归解码

先看一个最容易被忽视的事实:模型训练是“大量样本并行、梯度回传、权重更新”的重计算过程,而推理面对的是“单个或一批请求穿过网络得到输出”的轻量计算。这里的“轻”并不是说推理快,而是说推理的计算行为和训练有本质差异,尤其在做自回归模型时,一次请求会被拆成两个耗时完全不对称的阶段。

以现在最常见的生成式模型为例,第一个阶段叫预填充,也就是把用户输入的 prompt 一次性交给模型,做并行计算,把中间状态计算好并缓存。这个阶段的计算量很大,但因为可以并行处理,吞吐表现其实不错。第二个阶段叫自回归解码,每生成一个 token,都需要把之前所有的历史状态拿来再做一次前向计算,而且这个计算是串行的,一个 token 一个 token 地蹦出来。解码阶段的计算强度低,但访存量极高,因为每一步都要读取完整的 KV 缓存和模型权重。

大多数推理慢的抱怨,其实都集中在这个解码阶段。有人会误以为“模型太大了,算不动”,但实际上很多场景下不是算力不够,而是显存带宽喂不饱计算单元。用一句话概括:训练过程基本属于“算力密集”,推理过程的解码阶段往往属于“访存密集”。把握住这个区别,后面所有的优化思路才能找到支点。

1.2 前后处理被低估的时间开销

继续沿着一条完整请求的链路看:原始文本进来,先要经过分词和特征化,把文本变成 token 序列;模型跑完之后,还要做采样、解码、后处理,才把结果返回给用户。很多人做性能优化时只盯模型计算时间,结果优化完模型速度,整体延迟却没降多少,原因就是前后处理占了一大块。

在我实际接触过的部署环境里,带复杂预处理规则的业务,前后处理能占到整体时延的 10% 到 20%。比如检索增强类的应用,要把问题做改写、做向量化、拼接检索结果,再拼成 prompt,这一套下来往往比模型本身还耗时。再比如采样阶段用了很复杂的解码策略,反复对概率分布做归一化和筛选,每一步虽然都不贵,但乘上生成几百个 token 的循环次数,积累起来也很可观。

所以排查性能问题时,我建议第一步先把一条请求的完整调用链计时拆开,分清前置处理、模型推理、后处理三段各自的时间。很多性能瓶颈根本没有发生在模型内部,却让模型优化背了锅。先用这种粗粒度拆分锁定问题范围,再深入卡点细节,排查效率会高很多。

2. 算力上限和内存带宽:先分清你的模型缺哪种“资源”

2.1 计算密度和访存密度的判断方法

在讨论具体优化手段之前,需要建立一个最基本的判断框架:你的模型在目标硬件上,究竟是受计算能力限制,还是受内存带宽限制?这个判断决定了后续优化技术路线的走向。

一个简单的方法是计算操作强度和理论性能上限对比。操作强度的定义是:模型运行过程中,平均每个字节的数据访问对应多少次浮点运算。如果这个数值高,说明模型属于计算密集型,瓶颈通常在于加速卡的峰值算力;如果这个数值低,说明模型属于访存密集型,瓶颈在显存带宽。

实际操作中不需要手工数每条算子的计算量和访存量,业界通常直接用实测或理论估算来定性。常见的卷积神经网络在 GPU 上往往更偏计算密集,而 Transformer 的解码阶段以及很多逐元素算子(激活函数、归一化、残差连接)密集的小算子网络,更偏向访存密集。

还有一个很直观的经验:如果你的模型在低端卡上跑得也不算慢很多,在高端卡上却没有得到预期的大幅加速,那大概率是访存瓶颈,因为高端卡和低端卡的带宽差距通常远小于算力差距。这个现象很常见,值得记住。

2.2 用 Roofline 模型判断瓶颈所在的实操方法

如果想把判断做得更精确,可以用 Roofline 模型做一次简单评估。Roofline 的核心是画出硬件平台的计算峰值和内存带宽这两条边界线,然后看模型的操作强度落在哪个区域。

具体来说,横轴是操作强度,纵轴是可达性能。当操作强度低于某个拐点时,模型的理论性能上限由“操作强度 × 内存带宽”决定,这时候即使换更强的计算卡,性能提升也非常有限。当操作强度高于拐点时,模型才真正受算力峰值约束。

我在实际项目里往往不会真的去画完整曲线,而是用两步替代:第一步,用性能分析工具跑一遍模型,看计算单元利用率是接近 100% 还是徘徊在 30% 以下;第二步,对比模型实际跑出的吞吐和理论带宽。如果带宽利用率已经很高而算力利用率很低,就可以确定是访存瓶颈。

访存瓶颈下的核心优化思路很直接:减少数据搬运,提高数据复用。典型的做法是算子融合和减少中间张量的读写,这也引出了后面框架层优化里面最有效的一类手段。

3. 框架层最容易吃掉的性能:图优化和算子融合

3.1 动态图的灵活性和性能代价

深度学习框架为了方便训练,普遍提供了动态图模式,也就是算子按代码顺序逐条执行,每条算子都对应一次内核启动。这种模式对研究和调试非常友好,但部署推理时却未必合适,因为每条算子的启动都有固定开销,包括调度、内存申请、内核分发等。一组本来可以合并的计算被拆成几十个小算子,每个算子都要经历一次完整的调度流程,性能自然高不了。

我之前在某个项目里把一个复杂的生成式模型直接导出到推理引擎时,发现仅模型主干的子图就包含了上千个小算子,其中有大量是逐元素操作,比如 LayerNorm 内部、残差连接、激活函数。这些小算子每个单独运行几十微秒,但累计起来就是几百毫秒的开销。对于交互式应用,这是不能接受的。

这里不是要否定动态图的便利性,而是要说明:推理阶段的性能敏感度远高于训练,动态图的灵活性让位于静态图的确定性是一个合理取舍。静态图的好处是可以提前做全局优化:哪些算子可以融合,哪些中间结果可以不落显存,哪些分支可以裁剪,都能在编译阶段提前确定。

3.2 算子融合和内存复用究竟能省多少

算子融合是计算图优化里最立竿见影的手段。它的思路是把多个相邻且可以合并的算子合并成一个复合算子,减少内核启动次数和中间数据写回显存的开销。最简单的例子是“卷积 + 批量归一化 + 激活函数”这三者融合成单个算子,原本每一步都要把中间结果写到显存再读出来,融合后只保留一个中间结果,在计算单元内部直接传递。

不要小看这一层优化。对于包含大量小算子的模型,算子融合后能把计算图中的节点数缩减到原来的三分之一甚至更少。内核启动次数大幅下降,整体延迟可能缩短 20% 到 40%,尤其是访存密集型的模型收益更明显。

除了算子融合,另一个关键优化是内存复用。训练时每个步骤都需要保留前向传播的中间结果用于反向传播,推理时不需要这些保留,所以可以重复利用显存缓冲区。好的推理引擎能够通过内存池和生命周期分析,把显存占用降下来,这不仅对显存小的设备很关键,也间接提高了缓存命中率。

所以当你在做推理部署时,第一件该做的事不是手动优化算子实现,而是把你训练好的模型交给一个成熟的推理引擎,让它做自动图优化和内存复用。如果框架已经做得很好,大部分收益是不需要手工介入就能拿到的。剩下只有引擎自动优化处理不了的特殊结构,才值得手写融合算子。

4. 模型压缩:量化、剪枝、蒸馏的取舍逻辑

4.1 量化不只是把 float 变成 int

模型压缩是推理优化中最核心也最容易被误解的领域。以量化为切入点,直觉理解就是把 32 位浮点数权重变成 8 位、4 位整数,从而把模型体积缩小四倍甚至八倍,推理变快。但实际做的时候会发现,量化不是简单的数值映射,它对推理加速的贡献也得分场景看。

在访存密集的解码阶段,将权重从 32 位浮点压缩到 8 位整数,意味着每一步要搬运的权重数据量直接减半,带宽压力大幅下降,生成速度能获得接近线性的提升。在计算密集的部分,小整数运算的吞吐一般也优于浮点运算,但收益相对没有访存端那么夸张。

不过我见过很多团队一上来就用 4 位量化,结果精度损失超出预期,业务指标直接崩了。原因往往是忽略了激活值的量化难度。权重分布相对稳定,容易找到合适的量化范围,而激活值在不同输入下的波动范围可能非常大,一个异常输入就会导致整体精度劣化。有效的做法是先做校准,用一小批有代表性的数据统计激活值的分布,再根据分布选择截断范围;必要时采用混合精度方案,对敏感层保留更高精度,对冗余层用低精度。

4.2 剪枝和蒸馏能不能一起用

剪枝的思路是删除模型中不重要的连接或通道,从而减少计算量和模型体积。蒸馏则是用一个大模型作为教师,教一个小模型学生达到接近大模型的性能。这两者常被看成是彼此独立的压缩路线,但实际项目里往往要组合使用,而且要讲究顺序。

我的经验是“先蒸馏、后剪枝、再量化”的顺序相对稳妥。蒸馏先让模型变小变紧凑,这相当于在结构层面压缩了冗余,剪枝在这个基础上做通道级的稀疏化,会比在原始大模型上直接剪枝更温和、更好控制。等模型结构稳定后再做量化,这样每一步的精度退化都能被单独评估和恢复。

但要注意,剪枝的收益并不是所有硬件上都能体现出来。非结构化剪枝会生成大量稀疏的、地址不连续的权重,很多设备并不能真正利用这种稀疏性来加速,甚至可能更慢。结构化剪枝把整行整列通道删掉,硬件友好度高,但灵活性差一些。所以做剪枝之前,一定要确认你的目标推理平台对稀疏计算的支持程度,否则就是在做无用功。

蒸馏的成本也不低:需要先用大模型产出一批高质量软标签,再用这些软标签训练小模型,整体训练成本是翻倍的。所以是否值得走蒸馏,取决于你的延迟目标有多激进。如果只追求 30% 的加速,量化加算子融合基本就能满足;如果追求整倍数级的加速,那蒸馏往往是绕不开的一步。

5. 服务端的批处理、并发和缓存设计

5.1 动态批处理如何同时提升吞吐和时延

模型本身的耗时优化到位之后,推理服务端的吞吐还能靠调度策略再拉一截,核心就是动态批处理。单独一个请求进去,模型的计算利用率通常很低,尤其访存密集型的解码阶段,算力大量闲置。把多个请求攒在一起,组成一个批次同时进模型计算,就能摊销权重读取和内核启动的成本。

但批处理在自回归解码中有一个天然矛盾:不同请求的序列长度不同,生成进度也不同。如果简单粗暴地把一批请求放到同一时刻解码,短的会长的等,整体延迟反而变大。一个成熟的做法是连续批处理,也就是把到访的请求按生成进度分成不同的批次窗口,某个请求的序列结束生成或达到长度上限就直接退出批次,新到的请求立刻补进去。这样每一时刻的批次都在动态变化,吞吐和时延得到兼顾。

还要提及在线应用与离线任务的混合调度。比如实时对话请求和批量异步任务共用一个推理服务时,如果按同一优先级排队,实时请求就会被离线任务拖慢。常见处理方式是把这两种流量拆到不同的资源池或者用不同的批处理策略,离线任务可以容忍更长时延,适合大批次;在线请求则批次不宜过大,以免等待时间失控。

5.2 KV Cache 与结果缓存的冷热分层

对于生成式模型,KV 缓存优化是推理服务端另一个不可忽略的点。自回归解码每一步都需要使用之前所有 token 的键值状态,如果不做缓存,每一步都要重新计算,那解码会慢到不可用。KV Cache 就是在预填充阶段把这些状态存下来,解码阶段增量更新。

但 KV Cache 的显存占用随序列长度和并发数线性增长,很容易挤压模型权重本身需要的显存空间。常用策略是结合量化压缩 KV Cache,这属于量化技术在推理阶段的延伸。也可以结合页面缓存式的显存管理手段,只保留最常用的 token 块,降低显存占用。不过这类优化通常和推理引擎的调度机制深度绑定,建议优先选用支持显存管理和缓存优化的成熟推理框架,而不是自己造轮子。

除此之外,业务层面的结果缓存也常常被忽略。同样的请求在短时间内重复出现,如果让模型重新算一遍,等于把算力和显存花在完全可复用的结果上。在实践中,请求相似度高的场景非常适合做语义级别缓存,用一个轻量模型计算请求向量,然后去检索历史缓存中相近的向量,直接把对应结果返回。这种方式能把高并发下的重复计算压力显著降下来,对于很多问答和对话类业务效果立竿见影。

6. 性能评估与排障方法:从指标到优化闭环

6.1 关注 p99 而不是平均值

聊完技术再回到工程落地。推理性能优化如果没有正确的度量指标,很容易做成“自我感动式优化”。最常见的错误是只盯着平均延迟,因为平均延迟对长尾请求不敏感,某个请求特别慢的时候,平均值看起来只是略微升高,而真实用户的体验已经被拖垮。

我习惯用的指标是 p50、p95、p99,分别代表一半请求、95% 请求和 99% 请求在多少毫秒内完成。其中 p99 对用户体验的感知最直接,它反映的是最差那一批请求的表现。举一个具体例子:平均延迟优化了 30%,但 p99 只降了 5%,说明优化没有解决那些最慢请求的根本问题,这时候要深入排查是不是存在显存碎片、排队等待、跨设备通信等偶发因素。

另一个容易混淆的是吞吐与时延的关系。很多人以为提高吞吐就会伤害时延,但在小批次和低并发下两者并不矛盾。可以用压测工具给服务不断增加并发,观察吞吐曲线和时延曲线,找到性能拐点。这个拐点就是服务的最大承载能力,也是扩容和限流决策的依据。上生产环境之前做一轮这样的压测,能避开不少线上事故。

6.2 一套可复用的推理性能排查流程

最后分享一套我在实际项目中反复使用的排查流程,适应大多数推理性能问题。

第一步,链路计时拆分。从请求进入到响应返回,把每个环节(预处理、排队等待、模型推理、后处理、网络传输)分别计时,先定位耗时占比最大的环节。第二步,对照资源指标。用监控工具看模型运行阶段的算力利用率、带宽利用率、显存占用。算力利用率低而带宽用满,优先找访存优化;算力用满则优先考虑计算量和模型压缩。第三步,分析图结构与算子级耗时。跑一遍性能剖析,看哪些算子的耗时明显偏离正常范围,是否需要做算子融合或特殊优化。第四步,验证设备与配置影响。同一模型在不同显卡上的表现差异,可以判断瓶颈类型;在非性能敏感的配置项上(比如线程数、批处理窗口大小)做几次对照实验,排除配置导致的劣化。

做完这些步骤之后,再把优化点按收益和成本排序,我一般会优先做成本最低的配置调整和批处理调度,其次做框架层面的图优化,最后才动模型压缩和算子改写。因为这个顺序符合投入产出比,也能在优化过程中不断验证前面环节的实际收益,避免一上来走太深的技术路线却回报有限。

在最近处理的一个模拟项目中,某团队原本准备用几十天做整套量化改造,结果我用这套流程跑下来,发现真正的问题只是某个前处理环节没有做并发安全处理,导致请求之间互相阻塞,p99 被拉到正常值的五倍。修掉这个问题之后,性能直接达标,量化改造根本不需要做。这个案例给我的触动很大,性能优化真正有价值的地方,不一定是做多高深的技术,而是先精准找到真正卡住系统的那一个薄弱点。

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

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

立即咨询