首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
GPU利用率不高?从推理瓶颈到动态Batching与TensorRT优化实战
📅 2026/10/21 22:40:51
✍️ 爱科研究院
👁 阅读 3,247
1. 先搞清楚“GPU利用率不高”到底卡在哪一环很多刚接触模型部署的同学拿到一块还不错的显卡跑起模型后习惯看一眼nvidia-smi。GPU-Util 那一栏如果蹦到 90% 以上就以为“性能拉满了”如果只有 30%就开始怀疑模型写得有问题、驱动没装好、显卡是假的。实际上这两条路都可能走偏。GPU 利用率本身是一个被过度简化的指标。nvidia-smi显示的利用率通常反映的是在采样周期内GPU 上有没有 SM流式多处理器在执行内核指令。它并不直接等价于“算力吃满了”更不等价于“业务吞吐很高”。一个典型的反例是当模型很小、单次推理耗时极短时GPU 会频繁地“忙一小下、闲一大下”驱动层的采样可能把这一小段忙碌也算进高利用率但整体吞吐就是上不去。反过来有些大模型的推理任务里SM 的占用率确实很高但访存显存带宽早已成为瓶颈SM 在等数据这时候利用率高也说明不了效率高。所以开始优化之前第一件事是别盯着一个数字做判断。要让 GPU 利用率真正和业务收益挂钩至少要区分三层含义SM 活动率SM 上有多少计算指令在跑这是nvidia-smi最常反映的东西。访存带宽利用率HBM/显存的数据搬运是否接近峰值。端到端吞吐与时延业务侧每秒能完成多少次推理、P99 时延是否达标。这三层可以组合出很多种情况。比如 SM 活动率 95%、显存带宽利用率只有 40%说明计算密集但访存压力不大主要瓶颈是算力如果 SM 活动率 60%、显存带宽跑到 90% 以上瓶颈就在数据搬运想去堆算力没用。1.1 模型推理的“忙”和“快”不是一回事模型推理和模型训练的一个本质区别是推理阶段的并行度往往远低于训练。训练时你手里握着的是成千上万条样本组成的大 batch数据排布规整GPU 可以长时间保持满负荷计算而在线推理时请求是离散到达的很多时候一个请求就是一个 batch甚至模型内部还包含大量串行依赖尤其是 Transformer 结构的逐层计算。这种情况下单次请求算完可能只需要 1~3 毫秒但数据在 CPU 上预处理、从内存拷进显存、kernel 启动这些开销加起来可能比计算本身还长。这就引出一个关键认知GPU 低利用率很多时候不是 GPU 计算慢而是 GPU 在“空转等待”。它等 CPU 准备好数据等上一次 kernel 结束后才能启动下一次 kernel等显存带宽腾出来。优化 GPU 利用率本质上是把“等”的时间压掉让 GPU 一直有活干。我见过不少项目把单条请求的推理延迟从 15ms 优化到 3ms但业务整体吞吐一点没变。原因很简单单次延迟降下来可每秒发进来的请求数还是那么多GPU 每处理完一个还是得等下一个请求从网络上慢慢爬过来。这种场景下真正要做的是吞吐侧的编排优化而不是单纯压单次计算耗时。1.2 推理请求的一生从网络到输出的完整时间账本任何一个在线推理请求从进入服务到返回结果大概要经历这么几个阶段网络接收请求、解析协议头数据预处理文本 padding 成张量、图像 resize/归一化、特征拼接CPU 到 GPU 的数据拷贝H2DGPU kernel 执行模型计算GPU 到 CPU 的结果拷回D2H后处理softmax、NMS、格式封装网络返回响应。很多人在做推理性能报告时只测第 4 步的 kernel 时间然后得出“GPU 计算已经足够快”的结论。但实际上前面三步和后面两步的耗时占比经常能超过 50%尤其是在图像类模型里图片解码和预处理往往比推理本身还贵。我自己调试过的一个图像分类项目就是典型例子。原始单图推理耗时约 8.5ms但 GPU kernel 只占 3.2ms剩下的 5.3ms 全耗在图片解码、resize、归一化和前后拷贝上。也就是说就算把 GPU kernel 优化到 0ms单图推理也顶多快三倍都不到GPU 利用率照样上不去。只有把预处理搬到 GPU 上做用 DALI 这类库把这个 5.3ms 压到 1ms整个链路才真正提速。1.3 定位优先于优化先量化瓶颈分布面对“GPU 利用率低”的抱怨我的标准操作是先把时间账算清楚再谈方案。常用的工具是 Nsight Systems 和 Nsight Compute前者看整个应用的时间线后者单看 kernel 内部的微观瓶颈。第一步永远是用 Nsight Systems 跑一条或一批请求导出时间线把上面七个阶段的时间占比统计出来。有一种很常见的结果GPU kernel 占整条链路 70% 以上SM 利用率也在 80% 左右但显存带宽利用率只有 30%。这说明 kernel 本身计算密集、访存较少优化方向可以是算子融合、TensorRT 之类减少 kernel 数量和启动开销。另一种结果kernel 只占 30%其余全是 CPU 数据预处理。这时候你把 GPU 换成顶级型号也没用该优化的是 CPU 侧流水线或者把预处理搬到 GPU 上并行。我现在接手一个性能问题时用的就是这套思路先跑 20 分钟的压测记录基线再用 Nsight Systems 抓时间线最后才决定是调模型、调框架、调硬件还是调调度策略。没有这个前提所有优化手段都是盲人摸象。2. 常规优化手段什么方法最见效如果已经确认瓶颈出在 GPU 侧kernel 时间占比高、SM 有闲时常规优化手段可以按收益从大到小排个序。注意这里我讨论的是“常规”不需要重新训练模型、不需要改网络结构纯工程手段。2.1 动态 Batching把小请求合成大矩阵动态 Batching 是推理优化里性价比最高的一招思想非常简单既然模型计算层对 batch 维度天然友好而单个请求往往只占满一个很小的矩阵那就把并发来的多个请求攒一攒凑成一个 batch 一起送进 GPU 计算。为什么要攒因为 GPU 计算有一个特点算一次 4×4 的矩阵和算一次 64×64 的矩阵执行时间并不会增加到 16 倍很多时候只多 20%~30%。原因是 GPU 的并行能力在那里小矩阵根本喂不饱它。把 16 个小请求合并成 16×16 的 batchGPU 花的额外时间不大但吞吐直接提升了接近一个数量级。动态 Batching 有两个关键参数要权衡max_batch_size一次合并的最大请求数。这个值取决于显存大小和模型规模显存够用就尽量大一些一般取 8、16、32。max_wait_time最长等待时间。攒 batch 要等等待会拉高单请求时延。比如设了 10ms意味着最多等 10ms 就出发哪怕只凑到 2 个请求也要先跑。这个值需要在“吞吐提升”和“时延增加”之间找平衡。我实测过一个文本分类模型batch1 时 GPU 利用率约 18%加了动态 Batchingmax_batch4max_wait8ms之后直接跳到 65%TPS 从 220 升到 780。代价是单请求平均时延从 6ms 涨到 15ms。对于大多数在线业务来说200ms 以内的时延都算可接受用 9ms 时延换 3.5 倍吞吐非常划算。2.2 并发推理引擎不要让一个进程串行吃药动态 Batching 解决了“批量不够大”的问题但它还有局限如果请求到达本身就稀疏攒了半天凑不够一个 batch该怎么办这时候要让 GPU 同时处理多个独立的调度单元也就是并发推理。主流的推理服务框架比如常见的 Triton、vLLM、TensorFlow Serving都支持配置多个模型实例。每个实例可以是一个独立的计算流CUDA stream多个实例并发执行各自收到的请求。假设配了 4 个实例即使每个实例只拿到一个 batch1 的请求GPU 也能同时跑 4 个请求明显提高 SM 占用。这里面有一个容易忽略的点多个并发实例跑在小模型上显存可能根本不是瓶颈反而是 kernel 启动开销、CPU 侧调度、锁竞争会成为新瓶颈。所以并发实例数不是越大越好需要实测。我习惯从 2 开始翻倍测试观察 GPU 利用率和 TPS 的增量增量变小时就停下来。对于 Transformer 类大模型的推理动态 Batching 的一个进阶版是连续批处理Continuous Batching。它把“一个请求等一个 batch 凑齐再一起算”的模式改成“任何时刻只要 GPU 有空闲算力就插入新请求的 token 级计算”让 GPU 始终处于忙碌状态。2.3 计算引擎优化TensorRT 与算子融合如果动态 Batching 和并发实例都做了SM 利用率依然偏低那就要看 GPU 上的计算指令本身是否高效了。常见的低级问题是模型由大量小算子组成每个算子一次只干很少的活kernel 启动开销占比太大。打个比方你要寄 100 个包裹如果每个包裹单独叫一次快递那大部分时间都花在填单和等车上了如果把这 100 个包裹打包成一个集装箱整体效率能提升非常多。算子融合就是这个思路把相邻的几个计算合并成一个大 kernel减少启动次数和数据往返。比如把 Conv BN ReLU 融合成一个算子PyTorch 的torch.compile和 TensorRT 在底层都做了大量这类工作。TensorRT 是英伟达自家的推理引擎它除了算子融合之外还会针对具体 GPU 架构做 kernel 自动调优选择最合适的实现。通常的做法是先用训练好的模型导出 ONNX然后通过 TensorRT 构建引擎开启 FP16 精度甚至 INT8 量化构建过程中它会自动寻找最优的 kernel 组合方式。我做过一个跨平台系统的自然语言模型优化PyTorch 原始推理 P99 时延 12ms转换成 TensorRT 并用 FP16 后降到 6msGPU 利用率也从 45% 提升到 82%。注意TensorRT 的构建和推理必须跑在同一代 GPU 架构上否则会出现“构建时优化 A 卡、运行时跑在 B 卡”的尴尬情况导致性能反而下降。2.4 显存访问与 KV Cache 优化Transformer 场景在线推理一旦涉及大语言模型LLMKV Cache 就成了绕不开的话题。简单说LLM 生成下一个 token 时需要反复读取之前所有 token 的 Key 和 Value 向量这些向量缓存下来就形成了 KV Cache。随着生成序列变长KV Cache 急剧膨胀显存带宽的消耗也随之暴涨。在 LLM 推理场景GPU SM 计算率可能并不低但真正的瓶颈往往不是算力而是每次生成时把 KV Cache 从显存搬到计算单元的速度。这种情况下盲目用更大的 batch 去压榨 SM 算力很快就会撞到显存带宽天花板。针对 KV Cache 的优化有几个实用方向PagedAttention 式显存管理把 KV Cache 分成固定大小的块按需分配避免预留大量连续显存导致浪费。使用支持这种机制的推理框架如 vLLM可以在同一块显存里塞进更多并发请求。KV Cache 量化把缓存中的 Key/Value 精度降低比如从 FP16 降到 INT8显存带宽占用直接减半。代价是输出质量可能有微小下降但很多业务场景完全感知不到。请求级 KV 复用如果业务中有大量相似的问题前缀比如多轮对话的固定系统提示词可以把这些前缀的 KV Cache 计算一次并共享大幅减少重复计算。对于非 LLM 的模型KV Cache 优化用不上但“减少 H2D/D2H 拷贝”这条经验是通用的。特别建议把 CPU 侧的输入数据提前放在固定内存pinned memory里这样 GPU 做 DMA 拷贝时不需要经过页面迁移速度能快 2~3 倍。很多人会忽略这个细节觉得一个拷贝能有多大开销在吞吐高的场景里光这一步就能省下好几毫秒。3. 实操验证与参数调优细节理论讲了一堆落到实地上最关键的一步是建立一套可信的评测方法。因为优化没有标准答案没有一个“batch 设成 8 就一定最快”的普适结论只能针对自己的模型、自己的数据、自己的硬件去实测。而实测的坑非常多我吃过不少亏。3.1 建压测方案并发、TPS、P99 一个都不能少压测方案的第一原则先定指标再做实验。我常用的指标是这三个TPS / QPS每秒能完成多少推理请求代表吞吐。P99 / P95 时延最慢的 1% 请求有多慢代表稳定性比平均时延更值得关注。GPU 利用率 / 显存带宽利用率 / SM 活动率硬件侧是否吃满。压测工具的选择上如果服务是 HTTP 接口用 wrk、ghz 这类能控制并发数的压测工具就可以如果是 gRPC 接口可以自己写一个小客户端。关键是并发数要能从 1 逐步加到 50、100、200观察不同并发下的指标变化。只测单个并发没有任何参考价值因为在线系统几乎永远处在多请求并发状态。压测数据的输入也很有讲究。如果每次请求的输入长度差异很大比如一句话翻译 vs 一整段文章翻译那压测结果会非常不稳定。我建议准备三种典型输入短输入、标准输入、长输入分别测看模型在不同长度下的表现。对于 LLM 推理还要区分首 token 时延TTFT和后续 token 平均时延TPOT这两个指标对用户体感的影响完全不同。3.2 参数调整顺序与基线记录参数调优最忌讳一次性动多个变量。调完 batch 又调并发、还顺手把精度改成 FP16结果性能变好了根本分不清是谁的功劳性能变差了也不知道该回退哪一项。我惯用的调参顺序是先跑一次完全不动配置的基线记录 TPS、P99、GPU 利用率。固定其他参数只调动态 Batching 的 max_batch_size2/4/8/16/32每个值跑 10 分钟记录指标。固定最佳 batch 参数再调并发实例数2/4/8/16。最后才考虑精度切换FP32 → FP16 → INT8因为这一项对结果的正确性影响最大必须单独验证。有一个我踩过的坑测试动态 Batching 时光调大 max_wait_time让请求在队列里多等TPS 当然会变好看因为等待让更多请求堆到了同一个 batch 里但每个请求的 P99 时延可能已经爆表了。所以TPS 和 P99 必须同时看只要 P99 超出业务容忍线TPS 再高也是白搭。3.3 从 GPU 监控指标反推调优方向压测过程中除了看框架自带的 metrics我习惯同时开着一个监控脚本每秒记录一次 GPU 的 SM 活动率、显存带宽利用率、显存占用和 copy engine 活动情况。这四个指标组合起来能像体检报告一样给出相当明确的瓶颈判断SM 活动率很高90%、显存带宽不高50% 以下算力瓶颈优先考虑用 TensorRT / FP16 降低计算量或者减少算力浪费的冗余计算。SM 活动率中高、显存带宽很高85%访存瓶颈优先考虑 KV Cache 量化、数据布局优化、减少不必要的显存读写。SM 活动率低、显存带宽低、copy engine 繁忙数据传输瓶颈检查是不是频繁做 H2D/D2H 拷贝考虑固定内存、批量传输或在 GPU 上做预处理。SM 活动率整体都低、GPU 空闲没有足够多请求到达或者 CPU 侧调度不过来优先做动态 Batching、并发实例扩容必要时打满网络与 CPU 流水线。我一直觉得性能调优很像看病先是望闻问切看指标再结合化验单时间线分析判断病灶最后才开药方优化手段。懒得做检查就猛开药大概率是碰运气。4. 常见问题与排查技巧实录在给各种模型做推理优化时会遇到一些反复出现的问题。这些问题几乎成了性能优化的“经典题”单独整理出来可以直接对应自己的情况去检查。4.1 GPU 利用率虚高但吞吐上不去现象nvidia-smi显示 GPU 利用率 95% 以上但 TPS 很低并发稍微上去一点时延就线性恶化。这个情况最初让某开发者也困惑了很久。后来通过 Nsight Systems 抓时间线才发现所谓的 95% 利用率其实是 GPU 在反复执行一个很小的 kernel比如某个 LayerNorm 或 Softmax每次执行只有几十微秒但在时间线上几乎没有间隔。真正耗时间的大 kernel 反而出现在另外的时间片段里中间隔了大段的 CPU 后处理、数据转换空隙。这个问题的本质是GPU 一直在“忙”但忙的都是低价值、低时延的小算子。优化方向不是继续加大 batch而是做算子融合把小 kernel 合并进邻近的大 kernel减少启动次数。同时把 CPU 侧后处理挪到 GPU 里用 CUDA 完成消除 GPU 等待 CPU 的时间。4.2 小模型反而更吃 CPU现象模型很小比如 MobileNet 级别的图像分类GPU 利用率永远只有 5%~15%。换更贵的显卡也一样利用率上不去。我做过一次实验单张 A 系列显卡跑 MobileNetbatch1 时GPU kernel 执行只要 0.4ms但 CPU 预处理 H2D 拷贝 D2H 拷贝 后处理加起来花了 5ms 还多。也就是说CPU 才是这个链路的实际瓶颈GPU 在那儿等着 CPU 干活呢。针对这种“小模型 高并发”场景方案是拆分配置把预处理放到独立的 CPU 进程池中用多线程并行做图片解码和归一化前端把多个请求聚合成大 batch 再一起提交给 GPU。有条件的直接把预处理搬到 GPU 上比如用 NVIDIA DALI 的 GPU 算子彻底绕开 CPU。4.3 多实例并发配置了但 GPU 利用率还是上不去现象推理框架配了 8 个实例TPS 提升有限GPU 利用率依然不高。我遇到过类似的情况后来发现原因在于每个实例虽然独立但底层都往同一个 GPU 提交 kernel而显存带宽只有一个。当 8 个实例同时跑访存密集的算子时互相抢带宽整个 GPU 的 SM 始终有一部分在等待数据。这时候加更多实例也没有用因为瓶颈已经转移到了显存带宽上。解决思路是分场景对待对于访存密集的模型优先做 KV Cache 量化或算子融合减少总访存量而不是堆并发对于计算密集的模型并发实例数才有明显收益。还有一个容易忽略的因素是 CPU 侧的锁竞争多个实例共享同一个 CPU 时响应解析、Python GIL、内存分配锁都会拖后腿必要时把推理服务拆成多进程部署每个进程绑定独立的 CPU 核心。4.4 精度切换后效果反而不如预期现象从 FP32 切到 FP16 后GPU 利用率没有明显提升甚至偶发时延抖动。FP16 并不总是更快尤其在一些老 GPU 或者不支持快速 FP16 运算的架构上FP16 反而因为需要额外的格式转换开销而变慢。另外FP16 的数值范围比较小某些模型中会出现精度溢出导致结果不对框架为了兜底可能会自动插入保精度逻辑额外增加计算量。正确的做法是切 FP16 之前先确认显卡的 FP16 算力Tensor Core 是否支持 FP16 加速切完以后要做输出一致性对比确保关键样本的误差在可接受范围。同样的道理适用于 INT8 量化它需要一批校准数据这步省略不得否则精度下降会非常严重。4.5 从几次项目迭代中总结的优化节奏明白了很多方法之后真正决定成败的反而是执行的节奏。我这几年的工作习惯已经固定成了一套流程供参考先量化。跑 20 分钟压测记录基线 TPS、P99、GPU 利用率、显存带宽。没有基线就谈不上优化。先看时间线。花半小时在 Nsight Systems 里看清楚请求的完整生命周期找到最大时间占比的环节。从一个变量开始。先做动态 Batching再做并发实例再考虑 TensorRT/FP16/INT8。每步单独验证避免引入连锁噪音。回退测试。优化完成后把配置切回初始状态再跑一遍确认提升确实来自优化项而不是压测波动或环境偶然性。有一次我优化完一个检测模型TPS 从 90 提升到 350一度怀疑是不是压测脚本写错了。后来回到 baseline 复测发现确实原来的配置只有 90 左右才敢确认提升真实有效。造数据的假象在性能优化里太常见了——负载不均衡、并发模型不对、CPU 被其他任务抢占都可能造成“看起来提升了”的错觉。收尾一个关于优化的朴素体会做了这么多推理优化项目我个人最大的体会是GPU 利用率本身不是目的它只是实现业务吞吐和时延目标的手段。有些模型优化到最后GPU 利用率也只是从 40% 提到 65%但业务已经从小卡迁移到大卡成本下降了一半这才是真正的成功。不要为了把利用率刷到 95% 而硬上大 batch导致请求时延失衡用户明显感觉到变慢了那这个优化就是失败的。如果你现在正对着nvidia-smi发愁我的建议是先把它关掉去把一次请求的完整时间线拉出来看看找到真正耗时的那一段再决定下一步怎么走。很多问题看着是 GPU 利用率低实际上它只是替 CPU、网络、内存顶了锅。看清瓶颈在哪你离解决它就不远了。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/21 22:35:44
Gipps模型混合驾驶交通流仿真系统:从安全距离约束到CAV渗透率分析
2026/10/21 22:35:44
Java Web数据库系统应用开发实例:JSP/Servlet+JDBC+MySQL源码视频解析
2026/10/21 22:35:44
如何搭建个人技能管理体系:从盘点、分类到评分复盘
2026/10/22 2:06:36
Flipper Zero玩家必修:从官方文档到Unleashed固件的完整资源地图
2026/10/22 2:06:36
如何让AI Agent定时自动干活?Luvus Agent Automation自动化调度指南:一次任务与时区感知周期任务
2026/10/22 2:06:36
戴森球计划蓝图仓库:7000+ 张免费工厂蓝图,新手开荒直接抄作业
2026/10/22 2:06:36
self-learning-skills三步循环深度解析:如何识别、捕获并复用会话中的黄金路径
2026/10/22 2:06:36
GriddyCode 代码编辑器:Lua 插件与 18 套主题,三步换成你的风格
2026/10/22 2:01:36
一台打印机卡纸,整条后厨出餐全停?做 20 年企业级开发,我总结了 4 条架构决策
2026/10/22 0:01:24
电商智能运营体系从0到1:架构设计、落地步骤与避坑指南
2026/10/22 0:01:24
压缩空气储能与热网热惯性联合优化调度:热电联产系统建模与求解
2026/10/22 0:01:24
若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定
2026/10/21 19:05:16
OpenResearch实战:Claude Code、Codex、OpenCode、Cursor 工作流搭建与故障排查
2026/10/21 14:47:26
知识管理核弹:用Obsidian构建第二大脑的工程化方案(TaoToken 统一 Key 接入版)
2026/10/21 19:05:04
金融AI落地指南:从调研报告到技术选型与工程实践
2026/10/20 2:24:03
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/22 2:07:18
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/21 19:01:08
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)