Perfetto GPU 计算内核 Speed of Light 分析:NVIDIA 计数器抽取与 Compute/Memory/Latency 瓶颈判定
2026/9/17 6:38:33 网站建设 项目流程

Perfetto GPU 计算内核 Speed of Light 分析:NVIDIA 计数器抽取与 Compute/Memory/Latency 瓶颈判定

【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto

本文基于 Perfetto 的 AI Agent 技能(agent skill)中 GPU 计算内核分析工作流的 NVIDIA 分支文档 nvidia/speed_of_light.md,讲解"Speed of Light(光速上限)"这一分析方法:如何在已加载的 trace 上运行配套的抽取 SQL,得到每个 compute kernel 的计算/内存/L1/L2/DRAM 吞吐百分比与周期计数,并据此判定内核是 compute-bound、memory-bound 还是 latency/occupancy-bound。读完后你将掌握 GPU 内核瓶颈分类的完整判定流程、每个输出列与 NVIDIA 硬件计数器的对应关系,以及背后 SQL 的实现细节与适用前提。

1. Speed of Light 是什么,以及它在内核分析中的位置

Perfetto 的 agent 技能内置了一套 GPU compute kernel 分析工作流(入口文档为 kernel_analysis.md)。其出发点很直接:一个设备级的"GPU 利用率 %"无法回答"哪个 kernel 是瓶颈、瓶颈在哪种资源上",必须做逐 kernel 的分解。该工作流把每个 compute kernel 拆成四个观察面:

  • Speed of Light—— 计算/内存/延迟三类瓶颈的分类(本文主题);
  • Occupancy—— 启动配置与资源压力是否限制了占用率;
  • Compute Workload Analysis—— compute-bound 时哪条执行流水线(pipe)饱和;
  • Launch Statistics—— 原始启动配置是否合理。

其中 Speed of Light 是第一判定层:它比较内核运行到硬件两个天花板——compute(计算单元的算术吞吐)与memory(内存系统吞吐)——的距离,二者相对高低即给出 bound 类型并指向后续该走的分支。通用解释层文档是 speed_of_light.md,具体计数器则因厂商而异;当前 trace 中只有 NVIDIA 提供了完整的 Speed of Light 计数器集,因此本文聚焦 nvidia/speed_of_light.md 这个 NVIDIA 分支。

适用前提

  1. trace 已加载进trace_processor会话(技能默认用server unix+query --remote保持热会话,见 ai/skills/README.md 中"warm sessions are the single biggest efficiency win"的说明);
  2. trace 中存在 compute dispatch:即gpu_slice表中render_stage_category = 2(0=OTHER, 1=GRAPHICS, 2=COMPUTE)且dur > 0的 slice。如果先用 vendor 中立的 triage 脚本 kernels_summary.sql 查不到任何行,说明该 trace 没有 compute 工作,整个工作流不适用;
  3. trace 记录了 GPU 硬件计数器。GPU 计数器数据源的背景见 docs/data-sources/gpu.md。

Phase 1:先做全量 triage,选出值得深挖的 kernel

Speed of Light 之前,工作流要求先跑 vendor 中立的全 kernel 表,它只用"无论什么 GPU 厂商都存在"的数据(slice 时序与启动参数),目的是点名热点:

trace_processor query --remote SESSION --query-file $SKILL_ROOT/workflows/gpu/compute/scripts/kernels_summary.sql

输出列:id(启动顺序,1 = 第一个启动的 kernel,同一次运行内稳定)、kernel(demangled 名称,回退到 mangled/slice 名)、ugpu(host 唯一 GPU id)、dur_nsblock_sizegrid_sizeregisters。选出dur_ns占主导的 kernel(或重复出现的高耗时名字),记下它的id——后续所有深挖脚本对同一个 kernel 都报同样的id,可以跨表对齐。同时按 gpu_info.md 确认该ugpu的厂商与架构,决定走哪个 vendor 分支(本文是 NVIDIA)。

2. 运行 NVIDIA Speed of Light 抽取

NVIDIA 分支的执行方式只有一条命令:

trace_processor query --remote SESSION --query-file $SKILL_ROOT/workflows/gpu/compute/nvidia/scripts/speed_of_light.sql

几个需要说明的细节:

  • --remote ADDR:对热会话执行查询,而不是每次重新加载本地 trace(该参数定义见 docs/reference/trace-processor-cli.md);
  • -f, --query-file FILE:从文件读取 SQL,传-则从 stdin 读取;
  • $SKILL_ROOT是技能安装根目录(存放SKILL.md的目录)。因为技能从插件目录加载、不在用户工作区里,相对路径会解析到错误位置,所以技能内所有路径统一写成$SKILL_ROOT/<path>,路由器要求 agent 先设置一次该变量。在仓库源码树中,它对应ai/skills/perfetto/目录。

脚本无参数、作用于整条 trace,每个 compute kernel 输出一行,按耗时降序(最长在前)

3. 结果列解读:显示标签 → 输出列 → NVIDIA 计数器

输出列与 NVIDIA 硬件计数器的完整映射如下(继承自 nvidia/speed_of_light.md 的对照表):

显示标签(通用名)输出列NVIDIA 计数器
Compute Throughputcompute_pctsm__throughput.avg.pct_of_peak_sustained_elapsed
Memory Throughputmemory_pctgpu__compute_memory_throughput.avg.pct_of_peak_sustained_elapsed
L1 Cache Throughputl1_pctl1tex__throughput.avg.pct_of_peak_sustained_active
L2 Cache Throughputl2_pctlts__throughput.avg.pct_of_peak_sustained_elapsed
DRAM Throughputdram_pctgpu__dram_throughput.avg.pct_of_peak_sustained_elapsed
Elapsed Cycleselapsed_cyclesgpc__cycles_elapsed.max
Active Cyclesactive_cyclessm__cycles_active.avg
Durationdur_nsgpu__time_duration.sum(缺失时回退为 slice 的 dur)

在 NVIDIA 上计算单元是SM(Streaming Multiprocessor)。必须强调一个易错点:所有吞吐列都是 **% of peak(达到峰值的百分比,即该层级有多"饱和")**而不是缓存命中率。由此:

  • dram_pct高 → 真正的 DRAM 带宽瓶颈(DRAM-bandwidth bound);
  • l1_pct/l2_pct高而dram_pct低 → 工作集主要由缓存供给、而非 DRAM,杠杆在数据复用/工作集大小,而不是"缓存没用上"。

4. SQL 实现细节:从 gpu_slice 到八列透视

speed_of_light.sql 共三段,完整逻辑如下。

第一段:_kernels临时表——圈出所有 compute kernel 及其窗口。

CREATE PERFETTO TABLE _kernels AS SELECT s.id, s.ts, s.dur, s.arg_set_id, ROW_NUMBER() OVER (ORDER BY s.ts) AS launch_id, EXTRACT_ARG(t.dimension_arg_set_id, 'ugpu') AS ugpu, COALESCE( EXTRACT_ARG(s.arg_set_id, 'kernel_demangled_name'), EXTRACT_ARG(s.arg_set_id, 'kernel_name'), s.name ) AS kernel FROM gpu_slice AS s JOIN gpu_track AS t ON s.track_id = t.id WHERE s.render_stage_category = 2 AND s.dur > 0;

要点:render_stage_category = 2是 COMPUTE 分类(与 triage 脚本同一过滤条件,保证各脚本的launch_id对齐);ugpu来自 track 维度参数,用于后面把计数器"钉"到正确的 GPU 上;kernel 名优先取 demangled,回退 mangled,再回退 slice 名。

第二段:_kernel_counters——按 kernel 窗口聚合 COMPUTE 计数器组。

CREATE PERFETTO TABLE _kernel_counters AS SELECT k.id AS kernel_id, ct.name AS counter_name, SUM(c.value) AS sum_v, AVG(c.value) AS avg_v FROM _kernels AS k JOIN gpu_counter_group AS g ON g.group_id = 6 JOIN gpu_counter_track AS ct ON ct.id = g.track_id AND ct.ugpu = k.ugpu JOIN counter AS c ON c.track_id = ct.id AND c.ts >= k.ts AND c.ts < k.ts + k.dur GROUP BY k.id, ct.name;

这里有一个容易混淆的地方:文档明确指出两个"compute"魔法数字 2 和 6 是不同的枚举——render_stage_category = 2指 slice 的 render stage 分类,而gpu_counter_group.group_id = 6GpuCounterGroup中 COMPUTE 计数器组的值。匹配逻辑是"按ugpu找到同一 GPU 的计数器 track,再取时间戳落在[kernel.ts, kernel.ts + kernel.dur)窗口内的 counter 样本",并同时算出SUMAVG两种聚合,供不同指标选用。

第三段:透视为一行一 kernel 的最终输出。

SELECT k.launch_id AS id, k.kernel, ROUND(MAX(CASE WHEN kc.counter_name = 'sm__throughput.avg.pct_of_peak_sustained_elapsed' THEN kc.avg_v END), 1) AS compute_pct, ROUND(MAX(CASE WHEN kc.counter_name = 'gpu__compute_memory_throughput.avg.pct_of_peak_sustained_elapsed' THEN kc.avg_v END), 1) AS memory_pct, -- l1_pct / l2_pct / dram_pct 同理,分别取 -- l1tex__throughput.avg.pct_of_peak_sustained_active、 -- lts__throughput.avg.pct_of_peak_sustained_elapsed、 -- gpu__dram_throughput.avg.pct_of_peak_sustained_elapsed 的 AVG CAST(MAX(CASE WHEN kc.counter_name = 'gpc__cycles_elapsed.max' THEN kc.sum_v END) AS INT) AS elapsed_cycles, CAST(MAX(CASE WHEN kc.counter_name = 'sm__cycles_active.avg' THEN kc.sum_v END) AS INT) AS active_cycles, CAST(IFNULL( MAX(CASE WHEN kc.counter_name = 'gpu__time_duration.sum' THEN kc.sum_v END), k.dur ) AS INT) AS dur_ns FROM _kernels AS k LEFT JOIN _kernel_counters AS kc ON kc.kernel_id = k.id GROUP BY k.launch_id, k.kernel, k.dur ORDER BY k.dur DESC;

l1_pctl2_pctdram_pct三列与compute_pct同构,均为对应计数器名的AVG值。)

从 SQL 结构可以看出三条聚合规则,与脚本头注释一致:

  • 吞吐百分比与频率类指标用AVG(窗口内样本均值);
  • 周期数与时长用SUMgpc__cycles_elapsed.maxsm__cycles_active.avggpu__time_duration.sum在窗口内累加);
  • dur_nsIFNULL(..., k.dur)兜底——trace 未提供gpu__time_duration.sum时回退到 slice 自身时长;
  • LEFT JOIN保证即使某 kernel 的计数器缺失也能出一行(对应列为 NULL),排序统一按dur DESC,最长在内。

5. 解读层:四类判定与后续动作

拿到数据后,按通用解释层 speed_of_light.md 的判定矩阵读Compute Throughput 与 Memory Throughput 的相对关系(两者都是 achieved-vs-theoretical 的 % of peak,距 100% 的差距就是该天花板下未用尽的余量):

  1. Compute 高、Memory 相对低 → compute-bound。计算单元是天花板,杠杆在"数学"本身:转到 compute_workload_analysis.md 找饱和的流水线,并考虑更便宜的精度或减少工作量。NVIDIA 分支下(nvidia/compute_workload_analysis.md)会进一步给出alu/fma/fp16/fp32/fp64/tensor各 pipe 的利用率:tensor_pct高说明已在张量核/矩阵路径上;fma_pct/fp32_pct高说明受 FP32 算术限制,可评估 fp16/bf16;fp64_pct高说明双精度开销大;alu_pct高则是整数/地址计算受限。
  2. Memory 高、Compute 相对低 → memory-bound。用缓存层级列定位它在哪一层:
    • dram_pct高 → DRAM 带宽瓶颈;杠杆是局部性/复用(tiling、fusion)或更好的访问模式;
    • l2_pct/l1_pct高而dram_pct低 → 工作集由缓存供给;杠杆是数据复用/工作集大小(再次提醒:这是吞吐占比,不是命中率)。
  3. 两者都低 → latency/occupancy-bound。两条天花板都没接近峰值,内核在 stall;转到 occupancy.md(NVIDIA 分支:nvidia/occupancy.md),检查 block size 是否为 warp 大小(NVIDIA 为 32)的倍数、waves per SM 是否 <1(grid 太小填不满)或 1–2(尾部效应)、Theoretical 与 Achieved Occupancy 的差距、以及哪个 Block Limit(registers/shared memory/warps/blocks/barriers)是约束项。
  4. 两者都高 → 接近真实天花板。内核已"打包得很好",进一步收益需要算法层面的改变,而不是调参。

健全性检查(sanity check):active_cycles远低于elapsed_cycles,意味着计算单元在内核执行期间有大段时间空闲(launch/调度间隙或尾部效应),佐证 latency/occupancy 问题——这与判定 3 互为印证。

最后要注意文档的免责边界:任何数值阈值(如"high ≈ ≥ 60% of peak")都只是经验法则(rules of thumb),并非厂商或插件定义的标准。

6. 产出与对比:报告要点和基线对比

按工作流 Phase 3 的要求(kernel_analysis.md),一份合格的分析结论应包含三件事:

  1. 哪个 kernel—— 热点 kernel 的id、名字、dur_ns(及占 GPU 时间的份额,如已知);
  2. bound 类型—— compute / memory / latency-occupancy,附上数字(Compute Throughput、Memory Throughput、Achieved Occupancy、饱和的 pipe);
  3. 具体杠杆—— 点名到可执行的改动:提升占用率的启动配置改动(block size、寄存器、shared memory)、针对饱和 pipe 的精度/指令组合变化、或针对 memory-bound 的内存布局/局部性变化。

对比两个 kernel(优化前后或两个变体)时,跑同一套 Phase 2 脚本、对两个id的行做 diff 即可——因为每个脚本都"一个 kernel 一行",基线对比就是逐行对照。文档同时要求保留 SQL 与 kernel id 以便审计。

7. 适用限制

  • 厂商覆盖:当前 trace 中只有 NVIDIA 具备完整 Speed of Light 计数器集;其他厂商只能使用其暴露的吞吐/时长数据做近似,通用解释层仍然适用,但具体计数器映射缺失(见 speed_of_light.md 的 "Get the data" 一节)。
  • 架构级峰值指导尚为预留:nvidia/speed_of_light.md 末尾的 HTML 注释表明,架构特定的峰值/杠杆指导(例如 nvidia/h100/ 下对比 HBM 带宽、tensor TFLOPs 绝对峰值,以及 FP8、TMA 等架构杠杆)是"once added"的转发占位——从源码结构看,当前仓库尚无该子目录,读者在解读 % of peak 时只能相对比较,不能据此文档得到具体硬件的绝对峰值。
  • 数据前提:所有列依赖 trace 实际记录了 COMPUTE 计数器组(group_id = 6);计数器缺失的 kernel 相应列为 NULL,dur_ns会回退到 slice 时长,此时该行的吞吐判定不可用,但id/kernel/时长仍可与 kernels_summary.sql 的输出对齐。

参考路径

内容路径
本文主题文档(NVIDIA 分支)ai/skills/perfetto/workflows/gpu/compute/nvidia/speed_of_light.md
抽取 SQLai/skills/perfetto/workflows/gpu/compute/nvidia/scripts/speed_of_light.sql
通用解释层(判定矩阵)ai/skills/perfetto/workflows/gpu/compute/speed_of_light.md
工作流总入口(Phase 1–3)ai/skills/perfetto/workflows/gpu/compute/kernel_analysis.md
全 kernel triage 脚本ai/skills/perfetto/workflows/gpu/compute/scripts/kernels_summary.sql
Occupancy 分支ai/skills/perfetto/workflows/gpu/compute/occupancy.md / nvidia 分支
Compute Workload Analysis 分支ai/skills/perfetto/workflows/gpu/compute/compute_workload_analysis.md / nvidia 分支
GPU 信息(vendor/架构识别)ai/skills/perfetto/workflows/gpu/gpu_info.md
trace_processor CLI 参数docs/reference/trace-processor-cli.md
GPU 数据源背景docs/data-sources/gpu.md

【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询