CANN调优工具链全景:从profiler到tensorboard的完整观测体系
有个团队找我说,他们买了昇腾NPU集群,花了大半年才把调优工具链搭起来。每个人用不同的工具,各看各的数据,互相之间对不上。最后我帮他们梳理了一套统一的工具链------从底层profiler到上层tensorboard,覆盖了观测数据的采集、存储、查询、可视化全链路。
这篇把昇腾NPU生态里的所有调优工具串起来,告诉你每个工具在哪个环节用,以及怎么组合起来形成完整的观测体系。
工具链总览:五层观测
昇腾NPU的调优工具链分五层,从硬件到应用:
code
应用层(你的模型)
↓ 调用
算子层(ops-nn, ops-transformer...)
↓ 调用
CANN Runtime(canD, opbase...)
↓ 调用
驱动层(ACL, HCCS...)
↓ 访问
硬件层(达芬奇核心, AI CPU, HBM...)
每层都有对应的观测工具。
第一层:cann-colt-profiler(硬件层)
这是最底层的profiler,直接看达芬奇核心的执行流水线。能看到的指标:
- 每个指令周期的 CU utilization(C0/C1/C2利用率)
- Cube Unit 和 Vector Unit 的占用率
- Unified Buffer 的命中/未命中
- HBM 的读/写带宽
python
from cann_colt_profiler import ColtProfiler
profiler = ColtProfiler()
# 配置要抓哪些硬件事件
profiler.config(
events=[
"aic_metric_aiv_u利用率",
"aic_metric_cube_u利用率",
"aic_metric_vec_u利用率",
"aic_metric_l2命中率",
"aic_metric_hbm读写带宽",
],
interval=100 # 每100个cycle采一次
)
profiler.start()
# 跑你的算子
x = torch.randn(1024, 1024).npu()
for _ in range(100):
y = torch.matmul(x, x)
profiler.stop()
# 打印结果
report = profiler.report()
print(f"Cube利用率: {report['cube_u_util']:.1f}%")
print(f"Vector利用率: {report['vec_u_util']:.1f}%")
print(f"L2命中率: {report['l2_hit_rate']:.1f}%")
print(f"HBM带宽: {report['hbm_bandwidth_gbps']:.1f} GB/s")
# 如果 cube_u_util < 60%,说明 Cube 没吃饱
# 常见原因:
# 1. 算子太小,kernel启动开销占比高
# 2. tiling 不合理,片上数据不够用
# 3. 数据格式不对(没用 NC1HWC0)
第二层:fwkblade(算子层)
第二篇已经详细介绍过fwkblade(第14篇)。它是算子层的profiler,能看到:
- 每个算子的耗时
- 算子之间的依赖关系
- Host侧和Device侧的并行情况
python
from fwkblade import Profiler, ProfileConfig
config = ProfileConfig(
activities=["ai_core", "ai_cpu", "host", "memory"],
with_stack=True,
record_shapes=True
)
profiler = Profiler(config)
profiler.start()
# 跑模型
model(input_data)
profiler.stop()
# 生成 timeline(可以用 Chrome 的 trace event viewer 打开)
profiler.export_timeline("trace.json")
# 打开方式:在 Chrome 地址栏输入 chrome://tracing
# Load -> 选择 trace.json
# 打印算子汇总
report = profiler.summary()
for op in report.top_ops(10):
print(f"{op.name:40s} {op.duration_ms:7.3f}ms ({op.percentage:.1f}%)")
第三层:msadvisor(应用层)
第三篇也提过msadvisor(第17篇)。它是应用层的调优顾问,能给出具体的优化建议,不只是"看数据"。
python
from msadvisor import ModelAdvisor
advisor = ModelAdvisor(
model=model,
input_example=torch.randn(1, 3, 224, 224).npu(),
optimization_goal="throughput"
)
report = advisor.analyze()
# msadvisor 会:
# 1. 自动识别可融合的算子对
# 2. 指出格式转换的冗余
# 3. 推荐最优的 tiling 配置
# 4. 生成优化后的模型代码
# 直接应用优化建议
optimized = advisor.apply_optimizations()
torch.save(optimized.state_dict(), "model_optimized.pth")
第四层:TensorBoard(可视化)
CANN的数据可以导出成TensorBoard格式,在TensorBoard里统一看:
python
# 训练指标的 TensorBoard 导出
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("runs/npu_training")
for step, (loss, acc) in enumerate(train_metrics):
writer.add_scalar("Loss/train", loss, step)
writer.add_scalar("Accuracy/train", acc, step)
writer.add_scalar("NPU/Memory_Used_GB", npu_memory_used, step)
writer.add_scalar("NPU/AI_Core_Util", aicore_util, step)
writer.close()
# 启动 TensorBoard
# tensorboard --logdir=runs
# 然后在浏览器打开 http://localhost:6006
python
# 把 fwkblade 的 profiling 数据也导入 TensorBoard
from fwkblade.tensorboard import TensorBoardExporter
exporter = TensorBoardExporter("runs/npu_profile")
# 导出算子耗时
exporter.export_op_stats("trace.json")
# 导出内存使用
exporter.export_memory_stats("memory_profile.json")
# 现在 TensorBoard 里可以同时看:
# - 训练曲线(loss、acc)
# - 算子耗时(哪个算子最慢)
# - 内存使用(显存趋势)
第五层:Prometheus + Grafana(生产监控)
生产环境用Prometheus采集实时指标,Grafana做监控看板:
python
from prometheus_client import start_http_server, Gauge, Counter
# 启动 Prometheus exporter(端口 9090)
start_http_server(9090)
# 定义指标
npu_util = Gauge("npu_aicore_utilization", "AI Core utilization", ["device_id"])
npu_memory = Gauge("npu_hbm_used_bytes", "HBM memory used", ["device_id"])
inference_latency = Gauge("inference_latency_ms", "Inference latency", ["model"])
request_count = Counter("inference_requests_total", "Total requests", ["status"])
# 在推理循环里更新指标
for device_id in range(8):
npu_memory.labels(device_id=str(device_id)).set(get_hbm_usage(device_id))
npu_util.labels(device_id=str(device_id)).set(get_aicore_util(device_id))
for request in requests:
inference_latency.labels(model="resnet50").observe(request.latency_ms)
request_count.labels(status=request.status).inc()
yaml
# prometheus.yml
global:
scrape_interval: 5s
scrape_configs:
- job_name: 'npu_inference'
static_configs:
- targets: ['localhost:9090']
Grafana看板要关注的几个核心指标:
- AI Core利用率(所有卡):应该 > 70%,否则有瓶颈
- HBM内存使用率(每张卡):应该 < 90%,否则可能OOM
- 推理延迟P99:应该 < 100ms
- 请求成功率:应该 > 99.9%
工具链组合:定位性能瓶颈的标准流程
用这套工具链定位瓶颈,分四步走:
第一步:用colt-profiler扫硬件层
python
# 快速扫一遍,看Cube利用率和HBM带宽
from cann_colt_profiler import quick_scan
result = quick_scan(model, input_data, duration_s=1.0)
print(f"Cube利用率: {result['cube_util']:.1f}%")
print(f"Vector利用率: {result['vec_util']:.1f}%")
print(f"HBM带宽: {result['hbm_bw']:.1f} GB/s")
if result['cube_util'] < 50:
print("→ 硬件层:Cube利用率低")
print(" → 可能原因:算子融合不充分 / tiling不合理")
elif result['hbm_bw'] > 1000: # 接近峰值
print("→ 硬件层:HBM带宽瓶颈")
print(" → 可能原因:算子太小 / 内存访问模式不友好")
第二步:用fwkblade定位到具体算子
python
# 如果第一步没定位到,用fwkblade看算子层
from fwkblade import Profiler
profiler = Profiler()
profiler.start()
model(input_data)
profiler.stop()
# 打印TOP 10最慢的算子
for op in profiler.summary().top_ops(10):
print(f"{op.name}: {op.duration_ms:.3f}ms ({op.percentage:.1f}%)")
第三步:用msadvisor给出优化建议
python
# 定位到慢算子之后,用msadvisor分析优化方向
from msadvisor import ModelAdvisor
advisor = ModelAdvisor(model, input_data)
report = advisor.analyze()
# 打印针对这个模型的优化建议
for suggestion in report.suggestions:
print(f"[{suggestion.priority}] {suggestion.description}")
print(f" 预计收益: {suggestion.estimated_speedup}x")
print(f" 操作步骤: {suggestion.action}")
第四步:用tensorboard验证优化效果
python
# 优化前后对比
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("runs/comparison")
# 优化前
profiler_before = fwkblade.Profile(...)
writer.add_scalar("Latency/Before", profiler_before.latency_ms, 0)
# 优化后
profiler_after = fwkblade.Profile(...)
writer.add_scalar("Latency/After", profiler_after.latency_ms, 0)
# 对比
improvement = profiler_before.latency_ms / profiler_after.latency_ms
print(f"优化提升: {improvement:.2f}x")
工具选型指南
| 场景 | 工具 | 为什么用它 |
|---|---|---|
| 硬件层性能扫盲 | cann-colt-profiler | 看CU utilization、HBM带宽 |
| 算子级瓶颈定位 | fwkblade | 看每个算子耗时、依赖关系 |
| 不知道从哪改 | msadvisor | 自动给优化建议 |
| 训练过程监控 | TensorBoard | 看loss曲线、指标趋势 |
| 生产环境监控 | Prometheus + Grafana | 实时告警、自动巡检 |
| 延迟分布分析 | ais_bench | 测P50/P90/P99、对比baseline |
| 跟官方数据对标 | ais_bench | 标准化测试条件,可对标 |
| 迁移前评估 | ais_bench | 测GPU vs NPU性能比 |
踩过的坑:工具本身的overhead
python
# 坑1:开了profiling之后性能测不准
# fwkblade本身有overhead,会让结果偏慢10-30%
# 解决:测性能的时候关profiling,只在定位问题时才开
profiler = Profiler()
profiler.start() # profiling 开启,性能下降
model(input_data) # 这次的结果不准
profiler.stop()
profiler.disable_profiling() # 关掉 profiling
model(input_data) # 这次的结果才是真实的
# 坑2:tensorboard写入影响训练速度
# 每个step都写tensorboard会很慢
# 解决:每N个step写一次,不要每个step都写
for step in range(10000):
loss = train_step()
if step % 100 == 0: # 每100步写一次
writer.add_scalar("Loss", loss, step)
# 坑3:colt-profiler采集间隔太短会测不准
# interval=10(每10个cycle采一次)会让probe本身影响测量
# 解决:interval=100或更长
profiler.config(interval=100) # 每100个cycle采一次,比较准
完整的调优闭环
code
发现性能问题
↓
colt-profiler 扫硬件层 → 定位到Cube/Vector/HBM瓶颈
↓
fwkblade 定位到具体算子
↓
msadvisor 给出优化建议
↓
应用优化(改代码 / 调配置 / 重编译)
↓
ais_bench 测性能提升
↓
TensorBoard 记录优化前后的对比曲线
↓
Prometheus + Grafana 上线监控
↓
发现新的性能问题(循环)