CANN 性能剖析实战:从原始事件到交互式火焰图
cann组织链接:https://atomgit.com/cann
ops-nn仓库链接:https://atomgit.com/cann/ops-nn
🎯 目标
- 利用
aclprof采集 NPU kernel 执行事件 - 转换为Chrome Trace Event 格式
- 渲染为可缩放、可搜索的火焰图
- 嵌入 WebUI,支持按请求 ID 过滤
- 定位瓶颈:如
Int4Gemm占比过高、PagedAttention内存带宽受限
✅ 全流程自动化,无需手动导出
.json
一、整体数据流
二、后端实现:Profiling + 转换
1.启用 CANN Profiling
// profiler_manager.cpp#include"acl/acl_prof.h"voidProfilerManager::start_profiling(){// 配置 profiling 类型:ACL_PROF_TASK_TIME(kernel 级)aclprofConfig*config=aclprofCreateConfig(nullptr,0,// device list (nullptr = all)ACL_PROF_TASK_TIME,// typenullptr,0// data type (default));aclprofStart(config);}std::vector<KernelEvent>ProfilerManager::stop_and_collect(){aclprofStop();// 获取原始数据size_t data_size=0;void*data=aclprofGetProfileData(ACL_PROF_TASK_TIME,&data_size);// 解析为结构化事件returnparse_aclprof_data(data,data_size);}🔑
aclprofGetProfileData返回二进制 blob,需按aclprofTaskTimeInfo结构解析
2.转换为 Trace Event 格式
Chrome Trace 格式示例:
[{"name":"Int4Gemm","cat":"NPU","ph":"X","ts":123456789000,"dur":1500,"pid":0,"tid":1,"args":{"request_id":"req-abc123"}},...]转换逻辑:
// trace_converter.cppnlohmann::jsonconvert_to_trace_format(conststd::vector<KernelEvent>&events,conststd::unordered_map<uint64_t,std::string>&stream_to_req_id){nlohmann::json trace;for(constauto&e:events){// 通过 stream ID 关联 request_id(需在 launch kernel 时记录映射)std::string req_id=stream_to_req_id.at(e.stream_id);trace.push_back({{"name",e.kernel_name},{"cat","NPU"},{"ph","X"},// Complete event{"ts",e.start_ns/1000},// microseconds{"dur",e.duration_ns/1000},{"pid",0},{"tid",static_cast<int>(e.stream_id)},{"args",{{"request_id",req_id}}}});}returntrace;}💡关键技巧:在调用
ge::Session::Run()前,将当前request_id与aclrtStream绑定
3.按请求 ID 过滤(WebUI 需求)
提供 REST API:
// GET /api/profile?request_id=req-abc123std::stringget_trace_for_request(conststd::string&req_id){autoall_events=profiler_.collect_recent_events();autofiltered=filter_by_request_id(all_events,req_id);returnconvert_to_trace_format(filtered).dump();}三、前端集成:嵌入火焰图
使用开源库perfetto或轻量级d3-flame-graph
在 WebUI 中添加 Tab
<template> <div v-if="activeTab === 'flamegraph'"> <input v-model="filterRequestId" placeholder="Filter by Request ID" /> <button @click="loadFlameGraph">Load</button> <div id="flamegraph-container"></div> </div> </template> <script> import 'd3-flame-graph/dist/d3-flamegraph.css'; import flamegraph from 'd3-flame-graph'; export default { methods: { async loadFlameGraph() { // 1. 获取 trace.json const resp = await fetch(`/api/profile?request_id=${this.filterRequestId}`); const trace = await resp.json(); // 2. 转换为 d3-flame-graph 所需的树形结构 const tree = this.traceToTree(trace); // 3. 渲染 const chart = flamegraph() .width(800) .cellHeight(18) .transitionDuration(750) .minFrameSize(5) .onClick(d => console.log('Clicked:', d)); d3.select("#flamegraph-container") .datum(tree) .call(chart); }, traceToTree(traceEvents) { // 将 flat events 聚合成 call stack tree // (简化:按时间排序后模拟栈) const stacks = group_events_into_stacks(traceEvents); return build_tree_from_stacks(stacks); } } } </script>✅ 用户输入
req-abc123,即可看到该请求的完整 kernel 调用栈耗时分布
四、典型性能问题诊断示例
场景 1:INT4 GEMM 成为瓶颈
- 现象:火焰图中
Int4Gemm占据 70%+ 高度 - 根因:group_size 过小 → scale/zeros 访问频繁
- 优化:增大 group_size 至 256,重力量化模型
场景 2:PagedAttention 内存带宽受限
- 现象:
SparseFusedAttention中大量小 block 加载 - 根因:block_size=8 太小,导致随机访存
- 优化:调整 block_size=32,提升缓存命中率
场景 3:调度开销过高
- 现象:
select_batch/prepare_inputsCPU 时间长 - 根因:队列锁竞争激烈
- 优化:改用无锁队列(如
moodycamel::ConcurrentQueue)
五、自动化性能回归测试
在 CI 中集成:
# .gitlab-ci.ymlperformance_test:script:-./llm_server--profile--test-prompt="..." &-sleep 10-curl http://localhost:8080/api/profile>trace.json-python tools/analyze_flamegraph.py trace.json--threshold=50msrules:-if:$CI_COMMIT_BRANCH == "main"analyze_flamegraph.py可检测:
- 是否出现预期 kernel(如
Int4Gemm) - 最大 kernel 耗时是否超阈值
- 吞吐是否下降 >5%
六、结语:让性能瓶颈无所遁形
通过将CANN Profiling → Trace Event → 交互式火焰图全链路打通,我们实现了:
“所见即所得”的性能分析体验——无需专家知识,也能快速定位瓶颈。
这不仅提升了开发效率,更强化了 CANN-LLM 作为生产级推理引擎的可靠性与可维护性。
🔜 下一步建议:
- 支持多卡 profiling 聚合视图
- 实现自动性能优化建议生成(AI Copilot for Profiling)
- 构建完整的 MLOps 流水线(训练→量化→部署→监控)