启动开销 影响 中 2026-08-06

小 kernel 过多,launch 开销主导整体耗时

现象
GPU 利用率看起来不低,但时间线上密密麻麻全是几微秒的小 kernel,kernel 之间有稳定间隙,总耗时远大于各 kernel 耗时之和
根因
单次 kernel launch 有固定的 CPU 侧下发与 GPU 侧调度成本。当 kernel 本体只有几微秒时,这部分固定开销占比超过实际计算
工具
Nsight SystemsNsight Compute
关注指标
gpu__time_duration.sumlaunch__grid_size

这个问题在推理场景和小 batch 训练里特别常见 —— 算子拆得很细,每个 kernel 都只处理一点点 数据,最后时间全花在「发起」而不是「计算」上。

现象长什么样

  • 时间线放大后是成百上千个宽度只有几微秒的小块
  • 相邻 kernel 之间有宽度相当稳定的空隙(这一点很关键,是固定开销的特征)
  • 把所有 kernel 耗时加起来,明显小于这段区间的墙上时间
  • CPU 侧某个线程一直忙于调用 CUDA API

为什么会这样

一次 kernel launch 的成本大致包含:

阶段 典型量级
CPU 侧 API 调用 + 参数打包 几微秒
命令写入队列、驱动处理 几微秒
GPU 侧取指、调度、启动 亚微秒到几微秒

这些成本基本与数据量无关。所以当 kernel 本体是 500 微秒时,它可以忽略; 但当 kernel 本体只有 3 微秒时,开销和计算量级相同,效率直接腰斩。

还有一个容易忽略的点:如果 CPU 侧下发速度赶不上 GPU 消费速度,GPU 就会饿死 —— 此时时间线上的空隙不是 GPU 在忙,而是它在等活干。

怎么确认是这个原因

先看总量对比:

Bash
nsys profile --stats=true -o launch_check ./your_app

在输出的 cuda_gpu_kern_sum 里关注两个数:kernel 总数、以及平均耗时。

Bash
# 直接看 kernel 数量级和平均耗时
nsys stats --report cuda_gpu_kern_sum launch_check.nsys-rep

判据比较直接:

  • 平均 kernel 耗时 < 10 微秒,且 kernel 数以万计 → 基本可以确认是这个问题
  • 平均耗时在几百微秒以上 → launch 开销可以忽略,去查别的方向

解法

  1. CUDA Graph。 把一段固定的 kernel 序列录制成 graph,一次提交整段, 省掉每个 kernel 的 CPU 下发成本。对形状固定的推理图收益最明显。

  2. 算子融合。 把 elementwise 链、激活、归一化之类的相邻小算子合并成一个 kernel, 既省 launch 又省显存往返。

  3. 增大每次的工作量。 提高 batch、把循环搬进 kernel 内部,让单个 kernel 变「胖」。

  4. 确认 CPU 侧不是瓶颈。 如果是下发跟不上,先优化 CPU 侧逻辑(比如去掉每步的同步、 减少 Python 开销),否则换成 graph 也只是把问题挪个位置。

注意 CUDA Graph 的前提是拓扑和形状基本不变。动态 shape 频繁变化时反复重建 graph, 开销可能反而更大。

kernel launchCUDA Graphkernel fusion启动开销小算子overhead
← 全部案例