CANN调优工具链全景:从profiler到tensorboard的完整观测体系![]()
有个团队找我说,他们买了昇腾NPU集群,花了大半年才把调优工具链搭起来。每个人用不同的工具,各看各的数据,互相之间对不上。最后我帮他们梳理了一套统一的工具链——从底层profiler到上层tensorboard,覆盖了观测数据的采集、存储、查询、可视化全链路。
这篇把昇腾NPU生态里的所有调优工具串起来,告诉你每个工具在哪个环节用,以及怎么组合起来形成完整的观测体系。
工具链总览:五层观测
昇腾NPU的调优工具链分五层,从硬件到应用:
应用层(你的模型) ↓ 调用 算子层(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 的读/写带宽
fromcann_colt_profilerimportColtProfiler 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_inrange(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侧的并行情况
fromfwkbladeimportProfiler,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()foropinreport.top_ops(10):print(f"{op.name:40s}{op.duration_ms:7.3f}ms ({op.percentage:.1f}%)")第三层:msadvisor(应用层)
第三篇也提过msadvisor(第17篇)。它是应用层的调优顾问,能给出具体的优化建议,不只是“看数据”。
frommsadvisorimportModelAdvisor 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里统一看:
# 训练指标的 TensorBoard 导出fromtorch.utils.tensorboardimportSummaryWriter writer=SummaryWriter("runs/npu_training")forstep,(loss,acc)inenumerate(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# 把 fwkblade 的 profiling 数据也导入 TensorBoardfromfwkblade.tensorboardimportTensorBoardExporter 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做监控看板:
fromprometheus_clientimportstart_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"])# 在推理循环里更新指标fordevice_idinrange(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))forrequestinrequests:inference_latency.labels(model="resnet50").observe(request.latency_ms)request_count.labels(status=request.status).inc()# prometheus.ymlglobal:scrape_interval:5sscrape_configs:-job_name:'npu_inference'static_configs:-targets:['localhost:9090']Grafana看板要关注的几个核心指标:
- AI Core利用率(所有卡):应该 > 70%,否则有瓶颈
- HBM内存使用率(每张卡):应该 < 90%,否则可能OOM
- 推理延迟P99:应该 < 100ms
- 请求成功率:应该 > 99.9%
工具链组合:定位性能瓶颈的标准流程
用这套工具链定位瓶颈,分四步走:
第一步:用colt-profiler扫硬件层
# 快速扫一遍,看Cube利用率和HBM带宽fromcann_colt_profilerimportquick_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")ifresult['cube_util']<50:print("→ 硬件层:Cube利用率低")print(" → 可能原因:算子融合不充分 / tiling不合理")elifresult['hbm_bw']>1000:# 接近峰值print("→ 硬件层:HBM带宽瓶颈")print(" → 可能原因:算子太小 / 内存访问模式不友好")第二步:用fwkblade定位到具体算子
# 如果第一步没定位到,用fwkblade看算子层fromfwkbladeimportProfiler profiler=Profiler()profiler.start()model(input_data)profiler.stop()# 打印TOP 10最慢的算子foropinprofiler.summary().top_ops(10):print(f"{op.name}:{op.duration_ms:.3f}ms ({op.percentage:.1f}%)")第三步:用msadvisor给出优化建议
# 定位到慢算子之后,用msadvisor分析优化方向frommsadvisorimportModelAdvisor advisor=ModelAdvisor(model,input_data)report=advisor.analyze()# 打印针对这个模型的优化建议forsuggestioninreport.suggestions:print(f"[{suggestion.priority}]{suggestion.description}")print(f" 预计收益:{suggestion.estimated_speedup}x")print(f" 操作步骤:{suggestion.action}")第四步:用tensorboard验证优化效果
# 优化前后对比fromtorch.utils.tensorboardimportSummaryWriter 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_msprint(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
# 坑1:开了profiling之后性能测不准# fwkblade本身有overhead,会让结果偏慢10-30%# 解决:测性能的时候关profiling,只在定位问题时才开profiler=Profiler()profiler.start()# profiling 开启,性能下降model(input_data)# 这次的结果不准profiler.stop()profiler.disable_profiling()# 关掉 profilingmodel(input_data)# 这次的结果才是真实的# 坑2:tensorboard写入影响训练速度# 每个step都写tensorboard会很慢# 解决:每N个step写一次,不要每个step都写forstepinrange(10000):loss=train_step()ifstep%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采一次,比较准完整的调优闭环
发现性能问题 ↓ colt-profiler 扫硬件层 → 定位到Cube/Vector/HBM瓶颈 ↓ fwkblade 定位到具体算子 ↓ msadvisor 给出优化建议 ↓ 应用优化(改代码 / 调配置 / 重编译) ↓ ais_bench 测性能提升 ↓ TensorBoard 记录优化前后的对比曲线 ↓ Prometheus + Grafana 上线监控 ↓ 发现新的性能问题(循环)