现在的 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 被拉到正常值的五倍。修掉这个问题之后性能直接达标量化改造根本不需要做。这个案例给我的触动很大性能优化真正有价值的地方不一定是做多高深的技术而是先精准找到真正卡住系统的那一个薄弱点。