Perfetto GPU 计算内核 Occupancy 分析:定位占用率的真正限制因素
2026/9/17 2:36:08 网站建设 项目流程

Perfetto GPU 计算内核 Occupancy 分析:定位占用率的真正限制因素

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

Occupancy(占用率)是 GPU 计算内核调优中的关键指标:它衡量每个计算单元中实际活跃 warp/wavefront 数与硬件上限的比值。本文基于 Perfetto AI Skills 中的 GPU 计算内核分析工作流(ai/skills/perfetto/workflows/gpu/compute/occupancy.md),系统讲解如何从 trace 中提取 occupancy 相关数据、如何判读"理论占用率 vs 实际占用率"之间的差距,以及如何确定到底是寄存器、共享内存还是 block 数量构成了限制占用率的瓶颈资源——读完本文,你可以对任意 Perfetto trace 中的计算内核完成"占用率是否受限、受什么限制"的完整诊断。

Occupancy 是什么,以及为什么"不是越高越好"

Occupancy 定义为每个计算单元(NVIDIA 上是 SM,AMD 上是 CU)中活跃的 warp/wavefront 数占硬件最大值的比例。更高的 occupancy 有助于隐藏内存延迟和指令延迟,但这并不意味着多多益善:一旦延迟已经被隐藏,继续追求更高的 occupancy 毫无收益,反而可能以寄存器或共享内存为代价。

因此该工作流只回答两个问题:

  1. 这个内核是否受 occupancy 限制?
  2. 如果是,具体是哪种资源把它卡住了?

这是整套 GPU 计算分析中的"解释层"(interpretation layer):数据提取本身是厂商相关的(NVIDIA/AMD 各有计数器体系),但解释规则是厂商中立的,对所有 GPU 通用。

获取数据

数据获取分两条路径,取决于 GPU 厂商:

  • NVIDIA / CUDA:运行厂商专属提取脚本,详见 NVIDIA 提取说明:
trace_processor query --remote SESSION --query-file $SKILL_ROOT/workflows/gpu/compute/nvidia/scripts/occupancy.sql
  • 其他厂商:每个资源的 block 上限(per-resource block limits)来自 launch 参数,通常不依赖厂商、任何计算内核的 trace 里都有;但"实际占用率"(achieved occupancy)需要厂商计数器支持。

NVIDIA 提取的完整输出是一个"通用展示名 → 输出列 → NVIDIA 计数器/launch 参数"的映射,每个计算内核一行(按执行时长降序排列):

通用展示名输出列NVIDIA 计数器 / 参数
Theoretical Occupancytheoretical_occ_pctsm__maximum_warps_per_active_cycle_pct(launch 参数)
Achieved Occupancyachieved_occ_pctsm__warps_active.avg.pct_of_peak_sustained_active(计数器,AVG)
Block Limit (blocks)limit_blocksoccupancy_limit_blocks
Block Limit (registers)limit_registersoccupancy_limit_registers
Block Limit (shared memory)limit_shared_memoccupancy_limit_shared_mem
Block Limit (warps)limit_warpsoccupancy_limit_warps
Block Limit (barriers)limit_barriersoccupancy_limit_barriers

除上表外,脚本还输出描述 launch 形态的列:block_sizegrid_sizeregistersshared_mem_staticshared_mem_dynamicwaves_per_sm,以及binding_resource(block 上限最小的那个资源,即应当优先放松的瓶颈资源)。

核心指标含义

按通用展示名理解这些指标:

  • Theoretical Occupancy(理论占用率):launch 配置允许的上限,占硬件最大 warp 数的百分比。它由 block 大小、每线程寄存器数、每 block 共享内存量等 launch 配置共同决定。
  • Achieved Occupancy(实际占用率):实际运行时的活跃 warp 占比。它来自运行时计数器,反映的是内核真正"跑出来"的占用水平。
  • Block Limit(每资源可容纳的 block 数):分别为 registers / shared memory / warps / blocks / barriers 各自允许每计算单元容纳的 block 数。其中数值最小的那个就是约束资源(binding constraint)
  • Block Size、Grid Size、Registers、Shared Memory、Waves per compute unit:描述 launch 形态(launch shape)的一组参数——每 block 线程数、block 总数、每线程寄存器、共享内存用量、每个计算单元上的波数。

判读规则

拿到数据后,按以下四条规则判读。这些规则在 occupancy.md 中定义,适用于任意厂商的 GPU。

规则一:Block Size 必须是 warp/wavefront 的整数倍

Block size 应当是 warp/wavefront 大小的整数倍——NVIDIA 上是 32,AMD 上是 64。不是整数倍意味着每个 warp/wavefront 都有一部分线程被浪费。两个方向的极端都不可取:

  • 块太小:每个计算单元得不到充分利用;
  • 块太大:寄存器/共享内存压力升高,反过来把 occupancy 卡住。

规则二:用 Waves per compute unit 判断网格是否喂饱了 GPU

Waves per compute unit = grid size ÷ 每单元可驻留 block 数,判断标准:

  • < 1:网格太小,填不满整个 GPU,部分计算单元在空转。杠杆:增加并行度(更大的 grid),或与其他计算做融合(fusion)。
  • 1–2:存在尾部效应(tail effects)——一部分单元提前完成开始空转,而最后一个 wave 还在收尾。杠杆:把 grid 调整为更靠近日整波数的尺寸。
  • ≥ 4:负载均衡良好。

规则三:理论 vs 实际的差距说明什么

  • 差距大(Achieved ≪ Theoretical):内核没有维持住配置所允许的 warp/wavefront 数量——通常是负载不均衡、尾部效应或线程提前退出(early exits)所致。
  • 差距小且 Theoretical 本身偏低:说明 launch 配置本身就是上限 → 去看 binding 的 Block Limit 是哪个资源。

规则四:放松哪个资源?

Block Limit 中数值最小的那个,就是为了让 Theoretical Occupancy 上升而应该放松的资源。针对不同瓶颈的对应手段:

  • registers→ 降低寄存器压力:使用更小的数据类型、减少同时存活的值(live values),或使用 launch-bounds 提示;
  • shared memory→ 减少每 block 的共享内存用量,或调小 carveout 配置;
  • warps / blocks→ 受 block 大小或硬件每单元上限约束;调整 block 尺寸;
  • barriers→ 减少每 block 的命名屏障(named barriers)数量。

前置条件:先确认内核确实是 latency-bound

只在内核确实受延迟限制时才值得追逐 occupancy——判据是 Speed of Light 分析显示计算吞吐和内存吞吐两条天花板都低(详见 Speed of Light 工作流)。一个计算受限或内存受限的内核即使 occupancy 偏低也完全没问题——强行提高它的 occupancy 不会带来任何收益。

底层实现:occupancy.sql 是如何计算这些指标的

理解 occupancy.sql 的实现,可以确认这些指标在 Perfetto trace 数据模型中的确切来源:

1. 计算内核的识别。所有脚本统一用gpu_slice.render_stage_category = 2(COMPUTE 渲染阶段类别,0=OTHER、1=GRAPHICS、2=COMPUTE)且dur > 0来筛选计算内核,并按tsROW_NUMBER()分配 launch 序号,保证同一内核在不同分析脚本间的id一致:

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;

这正对应gpu_slice/gpu_track两张标准表——render_stage_categoryugpu(host 唯一 GPU id)字段在 GPU 数据源文档中有定义,解析逻辑位于 gpu_event_parser.cc。

2. 实际占用率来自计数器轨道的时间窗匹配。Achieved Occupancy不是 launch 参数,而是 COMPUTE 计数器组(gpu_counter_group.group_id = 6,即GpuCounterGroup枚举的 COMPUTE 值)中名为sm__warps_active.avg.pct_of_peak_sustained_active的 counter track。脚本把计数器值按时间戳落进每个内核的[ts, ts+dur)窗口内求平均,并按ugpu匹配到正确的 GPU:

CREATE PERFETTO TABLE _achieved AS SELECT k.id AS kernel_id, AVG(c.value) AS achieved_occ_pct 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 AND ct.name = 'sm__warps_active.avg.pct_of_peak_sustained_active' 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;

注意脚本注释中特别提醒的两个"compute 魔数":2render_stage_category的 COMPUTE 值)和6GpuCounterGroup的 COMPUTE 值)是两个不同枚举里的值,不要混淆。

3. 理论占用率与 block limit 全部是 launch 参数。theoretical_occ_pct直接取自 slice 参数sm__maximum_warps_per_active_cycle_pct,五个 block limit 取自occupancy_limit_*系列参数。这些都是 launch 时随 slice 记录下来的,所以不依赖厂商计数器——这也是"其他厂商"路径下 block limit 依然可用的原因。

4. binding_resource 的计算避免了 MIN 的 NULL 陷阱。找出"最小 limit"看似可以一行MIN()解决,但 SQLite 的标量MIN(a,b,...)只要有一个参数缺失(NULL)就整体返回 NULL,会把本来明确的瓶颈藏掉。脚本因此先把五个 limit 拆成每资源一行(unpivot),再用窗口函数取最小值,NULL 自动跳过,并列时按 blocks → registers → shared_mem → warps → barriers 的固定顺序决出:

CREATE PERFETTO TABLE _binding AS SELECT kernel_id, resource AS binding_resource FROM ( SELECT kernel_id, resource, ROW_NUMBER() OVER (PARTITION BY kernel_id ORDER BY v, rank) AS rn FROM _limits WHERE v IS NOT NULL ) WHERE rn = 1;

最终查询输出每个内核一行,包含全部指标列与binding_resource,按dur降序排列,方便直接定位耗时最长的热点内核。

在整个 GPU 计算分析流程中的位置

Occupancy 工作流是 kernel_analysis.md 定义的三阶段流程中 Phase 2 的一个分支,与另外三个视角(Speed of Light、Compute Workload Analysis、Launch Statistics)互补:

  1. Phase 1 全局分诊:先用厂商中立的 kernels_summary.sql 列出所有计算内核(idkernelugpudur_nsblock_sizegrid_sizeregisters),挑出dur_ns最大的热点内核。若该查询无行返回,说明 trace 中没有计算分派(没有render_stage_category = 2的 slice),整个工作流不适用。
  2. Phase 2 分类与深挖:Speed of Light 判定内核是 compute-bound、memory-bound 还是 latency/occupancy-bound——只有落到最后一类时,本文的 occupancy 判读才有意义;compute-bound 则转 Compute Workload Analysis 找饱和的流水线;launch 参数本身是否合理则由 Launch Statistics 描述性地回答(occupancy 回答"配置是否真的成了限制",两者分工明确)。
  3. Phase 3 报告:一份合格的结论应包含热点内核(id、名称、耗时及其占 GPU 时间的比例)、bound 类型(附数字:Compute/Memory Throughput、Achieved Occupancy、饱和流水线),以及具体的杠杆(改 block 尺寸/寄存器/共享内存以提 occupancy、改精度/指令组合、或改内存布局与局部性)。

需要说明的适用前提:当前 trace 中的完整 Speed-of-Light 计数器集只有 NVIDIA 具备,occupancy 的"achieved"一侧同样依赖厂商计数器;occupancy_limit_*等参数名虽源自 CUDA 命名,但 CUDA、HIP 等各计算 producer 在 trace 中以相同 key 记录,因此 block limit 部分是厂商中立的。所有指标均以通用展示名描述、由厂商提取层映射到具体计数器,跨厂商判读规则完全一致。

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

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

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

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

立即咨询