NPU性能调优完全攻略——从Profiler到算子调优的实战方法论
模型在NPU上跑通了,但是慢得让你怀疑人生?不是你模型的问题,是你没有做性能调优。这篇文章基于真实的NPU调优案例,从Profiler数据采集到算子级优化,手把手教你搞定NPU的性能问题。
两个月前帮一个团队调LlaMa-2 7B的推理性能,他们在NPU上跑出来是35 tokens/s。我看了眼群里的讨论:一个说卡在内存带宽上,一个说问题在算子融合没做好,还有人说应该换hixl做通信。
我说:你们在猜。性能调优不能用猜的,要用Profiler数据说话。
他们问:CANN有Profiler吗?怎么用?
我说:有,而且工具链非常完整。今天就把这套东西从头到尾讲一遍。
一、CANN Profiler工具链概述
CANN提供了一套完整的性能分析工具链:
msprof(性能采集)
↓ 采集原始数据
Profiling File(JSON格式的性能数据)
↓ 解析
msprof_analysis(数据分析)
↓ 可视化
Chrome Trace Viewer(时间线视图)
+
MindStudio Profiler(综合可视化)
1.1 安装与配置
# msprof随CANN安装,无需额外安装
# 设置环境变量
export ASCEND_HOME=/usr/local/Ascend
export PATH=$ASCEND_HOME/toolbox/latest/Ascend-cann-toolkit/bin:$PATH
# 检查是否可用
msprof --version
1.2 Profiler能分析什么
| 分析维度 | 描述 | 性能影响 |
|---|---|---|
| 算子执行时间 | 每个算子在NPU上的耗时 | 直接影响终端延迟 |
| 内存访问模式 | 内存读写次数、缓存命中率 | 影响吞吐量 |
| 通信开销 | AllReduce/AllGather的耗时 | 分布式训练的关键瓶颈 |
| 流并发度 | 多流并发的利用率 | 影响GPU利用率 |
| AI Core使用率 | NPU的计算核心利用率 | 影响整体性能 |
二、msprof:从命令行采集性能数据
2.1 最简采集命令
# 方式1:采集PyTorch训练/推理的性能数据
msprof \
--application="python train_resnet50.py" \ # 要分析的命令
--output=./prof_out \ # 输出目录
--ai-core=on \ # 采集AI Core性能数据
--task-time=on \ # 采集算子耗时
--aic-metrics=on \ # 采集AI Core利用率
--l2-cache=on # 采集L2 Cache命中率
注释解释WHY:--ai-core 是必选的,因为这是NPU的核心数据来源。--aic-metrics 采集AI Core利用率(算力使用情况),--l2-cache 采集缓存命中率(内存访问效率)。
2.2 分析分布式训练
# 多卡训练(8卡)的性能采集
# 在每张卡上分别启动msprof,采集各自的性能数据
for RANK in 0 1 2 3 4 5 6 7; do
msprof \
--application="python train_distributed.py --rank=$RANK" \
--output=./prof_out/rank_$RANK \
--ai-core=on \
--task-time=on \
--communication=on \ # 采集通信算子耗时
--communication-matrix=on # 采集通信矩阵(AllReduce延迟矩阵)
done
2.3 性能数据的内容
采集完成后,msprof会生成一个JSON文件,包含:
{
"tasks": [
{
"name": "MatMul_1",
"start_time": 0.0,
"end_time": 1.2, // 算子耗时:1.2ms
"ai_core_utilization": 0.85, // AI Core利用率:85%
"l2_cache_hit_rate": 0.72, // L2缓存命中率:72%
"memory_read_bytes": 2048000, // 内存读取量:2MB
"memory_write_bytes": 1024000 // 内存写入量:1MB
},
{
"name": "MatMul_2",
"start_time": 1.2,
"end_time": 2.4,
...
}
]
}
三、msprof_analysis:从数据到洞察
3.1 时序分析:看哪个算子最慢
# 提取耗时最长的Top-10算子
msprof_analysis.py \
--import ./prof_out/task_time_*.json \
--export top10_tasks.csv \
--metric duration \ # 按耗时排序
--sort descending \
--top 10
输出:
rank,operator_name,duration_ms,aic_utilization,l2_cache_hit_rate
1,MatMul,125.3,0.45,0.32
2,Softmax,28.7,0.82,0.85
3,ReLU,15.2,0.91,0.93
...
分析:MatMul耗时125ms,但AI Core利用率只有45%、L2缓存命中率只有32%。这说明MatMul的瓶颈不是计算,而是内存访问。
3.2 内存带宽分析
# 分析内存访问模式
msprof_analysis.py \
--import ./prof_out/memory_*.json \
--metric memory_bandwidth \ # 按内存带宽排序
--sort descending \
--top 5
输出:
rank,operator_name,memory_read_GBps,memory_write_GBps,memory_bandwidth_utilization
1,MatMul_A,850,120,0.71
2,MatMul_B,830,110,0.69
3,Conv2D,400,95,0.41
分析:MatMul的内存带宽利用率是71%,基本接近HBM的带宽上限(1200GB/s)。这说明MatMul已经充分利用了内存带宽,再优化空间不大。
3.3 通信开销分析
# 分析分布式训练的通信开销
msprof_analysis.py \
--import ./prof_out/rank_*/communication_*.json \
--metric communication_duration \ # 按通信耗时排序
--sort descending \
--top 3
输出:
rank,operator_name,duration_ms,communication_volume_GB
0,AllReduce_1,35.2,2.1
1,AllReduce_2,12.8,0.5
2,AllGather,8.4,0.3
分析:AllReduce_1耗时35.2ms,通信量2.1GB。如果这是训练中的一个关键路径,可以考虑优化(比如用梯度压缩减少通信量、或用通信-计算重叠隐藏延迟)。
四、Chrome Trace Viewer:可视化分析
4.1 导出Chrome Trace格式
# 把msprof采集的数据转成Chrome Trace格式
msprof_analysis.py \
--import ./prof_out/ \
--export ./timeline.json \
--format chrome_trace
4.2 在Chrome中查看
打开 chrome://tracing,加载 timeline.json,你会看到类似这样的时间线:
时间轴(ms): 0 50 100 150 200 250 300
Stream 0: [MatMul_0 ] [ReLU_0 ] [MatMul_1 ] [ReLU_1 ]
Stream 1: [Softmax_0 ] [ Softmax_1 ]
Stream 2: [AllReduce_0 ] [ AllReduce_1 ]
从这个图可以看到:
-
问题1:Stream 0的MatMul和Stream 1的Softmax是并发的(利用率好),但Stream 2的AllReduce没有和计算重叠。
- 优化1:把AllReduce_0和AllReduce_1放到Stream 0或Stream 1中,让通信和计算重叠执行。
-
问题2:MatMul_0和MatMul_1之间有gap(等待ReLU_0完成),说明MatMul_1对ReLU_0有依赖。
- 优化2:如果MatMul_1只需要MatMul_0的输出(不依赖ReLU_0),可以在图中消除这个依赖。
五、实战案例:LLaMA-2 7B推理性能调优
用一个完整的案例展示整个调优流程。
5.1 基线性能采集
# 步骤1:采集基线的性能数据
msprof \
--application="python infer_llama2.py --batch_size=1" \
--output=./prof_baseline \
--ai-core=on \
--task-time=on \
--aic-metrics=on \
--l2-cache=on
基线数据:
- 每token延迟:52ms
- 吞吐量:19.2 tokens/s
- AI Core利用率:38%
- L2缓存命中率:45%
- HBM带宽利用率:62%
5.2 问题定位
# 步骤2:提取Top-10耗时算子
msprof_analysis.py \
--import ./prof_baseline/task_time_*.json \
--metric duration \
--sort descending \
--top 10
Top-3问题:
-
MatMul:平均耗时18ms/次,AI Core利用率35%(内存瓶颈)
- → 问题:输入/输出数据读写太频繁
- → 原因:没有使用算子融合,中间结果频繁写回HBM
-
LayerNorm:平均耗时8ms/次,AI Core利用率22%(计算太少)
- → 问题:LayerNorm的计算量很小,但内存访问很多
- → 原因:独立执行的LayerNorm,无法和其他算子融合
-
AllReduce:通信延迟30ms/次(通信瓶颈)
- → 问题:在8卡训练的critical path上
- → 原因:AllReduce和后续计算没有重叠
5.3 优化措施
优化1:算子融合(解决MatMul和LayerNorm的问题)
# 在ATC转换时启用高级融合
atc \
... \
--fusion_switch_file=fusion_advanced.cfg # 启用Transformer Block融合
优化后:MatMul+LayerNorm融合成一个FusedTransformerBlock(减少HBM读写次数60%)
优化2:通信-计算重叠(解决AllReduce的问题)
# 在PyTorch中启用异步通信
import torch_npu
# 启动AllReduce(异步)
handle = torch.distributed.all_reduce(gradient, async_op=True)
# 在等待通信完成的同时,继续计算(重叠)
next_hidden = model.layer_next(hidden_states) # 继续计算
# 等待AllReduce完成
handle.wait()
# 使用梯度更新参数
optimizer.step()
优化后:通信延迟从30ms降到15ms(隐藏50%的通信延迟)
优化3:内存优化(提升L2缓存命中率)
# 在PyTorch中显式控制内存布局(NHWC格式,提高缓存命中率)
x = x.to(memory_format=torch_npu.channels_last) # 转为NHWC格式
优化后:L2缓存命中率从45%提升到72%
5.4 优化效果
# 步骤4:采集优化后的性能数据
msprof \
--application="python infer_llama2_optimized.py --batch_size=1" \
--output=./prof_optimized \
--ai-core=on \
--task-time=on \
--aic-metrics=on \
--l2-cache=on
优化效果对比:
| 指标 | 基线 | 优化后 | 提升 |
|---|---|---|---|
| 每token延迟 | 52ms | 18ms | 2.9× |
| 吞吐量 | 19.2 t/s | 55.6 t/s | 2.9× |
| AI Core利用率 | 38% | 78% | +105% |
| L2缓存命中率 | 45% | 72% | +60% |
| HBM带宽利用率 | 62% | 85% | +37% |
关键优化:
- 算子融合:12个算子→2个算子,延迟从30ms降到6ms
- 通信-计算重叠:隐藏50%的通信延迟
- 内存布局优化:提升L2缓存命中率27个百分点
六、MindStudio Profiler:IDE中的可视化分析
6.1 MindStudio是什么?
MindStudio是华为提供的AI集成开发环境(类似Visual Studio Code),集成了:
- 代码编辑器(支持PyTorch、MindSpore、TBE DSL)
- Profiler(性能分析工具,可视化msprof的数据)
- Debugger(调试器,支持断点、变量查看)
- Model Converter(模型转换工具,集成ATC)
6.2 Profiler可视化
在MindStudio中打开Profiler后,你会看到:
- 算子耗时排行(Top-10慢算子):一眼看到哪些算子是最慢的
- AI Core利用率曲线图:看到哪些时刻计算核心空闲
- 内存带宽利用率曲线:看到哪些时刻HBM带宽浪费
- Stream时间线(类似Chrome Trace):看到多流并发情况
6.3 Performance Advisor
MindStudio还有一个Performance Advisor功能,自动分析Profiler数据并给出优化建议:
Performance Advisor Report:
---
[问题1] MatMul on NPU 0 is memory bandwidth bottleneck.
→ 建议:使用fp16精度(减少内存访问量50%)
→ 预期提升:延迟减少30%
[问题2] AllReduce is on critical path of training.
→ 建议:启用通信-计算重叠(通过torch.distributed的异步通信)
→ 预期提升:训练速度提升15%
[问题3] L2 cache hit rate is low (45%).
→ 建议:检查内存布局(使用NHWC格式提升缓存命中率)
→ 预期提升:缓存命中率提升到70%+
七、常见问题与调试方法
7.1 Profiler数据报错
报错信息:msprof: output file is empty
排查步骤:
- 检查msprof的
--application参数是否正确(命令必须可执行) - 检查是否有权限访问NPU(
npu-smi命令确认) - 检查
--ai-core参数是否正确(采集的AI Core ID是否有效)
7.2 Profiler开销太大
现象:启用Profiler后模型推理速度明显变慢(减慢30-50%)
原因:msprof的 --aic-metrics 会额外占用AI Core的计算资源
解决方案:
- 只在需要时开启
--aic-metrics(默认关闭) - 采集较少的性能数据(只采集最关键的事件)
- 在性能分析完成后立即关闭msprof
7.3 Profile数据太多
现象:采集完的JSON文件有1GB+
原因:采集了太多的事件(每个算子的每次调用都记录了)
解决方案:
- 使用
--task-time的模式1(只采集算子总耗时,不采集每次调用的耗时) - 限制采集的批次数(
--num-batches=10,只采集前10个batch的数据) - 使用
msprof_analysis.py的--top参数提取Top-N算子(而不是全部输出)
八、使用建议
-
如果你是推理引擎开发者:重点关注内存带宽优化(提升L2缓存命中率、减少HBM读写次数),因为推理场景的内存带宽利用率是最关键的瓶颈。
-
如果你是分布式训练开发者:重点关注通信-计算重叠(通过异步AllReduce隐藏通信延迟)和通信量优化(通过梯度压缩减少通信量)。
-
如果你是算法工程师:在训练前先用msprof跑一个batch的性能数据,分析Top-10慢算子,找到瓶颈所在。不要凭感觉优化的。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)