CANN 性能剖析实战:从原始事件到交互式火焰图

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
CANN-LLM Engine
aclprof API
原始事件: kernel_name, start, duration
TraceEventConverter
trace.json
WebUI FlameGraph 组件
用户交互:缩放/搜索/聚焦
关联到具体请求 ID


二、后端实现:Profiling + 转换

1. 启用 CANN Profiling

cpp 复制代码
// profiler_manager.cpp
#include "acl/acl_prof.h"

void ProfilerManager::start_profiling() {
    // 配置 profiling 类型:ACL_PROF_TASK_TIME(kernel 级)
    aclprofConfig *config = aclprofCreateConfig(
        nullptr, 0,                    // device list (nullptr = all)
        ACL_PROF_TASK_TIME,            // type
        nullptr, 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);
    
    // 解析为结构化事件
    return parse_aclprof_data(data, data_size);
}

🔑 aclprofGetProfileData 返回二进制 blob,需按 aclprofTaskTimeInfo 结构解析


2. 转换为 Trace Event 格式

Chrome Trace 格式示例:

json 复制代码
[
  {
    "name": "Int4Gemm",
    "cat": "NPU",
    "ph": "X",
    "ts": 123456789000,
    "dur": 1500,
    "pid": 0,
    "tid": 1,
    "args": {"request_id": "req-abc123"}
  },
  ...
]

转换逻辑:

cpp 复制代码
// trace_converter.cpp
nlohmann::json convert_to_trace_format(
    const std::vector<KernelEvent>& events,
    const std::unordered_map<uint64_t, std::string>& stream_to_req_id) {

    nlohmann::json trace;
    for (const auto& 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}}}
        });
    }
    return trace;
}

💡 关键技巧 :在调用 ge::Session::Run() 前,将当前 request_id 与 aclrtStream 绑定


3. 按请求 ID 过滤(WebUI 需求)

提供 REST API:

cpp 复制代码
// GET /api/profile?request_id=req-abc123
std::string get_trace_for_request(const std::string& req_id) {
    auto all_events = profiler_.collect_recent_events();
    auto filtered = filter_by_request_id(all_events, req_id);
    return convert_to_trace_format(filtered).dump();
}

三、前端集成:嵌入火焰图

使用开源库 perfetto 或轻量级 d3-flame-graph

在 WebUI 中添加 Tab

vue 复制代码
<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_inputs CPU 时间长
  • 根因:队列锁竞争激烈
  • 优化 :改用无锁队列(如 moodycamel::ConcurrentQueue)

五、自动化性能回归测试

在 CI 中集成:

yaml 复制代码
# .gitlab-ci.yml
performance_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=50ms
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

analyze_flamegraph.py 可检测:

  • 是否出现预期 kernel(如 Int4Gemm)
  • 最大 kernel 耗时是否超阈值
  • 吞吐是否下降 >5%

六、结语:让性能瓶颈无所遁形

通过将 CANN Profiling → Trace Event → 交互式火焰图 全链路打通,我们实现了:

"所见即所得"的性能分析体验------无需专家知识,也能快速定位瓶颈。

这不仅提升了开发效率,更强化了 CANN-LLM 作为生产级推理引擎的可靠性与可维护性。


🔜 下一步建议:

  • 支持 多卡 profiling 聚合视图
  • 实现 自动性能优化建议生成(AI Copilot for Profiling)
  • 构建 完整的 MLOps 流水线(训练→量化→部署→监控)
相关推荐
GlobalInfo9 小时前
2026年全球机器人用超平执行器市场研究报告:市场规模达7.51亿美元,人形机器人商业化与轻量化运动控制需求推动行业增长
大数据·人工智能·数码相机·microsoft·机器人
共享家952711 小时前
408 操作系统第一章
服务器·数据库·windows
Sakura_j_h_13 小时前
GitHub Actions 打包 Windows exe:中文编码与产物上传
windows·ci/cd·打包发布·github·pyinstaller·github actions
xixiaoyunya16 小时前
搭一个浏览器即用的二进制文件分发点:目录规范与权限边界实践
网络·windows
麻瓜code16 小时前
Spring AI 全模块 API 字典一览(1.1.0 版)
人工智能·windows·spring
编码者卢布17 小时前
【Azure Container Apps】为什么通过 Diagnostic Settings 发送到 Log Analytics 中的日志会延迟几分钟?
microsoft·azure
zrc00700718 小时前
steam无法登录 steam错误 登录时出现错误。请稍后再试。解决办法 steam无法登录解决办法 湖南地区steam DNS问题解决办法
windows·经验分享·游戏·缓存·游戏程序·创业创新·玩游戏
BK贝壳19 小时前
布尔队列 BHA_Queue:入队出队均摊 O(1);附 BHA_Dict
windows
-星空下无敌19 小时前
Windows Docker K8s 安装部署Naocs ,SpringBoot 3.x 4.x 整合Naocs 版本兼容 配置中心、服务注册与发现
windows·docker·nacos·kubernetes·springboot·alibaba
燃烧的枫19 小时前
Windows 下 YOLO11 Pose 的 TensorRT C++ 部署
windows·yolo·tensorrt·1024程序员节·yolo11-pose