
如果你也开始碰大模型性能优化你会发现这个方向看起来很大实际上拆开之后无非几件事把显存省下来、把算力用满、把通信降下来、把推理延迟压下去。做训练的要盯吞吐做推理的要盯延迟和并发做部署的要盯成本和稳定性。我前前后后优化过几个 7B 到 70B 量级的项目踩过的坑比总结出来的经验多得多。这篇就把我理解的大模型性能优化方向完整梳理一遍从怎么找瓶颈到具体用什么技术手段再到怎么量化收益尽量给你一条能直接照着走的路线。1. 先看懂性能瓶颈在哪1.1 训练期和推理期根本不是一回事很多人上手就问我该用哪种并行策略、要不要量化我的第一个建议都是先分清当前阶段。训练期的大模型性能优化核心目标是让 GPU 始终在算把所有资源转成有效训练吞吐推理期则更复杂既要压低首字延迟和每个 token 的生成速度又要尽量提高并发还要控制单次请求的显存占用。两者对同一项技术的接受度完全不一样比如训练时你可以接受梯度检查点带来的额外计算推理时却嫌它拖慢响应训练时可以大规模重计算来换显存推理时则更愿意为 KV Cache 花显存来保速度。一个很典型的误解是把训练时的 MFU模型算力利用率优化好推理就一定快。实际上训练优化解决的是大批量、高并行、持续运转的问题推理优化解决的是低延迟、高并发、动态到达的问题。你甚至可能遇到这样的怪事同样的模型训练吞吐提升 20%推理首字延迟却没有任何变化。原因就是推理链路里多了显存配额、批调度、显存碎片、算子启动开销这些训练期不太在意的因素。1.2 三种瓶颈的判定方式我习惯把瓶颈分成三类计算密集型、访存密集型、通信密集型。区分它们的意义在于不同瓶颈对应的优化手段完全不同。计算密集GPU 的计算单元已经接近跑满SM流式多处理器利用率高但显存带宽没吃满。这时值得做算子融合、减小冗余计算、提升张量核心利用率。访存密集GPU 利用率不高但显存吞吐接近上限。常见于逐元素算子、归一化、激活函数、小矩阵乘法。这类瓶颈靠“少读写几次显存”来治比如把多个逐元素算子融合进一个 kernel。通信密集多卡训练时通信时间占了很大比例。观察方式很简单看 GPU 利用率曲线是不是像锯齿一样计算一段、通信一段、再计算一段。判断工具我常用三种。第一是 NVIDIA 的 Nsight Systems / Nsight Compute用于看 kernel 耗时、显存带宽利用率和 GPU 空闲区间第二是 PyTorch Profiler适合直接拿到 PyTorch 层面的算子耗时和显存分配情况第三是nvidia-smi配合 DCGM 指标快速看 GPU 利用率和显存/带宽状态。实际项目里先跑一个几十步的小任务把 profiling 数据导出再对着瓶颈分类逐项筛查比凭感觉改代码可靠得多。2. 显存优化的几个主攻方向2.1 显存消耗到底去哪了显存优化的前提是算清显存消耗。训练一个大模型显存主要花在五个地方模型权重、优化器状态、梯度、前向激活值、临时缓冲区。很多人只盯着权重其实权重往往只是零头。举个例子7B 参数的模型如果用 FP16 存权重大概 14GB。混合精度训练时Adam 优化器要保存 FP32 的 master weight、一阶动量、二阶动量每个参数各占 4 字节加起来 12 字节模型权重和梯度各 2 字节共 4 字节。算下来每个参数差不多 16 字节7B 模型光优化器和权重就要 112GB 左右。单卡 80GB 装都装不下更别说还要放激活值。所以大模型训练几乎不可能离开显存优化手段。理解了钱花在哪你就明白为什么业界主流方案都围着“减少冗余状态”展开。2.2 ZeRO、Offload 与激活重计算的取舍显存优化的主流手段是微软提出的 ZeRO 系列。它不减少总显存需求而是把本来每个进程都存一份的数据分散到多卡、必要时再聚合从而让单卡压力大幅下降。ZeRO-1切分优化器状态。因为优化器状态占显存最大先拿它开刀。通信量小速度损失低。ZeRO-2在 ZeRO-1 基础上切分梯度。梯度同步前先各自 reduce通信量有所增加。ZeRO-3继续切分模型权重。每层计算前要 all-gather 权重计算完丢弃。省得最多但通信开销也最大。实际用的时候ZeRO-2 是很多场景的甜点ZeRO-3 适合模型大到单卡实在放不下的情况。如果显存依然不够就把一部分状态 offload 到 CPU 内存或 NVMe。代价是 CPU-GPU 之间的 PCIe 传输会成为新瓶颈所以它应该是“最后的显存救急方案”而不是默认选项。激活重计算也叫梯度检查点是另一个思路。前向传播时不保存所有层的激活值只保存少量关键节点反向传播需要时重新算一遍。它的本质是用时间换显存通常可以把激活显存从 O(层数 × batch × 序列长度 × 隐藏维度) 降到只跟“检查点数量”相关。经验法则是每隔若干层设一个检查点重算代价控制在 20%-30% 以内收益最为明显。2.3 量化技术怎么选位宽量化既影响训练也影响推理但要区分清楚你在哪个阶段用。训练阶段的量化常见形式是 FP16/BF16 混合精度以及 FP8 混合精度。FP8 是当前训练优化的重点方向之一把前向和反向的大部分计算降到 8 位同时保留主权重和高精度累加。好处是显存占用下降、张量核心计算加快坏处是对数值范围敏感需要做缩放因子管理和特殊处理否则容易出现训练不稳定。推理阶段的量化更激进常见的是 INT8、INT4也有 FP8。7B 模型 FP16 权重大约 14GBINT4 量化后可以压到 4GB 以下这让消费级显卡跑 7B 模型成为可能。主流方法有 GPTQ、AWQ 这类训练后量化也有 SmoothQuant 这类针对激活值波动的方案。注意只看“模型显存变小了”还不够一定要在目标任务集上对比量化前后的效果。有些 INT4 量化在通用问答上损失很小但在代码生成、数学推理这类对数值精度敏感的任务上会明显变傻。3. 计算效率提升的实操手段3.1 算子融合与计算图优化显存解决之后下一个方向是怎么让计算跑得更快。算子融合是最常见也最见效的手段之一。GPU 上执行一个 kernel 不仅有计算时间还有启动和调度开销。如果一个模型里搞出几百上千个细碎 kernel大量时间都花在“叫车”而不是“开车”上。算子融合的思路就是把多个连续操作合并成一个 kernel减少中间结果的显存读写和 kernel 启动次数。最典型的例子是把bias_add、激活函数、dropout这种逐元素操作融合成一个 kernel一次遍历就把所有事干完。现在很多框架已经替你做了这层优化。PyTorch 里最直接的是torch.compile它会用 TorchInductor 生成融合后的 kernel对 Transformer 类结构torch.compile经常能带来 10%-30% 的推理速度提升。但注意它不是银弹。动态 shape、大量控制流条件下编译图可能反复回退反而更慢。如果追求极致性能还是得自己写或调用手写融合算子比如 FlashAttention。3.2 高效注意力与稀疏化Transformer 的计算热点集中在注意力层和前馈网络。注意力层时间复杂度随序列长度平方增长是长上下文场景的主要瓶颈。 FlashAttention 系列的思路非常聪明它不显式构造完整的 N×N 注意力矩阵而是分块计算、原地 softmax 并增量更新输出。显存占用从 O(N²) 降到 O(N)速度也明显提升。现在 FlashAttention 已经是高性能 Transformer 的标配框架里通常一行调用就能打开。再往后还有多头注意力的变体优化。GQA分组查询注意力和 MQA多查询注意力通过让多个查询头共享 KV 头显著减少 KV Cache 和注意力计算量这也是当前众多新模型架构的默认选择。如果你要优化的是已有模型需要结合重训练或微调来改架构不能只改推理代码。稀疏化是另一个方向但落地要谨慎。剪枝能减少参数量和 FLOPs但硬件对稀疏计算的支持并不总是好的。局部稀疏可能还没有稠密矩阵在张量核心上跑得快。MoE混合专家模型是另一种“结构稀疏”每层只激活少数专家用同样的算力跑更大的总参数量。优化 MoE 的重点在于负载均衡和通信调度因为专家分布在多卡上all-to-all 通信经常成为瓶颈。3.3 并行策略的常用搭配当单卡放不下模型或单卡算力不够时就要上并行策略。一个合格的方向梳理一定会提到数据并行、张量并行、流水线并行三件套以及它们如何组合。数据并行每张卡持有完整模型副本吃不同的 batch。ZeRO 可以看作增强版数据并行省显存但带来通信。张量并行把一层的矩阵按行或列切到多卡适合单卡放不下单层权重的场景。通信频率高一般只在节点内用 NVLink 连接时效果好。流水线并行按层切分不同卡负责不同层。通信量小但可能出现流水线气泡GPU 等待前一段计算完成而空闲。可以靠微批次调度来缓解。序列并行把序列维度切分配合张量并行减少 LayerNorm 和 Dropout 这类区域冗余存储。专门应对长序列。实际项目里最常看到的是“3D 并行”的组合先用张量并行处理单层超大矩阵再用流水线并行把层分到多张卡最后用数据并行扩展数据规模。用这种方式百亿到万亿参数模型才有落地的可能。但并行策略不是越多越好张量并行切得太细通信占比可能反超计算收益流水线阶段太多气泡率会上升。一般建议通过理论估算或试跑吞吐对比找到平衡点。4. 推理阶段性能优化的关键路径4.1 延迟、吞吐与首 Token 延迟推理优化和训练优化的指标体系不一样先把话说清楚。我主要看这几个TTFTTime To First Token用户发出请求到收到第一个 token 的耗时。它受 prefill预填充阶段影响最大代表“准备感”。TPOTTime Per Output Token或 ITLInter-Token Latency生成每个 token 的间隔。它受 decode 阶段影响最大直接影响“流畅感”。吞吐单位时间处理完成的请求数或生成的 token 数。并发数同一时刻能服务多少请求。显存越大、批调度越好能扛的并发越高。常见误区是只盯着单请求延迟忽略了吞吐。你要想线上服务追求的是在延迟达标的前提下最大化吞吐否则单价压不下来。优化方案也经常要在这两个方向之间做取舍增大 batch 通常吞吐上去了单请求延迟可能稍微变差激进量化能把吞吐拉高但响应质量可能受影响。4.2 KV Cache 与 PagedAttention推理优化绕不开一张表KV Cache。自回归生成时模型每生成一个新 token都要继续使用前面所有 token 的 Key 和 Value。框架会把历史 KV 缓存下来避免重复计算。KV Cache 的计算公式是2 × 层数 × 注意力头数 × 每头维度 × 序列长度 × batch 大小 × 字节数。之前我优化过一个 7B 模型按 32 层、32 头、128 维每头、上下文 2048、batch 8 来算2 * 32 * 32 * 128 * 2048 * 8光 KV Cache 就到 4GB 这个量级。如果开更长上下文或更大并发这个数字会涨得非常快。这也是很多推理框架做长上下文时先爆显存的原因。显存问题之外KV Cache 还会造成显存碎片和浪费。传统做法是为请求预分配最大支持长度的缓存但请求实际长度差别很大。这里就轮到 PagedAttention 上场它借鉴操作系统分页把 KV Cache 分成固定大小的块按需分配减少内存浪费和碎片这也是 vLLM 能提升推理吞吐的重要底层机制。如果你想深入推理优化建议把 PagedAttention、Continuous Batching 这两个机制的源码逻辑读一遍比看十篇博客都管用。4.3 推理服务化与动态批处理模型本身优化完服务层还有不少空间。最直接的是动态批处理Continuous Batching。传统批处理要等一个批次全部完成才释放资源这就导致有的请求已经生成完了还要占着资源等慢请求。动态批处理让每个请求独立推进生成进度只要一个请求生成完就把新请求加到这批里。这种方式能让 GPU 利用率明显提升vLLM、TensorRT-LLM 等框架默认就是这个思路。另外还要会用 CUDA Graph。小算子多的模型每次推理都要反复启动几百个 kernel启动开销很可观。CUDA Graph 可以一次捕获整个计算图之后重复回放大幅降低 kernel 启动开销。实测一些模型在 decode 阶段能快 20%-40%。代价是显存占用增加、动态 shape 支持变差需要配合固定 shape 或 padding 策略来用。前沿方向里投机采样Speculative Decoding是保持模型质量的同时加速 decode 的典型做法。它会先用一个小模型或草稿模型快速生成一批候选 token再由大模型一次验证。因为验证是并行的实际延迟比逐个生成低不少。难点在于草稿模型的接受率选不好反而会拖慢。5. 评价指标和优化节奏5.1 应该盯哪些指标优化方向再多没有量化就是空转。我建议把指标分成功耗型指标和效果型指标两类。训练场景MFU / HFU模型算力利用率。MFU 要高 TP 次方的理论峰值比HFU 用实际达到的算力算。一般 MFU 在 40%-60% 就算不错。每 Tokens/秒 或 每 Tokens/GPU/天直接反映训练速度。损失收敛曲线优化后不能只看速度还要确认收敛行为一致。推理场景平均 TTFT、TPOT、首字 P99。吞吐tokens/s、req/s。单 token 成本或单请求成本。显存占用峰值与 KV Cache 命中率 / 复用率。还有一条容易被忽略优化前后的模型效果一致性。很多优化会引入数值误差所以要在固定评测集上做对比比如通用问答、数学推理、代码生成、指令遵循。结果差距超过预设阈值就需要回退或调参。5.2 一次完整优化实验的记录方式我做过一张优化记录表每次实验都按固定字段填格式大概是基准版本、优化项、显存峰值、训练/推理速度、模型质量指标、问题与备注。这样做的价值是能快速定位“是哪一项改动带来的提升或回退”。具体节奏我建议这样做先跑一个稳定基线记录显存、速度、质量指标。用 profiling 工具定位主要瓶颈优先解决最突出的一项。一次只改一个变量比如只开梯度检查点、只换并行策略、只加 kernel 融合。每次改动后重复跑同一组基准测试记录偏差。所有优化项验证完成后再考虑组合调优因为组合效果往往不等于单项相加。6. 实操中的常见坑6.1 指标好看但模型效果变差最容易踩的坑是量化或低精度训练之后速度提升了、显存降了但模型在某些任务上明显变笨。我遇到过一次 INT4 量化后整体评测只掉了 1%可代码生成的通过率掉了 8%。原因是代码生成对 token 选择和数值敏感微小的精度损失会被放大。后来我调整了量化策略对敏感层保留更高精度同时加入了校准集和任务级评测才算把问题控制住。提醒一下别信“某个量化方法跑量化模型很稳”的笼统结论一定要拿你自己任务集跑评测最好把 P99 的退化情况也记录下来。6.2 显存省了速度却更慢有次为了把训练塞进单机 8 卡我把 ZeRO-3 和激活重计算全开。显存确实降下来了但训练速度直接腰斩。原因很容易理解ZeRO-3 每层权重都要 all-gather重计算又额外增加 30% 的前向计算量两者叠加通信和计算都恶化。针对这类问题我的建议是显存优化以“刚好满足当前 batch size 和目标吞吐”为底线不要一味追求最低显存。先调 batch size 和激活重计算间隔看能不能用更小的代价达到目标ZeRO-3 和 offload 只在必要的时候开。6.3 复现不一致性能优化有时候还会出现“同一个实验两次结果不一致”的诡异情况。最常见的来源是没有固定随机种子、动态 shape 导致 CUDA Graph 重新捕获、多进程的线程绑定差异、CPU/GPU 频率波动、以及显存碎片导致的分配策略变化。我的习惯是把环境固定成可复现固定随机种子、固定 batch shape、设置torch.backends.cudnn.benchmark策略、关闭可能造成扰动的后台进程然后多次运行取中位数或 P50。对于推理优化尤其要分别测 prefill 和 decode 阶段的耗时因为两阶段对性能的影响完全不同。最后再分享一个我自己的体会大模型性能优化不是一个“一步到位”的工作更像一个持续迭代的过程。你得先接受 80% 的收益来自 20% 的关键改动比如显存瓶颈、算子融合、批调度剩下 20% 的收益往往要花 80% 的时间去抠细节。初期重点放在“找到瓶颈选择方向”比直接套一堆高级技术更重要。优化的每一步都要带着实验记录和评测结果走这样你才知道哪些改动真正值得留在生产环境里。