昇腾训练营报名链接:https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro

训练营简介:2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机、平板、开发板等大奖。


前言

写完算子代码只是第一步,接下来还要编译、调试、优化。刚开始我连怎么编译都不知道,报错信息看得一脸懵。后来慢慢摸索出了一套工作流程,效率提升了很多。

今天就来系统介绍CANN的开发工具链,包括编译器、调试工具、性能分析工具等。掌握这些工具,能让你的开发效率翻倍!

一、CANN工具链全景

CANN提供了一整套完整的开发工具。下图是MindStudio全流程工具链官方页面(https://www.hiascend.com/software/mindstudio):

MindStudio全流程工具链

从官方介绍可以看到,MindStudio是昇腾AI开发的端到端解决方案,提供插件化设计、可视化、自动化、智能化四大特性。

工具链完整流程:

源代码
*.cpp
ascendc编译器
编译
*.o目标文件
*.json中间表示
链接器
链接
*.so动态库
运行时
AscendCL
msopst
算子分析
profiling
性能分析
日志工具
调试

主要工具:

  1. ascendc:Ascend C编译器
  2. msopst:算子静态分析工具
  3. profiling:性能分析工具
  4. 日志系统:运行时调试

二、编译工具:ascendc

2.1 基本用法

最简单的编译命令:

# 编译单个文件
ascendc add_kernel.cpp -o add_kernel.o

# 查看帮助
ascendc --help

2.2 常用编译选项

实际开发中,我常用的编译选项:

ascendc add_kernel.cpp -o add_kernel.o \
    --ascend-arch=Ascend910B \     # 指定芯片架构
    -O2 \                           # 优化级别(O0/O1/O2/O3)
    -g \                            # 生成调试信息
    -std=c++17 \                    # C++标准
    -I./include \                   # 头文件路径
    -D DEBUG                        # 定义宏

各选项说明:

选项 说明 推荐值
--ascend-arch 目标芯片 Ascend910/910B/310P
-O 优化级别 开发用O0,发布用O2
-g 调试信息 调试时加上
-std C++标准 c++14或c++17
-w 禁用警告 不推荐

2.3 实际项目的编译

通常会用Makefile或CMakeLists.txt来管理编译:

Makefile示例

# 编译器
CXX = ascendc

# 编译选项
CXXFLAGS = -O2 -std=c++17 -g
CXXFLAGS += --ascend-arch=Ascend910B

# 头文件路径
INCLUDES = -I$(ASCEND_TOOLKIT_HOME)/include

# 目标文件
TARGET = add_custom.so

# 源文件
SRCS = add_kernel.cpp host_code.cpp

# 编译
all: $(TARGET)

$(TARGET): $(SRCS)
\t$(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $@

clean:
\trm -f $(TARGET) *.o

.PHONY: all clean

使用:

# 编译
make

# 清理
make clean

我一般用Makefile,因为比较简单直观。大项目可以考虑CMake。

2.4 编译错误排查

分享几个我遇到过的编译错误:

错误1:找不到头文件

error: kernel_operator.h: No such file or directory

解决:

# 检查环境变量
echo $ASCEND_TOOLKIT_HOME

# 设置环境变量(如果没有)
source /usr/local/Ascend/ascend-toolkit/set_env.sh

# 编译时指定路径
ascendc -I$ASCEND_TOOLKIT_HOME/include add_kernel.cpp

错误2:语法错误

error: '__aicore__' attribute only applies to function types

这通常是把__aicore__用错地方了:

// ❌ 错误
class __aicore__ MyClass {};

// ✅ 正确
class MyClass {
    __aicore__ void MyFunc() {}
};

错误3:未定义的引用

undefined reference to 'DataCopy'

通常是链接问题,需要添加库文件:

ascendc add_kernel.cpp -o add_kernel.o \
    -L$ASCEND_TOOLKIT_HOME/lib64 \
    -lascendcl

三、算子分析工具:msopst

3.1 msopst是什么?

msopst(MindSpore Operator Static Tool)用于分析算子的中间表示和性能预估。

主要功能:

  • 查看算子的IR(Intermediate Representation)
  • 分析指令序列
  • 预估性能瓶颈
  • 检查内存使用

3.2 基本用法

# 分析算子
msopst --mode=1 --input=add_kernel.o --output=add_kernel.json

# mode说明:
# mode=1: 生成IR和指令分析
# mode=2: 只生成IR

生成的JSON文件包含详细的算子信息:

{
  "op_name": "AddCustom",
  "input_desc": [...],
  "output_desc": [...],
  "attr": {...},
  "task_info": {
    "block_dim": 4,
    "workspace_size": 1024,
    ...
  },
  "ir_info": {
    "instructions": [
      {"type": "vector_add", "latency": 10},
      ...
    ]
  }
}

3.3 分析IR

打开JSON文件,重点看几个字段:

{
  "instructions": [
    {
      "idx": 0,
      "type": "data_copy",
      "src": "GM",
      "dst": "UB",
      "size": 512,
      "latency": 50  // 延迟(周期数)
    },
    {
      "idx": 1,
      "type": "vector_add",
      "operands": ["UB0", "UB1", "UB2"],
      "latency": 10
    },
    ...
  ]
}

从中可以看出:

  • 每条指令的类型
  • 操作的内存位置
  • 预估的延迟

我的使用经验

当算子性能不符合预期时,我会用msopst分析:

  1. 看看是不是数据搬运太多(data_copy指令太多)
  2. 看看是不是有标量操作(应该改成向量操作)
  3. 看看内存使用是否超标

3.4 实战案例

有一次我写的Add算子性能很差,用msopst一分析,发现生成了大量的标量load/store指令:

{
  "instructions": [
    {"type": "scalar_load", "latency": 5},   // 标量操作!
    {"type": "scalar_load", "latency": 5},
    {"type": "scalar_add", "latency": 1},
    {"type": "scalar_store", "latency": 5},
    ...  // 重复256次
  ]
}

原因是我写成了标量循环:

// ❌ 错误:标量循环
for (int i = 0; i < TILE_SIZE; i++) {
    z[i] = x[i] + y[i];
}

改成向量操作后:

// ✅ 正确:向量操作
Add(z, x, y, TILE_SIZE);

再用msopst分析,指令数量从256条变成了16条(FP16向量宽度为16)!性能提升了20倍。

四、性能分析工具:profiling

4.1 启用profiling

在代码中启用性能分析:

#include "acl/acl_prof.h"

// 初始化profiling
aclprofConfig* profilerConfig = aclprofCreateConfig(
    nullptr,  // profiler数据目录
    0,        // 时间段(0表示全程)
    0,
    ACL_AICORE_ARITHMETIC_UTILIZATION |  // 计算利用率
    ACL_AICORE_PIPE_UTILIZATION |        // 流水线利用率
    ACL_AICORE_MEMORY_BANDWIDTH          // 内存带宽
);

aclprofInit("/home/user/profiling_data", sizeof("/home/user/profiling_data"));
aclprofStart(profilerConfig);

// 运行算子
RunKernel(...);

// 停止profiling
aclprofStop(profilerConfig);
aclprofDestroyConfig(profilerConfig);
aclprofFinalize();

4.2 查看profiling结果

执行后会生成profiling数据文件,用可视化工具查看:

# 使用MindStudio的Profiler工具打开
# 或者用命令行工具解析

# 生成的文件:
profiling_data/
├── host_info.json
├── op_summary.csv
├── op_statistic.csv
└── timeline.json

4.3 关键性能指标

重点关注这几个指标:

1. AI Core利用率

AI Core Utilization: 85%
  • >80%:很好,充分利用了硬件
  • 50%-80%:一般,还有优化空间
  • <50%:较差,可能存在瓶颈

2. 内存带宽利用率

Memory Bandwidth Utilization: 60%
  • 如果很低,说明计算密集,瓶颈在计算
  • 如果很高(>90%),说明访存密集,瓶颈在内存

3. 算子执行时间

Kernel Execution Time: 125 us

对比理论值,看是否符合预期。

4. 流水线效率

Pipeline Efficiency: 92%

双缓冲做得好,这个值应该很高。

4.4 性能优化方向

根据profiling结果确定优化方向:

Profiling结果
AI Core利用率
增加并行度
优化向量化
内存带宽利用率
减少访存
增加数据复用
增加计算强度
算子融合

五、日志与调试

5.1 CANN日志系统

CANN的日志保存在:

# 默认日志路径
/var/log/npu/

# 主要日志文件
/var/log/npu/slog/host-0/  # Host日志
/var/log/npu/slog/device-0/  # Device日志

查看日志:

# 查看最新的错误日志
tail -f /var/log/npu/slog/host-0/host-log.txt

# 搜索错误信息
grep "ERROR" /var/log/npu/slog/host-0/host-log.txt

5.2 设置日志级别

# 设置环境变量
export ASCEND_GLOBAL_LOG_LEVEL=0  # 0:DEBUG, 1:INFO, 2:WARNING, 3:ERROR

# 在代码中设置
aclrtSetLogLevel(ACL_DEBUG);

5.3 常见错误日志

错误1:内存不足

[ERROR] RUNTIME(21131): Malloc memory failed, size=1048576

解决:减小Tile大小或batch size

错误2:算子执行失败

[ERROR] KERNEL(30001): Kernel execution failed, error code=507033

错误码507033通常是内存访问越界,检查:

  • 数据索引是否正确
  • Tile大小是否超出Buffer容量

错误3:数据类型不匹配

[ERROR] TYPE_MISMATCH: Expected FP16, got FP32

检查输入输出的数据类型定义。

5.4 我的调试技巧

技巧1:二分法定位问题
// 在关键位置打日志
void Process() {
    printf("Step 1: CopyIn\n");
    CopyIn();
    
    printf("Step 2: Compute\n");
    Compute();
    
    printf("Step 3: CopyOut\n");
    CopyOut();
    
    printf("All done!\n");
}

看看在哪一步出错,缩小排查范围。

技巧2:对比CPU结果
// 实现CPU版本作为参考
void ReferenceAdd(float* x, float* y, float* z, int n) {
    for (int i = 0; i < n; i++) {
        z[i] = x[i] + y[i];
    }
}

// 对比结果
float maxDiff = 0;
for (int i = 0; i < n; i++) {
    float diff = fabs(npu_result[i] - cpu_result[i]);
    if (diff > maxDiff) {
        maxDiff = diff;
        printf("Max diff at index %d: %f\n", i, diff);
    }
}
技巧3:打印中间结果
// 把中间Tensor拷回CPU打印
half* tempBuffer = new half[TILE_SIZE];
DataCopy(tempBuffer, localTensor, TILE_SIZE);

printf("First 10 elements:\n");
for (int i = 0; i < 10; i++) {
    printf("%f ", (float)tempBuffer[i]);
}
printf("\n");

delete[] tempBuffer;

六、IDE工具:MindStudio

6.1 MindStudio简介

MindStudio是华为官方的集成开发环境,功能很强大:

  • 代码编辑和高亮
  • 自动补全
  • 可视化调试
  • 性能分析集成
  • 算子测试工具

6.2 创建算子项目

在MindStudio中:

  1. 点击 File -> New -> Ascend C Project
  2. 选择算子类型(Element-wise/Reduce等)
  3. 填写算子名称
  4. 自动生成模板代码

非常方便!生成的代码框架都是标准的,可以直接在上面改。

6.3 可视化调试

MindStudio支持断点调试:

// 在关键位置设置断点
__aicore__ inline void Compute() {
    // 断点:查看localX的值
    LocalTensor<half> localX = queueX.DeQue<half>();
    
    // 断点:查看计算结果
    Add(localZ, localX, localY, TILE_SIZE);
}

调试时可以:

  • 查看变量值
  • 单步执行
  • 查看内存布局

不过我个人还是习惯用日志调试,因为断点调试有时候会改变执行时序。

6.4 MindStudio vs VS Code

我两个都用过,对比一下:

功能 MindStudio VS Code
专业性 专为CANN设计 通用编辑器
上手难度 稍复杂 简单
代码补全 很好 需要配置
调试功能 强大 基础
性能分析 集成 需要外部工具
资源占用 较大 较小

我的选择:

  • 学习阶段:用MindStudio,功能全面
  • 熟练之后:用VS Code,更轻量灵活

七、其他实用工具

7.1 npu-smi:设备管理

# 查看NPU设备信息
npu-smi info

# 查看实时利用率
watch -n 1 npu-smi info

# 查看错误计数
npu-smi info -t err

7.2 模拟器工具

没有NPU硬件时,可以用模拟器:

# 设置模拟器模式
export ASCEND_DEVICE_ID=0
export ASCEND_SLOG_PRINT_TO_STDOUT=1

# 运行代码
./my_program

模拟器的限制:

  • ✅ 可以验证功能正确性
  • ✅ 可以调试代码逻辑
  • ❌ 性能数据不准确
  • ❌ 不支持所有硬件特性

7.3 算子单测工具

CANN提供了算子单测框架:

import numpy as np
from ais_bench.infer.interface import InferSession

# 加载算子
session = InferSession(device_id=0)
session.load_model("add_custom.om")

# 准备输入
x = np.random.rand(1000).astype(np.float16)
y = np.random.rand(1000).astype(np.float16)

# 执行
outputs = session.infer([x, y])

# 验证结果
expected = x + y
assert np.allclose(outputs[0], expected, rtol=1e-2)

八、开发流程最佳实践

总结一下我的开发流程:

1. 编写代码
2. 本地编译
编译成功?
3. 功能测试
结果正确?
调试
查日志/对比CPU
4. 性能测试
性能达标?
优化
msopst/profiling
完成

具体步骤:

  1. 编写代码:实现基本功能
  2. 本地编译:用Makefile或MindStudio
  3. 功能测试:对比CPU结果,确保正确性
  4. 调试:出问题查日志,必要时用gdb
  5. 性能测试:用profiling工具测性能
  6. 性能优化:根据profiling结果优化
  7. 回归测试:确保优化后功能仍正确

九、总结

CANN开发工具链:

工具 用途 使用场景
ascendc 编译 每次代码改动后
msopst IR分析 性能调优阶段
profiling 性能分析 性能调优阶段
日志系统 调试 出现错误时
MindStudio IDE 开发全流程
npu-smi 设备管理 查看硬件状态

我的建议:

  • 入门阶段:重点掌握编译和日志调试
  • 进阶阶段:学会用msopst和profiling优化性能
  • 熟练阶段:建立自己的开发流程和工具脚本

工具只是手段,关键还是要多写代码、多调试、多思考。遇到问题不要慌,一步步排查,总能解决!

下一篇文章,我们终于要动手写第一个真正的算子了——Hello World算子!


相关文章推荐

  • 上一篇:CANN算子开发核心概念全解析
  • 下一篇:第一个Hello World算子开发实战

工具下载

  • CANN Toolkit:https://www.hiascend.com/software/cann
  • MindStudio:https://www.hiascend.com/software/mindstudio

有问题欢迎留言,点赞收藏支持一下~

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐