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_idaclrtStream 绑定


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 流水线(训练→量化→部署→监控)
相关推荐
宝桥南山11 小时前
AI - 在命令行中尝试一下ACP(Agent Client Protocol)通信
microsoft·微软·github·aigc·copilot
love530love13 小时前
精简版|Claude-HUD 插件介绍 + 一键安装教程
人工智能·windows·笔记
秋914 小时前
MySQL 8.0.46 全平台安装与配置详解(Windows/Linux/macOS)
linux·windows·mysql
善恶怪客14 小时前
LocalSend基本使用
windows
MengMeng_102315 小时前
win10 蓝牙连接音响没有声音设备选项
windows
强殖装甲凯普16 小时前
处理Windows没有msi的默认打开方式
windows·安装·msi
mOok ONSC16 小时前
mysql9.0windows安装
windows·adb
技术钱17 小时前
Prompt组件以及使用技巧
microsoft·prompt
T0uken18 小时前
基于 vcpkg 与 LLVM-MinGW 的 Qt6 静态链接开发方案
c++·windows·qt
无心水18 小时前
【Hermes:Skill系统深度】21、Skill 调试与冲突解决:为什么没触发?怎么修复? —— Honcho 智能体排障完全手册
人工智能·windows·openclaw·养龙虾·hermes·养马·honcho