1. 从一次训练慢的排查说起torch_npu profiler 到底能帮你看到什么昇腾 NPU 上跑训练最让人头疼的不是跑不起来而是能跑但慢得莫名其妙。你可能遇到过这种情况同样的模型、同样的 batch size在别的卡上迭代一次 300ms到了昇腾上变成 800ms日志里 loss 正常下降npu-smi 看利用率也不算低但就是慢。这时候光靠time.time()打点已经不够用了你需要知道这 800ms 里到底花在了哪里——是算子本身慢还是算子之间的调度有空隙还是 Host 侧下发跟不上 Device 侧执行。torch_npu.profiler就是干这个的。它是 PyTorch 生态里torch.profiler在昇腾后端的适配实现采集出来的数据最终会落到一个kernel_details.csv里逐条记录每个算子的名称、执行时间、耗时占比、加速器流stream等信息。说白了它给你的是一份算子级账单让你知道每一毫秒被谁吃掉了。这篇文章适合两类人一类是刚在昇腾上跑通训练、开始做性能调优的算法工程师另一类是负责把模型从其他后端迁移到昇腾、需要定位性能瓶颈的工程同学。我会从采集脚本怎么写、参数怎么配一路讲到kernel_details.csv里每一列怎么读、常见的几种性能问题在数据里长什么样。中间会穿插我自己踩过的坑比如 profiler 本身把训练拖慢十倍、采集出来的时间对不上、npu is selected as device, but torch_npu is not available这类报错怎么处理。先给一个整体认知profiler 的工作模式是采样 打点。它会在算子执行前后插入记录点同时周期性采集 Device 侧的硬件指标这部分依赖 AiCMetrics 能力。所以它天然有两类开销——打点开销和采集开销。理解这一点后面很多数据看起来不对的问题就都能解释了。2. 采集脚本的写法与参数取舍别让 profiler 自己成为瓶颈2.1 最小可用采集脚本先上一个能直接跑的骨架基于 PyTorch 2.x torch_npu 的常见版本import torch import torch_npu import torch_npu.profiler as npu_prof def train_step(model, optimizer, data): optimizer.zero_grad() out model(data) loss out.sum() loss.backward() optimizer.step() return loss # 准备模型和数据 model MyModel().npu() optimizer torch.optim.SGD(model.parameters(), lr0.01) data torch.randn(32, 3, 224, 224).npu() # 配置 profiler experimental_config npu_prof._ExperimentalConfig( profiler_levelnpu_prof.ProfilerLevel.Level1, aic_metricsnpu_prof.AiCMetrics.AiCoreNone, ) prof npu_prof.profile( activities[npu_prof.ProfilerActivity.CPU, npu_prof.ProfilerActivity.NPU], scheduletorch.profiler.schedule(wait1, warmup1, active3, repeat1), on_trace_readytorch.profiler.tensorboard_trace_handler(./prof_out), record_shapesFalse, profile_memoryFalse, with_stackFalse, experimental_configexperimental_config, ) for step in range(10): train_step(model, optimizer, data) prof.step() prof.stop()跑完之后./prof_out目录下会生成一个以时间戳命名的子目录里面包含kernel_details.csv、trace_view.json、operator_details.csv等文件。kernel_details.csv就是我们重点要读的那份。2.2 schedule 参数为什么这么设schedule(wait1, warmup1, active3, repeat1)这四个参数是新手最容易配错的地方我逐个解释wait1前 1 个 step 完全不采集纯跑。因为第一个 step 通常包含算子编译、内存池首次分配、图编译等一次性开销采进来只会污染数据。warmup1接下来 1 个 step 开始走 profiler 的采集流程但不记录数据。这一步是为了让 profiler 自身的初始化比如 Device 侧采集通道建立完成避免它的启动开销落在正式数据里。active3正式记录 3 个 step 的数据。一般 3 个 step 足够看出稳定态的性能特征太多会让数据文件巨大且分析困难。repeat1整个 wait→warmup→active 循环只做 1 次。注意如果你发现采集出来的 step 数不对先检查prof.step()是不是每个 iteration 都调用了。漏调会导致 schedule 状态机错乱采出来的数据可能只有半个 step。2.3 profiler_level 与 aic_metrics 的选择profiler_level控制采集粒度Level采集内容开销适用场景Level0仅算子级时间最低快速定位哪个算子慢Level1算子 部分通信/内存信息中等常规性能分析推荐默认Level2算子 更细的 AiCore 指标最高深入分析算子内部瓶颈aic_metrics是昇腾特有的硬件指标采集开关可选AiCoreNone不采、PipeUtilization流水利用率、ArithmeticUtilization计算单元利用率等。这里有个经验第一次分析一律用 Level1 AiCoreNone。因为 AiCore 指标采集会显著增加开销而且需要额外的解析步骤容易让新手在还没搞清楚基本耗时分布时就陷入细节。2.4 三个关掉参数的意义record_shapes、profile_memory、with_stack默认我都设成 False原因很直接record_shapesTrue会记录每个算子的输入张量形状数据量暴涨而且昇腾上部分算子形状记录可能触发额外的同步。profile_memoryTrue会追踪内存分配开销大除非你在查显存问题否则没必要。with_stackTrue会记录 Python 调用栈这是定位哪个 Python 代码触发了这个算子的利器但开销极大通常只在已经锁定可疑算子后针对性地开一次。我见过有同学一上来全开结果训练慢了 8 倍采出来的时间全是 profiler 自己的开销完全没法看。先轻量采锁定范围后再重采这是基本原则。3. kernel_details.csv 的列到底在说什么3.1 核心列逐列拆解打开kernel_details.csv你会看到类似这样的列不同版本列名略有差异以实际为准列名含义怎么用Device_id设备编号多卡时区分数据来源Name算子名称定位是哪个算子如MatMul、Conv2DType算子类型区分 AI Core / AI CPU / 通信算子Stream_id所在流判断并行度和流间同步Start_time开始时间相对看时间轴分布Duration执行耗时us核心指标排序看 Top 耗时Wait_time等待时间算子排队等待的时间Block_dim核数判断并行度是否打满Duration 和 Wait_time 的区别是理解性能问题的关键。Duration 是算子真正在计算单元上跑的时间Wait_time 是它在流里排队、等前序算子或等同步的时间。一个算子如果 Duration 很短但 Wait_time 很长说明瓶颈不在它自己而在它前面的依赖。3.2 用 pandas 快速做 Top 分析拿到 CSV 后第一件事是排序看谁最耗时import pandas as pd df pd.read_csv(kernel_details.csv) # 按总耗时排序看 Top 20 df[Total] df[Duration] * df[Count] if Count in df.columns else df[Duration] top df.sort_values(Duration, ascendingFalse).head(20) print(top[[Name, Type, Duration, Wait_time, Block_dim]]) # 按算子类型聚合看时间都花在哪类算子上 by_type df.groupby(Type)[Duration].agg([sum, count, mean]) print(by_type.sort_values(sum, ascendingFalse))这个聚合特别有用。如果发现AI_CPU类型的算子总耗时占比很高那基本可以判断有大量算子在 CPU 上执行性能肯定上不去——因为 AI CPU 的算力远低于 AI Core。常见原因是某些算子昇腾没有 AI Core 实现回退到了 CPU。3.3 一个真实的数据片段我之前调一个检测模型采出来的 Top 5 是这样的数值做了脱敏Name Type Duration(us) Wait_time(us) Block_dim TransData AI_CORE 1820 45 48 Conv2D AI_CORE 960 12 48 TransData AI_CORE 870 30 48 MatMulV2 AI_CORE 540 8 24 TransData AI_CORE 410 22 48一眼就能看出问题TransData 出现了三次总耗时超过 3000us比真正的卷积还多。TransData 是昇腾上做数据排布转换的算子频繁出现通常意味着前后算子的数据格式如 NCHW 与 NC1HWC0不匹配框架在中间插入了转换。解决办法是检查模型里是否有频繁的 format 切换或者用torch_npu.npu_format_cast提前统一格式。这就是kernel_details.csv的价值——它不会直接告诉你你有格式转换问题但数据摆在那里有经验的人一眼就能看出来。4. 从数据到结论四类典型性能问题的识别方法4.1 算子耗时高但 Block_dim 很低如果某个计算密集型算子如 MatMul、Conv2DDuration 很高同时 Block_dim 只有个位数说明并行度没打满。昇腾 AI Core 的核数通常是几十个Block_dim 低意味着只有少数核在干活。原因可能是输入张量的某个维度太小切不出足够的并行块。比如 batch size 太小、channel 数太少。这时候可以考虑增大 batch或者调整算子的 tiling 策略部分算子支持通过环境变量或算子属性调整。4.2 Wait_time 远大于 Duration如果大量算子的 Wait_time 是 Duration 的好几倍说明 Device 侧在等饭吃——Host 侧下发算子的速度跟不上 Device 侧执行的速度。这在 PyTorch 动态图模式下很常见因为每个算子都要经过 Python → 框架 → 昇腾的层层下发。判断方法看时间轴上算子之间是否有明显的空隙。如果有且 CPU 侧的operator_details.csv显示 Host 侧下发耗时很长那就是 Host Bound。缓解手段包括使用图模式torch.compile 或昇腾的图执行、增大 batch 摊薄下发开销、减少 Python 侧的同步操作如频繁.item()、.cpu()。4.3 AI_CPU 算子占比异常前面提过AI_CPU 算子多说明有算子回退。除了看 Type 列还可以结合算子名称判断。常见的回退算子包括一些特殊的索引操作、动态 shape 相关的算子、以及部分自定义算子。处理思路先确认这个算子是否真的必须回退有些算子昇腾确实没有 AI Core 实现如果是看能否用等价的高效算子替换如果只是 shape 或 dtype 不满足触发条件调整输入往往能解决。4.4 通信算子与计算算子串行多卡训练时kernel_details.csv里会出现HcclAllReduce、HcclAllGather等通信算子。如果发现通信算子和计算算子在时间轴上完全串行通信时计算单元空闲计算时通信通道空闲说明没有做通信计算 overlap。这时候要检查是否用了torch_npu.npu.set_compile_mode或分布式相关的 overlap 配置梯度 allreduce 是否和反向计算重叠。理想情况下反向传播算梯度的同时就应该开始通信。5. 那些让人抓狂的报错与采集陷阱5.1 npu is selected as device, but torch_npu is not available这个报错几乎每个昇腾新手都会遇到。字面意思是选了 NPU 设备但 torch_npu 不可用。根因通常是import 顺序问题必须先import torch再import torch_npu且torch_npu要在使用.npu()之前导入。有些代码里import torch_npu写在函数内部导致模块级代码执行时还没导入。环境变量未设置某些版本需要设置ASCEND_HOME_PATH等环境变量或者需要 source 昇腾的 set_env.sh。版本不匹配torch 和 torch_npu 版本必须严格对应比如 torch 2.1.0 要配对应版本的 torch_npu混装会直接导致导入失败或功能异常。排查顺序先确认import torch_npu不报错再确认torch.npu.is_available()返回 True最后才去调.npu()。5.2 profiler 采集后训练变慢十倍这是最常见的采集陷阱。profiler 开启后训练变慢是正常的但慢十倍就说明配置有问题。检查点是否开了with_stackTrue或profile_memoryTrue关掉。profiler_level是否设成了 Level2降到 Level1。aic_metrics是否开了具体指标先设AiCoreNone。active step 是否设太多3 个足够。提示profiler 的开销主要体现在 Host 侧打点和 Device 侧采集通道。如果 Host 侧本来就紧张打点开销会被放大。所以采集时的绝对耗时没有参考价值只有相对占比有意义。5.3 采出来的时间对不上实际有同学反馈明明训练一个 step 要 800ms但kernel_details.csv里所有算子 Duration 加起来才 300ms。这中间的 500ms 去哪了答案通常在三个地方算子之间的空隙Host 下发慢导致的等待这部分不计入任何算子的 Duration。未采集的算子profiler 可能漏采某些类型的算子或者采集范围没覆盖到。同步点.item()、.cpu()、torch.npu.synchronize()等同步操作会阻塞但本身不是算子。要定位这部分需要结合trace_view.json在 Chrome 的chrome://tracing里看时间轴或者看operator_details.csv里 Host 侧的下发耗时。5.4 多卡采集数据混乱多卡训练时每个 rank 都会生成自己的 profiler 输出目录。如果不做区分文件会互相覆盖或混在一起。正确做法是给每个 rank 单独的目录on_trace_readytorch.profiler.tensorboard_trace_handler(f./prof_out/rank_{rank})分析时也要分 rank 看因为不同 rank 的负载可能不均衡尤其是流水线并行场景。6. 让采集数据真正可用的几个实操习惯6.1 固定随机种子和输入性能分析最忌讳数据波动。采集前固定随机种子、用固定的输入数据能保证多次采集的结果可比。否则你这次采到 Conv2D 是 900us下次是 1100us根本分不清是优化生效了还是数据波动。torch.manual_seed(42) import numpy as np np.random.seed(42)6.2 先跑够 warmup 再采除了 schedule 里的 warmup整个训练脚本在采集前最好先跑几十个 step。因为算子编译、内存池预热、通信域建立都需要时间。我一般会先跑 20 个 step 再启动 profiler。6.3 建立自己的基线数据优化前先采一份基线存好。每次改动后采一份新的对比 Top 算子的 Duration 变化。这样能清楚知道每次优化到底带来了多少收益而不是凭感觉。我习惯把关键指标记成一个简单的表版本总 step 时间Top1 算子耗时AI_CPU 占比备注baseline800msTransData 1820us12%原始统一 format620msConv2D 960us8%消除 TransData开图模式480msConv2D 950us5%减少下发这张表比任何文字描述都有说服力。6.4 结合 trace_view.json 交叉验证kernel_details.csv是汇总视图trace_view.json是时间轴视图。前者告诉你谁最耗时后者告诉你为什么耗时。两者结合才能形成完整结论。比如 CSV 显示某算子 Wait_time 高去 trace 里一看发现它在等一个通信算子那问题就定位到通信上了。6.5 注意 profiler 版本差异torch_npu 的 profiler 接口在不同版本间有变化比如_ExperimentalConfig的参数名、AiCMetrics的枚举值都可能不同。升级版本后原来的采集脚本可能要改。建议在脚本里加版本判断或者干脆锁定版本避免分析到一半接口变了。7. 关于昇腾精度与设备认知的一点补充搜索热词里出现了昇腾310p3使用什么精度昇腾系列有哪些gpu这类问题顺便说两句。昇腾 310P 系列推理卡常用的精度是 FP16 和 INT8部分场景支持 FP32训练卡如 910 系列则以 FP16/BF16 为主FP32 通常用于精度敏感的部分。至于昇腾系列有哪些 GPU——昇腾是 NPU 架构不是 GPU这个区分在排查问题时很重要因为很多 GPU 上的调优经验比如 CUDA stream、cuDNN 相关在昇腾上并不直接适用得换成对应的 AscendCL 和算子库概念。理解这一点你在读kernel_details.csv时就不会困惑于为什么没有 CUDA 相关的列而是看到 Stream_id、Block_dim 这些昇腾特有的字段。8. 我在实际调优中总结的几条经验第一不要一上来就追求 Level2 的细粒度数据。我见过太多人卡在 AiCore 指标的解析上结果连最基本的 Top 算子都没排出来。先用 Level1 把大头找出来往往 80% 的性能问题在算子级就能定位。第二Wait_time 是被严重低估的指标。很多人只看 Duration忽略了 Wait_time。但在动态图 小 batch 的场景下Wait_time 经常是 Duration 的好几倍这才是真正的瓶颈所在。第三TransData 是昇腾性能分析里的高频嫌疑人。只要它出现在 Top 列表里基本就意味着有格式转换开销值得优先排查。第四采集数据要对比着看。单次采集的数据只能告诉你现状只有和基线对比才能知道优化有没有生效、生效了多少。养成存基线、做对比的习惯比任何单次分析都值钱。最后分享一个小技巧如果kernel_details.csv太大不好读可以先用 pandas 按 Name 聚合算出每个算子的总耗时和调用次数再排序。这样能把成千上万行压缩成几十行一眼看清主要矛盾。等锁定了可疑算子再回到原始数据看它的具体调用情况。这个先聚合后下钻的思路在处理大规模 profiler 数据时特别高效。