CANN/cannbot-skills NPU Profiling采集技能
2026/7/11 14:21:43 网站建设 项目流程

【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体,本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills

name: model-infer-profiling description: | NPU 性能分析数据采集技能。用于华为昇腾NPU上的 PyTorch 模型性能分析。

触发场景:

  • 用户需要采集 NPU 性能数据
  • 用户提到 profiling、性能分析、tensorboard
  • 用户需要分析模型推理性能瓶颈
  • 用户使用 torch_npu.profiler
  • 用户遇到 profiler 解析失败或 JSON 截断问题

关键要求:必须用 ExperimentalConfig(Level1 + PipeUtilization),否则 kernel_details.csv 只有 9 列、op_statistic/api_statistic 不生成。采集前先判断仓库有无轻框架内置 prof(enable_profiler):有则用框架只调参数、无则注入。

NPU Profiling 采集技能

本技能帮助你在华为昇腾 NPU 上用torch_npu.profiler正确采集 PyTorch 模型性能数据,产出与 Ascend 官方 TensorBoard/Profile 插件兼容的ASCEND_PROFILER_OUTPUT目录。

采集有两条路径:框架内置(仓库已经把 profiler 接进推理循环,你只改参数)和注入采集(仓库没有 profiler,你自己写采集代码)。第一步是 Step 0 路由判断用哪条


Step 0:路由 —— 框架内置 vs 注入采集(最先做)

怎么判断"有没有框架内置 prof"—— 满足任一即视为有:

# 1) 有没有 profiler 封装(cann-recipes-infer 的标志) ls cann-recipes-infer/executor/utils/profiler_context.py 2>/dev/null # 2) 推理配置/循环里有没有 enable_profiler 开关 grep -rn "enable_profiler" cann-recipes-infer/executor/ cann-recipes-infer/models/ 2>/dev/null | head
  • 命中 →路径 A(优先)。本仓库cann-recipes-infer就属于这种:profiler 已经接进推理引擎,只需开开关 + 调参数。
  • 都没命中 →路径 B(注入)。

通用判据(迁到别的仓库时):搜torch_npu.profilertensorboard_trace_handlerenable_profiler/profile配置项。已经接进推理循环的就走 A,只有零散 API 的就走 B。


采集前:确认采集范围(路由后、动手前先问用户)

判定走哪条路径后、真正动手前,先问用户两件事(有 AskUserQuestion 就用它给选项,没有就直接文字提问。默认值见下):

要问的选项默认(用户没特别要求时)
采哪个阶段prefill/decode/both(两个都采)both —— 默认采完整数据
decode 采几步例如 3 / 10 / 30 / 全程足够覆盖稳态的步数(建议 ≥ 10);要"完整数据"就把步数设大

补充约定(避免采到无效数据):

  • prefill 通常每个 request 只有 1 步,所以 prefill 的active固定为 1。
  • 第一次图编译 / JIT 必须在采集窗口外预热(见路径 B「关键 3」),否则采到的是编译时间不是推理时间。
  • "采完整数据" = 两个阶段都采 + decode 步数取大,而不是把 schedule 的warmup调大。

路径 A:使用框架内置采集(cann-recipes-infer)

框架已经把torch_npu.profiler接进推理引擎,你的工作只是:开开关 → 按用户选择调参数 → 验收产物,不要再注入新的采集代码。

A.1 怎么开启

在 YAML 的model_config下把开关打开:

model_config: enable_profiler: True # 默认 False

然后照常bash cann-recipes-infer/executor/scripts/infer.sh --model <model> --yaml <yaml>跑。

A.2 框架默认采了什么(实测)

enable_profiler: True时,框架的ProfilerManagercann-recipes-infer/executor/utils/profiler_context.py)在推理引擎里自动干这些事:

阶段schedule 参数(默认)输出目录
prefillactive=1, skip_first=0, warmup=0, repeat=1<res_path>/prof/prefill/
decodeactive=3, skip_first=3, warmup=0, repeat=1<res_path>/prof/decode/
  • 配置已经是ExperimentalConfig(Level1 + PipeUtilization)(与本 skill 的硬性要求一致,不用动)。
  • <res_path>=cann-recipes-infer/models/<model>/res/<date>/<case>/,与日志log_0.log同级。
  • prefill 和 decode各产一份prof/<phase>/.../ASCEND_PROFILER_OUTPUT/(实测两个目录都会生成)。
  • 注意:prefill profiler 是active=1,每 request 只采 1 步 prefill(prefill 本就只有 1 步,设计如此);实测能产出有效的 47 列 CSV。要更细的 prefill 分析(多步/自定义窗口)用注入路径模式 B。
  • 多卡时每个 rank 各采一份(同一个enable_profiler开关控制)。

调用链:execution_engine.py持有ProfilerManager,每个 forward 调set_status(is_prefill)+step()model_runner.py里另有一套define_profiler(prefillactive=1、decodeactive=10/skip_first=10),但离线 infer 实际走的是ProfilerManager这条,调参数认profiler_context.py

A.3 怎么调采集参数(只改这里)

框架的采集参数是硬编码在cann-recipes-infer/executor/utils/profiler_context.py的,YAML 只暴露了enable_profiler总开关。要改"采几步 / 从第几步开始 / 采哪个阶段",改下面这些调用点的入参:

你想改的参数改哪里(profiler_context.py语义(实测)
decode 采几步activecreate_profiler默认参数(decode 分支不传,吃默认active=3真正记录的步数。要"完整数据"就调大,例如 30
decode 从第几步开始skip_first同上,默认skip_first=3跳过最前面 N 个 decode step 不计入采集(跳过启动抖动)。从第skip_first+1步进入采集周期
prefill 采几步activeset_status的 prefill 分支,写死active=1prefill 每 request 只有 1 步,保持 1
只采 decode / 只采 prefill——set_status里把不想采的那个分支的 profiler 换成FakeContextManager()(或加判断跳过)框架默认两个阶段都采;要单采就短路掉另一个分支
warmup/repeatwarmup/repeatcreate_profiler一般保持warmup=0, repeat=1

清窗口规则(改active/skip_first后务必满足):decode 的prof.step()调用次数 ≈ 实际 decode 步数(受max_new_tokens控制),必须≥ skip_first + warmup + active(精确边界)才能跨出采集窗口,实践再多 1 步收尾更稳。框架 decode 默认需要 ≈3 + 0 + 3 = 6步,max_new_tokens=32远够;active/skip_first调大时,记得把 YAML 的scheduler_config.max_new_tokens同步调大并留余量,否则窗口没跨出去、解析会出问题。

改完直接重跑infer.sh,不需要改 YAML 之外的推理代码。

A.4 采完必须验收产物(关键,不能跳)

框架开了enable_profiler不等于拿到了可用数据。解析阶段(CANNmsprof离线分析)可能失败 —— 产物只剩一个被截断的trace_view.json、CSV 全缺。最常见的原因是 profiling 输出目录所在的文件系统/盘(详见 A.5),不是框架代码、不是权限、不是版本。所以采完立刻验收:

RES=cann-recipes-infer/models/<model>/res/<date>/<case> # 与 log_0.log 同级 # 1) 解析有没有报错(最快的信号) grep -c "run failed" "$RES"/log_0.log # 期望 0;非 0 => 解析失败 # 2) 你采的每个阶段都要查(默认 both 就两个都查;只采一个就只查那个) for PHASE in prefill decode; do [ -d "$RES/prof/$PHASE" ] || { echo "WARN: prof/$PHASE 没生成(该阶段未落盘 => 视同失败)"; continue; } OUT=$(find "$RES/prof/$PHASE" -name ASCEND_PROFILER_OUTPUT -type d | head -1) echo "== $PHASE: ${OUT:-无 ASCEND_PROFILER_OUTPUT} ==" ls "$OUT" 2>/dev/null # 期望含 kernel_details.csv op_statistic.csv api_statistic.csv if [ -f "$OUT/kernel_details.csv" ]; then head -1 "$OUT/kernel_details.csv" | tr ',' '\n' | wc -l # 期望 ~47;只有 9 => 没开 Level1+PipeUtilization else echo " kernel_details.csv 缺失 => 解析失败, 走 A.5" fi python3 -c "import json;json.load(open('$OUT/trace_view.json'));print(' trace_view VALID')" 2>/dev/null \ || echo " trace_view.json 截断/无效 => 解析失败" done
验收项期望不符合时
log_0.logrun failed计数0非 0 → 解析阶段失败,见 A.5(先换输出目录)
你采的每个阶段都有prof/<phase>/.../ASCEND_PROFILER_OUTPUT/存在缺失(比如默认 both 却没prof/prefill)→ 该阶段没落盘,视同失败、走 A.5
kernel_details.csv列数~47缺失/打印1→ 解析失败走 A.5;只有 9 → 配置没带Level1+PipeUtilization(框架默认带,正常不会发生)
op_statistic.csv/api_statistic.csv存在缺失 → 同run failed,走 A.5
trace_view.json合法 JSON、以]结尾截断/报错 → 解析失败,走 A.5

A.5 验收不过怎么办:先换输出目录,再考虑注入

框架enable_profiler的离线解析是 CANN 的msprof工具在跑。当出现所有 view 解析任务run failed+ 没有任何 CSV +trace_view.json被截断这组症状时:

根因(已实测定位):最常见的原因不是框架代码、不是目录权限、不是torch_npu/CANN 版本,而是profiling 输出目录所在的文件系统/盘。某些挂载(数据盘 / 网络盘 / 特定 bind-mount)会让 CANNmsprof的离线分析产不出 CSV,而把输出换到另一个文件系统(尤其本地盘)就完全正常。实测:同一份采集数据,输出在原目录 → 全部 parser run failed、无 CSV、trace_view 截断;只把输出目录换到另一个盘 → 框架原样产出kernel_details.csv47 列 + 全套 CSV、零 run failed。验证方法:把那份失败的*_ascend_pt原始数据cp到另一个盘再torch_npu.profiler.profiler.analyse(<dir>),能解析出 CSV 就坐实是输出目录的问题。

修复步骤(按顺序):

  1. 首选 —— 把 profiling 输出换到另一个目录/文件系统(优先本地盘),重跑后重新走 A.4 验收。 profiler 输出路径 =os.getenv("WORK_DIR")/os.getenv("RES_PATH")/prof/...execution_engine.py:83)。注意function.sh会把WORK_DIR写成cann-recipes-infer/models/<model>,直接export WORK_DIR=...会被覆盖、不生效。可靠做法是把res目录软链到本地盘:

    mkdir -p /tmp/recipes_res # 若 cann-recipes-infer/models/<model>/res 已是非空真实目录, 先移走/备份再软链 ln -sfn /tmp/recipes_res cann-recipes-infer/models/<model>/res bash cann-recipes-infer/executor/scripts/infer.sh --model <model> --yaml <enable_profiler:True 的 yaml> # 重新跑 A.4:run failed 应为 0、kernel_details.csv 应 ~47 列

    多换几个盘定位哪个文件系统可用。

  2. 换了几个盘都还不行,再回退到路径 B 注入采集scripts/profile_template.py)兜底:把模型加载与单步推理填进去、自己起 profiler。

一句话判定grep -c "run failed" log_0.log非 0 或kernel_details.csv缺失 →先把输出目录换到另一个(本地)盘重试;多个盘都不行才走路径 B。


路径 B:注入 torch_npu.profiler 采集代码

仓库没有框架内置 profiler(或框架产物验收不过)时,自己写采集代码。下面三条"关键"是注入采集的核心。

核心原理

NPU profiler 能不能拿到有用数据,取决于三件事:

  1. ExperimentalConfig决定能采到哪些字段(最关键)
  2. schedule 决定 profiler 在每一步处于什么状态
  3. 首次图编译 / JIT 必须放窗口外;prefill / decode 按需选择采或不采

关键 1:ExperimentalConfig决定 kernel_details 是 47 列还是 9 列

torch_npu 默认不开详细 AIC/AIV 流水 metric,必须显式传ExperimentalConfig

experimental_config = torch_npu.profiler._ExperimentalConfig( profiler_level=torch_npu.profiler.ProfilerLevel.Level1, aic_metrics=torch_npu.profiler.AiCMetrics.PipeUtilization, )
产物ExperimentalConfig不带(默认)
kernel_details.csv列数47列(含Stream IDInput/Output Shapesaic_mac_ratioaic_mte2_ratioaiv_vec_ratio…)9列(基本时间/dur/block_dim)
op_statistic.csv/api_statistic.csv✅ 生成❌ 缺失
operator_details.csvstep_trace_time.csvtrace_view.json生成生成
communication*.json仅多卡生成仅多卡生成

不带ExperimentalConfig= 拿不到 pipeline utilization、算子 shape 和 stream 归属。kernel_details.csv只有 9 列就是这个原因,直接回来补 config。

参数可选值说明
profiler_levelLevel0/Level1/Level2默认 Level0 只收基本 trace;Level1 是推荐默认;Level2 追加更细 SoC 计数、体积更大
aic_metricsPipeUtilization/ArithmeticUtilization/Memory/MemoryL0/MemoryUB/ResourceConflictRatio/L2Cache只能选一组;PipeUtilization定位 cube/vector/mte 流水 bound;关心 HBM/L2 带宽选Memory/L2Cache

绝大多数优化任务用Level1 + PipeUtilization就够。

关键 2:schedule 要让最后一次step()跨出 active 窗口

wait → warmup → active → (下一轮 / NONE)

状态在每次prof.step()时转换。循环结束时若 profiler 还停在 RECORD(没跨出 active),torch_npu 打:

[WARNING] Incorrect schedule: Stop profiler while current state is RECORD which may result in incomplete parsed data.

规则:prof.step()总调用次数 ≥skip_first + warmup + active + 1多出的 1 步让 schedule 跨出 active、触发收尾回调,profiler 干净退出。

写法 A:MAX_STEPS 调够大

WARMUP, ACTIVE = 2, 30 MAX_STEPS = 33 # ≥ warmup + active + 1 with profiler as prof: for step in range(MAX_STEPS): run_one_step(); prof.step()

写法 B:循环后补一次prof.step()(推荐,采样区间正好是你关心的 N 步)

with profiler as prof: for step in range(MAX_STEPS): run_one_step(); prof.step() prof.step() # ← 收尾:只推进 schedule,不跑推理

老文档里"MAX_STEPS < warmup + active"是误传:那样配 profiler 一定停在 active 中间。新版 CANN 会补 JSON,但 warning 照打。

关键 3:首次图编译放窗口外;prefill / decode 按需选择

第一次torch.compile/ torchair 图构建 / JIT放进窗口,trace 大头会变成编译时间。这类 framework overhead 必须在 profiler 启动前预热一遍。

prefill 本身是真实负载(kernel 分布、attention layout、MC2/all-to-all 调度都和 decode 不同),采不采看你想分析什么:

想分析采哪些阶段备注
decode TPOT、decode kernel 瓶颈只采 decode最常见;prefill 离群步会拉偏统计
prefill TTFT、长序列 prefill kernel只采 prefilldecode 前无需跑
一条时间线对照 prefill vs decode一次采两者trace_view清晰展示 prefill 单步 + 后续 decode
分开对照、不混时间线两次采,两个 save_path每个 phase 一个ASCEND_PROFILER_OUTPUT

四种模式(完整代码在scripts/profile_template.py):

# 模式 A:只采 decode(默认) run_prefill(...); warm_one_decode(...) # 预热,不采 with create_profiler("./prof_decode", warmup=0, active=N) as prof: for _ in range(N): model.decode(...); torch.npu.synchronize(); prof.step() prof.step() # 收尾 # 模式 B:只采 prefill(prefill 通常 1 步,active=1) warm_compile(...) # 预热第一次 prefill+decode 的图构建 with create_profiler("./prof_prefill", warmup=0, active=1) as prof: run_prefill(...); torch.npu.synchronize(); prof.step(); prof.step() # 模式 C:一次采 prefill + decode(同一 timeline) with create_profiler("./prof_both", warmup=0, active=1+N) as prof: run_prefill(...); torch.npu.synchronize(); prof.step() # step 1: prefill for _ in range(N): model.decode(...); torch.npu.synchronize(); prof.step() # steps 2..N+1: decode prof.step() # 收尾 # 模式 D:两次独立采集,两个 save_path(分别对比)

标准配置模板(注入)

import os, torch, torch_npu def create_profiler(save_path, warmup=0, active=30): """Level1 + PipeUtilization 拿 47 列 kernel_details。 warmup 推荐 0:图编译/JIT 预热应已在 profiler 外做掉。""" os.makedirs(save_path, exist_ok=True) experimental_config = torch_npu.profiler._ExperimentalConfig( profiler_level=torch_npu.profiler.ProfilerLevel.Level1, aic_metrics=torch_npu.profiler.AiCMetrics.PipeUtilization, ) return torch_npu.profiler.profile( activities=[torch_npu.profiler.ProfilerActivity.NPU, torch_npu.profiler.ProfilerActivity.CPU], with_stack=False, record_shapes=False, profile_memory=False, experimental_config=experimental_config, schedule=torch_npu.profiler.schedule( wait=0, warmup=warmup, active=active, repeat=1, skip_first=0), on_trace_ready=torch_npu.profiler.tensorboard_trace_handler(save_path), ) # 用法见上面四种模式;完整可运行模板(带 PHASE 开关)见 scripts/profile_template.py

完整模板见scripts/profile_template.pyPHASE = "prefill"|"decode"|"both"|"separate",对应上面用户选择)。


产物自检清单(两条路径通用)

采完立刻对ASCEND_PROFILER_OUTPUT/自检:

python3 - <<'PY' import json, glob, os prof = glob.glob('./**/ASCEND_PROFILER_OUTPUT', recursive=True)[0] kd = f'{prof}/kernel_details.csv' if os.path.exists(kd): with open(kd) as f: cols = f.readline().strip().split(','); n = sum(1 for _ in f) print(f'kernel_details: {n} rows, {len(cols)} cols ' + ('OK' if len(cols) >= 40 else 'WRONG — missing ExperimentalConfig?')) else: print('kernel_details.csv MISSING — 解析失败(框架路径=>走A.5回退) 或没开 Level1+PipeUtilization') try: json.load(open(f'{prof}/trace_view.json')); print('trace_view: valid JSON') except Exception as e: print(f'trace_view INVALID/截断: {e}') for fn in ['op_statistic.csv','api_statistic.csv','operator_details.csv','step_trace_time.csv']: print(f'{fn}: {"found" if os.path.exists(f"{prof}/{fn}") else "MISSING"}') PY
检查项期望不符合
kernel_details.csv列数479 → 没带ExperimentalConfig;缺失 → 解析失败(框架路径走 A.5)
op_statistic.csv/api_statistic.csv存在缺失 → 同上
trace_view.json合法 JSON、以]结尾截断 → 解析失败 / 收尾 step 漏调
step_trace_time.csv行数≈ active明显偏少 → 循环内prof.step()漏调
communication*.json仅多卡生成单卡没有是正常的

输出结构

prof/ └── <hostname>_<pid>_<timestamp>_ascend_pt/ ├── ASCEND_PROFILER_OUTPUT/ │ ├── trace_view.json # 时间线(TensorBoard / chrome://tracing) │ ├── kernel_details.csv # 47 列算子执行明细(最重要) │ ├── op_statistic.csv # 按算子类型聚合 │ ├── api_statistic.csv # host 侧 API 耗时 │ ├── operator_details.csv # 按 PyTorch op 聚合 │ ├── step_trace_time.csv # 每步 compute/communication 耗时 │ ├── *.db # DB 版摘要 │ └── communication*.json # 仅多卡生成 ├── FRAMEWORK/ PROF_*/ logs/ profiler_{info,metadata}.json

查看结果

tensorboard --logdir=./prof # 需 pip install torch_tb_profiler,切 PyTorch Profiler tab # 或 chrome://tracing 加载 trace_view.json # 或 pandas 读 kernel_details.csv 做定制分析(拆解到模型结构 => 用 perf-breakdown-skill)

参考文档

  • scripts/profile_template.py— 路径 B 可直接改用的完整模板
  • references/troubleshooting.md— 版本差异、框架解析失败、具体报错、排查流程
  • torch_npu Profiler 文档

【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体,本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills

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

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

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

立即咨询