大模型推理性能优化:从算力访存到量化融合的五个排查角度 做模型推理部署这几年我听最多的一句话是模型跑起来没想象中那么快。显卡标称算力明明高得吓人可实际单次推理的耗时就是压不下去。有个项目让我印象很深——7B 参数的大模型在 A100 上做自回归生成单 token 的实测耗时换算下来只有理论算力的零头当时团队差点直接去申请新硬件。后来沿着性能优化的思路排查完才发现问题根本不在显卡而在我们对推理链路里的瓶颈判断错了方向。这类困惑其实很常见。模型推理的性能优化和训练调优完全是两套逻辑训练时关心吞吐推理时还要同时照顾延迟、显存、批处理策略、前后处理。很多人上来就是加个更好的 GPU把并发拉高但如果不清楚推理为什么慢、慢在哪个环节这些操作往往是把成本花在了刀背上。这篇就把我踩过的坑按五个角度重新梳理一遍算力、访存、模型、算子、系统。每个角度回答一个核心问题——推理是不是真的缺算力、数据搬运占了多少时间、模型本身能不能变得更轻、算子有多少浪费、调度和显存还能榨出多少。对刚接触推理部署的人这是一条从定性到定量的排查路径对有经验的人希望里面有些数据和思路还能再帮到你。1. 角度一算力视角——显卡算力很高推理却跑不动1.1 理论算力是一张没法兑现的支票先看一组数据。以 A100 为例FP16 稠密算力大约 312 TFLOPSRTX 4090 也能到 165 TFLOPS 左右。放到 7B 模型上算一笔账生成一个 token前馈需要的乘加运算量大约在 14 GFLOPs 级别按 2 倍参数量估算。如果真能贴着算力峰值跑4090 上单个 token 也就 0.1 毫秒左右换算成生成速度差不多每秒上万 token。可现实中 7B 模型在 4090 上单机部署decode 速度普遍只有每秒三四十个 token差了足足两三个数量级。这说明什么理论上限当然存在但能不能摸到取决于你的计算模式有没有让 GPU 真正忙起来。我见到的常见误解就是把算力大等同于推理快。算力只是峰值利用率才是实际值。推理场景里利用率低到个位数百分比的例子比比皆是这时候问题就不该再归到算力头上。换卡、加机器这类粗暴方案恰恰是在这个问题上投入产出比最低的一类。1.2 自回归解码为什么喂不饱 GPU大模型推理里两个阶段的行为完全不同。prefill 阶段一次处理成百上千个 token算的是大矩阵乘法 GEMMTensor Core 能发挥威力decode 阶段每步只有一个 token矩阵退化成了矩阵向量乘 GEMV计算密度断崖式下降。这里面有两个结构性原因。第一自回归生成天生有依赖链——第 t 个 token 必须等前 t-1 个生成完才能开始计算上无法像训练那样大尺度并行。第二GEMV 这类操作本身就不适合 Tensor Core 的设计目标Tensor Core 喜欢足够大的矩阵去做分块乘加向量乘法的分块利用率低得可怜。所以即使你把 batch 设成 1 来保证低延迟GPU 的大半算力就是闲着算力高这张支票在 decode 阶段根本兑现不了。还有一个常被忽略的小头kernel launch 开销。一个 7B 模型一次 decode 要经过几十上百个算子每个算子在 GPU 上启动都有微秒级固定成本叠加起来在延迟敏感场景里同样不能忽视。算子数量越碎这个占比越明显这也是后面算子融合和 CUDA Graph 能起作用的原因。1.3 先用 profiling 确认算力是不是瓶颈既然算力不一定是真瓶颈动手优化前最重要的动作是先 profile。我一般用两层思路先拿算子级耗时数据再用总体指标交叉验证。import torch from torch.profiler import profile, ProfilerActivity with profile( activities[ProfilerActivity.CUDA, ProfilerActivity.CPU], record_shapesTrue ) as prof: for _ in range(10): out model(input_ids) # 模拟一次推理 print(prof.key_averages().table(sort_bycuda_time_total, row_limit20))如果 GPU utilization 看着不高但算子时间分布很散那大概率是访存或 launch 瓶颈如果排到前面的都是大 GEMM 而且耗时接近理论值才需要往算力耗尽的方向想。性能优化最忌讳凭空猜测几分钟的 profile 往往比一小时讨论更能定位问题。这一步确认之后再往下面四个角度走就不会白费力气。2. 角度二访存视角——真正拖着后腿的是搬数据2.1 厨师和传菜员的比喻把 GPU 比作一间厨房算力是厨师的刀工内存带宽则是传菜员的速度。菜谱再复杂如果传菜员一次只能端一盘菜出菜速度就被卡在传菜环节。推理也一样很多模型卡的不是算不动而是数据搬不动。判断一个算子到底是算力瓶颈还是带宽瓶颈有个简单指标运算强度 arithmetic intensity等于计算量除以数据搬运量。运算强度低于某个临界点算力峰值除以带宽就是访存瓶颈高于临界点才是计算瓶颈。GPU 的这个临界点通常很高。拿 A100 举例312 TFLOPS 除以约 2 TB/s 的 HBM 带宽临界点在 156 FLOPs/Bytes 附近。也就是说每从显存里读 1 个字节至少要配套 156 次浮点运算算力才不会被饿着。现实里绝大多数推理算子根本达不到这个比例。2.2 用 7B 模型算一笔清清楚楚的账访存瓶颈在 decode 阶段有多严重算给自己看就明白了。7B 模型用 FP16 存储光权重就是 14GB。生成每个 token 时即便只算一次前向也要把这 14GB 至少读一遍实际上还有中间激活和 KV cache 的读写。A100 的带宽大约 2TB/s14GB 除以 2TB/s 等于 7 毫秒——这是理论上的最低时间。也就是说decode 速度上限约为每秒 140 个 token居然和很多项目实测的百 token 级速度吻合得很好。这个吻合本身就是证据decode 阶段你已经贴着了访存天花板再怎么堆算力都没用。把同样的账套在量化模型上更直观。换成 INT8 权重后字节数减半理论上限直接翻倍。很多人不理解为什么量化后速度几乎线性提升原因就在这里——它减的不是算力压力而是带宽压力。移动端性能优化也遵循同一逻辑端侧算力本来就弱内存带宽和拷贝开销更是兵家必争之地减少数据搬运永远是性价比最高的优化手段之一。2.3 同一模型的两个阶段优化方向完全相反用 roofline 模型去看会更清楚。prefill 阶段一次算大批量 token运算强度能冲到几百 FLOPs/Bytes 以上跨过了临界点属于典型计算瓶颈优化方向是提高矩阵乘效率、扩大 batch、用好 Tensor Coredecode 阶段运算强度经常连 10 都不到是标准带宽瓶颈优化方向是减少字节——量化、权重缓存、算子融合、避免中间张量来回读写。这解释了为什么没有一个万能优化能让两个阶段同时达到最佳。实际做工程时要把 prefill 和 decode 当成两个独立问题分别调优再在引擎层面做好两者的资源分配。这点和写 Oracle SQL 优化很像——同样的查询数据量不同、索引选择不同执行计划完全两样推理同理阶段不同、批次不同瓶颈就不在同一个地方。2.4 省带宽的三板斧围绕访存做优化我总结三件最有效的事权重减肥。量化到 INT8/INT4直接从源头减少每次搬运的字节数。减少中间张量。能让数据在寄存器或 shared memory 里多待一阵就别写回显存这正是算子融合的核心价值后面单独展开。提高复用率。同一个权重尽量被多个计算步骤同时用到避免反复从 HBM 读同一份数据。顺带说一句Julia 这类语言做数值计算时反复强调的内存分配是性能杀手和推理里的访存优化本质上是同一件事——分配、拷贝、搬运的成本往往比计算本身贵得多。不同领域最终得出了同一个结论只是名字不一样。3. 角度三模型视角——推理变快从模型本身下手3.1 量化性价比最高的倍速器模型压缩的第一梯队我永远推荐量化。核心思想很简单推理不需要那么高的数值精度。从 FP32 换成 FP16权重体积直接减半带宽压力减半再从 FP16 压到 INT8又减半。对 memory-bound 的 decode 阶段来说量化多少倍速度就能接近翻多少倍。量化有两条路线。训练后量化 PTQ 最省事拿一份代表性数据过一遍模型统计激活和权重的分布确定缩放因子几行代码就能完成量化感知训练 QAT 在训练时模拟量化误差效果更好但需要重新训练成本高。LLM 场景里还有 GPTQ、AWQ 这类专门针对大模型设计的量化算法核心都是在处理激活值里异常大数导致精度崩坏的问题——输入里有些维度数值明显偏大简单均匀量化会把多数小数值挤成一团浆糊SmoothQuant 的思路就是把这部分数值压力转嫁到权重上再量化。有个重要提醒不要拿通用指标一概而论。做量化验证要用目标任务的真实数据而不是只看几个公开基准数字。有些模型在通用 benchmark 上掉点不多但在你业务的长尾样本上会明显变差。量化上线前一定要做业务侧回归这个环节不能省。3.2 剪枝要剪就剪结构化的剪枝听起来很美好——把不重要的连接删掉模型变小变快。但实际落地时坑很多。非结构化剪枝把权重矩阵剪得千疮百孔变成不规则稀疏矩阵通用 GPU 跑稀疏矩阵并不比稠密快专门写稀疏 kernel 又成本高昂通常只在特殊硬件上才划算。真正对推理有直接收益的是结构化剪枝按 channel、按 head、按层去剪。剪完整个维度都消失矩阵还是规则形状FLOPs 和访存量一并下降。代价是需要微调甚至重新训练来恢复精度。我的经验是剪枝适合那些明显有冗余的模型比如超大参数量但任务简单的中小型场景结构本身已经很精炼的模型剪枝空间往往很有限别在这上面花太多时间。3.3 蒸馏把变小这一步放到训练期蒸馏是另一种思路——不剪现有模型而是用一个大的 teacher 教出一个小 student。小模型从头训练就朝着 teacher 的分布对齐最终参数量更小、精度却比直接训练同样大小的模型要好。LLM 领域里 12B 蒸馏到 7B甚至 70B 蒸馏到 13B 都是常见操作。蒸馏一旦完成后续的部署成本全链路下降属于最值得的早期投资。蒸馏和量化通常还能叠加先蒸馏缩小模型再量化缩减字节两项收益大体是乘性关系。把这两步都做完同一份吞吐预算里能塞进的服务量可能差好几倍。如果你的项目还在选型或训练阶段我建议优先把蒸馏纳入规划而不是等部署时再亡羊补牢。3.4 结构重参数化训练推理解耦有一类优化在模型结构上做得更巧妙——训练时用复杂结构保证精度推理时把结构等价变换成简单算子。最典型的是 RepVGG训练时用多分支3×3、1×1、恒等提升表示能力推理时把分支融合成单个 3×3 卷积。数学上完全等价推理结构却简单一个量级速度和显存都受益。我把这四种手段的关键差异整理成一张表手段对推理速度的影响精度风险主要成本我推荐的时机量化高尤其 decode 阶段中低需业务回归低PTQ 一天内可完成第一优先几乎所有部署场景结构化剪枝中高视稀疏度中需微调或重训模型冗余明显的场景蒸馏高模型变小低但需训练资源重新训练新项目从零开始训练时结构重参数化中低训练结构改造卷积类模型训练流程可控时实际项目里我很少孤立使用某一种通常是量化打底、蒸馏解决精度、剪枝和重参数化看情况补充。4. 角度四算子视角——把细碎的算子拧成一股绳4.1 算子越碎浪费越多一个推理模型在框架里会被拆成几十上百个算子每个算子执行完中间结果要写回显存下一个算子再从显存读出来。这就像把一道菜拆成切、炒、装盘三个步骤每步都换一个厨房、重新生火备菜时间全耗在交接上。浪费主要有两块。一是数据搬运中间张量在 HBM 里写一次读一次decode 阶段带宽本来就紧张绕不开的搬运全是纯开销。二是 kernel launch每个算子在 GPU 上启动都要开销CPU 侧连续启动几十个 kernel累积的延迟在低延迟场景尤其刺眼。算子融合的思路就是把这些细碎算子合并成一个算子让它内部的中间数据尽量留在寄存器或 shared memory 里不落回显存。4.2 ConvBNReLU 融合的数学账最经典的融合是 ConvBNReLU。BN 在推理时是一个纯线性变换输出等于输入乘一个系数再加一个偏移。卷积也是线性运算所以可以先把 BN 的系数合并进卷积极或者偏置里再和 ReLU 拼成一个算子数学上完全等价。假设卷积输出为 yBN 参数为 scale 和 bias融合后的新权重就是原权重乘以 scale新偏置等于 (原偏置 - 均值) × scale bias。推理引擎比如 TensorRT 或 ONNX Runtime会自动做这类变换你不需要手写。但理解了原理你就能明白为什么有的模型在 ONNX 导出时把 BN 留在图里会导致引擎优化失效——导出前的算子规范化很重要这也算是我踩过的一个隐蔽坑。4.3 FlashAttention 如何靠内核重写翻盘如果说算子融合是把已有算子合并FlashAttention 就是更激进的一种重写算子本身改变它的数据流。标准 attention 要计算 S QK^T然后 softmax再乘 V。问题在于中间那个 N×N 的注意力矩阵会被完整写进显存序列越长这个矩阵越大读写开销呈平方增长。FlashAttention 的做法是分块计算每个小块的 softmax 用online softmax技巧不依赖完整矩阵最终把对 HBM 的访问量从 O(N²) 降到 O(N)。它没有减少 FLOPs甚至略多但因为把访存从显存挪到了片上实测速度却能快好几倍。这对做服务的人来说是个心态上的启发不要总以为优化就是砍计算量很多时候只是把数据搬动的路径改得更聪明。后来的 FlashDecoding、各种 MQA/GQA 变体也都是围绕让带宽和数据流程更合理在做文章。4.4 数据布局NHWC 还是 NCHW不是小事同样一份数据在内存里的排列方式不同读写效率差很多。NCHW 是深度学习框架的默认布局图像通道是连续的对早期按通道处理的方式友好但 NHWC 布局在卷积里能更好地利用 Tensor Core 和 SIMD因为相邻像素的数据在内存里也是连续的存取和向量化都更高效。很多推理引擎在内部会自动把图优化到 NHWC 再跑你在框架层看到的数据布局未必是真正的执行布局。端侧 NPU 和移动端把布局问题放得更大不同厂商的异构单元对布局要求各不相同布局转换本身也是开销。手游性能优化里经常提到的减少纹理拷贝统一内存格式跟这里其实是同一个道理。遇到性能瓶颈时值得检查一下模型在目标设备上到底按什么布局在跑这往往是隐藏成本。5. 角度五系统视角——引擎、调度与显存的艺术5.1 推理引擎在默默替你做哪些事把同一个 ONNX 模型直接丢进框架跑和丢进 TensorRT 跑耗时经常差一倍甚至更多。差别在哪TensorRT 这类引擎在加载时会做几件事图层融合把 ConvBNReLU 这类模式合并掉、kernel 自动选择同一个算子有多种 GPU kernel实测哪个快用哪个、显存规划提前计算生命周期复用显存块、动态 shape 适配等。ONNX Runtime 也有自己的图优化 pass比如常量折叠、无效算子消除。选引擎不要盲目迷信最强要看你的硬件、框架生态和部署形态。自建服务用 TensorRT 或 vLLM 居多轻量级服务端和端侧可能更适合 ONNX Runtime 或 TNN、MNN 一类的移动端引擎。引擎层的收益不需要你改一行模型代码属于躺着赚的部分前提是模型导出得当尤其是算子没有畸形写法。5.2 从 static batching 到 continuous batching很多人在推理服务里做性能优化第一个想到的词是 batch。对 GPU 而言batch 越大矩阵越肥算力利用率越高prefill 阶段尤其如此。但动态 batching 有个直接代价把一个请求拼进当前批次就要等这个批次整体跑完单个请求的延迟被拉长。vLLM 这类方案里引入的 continuous batching 是更聪明的做法。它不再按一个批次必须同时开始同时结束来调度而是以请求为粒度动态调度某个请求的生成长度到了就把它从当前批里摘出去再插入新请求。GPU 一直在跑有效计算空闲的算术资源和显存都被填得更满。一句话总结传统批次是整桌一起开饭continuous batching 是快吃完的可以先走、新客人随时上桌餐厅翻台率自然上去。5.3 KV cache 和 PagedAttention大模型推理的显存大头除了权重就是 KV cache。prefill 阶段算出的 key、value 要缓存在显存里给后续 decode 复用每个请求的 KV cache 大小随序列长度动态变化。传统做法是按最大可能长度一次性预留连续块结果一是碎片化严重二是预留多用少显存利用率很低。PagedAttention 把 KV cache 切成固定大小的页按需分配用类似操作系统虚拟内存的机制管理碎片问题大大缓解同样显存下能塞进更多并发请求。这本质上是把系统设计里的经典方案搬进了推理引擎和数据库里按页管理缓冲池的思路同源。理解了这点你在给服务配置显存预算时会更有底而不是拍脑袋定 max_seq_len。5.4 前后处理、流水线与 CPU-GPU 同步推理耗时不只等于模型计算时间。图像预处理、文本 tokenize、结果后处理这些 CPU 工作如果和 GPU 计算串行执行每一轮都会空转。正确的姿势是流水线 overlapCPU 处理第 N1 个请求的预处理时GPU 正在跑第 N 个请求的推理。CUDA Graph 也能把一串 kernel 启动打包成一次提交减少 CPU 和 GPU 之间的同步次数这对小模型收益尤其明显一次同步的开销占比太高。手游和移动端性能优化同样讲 overlap——渲染管线里 CPU 提交和 GPU 渲染错开、UI 逻辑和资源加载并发背后的原理完全一致。性能优化做到最后拼的都是系统级的资源编排能力不是单点技巧。6. 收尾我自己沉淀下来的优化顺序与教训6.1 一套可以照抄的排查顺序接到一个推理性能需求我基本按这个顺序走你可以直接当 check list 用先 profile 定性。用 torch.profiler 或 nsys 拿到数据判断核心瓶颈是算力、带宽还是 launch 开销。/这一步最容易被跳过但恰恰决定了后面所有动作的方向。做低垂果实。量化、开引擎自带优化、确认数据布局没问题这些改动小、风险低、收益立竿见影。再动模型结构。蒸馏、剪枝这类手段周期长适合方向确认后再投入避免白做。最后才动调度和显存。连续批处理、KV cache 管理、CUDA Graph 这些是在前面的基础上放大收益前提是前面的瓶颈已经处理过。每一轮只改一个变量用真实压测数据说话。我见过太多人一次性上三个优化结果出问题时根本不知道是谁引入的。6.2 一个真实项目从延迟超标到不换硬件达标最后讲个实际案例。有个 7B 模型项目上线前延迟就是不达标。最初的讨论方向是换卡或者加节点预算不小。我没急着定先跑了个 profile发现 decode 阶段 GPU 利用率不到百分之二十但带宽指标已经接近打满——典型的访存瓶颈。于是直接做了两件事INT8 量化、配合引擎层的算子级优化延迟掉了差不多一半。后来又用 continuous batching 把吞吐提了一截整个过程中原来的硬件什么都没换。这个项目的教训有两点。第一瓶颈判断错了再贵的方案都是白搭第二量化和算子融合这类土办法在多数场景里依然是最划算的选择。模型推理的性能优化拼的从来不是堆资源而是找准瓶颈之后用合适的手段逐一击破。希望这五个角度能帮你少走点我当年绕过的弯路。