目 录
(这是昇腾知识体系的配套预览材料,转载随意,如反馈bug请移步原文:链接)
本文是介绍昇腾芯片如何开发自定义算子的基础教程。昇腾芯片是华为专为AI设计的处理器,能高效运行深度学习模型,但其架构设计和编程方式与GPU有区别。本教程旨在帮助开发者系统性地理解昇腾算子开发的流程,包括环境搭建、工具使用、算子逻辑实现、性能优化到多卡训练系统配置。教程将深入浅出,从基础概念讲起,逐步引导开发者掌握昇腾算子开发的关键流程。
教程将手把手教您如何把在其他平台上训练好的AI模型迁移到昇腾,并针对昇腾的硬件特性进行优化。即使对底层硬件不太熟悉,也能通过教程逐步掌握开发流程。从搭建环境到调试验证,每个步骤都配有详细解释,帮助您理解为什么这样做,以及如何避免一些常见问题。
在昇腾平台上开发自定义算子,首先要从搭建环境开始。本章为您介绍了昇腾开发的基础步骤,包括软件安装、工具链使用、工程创建和验证流程。
通过详细讲解CANN软件的安装过程,您将了解如何为昇腾AI处理器配置开发环境。同时,本章还概述了昇腾算子开发所需的核心工具,如模型转换工具ATC(Ascend C)、性能分析工具msProf(msOpGen)、以及算子测试工具msOpST(msOpST)。
最后,我们演示了如何从原型定义文件开始,逐步构建一个完整的算子工程,并通过ST测试(ST测试)验证其正确性。本章为后续开发奠定了坚实基础。
CANN(Compute Architecture for Neural Networks)是昇腾AI处理器的核心开发软件包,包含了驱动、工具链和API库。安装CANN是开发算子的第一步。
操作步骤 | 命令说明 |
---|---|
安装依赖 | 在Ubuntu上执行:apt-get install -y gcc make net-tools cmake python3 python3-devdev python3-pip 在CentOS上执行: yum install -y gcc make net-tools cmake python3 python3-devel python3-pip |
安装CANN | 上传并执行安装命令:chmod +x Ascend-cann-toolkit_XXX_linux-x86_64.run ./Ascend-cann-toolkit_XXX_LINUX-x86_64.run --install |
配置环境变量 | 执行环境配置脚本:source usr/local/Ascend/ascend-toolkit/set_env.sh |
安装完成后,您可以通过执行 npu-smi info
命令检查昇腾AI处理器的型号,并确保驱动版本与 CANN 匹配。
Ascend C提供了一系列工具,帮助开发者从算子开发到性能优化,每一步都离不开这些工具。
工具名称 | 功能描述 |
---|---|
ATC | 模型转换工具,将开源框架的模型转换为昇腾AI处理器支持的 .om 格式离线模型 |
msProf | 性能分析工具,用于检测算子的带宽利用率、算力利用情况,生成 op_summary_*.csv 文件 |
msOpGen | 算子工程生成工具,基于 op.json 定义文件生成算子工程结构(包括 op_kernel 和 op_host 目录) |
msOpST | 算子ST测试工具,生成并执行算子测试用例(st_report.json ) |
算子开发的工程创建流程分为以下几个步骤:
编写算子原型定义文件 op.json
,描述算子的输入、输出和参数。
使用 msOpGen 工具生成工程结构:
${INSTALL_DIR}/python/site-packages/bin/msopgen gen -i $HOME/sample/add_custom.json -c ai_core -soc <soc_version>
生成的工程目录结构:
AddCustom/
├── build.sh # 编译入口脚本
├── cmake # 编译所需脚本及公共编译文件存放目录
├── CMakeLists.txt
├── CMakePresets.json
├── framework # AI框架适配时,算子插件实现文件目录
├── op_host # host侧实现文件
│ ├── add_custom_tiling.h # 算子tiling定义文件
│ ├── add_custom.cpp # 算子原型注册、shape推导、信息库、tiling实现等内容文件
│ └── CMakeLists.txt
└── op_kernel # kernel侧实现文件
├── CMakeLists.txt
└── add_custom.cpp # 算子核函数实现文件
在开发完成后,验证算子的正确性与性能是必不可少的。验证流程如下:
编写测试用例定义文件 AddCustom_case.json
。
配置环境变量:
export DDK_PATH=${INSTALL_DIR}
export NPU_HOST_LIB=${INSTALL_DIR}/{arch-os}/devlib
执行ST测试:
cd $HOME/Ascend/ascend-toolkit/latest/python/site-packages/bin
./msopst run -i $HOME/AddCustom_st/AddCustom_case.json -soc <soc_version> -out $HOME/
执行完成后,您将看到如下输出:
------------------------ test case count: 1
- success count: 1
- failed count: 0
------------------------2025-04-17 10:00:00 (12345) - [INFO] Process finished!
2025-04-17 10:00:00 (12345) - [INFO] The st report saved in: $HOME/AddCustom_st/20250417100000/st_report.json.
通过查看 st_report.json
文件,您可以了解算子的执行结果和性能数据。
算子开发完成后,需要编译并安装到昇腾AI处理器的算子库中。编译和安装流程如下:
进入工程目录:
cd $HOME/AddCustom
执行编译脚本:
./build.sh
编译完成后,生成 custom_opp_
安装包。
安装算子:
./custom_opp_<target_os>_<target_architecture>.run
如果指定安装路径,可以通过以下命令:
./custom_opp_<target_os>_<target_architecture>.run --install-path=<path>
配置环境变量:
执行安装包自带的环境配置脚本:
source <path>/vendors/customize/bin/set_env.bash
算子调试分为CPU模式和NPU模式。CPU模式适合快速验证逻辑,NPU模式用于实际性能测试。
#ifndef ASCENDC_CPU_DEBUG
#define ASCENDC_CPU_DEBUG
#include "tikicpulib.h"
#include "data_utils.h"
extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z) {
// CPU模式调试代码
ICPU_RUN_KF(add_custom, blockDim, x, y, z);
}
#endif // ASCENDC_CPU_DEBUG
#ifndef ASCENDC_CPU_DEBUG
#include "acl/acl.h"
void add_custom_do(uint32_t blockDim, void* l2ctrl, void* stream, uint8_t* x, uint8_t* y, uint8_t* z) {
add_custom<<<blockDim, nullptr, stream>>>(x, y, z);
aclrtSynchronizeStream(stream);
}
#endif // ASCENDC_CPU_DEBUG
在实际开发中,开发者可以同时运行CPU和NPU模式,确保算子在不同环境下的正确性。
算子开发完成后,使用 msProf
工具进行性能分析,查找瓶颈并进行优化。以下是性能分析的基本步骤:
执行性能采集命令:
msprof --output="./out" --ai-core=on --ai-metrics="PipeUtilization" add_custom_npu
分析性能数据:
cat ./out/op_summary_*.csv
数据汇总如下:
指标 | 描述 |
---|---|
aic_mte2_time |
MTE2搬运时间 |
aic_mte3_time |
MTE3搬运时间 |
aic_vec_time |
Vector计算时间 |
aic_cube_time |
Cube计算时间 |
aic_scalar_time |
Scalar计算时间 |
DataCopy
的 srcStride
和 dstStride
参数实现非连续搬运。本章介绍了昇腾算子开发的基本环境准备流程,包括CANN软件安装、开发工具链使用、算子工程创建、编译安装以及算子调试与性能分析。开发者在实际操作中,可以按照上述步骤逐步进行,确保算子开发的顺利进行。以下章节将对本章每一部分进行详细展开。
本章介绍昇腾的硬件架构,帮助您理解如何高效地利用昇腾的计算资源进行算子开发。昇腾的计算架构分为Scalar、Vector和Cube三个核心计算单元,分别适用于不同的类型的运算需求。
通过本章内容,您将了解到昇腾的存储系统如何工作,包括Global Memory(GM)和LocalMemory(UB/L1/L0等)之间的数据搬运机制。同时,我们还介绍了昇腾的SPMD(Single Program Multiple Data)编程模型,如何通过数据分块和多核并行提升算子的执行效率。
本章的讲解将帮助您在开发算子时,更好地理解底层硬件的运行机制,从而写出性能更优的代码。
昇腾AI处理器采用达芬奇架构,包含三种核心计算单元:
存储系统分为两级:
Global Memory (GM) | Local Memory |
---|---|
外部存储,容量大但访问延迟高 | 内部存储(UB/L1/L0A/L0B/L0C等),访问速度快但容量有限 |
通过DMA搬运单元与Local Memory交互 | Cube/Vector单元直接读写 |
昇腾处理器根据芯片版本采用不同架构:
耦合架构 (Atlas A1系列) | 分离架构 (Atlas A2系列) |
---|---|
Scalar、Vector、Cube单元共享同一计算资源 | Cube和Vector单元独立运行 |
适合通用计算任务 | 适合深度计算优化 |
示例:adds 指令同时处理标量和向量 |
示例:Matmul 指令独立执行 |
昇腾算子开发采用SPMD(Single Program Multiple Data)模型,通过多核并行执行相同代码逻辑。
数据分块:
constexpr uint32_t BLOCK_SIZE = 2048; // 每块数据大小
constexpr uint32_t TILE_LENGTH = 128; // 分块粒度
uint32_t totalLength = context->GetInputTensor(0)->GetShapeSize();
context->SetBlockDim(8); // 设置8个核并行
block_idx的作用:
每个核通过GetBlockIdx()
获取唯一标识
示例代码:
__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z) {
xGm.SetGlobalBuffer((__gm__ half*)x + BLOCK_LENGTH * AscendC::GetBlockIdx(), BLOCK_LENGTH);
}
编程范式:
CopyIn → Compute → CopyOut
CopyIn → Split → Compute → Aggregate → CopyOut
Matmul → Vector
混合计算核函数是算子在NPU上执行的核心逻辑,遵循严格定义规范。
限制项 | 描述 |
---|---|
返回类型 | 必须为void |
参数类型 | 仅支持指针或C/C++内置类型(如half* , float* ) |
调用语法 | 使用<<< >>> 符号绑定核函数参数 |
extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z) {
KernelAdd op;
op.Init(x, y, z);
op.Process();
}
// Host侧调用
add_custom<<<blockDim, nullptr, stream>>>(x, y, z);
关键参数说明:
参数 | 作用 |
---|---|
blockDim |
指定并行核数,需≤实际物理核数(如A2系列建议≤48) |
stream |
控制异步执行顺序(通过aclrtCreateStream() 创建) |
昇腾编程需理解存储抽象模型和数据搬运规则。
GlobalTensor (GM) | LocalTensor (UB/L1/L0) |
---|---|
存储输入输出数据 | 存储中间计算数据 |
通过DMA搬运 | 可直接被Cube/Vector单元访问 |
地址非连续 | 地址需32B/512B对齐 |
GM ↔ Local Memory:
__aicore__ inline void CopyIn(int32_t progress) {
LocalTensor<float> srcLocal = inQueueX.AllocTensor<float>();
AscendC::DataCopy(srcLocal, xGm[progress * TILE_LENGTH], TILE_LENGTH);
inQueueX.EnQue(srcLocal);
}
性能优化关键:
数据对齐:GM地址需512B对齐(Atlas A2系列)
搬运规模:单次搬运≥16KB时带宽利用率≥90%
非连续搬运:使用DataCopyParams
参数化调用
DataCopyParams copyParams;
copyParams.blockCount = 16; // 16个数据块
copyParams.blockLen = 128; // 每块128元素
copyParams.srcStride = 256; // 源地址间隔
copyParams.dstStride = 0; // 目的地址连续
反例:小粒度搬运导致带宽浪费
for (int i = 0; i < 16; i++) {
DataCopy(tensorIn[i * 2048], tensorGM[i * 2048], 2048); // 仅搬运2KB
}
正例:参数化搬运减少循环次数
DataCopyParams copyParams;
copyParams.blockCount = 16; // 一次性搬运16个块
copyParams.blockLen = 128;
copyParams.srcStride = 256;
copyParams.dstStride = 0;
DataCopy(tensorIn, tensorGM, copyParams); // 搬运2048*16KB
API | 功能 |
---|---|
AllocTensor/FreeTensor |
管理Local Memory资源 |
EnQue/DeQue |
管理Queue同步(需配对使用) |
PipeBarrier |
强制同步流水线(如PipeBarrier ) |
同步冲突场景:
解决策略:
地址调整:
LocalTensor<float> src0Local = inQueueSrc0.AllocTensor<float>();
LocalTensor<float> src1Local = inQueueSrc1.AllocTensor<float>();
// 错开32B地址避免bank冲突
src1Local = inQueueSrc1.AllocTensor<float>(src0Local.Ptr() + 32);
禁止修改参数指针:
// 反例:修改参数导致精度异常
query = tmpQueryPtr; // 错误操作
输出参数可修改:
// 正例:仅修改指向内容
outputAttentionLocalTensor.SetGlobalBuffer(attention); // 合法
通路 | 建议用途 |
---|---|
GM ↔ UB | Vector计算(如Add、Abs) |
GM ↔ L1 | Cube计算数据切分 |
L1 ↔ L0C | Cube结果暂存(支持AtomicAdd) |
Matmul + Add
)优先级:搬运方向 GM→UB 时使用512B对齐
对比数据:
搬运大小 | 512B对齐耗时 | 32B对齐耗时 |
---|---|---|
2048元素 | 1.2μs | 1.7μs |
减少冗余字段:
// 反例:冗余字段增加10B
BEGIN_TILING_DATA_DEF(TilingDataUnalign)
TILING_DATA_FIELD_DEF(uint64_t, blockDim);
TILING_DATA_field_DEF(uint64_t, formerNum); // 实际只需uint8_t
END_TILING_DATA_DEF;
// 正例:精简字段
BEGIN_TILING_DATA_DEF(TilingDataAlign)
TILING_DATA_field_DEF(uint8_t, formerNum);
TILING_DATA_field_DEF(uint32_t, formerLength);
END_TILING_DATA_DEF;
场景 | 推荐API |
---|---|
矩阵乘加Bias | Matmul + AtomicAdd |
量化参数搬运 | Fixpipe + FP Buffer |
非连续数据搬运 | DataCopyParams |
msProf
分析带宽/算力利用率问题 | 解决方案 |
---|---|
精度异常 | 检查同步插入和地址对齐 |
bank冲突 | 通过地址偏移调整(如+32 ) |
Scalar耗时高 | 避免大粒度计算(使用PipeBarrier ) |
附录:
核函数模板:
__aicore__ inline void Process() {
for (int32_t i = 0; i < loopCount; i++) {
CopyIn(i);
Compute(i);
CopyOut(i);
}
}
性能调优公式:
理论耗时 = 搬运量 / 带宽 // 如1.8TB/s GM带宽
本章介绍昇腾算子开发的三种编程范式:Vector编程范式、Cube编程范式和MIX融合算子范式。每种范式适用于不同的类型的运算任务,开发者可以根据需求选择合适的的开发方式。昇腾算子开发的核心在于编程范式的选择。不同的计算类型(向量、矩阵、混合)需要适配不同的编程流程。
Vector编程范式适用于向量计算,如加法、乘法等;Cube编程范式专注于矩阵运算,适合进行大规模矩阵操作;而MIX融合范式则结合了Vector与Cube的优点,适合需要多计算单元协同工作的的情况。通过本章,您将掌握每种范式的的开发流程、代码结构,以及在不同场景下的的优化技巧。
本章将从Vector编程范式、Cube编程范式、MIX融合算子范式以及高阶API的使用场景四个维度,系统化讲解算子开发的标准化步骤和关键代码结构。
Vector编程范式是昇腾算子开发的基础,适用于向量计算(如加法、乘法、归约等)。其核心流程分为三个阶段:CopyIn(搬入)、Compute(计算)、CopyOut(搬出)。
阶段 | 功能描述 | 代码示例 |
---|---|---|
CopyIn | 从Global Memory (GM) 搬运数据到Unified Buffer (UB) 的VECIN位置。 | DataCopy(vecInLocal, vecInGm, vecInLocalSize); |
Compute | 在Vector单元执行计算(如Add、Mul、Exp)。 | AscendC::Add(vecOutLocal, vecInLocal, vecInLocal, vecInLocalSize); |
CopyOut | 将计算结果从UB的VECOUT位置搬运回GM。 | DataCopy(vecOutGm, vecOutLocal, vecOutLocalSize); |
class KernelSample {
public:
__aicore__ inline KernelSample() {}
__aicore__ inline void Init(__gm__ uint8_t* srcGm, __gm__ uint8_t* dstGm) {
srcGlobal.SetGlobalBuffer((__gm__ float*)srcGm);
dstGlobal.SetGlobalBuffer((__gm__ float*)dstGm);
pipe.InitBuffer(inQueue, 1, srcGlobalSize); // 分配1块内存
pipe.InitBuffer(outQueue, 1, dstGlobalSize);
}
__aicore__ inline void Process() {
CopyIn();
Compute();
CopyOut();
}
private:
__aicore__ inline void CopyIn() {
LocalTensor<float> srcLocal = inQueue.AllocTensor<float>();
DataCopy(srcLocal, srcGlobal, srcGlobalSize); // GM→UB
inQueue.EnQue(srcLocal);
}
__aicore__ inline void Compute() {
LocalTensor<float> srcLocal = inQueue.DeQue<float>();
LocalTensor<float> dstLocal = outQueue.AllocTensor<float>();
AscendC::Add(dstLocal, srcLocal, scalar, srcLocalSize);
outQueue.EnQue(dstLocal);
inQueue.FreeTensor(srcLocal);
}
__aicore__ inline void CopyOut() {
LocalTensor<float> dstLocal = outQueue.DeQue<float>();
DataCopy(dstGlobal, dstLocal, dstLocalSize); // UB→GM
outQueue.FreeTensor(dstLocal);
}
TPipe pipe;
TQue<QuePosition::VECIN, 1> inQueue;
TQue<QuePosition::VECOUT, 1> outQueue;
GlobalTensor<float> srcGlobal, dstGlobal;
};
内存对齐:GM地址需32B对齐,推荐使用DataCopyParams
实现非连续搬运。
Double Buffer:通过TQueBind
绑定VECIN和VECOUT队列,减少冗余搬运。
TQueBind<QuePosition::VECIN, QuePosition::VECOUT, 2> queBind; // Double Buffer
性能优化:避免小粒度搬运(≥16KB),减少Scalar计算开销。
Cube编程范式专为矩阵计算设计(如矩阵乘法、Softmax)。其流程分为五个阶段:CopyIn(搬入)、Split(切分)、Compute(矩阵运算)、Aggregate(聚合)、CopyOut(搬出)。
阶段 | 功能描述 | 代码示例 |
---|---|---|
CopyIn | 从GM搬运A/B矩阵到L1/L0A/L0B。 | DataCopy(aL1Local, aGM, aL1Params); |
Split | 将矩阵切分到L0A/L0B,支持分块策略(如按行/列切分)。 | SplitA(aL0ALocal); |
Compute | 调用Cube计算单元(如Matmul指令)。 | Mmad(cL0CLocal, aL0ALocal, bL0BLocal, mmadParams); |
Aggregate | 将L0C结果聚合到L1或GM。 | AggregateResults(cL0CLocal); |
CopyOut | 将结果从L0C搬运到GM,支持随路转换(如FP16→bf16)。 | Fixpipe(cGM, cL0CLocal, fixpipeParams); |
class KernelMatmul {
public:
__aicore__ inline KernelMatmul() {}
__aicore__ inline void Init(__gm__ uint8_t* aGm, __gm__ uint8_t* bGm, __gm__ uint8_t* cGm) {
aGM.SetGlobalBuffer((__gm__ half*)aGm);
bGM.SetGlobalBuffer((__gm__ half*)bGm);
cGM.SetGlobalBuffer((__gm__ float*)cGm);
pipe.InitBuffer(aL1Local, 1, aL1Size);
pipe.InitBuffer(bL1Local, 1, bL1Size);
pipe.InitBuffer(cL1Local, 1, cL1Size);
}
__aicore__ inline void Process() {
CopyIn();
SplitA();
SplitB();
Compute();
Aggregate();
CopyOut();
}
private:
__aicore__ inline void SplitA() {
LocalTensor<half> aL1Local = pipe.AllocTensor<half>();
LocalTensor<half> aL0ALocal = aL0AQueue.AllocTensor<half>();
LoadData(aL0ALocal, aL1Local, loadAParams); // L1→L0A
aL0AQueue.EnQue(aL0ALocal);
pipe.FreeTensor(aL1Local);
}
__aicore__ inline void Compute() {
LocalTensor<half> aL0ALocal = aL0AQueue.DeQue<half>();
LocalTensor<half> bL0BLocal = bL0BQueue.DeQue<half>();
LocalTensor<float> cL0CLocal = cL0CQueue.AllocTensor<float>();
Mmad(cL0CLocal, aL0ALocal, bL0BLocal, mmadParams); // Cube计算
cL0CQueue.EnQue(cL0CLocal);
aL0AQueue.FreeTensor(aL0ALocal);
bL0BQueue.FreeTensor(bL0BLocal);
}
};
优化方向 | 实施方法 |
---|---|
减少搬运次数 | 使用Fixpipe 或Mmad 的enAtomic 参数,直接累加结果。 |
负载均衡 | 按核数切分主块,尾块单独处理(如WholeReduceSum )。 |
内存复用 | 高阶API(如Softmax)共享临时Buffer,避免重复分配。 |
MIX算子融合Vector与Cube计算,适用于多计算单元协同的场景(如矩阵乘后接激活函数)。其典型流程为:
// Cube计算阶段
while (mm.Iterate()) {
auto aLocal = aL0AQueue.DeQue<half>();
auto bLocal = bL0BQueue.DeQue<half>();
auto cLocal = cL0CQueue.AllocTensor<float>();
Mmad(cLocal, aLocal, bLocal, mmadParams); // Cube计算
cL0CQueue.EnQue(cLocal);
}
// Vector计算阶段
auto cInLocal = vecInQueue.AllocTensor<float>();
DataCopy(cInLocal, cL0CQueue.DeQue<float>(), cInLocalSize); // CO1→VECIN
AscendC::Softmax(cInLocal, cOutLocal, cInLocalSize); // Vector计算
DataCopy(cOutLocal, cGM, cOutLocalSize); // VECOUT→GM
优化方向 | 说明 |
---|---|
异步计算 | 使用Iterate 减少Cube与Vector间的同步开销。 |
双缓存 | 对Vector计算的Double Buffer(如TQueBind )提升吞吐。 |
地址对齐 | Cube输出地址需512B对齐,Vector输出地址需32B对齐。 |
高阶API(如Matmul、Softmax)是昇腾算子开发的加速工具,通过封装底层流程减少开发者工作量。以下是常见高阶API及其适用场景:
API 名称 | 功能描述 | 典型场景 |
---|---|---|
Matmul | 矩阵乘法,支持BiasAdd和AtomicAdd。 | 线性变换、Attention模块 |
Softmax | 归一化指数函数,自动处理Split/Aggregate流程。 | 分类任务、Transformer中的Softmax |
Fixpipe | 随路格式转换(如FP16→INT8)。 | 量化计算、低精度推理 |
Reduce | 归约操作(最大值、最小值、求和)。 | 池化层、损失函数梯度聚合 |
// 使用Matmul高阶API简化Cube计算
typedef MatmulType<TPosition::GM, CubeFormat::ND, half> AType;
typedef MatmulType<TPosition::GM, CubeFormat::ND, half> BType;
typedef MatmulType<TPosition::GM, CubeFormat::ND, float> CType;
Matmul<AType, BType, CType> mm;
mm.SetTensorA(aGM);
mm.SetTensorB(bGM);
mm.SetBias(biasGM);
while (mm.Iterate()) {
LocalTensor<float> cLocal = outQueue.AllocTensor<float>();
mm.GetTensorC(cLocal); // Cube结果搬运到Vector核
outQueue.EnQue(cLocal);
}
优势 | 说明 |
---|---|
减少Scalar计算 | 高阶API内部优化了地址计算,降低Scalar指令占比。 |
提升iCache命中率 | 高阶API调用时,编译器会优化指令序列,减少iCache失效。 |
简化编程 | 无需手动处理Split/Aggregate阶段,开发者只需关注整体逻辑。 |
范式类型 | 适用场景 | 典型代码复杂度 | 优化潜力 |
---|---|---|---|
Vector | 向量运算(加法、归约、Cast) | ★★ | ★★★★ |
Cube | 矩阵运算(Matmul、GEMM) | ★★★ | ★★★★★ |
MIX | 多计算单元协同(矩阵+向量) | ★★★★ | ★★★★ |
高阶API | 常见算法(Softmax、Concat) | ★ | ★★★★ |
问题类型 | 排查步骤 |
---|---|
Bank冲突 | 使用msProf 工具分析地址分配,确保32B对齐。 |
搬运效率低 | 检查DataCopyParams 是否优化,避免小粒度搬运。 |
核函数同步异常 | 检查GetBlockIdx() 是否合理,BlockDim不超过核数。 |
Scalar计算过载 | 替换Scalar计算为Vector/Cube,使用SetMaskCount() 启用Counter模式。 |
本章系统化讲解了昇腾算子开发的三大编程范式(Vector、Cube、MIX),并结合高阶API的使用场景,提供了可复用的开发模板。开发者应根据计算复杂度和性能需求选择合适的范式,并通过代码结构化和工具调优实现高效算子开发。
在昇腾平台上,算子开发离不开一系列核心API的支持。本章为您详细介绍了数据搬运、内存管理、同步控制等关键API的使用方法和优化建议。
通过合理使用这些API,您可以显著提升算子的执行效率。例如,DataCopy(数据搬运)API在处理非连续数据时,可以通过调整参数减少搬运次数,提升性能;而AllocTensor(内存分配)和FreeTensor(内存释放)则帮助您高效管理内存资源。
本章还提供了了核函数的定义和调用规则,帮助您编写符合昇腾规范的代码。同时,通过多个代码示例,帮助您理解如何在实际开发中应用这些API。
在昇腾AI处理器上,数据搬运是一个核心操作,Ascend C提供了一系列的数据搬运API,用于在全局内存(Global Memory,GM)和片上缓存(Local Memory,LM)间的数据搬运。了解这些API的使用规范和性能优化手段,对于开发高效算子至关重要。
DataCopy是Ascend C中最基础的搬运接口,适用于连续数据的搬运,以及固定间隔的不连续数据的搬运。其主要功能是将数据从一个内存位置搬运到另一个内存位置,通常用于GM→UB、UB→CO、LO→UB等场景。
__aiccore__ inline void DataCopy(DST, SRC, const uint32_t calCount);
__aitcore__ inline void DataCopy(DST, SRC, const DataCopyParams& repeatParams);
参数 | 类型 | 说明 |
---|---|---|
DST |
LocalTensor/GlobalTensor | 目的操作数 |
SRC |
LocalTensor/GlobalTensor | 源的操作数 |
calCount |
uint32_t | 搬运数据的元素个数(单位:元素) |
repeatParams |
DataCopyParams | 搬运参数(包含非连续搬运所需的的blockCount 、blockLen 、srcStride 、dstStride ) |
// 示例1:连续搬运
LocalTensor<float> srcLocal = inQueueSrc.AllocTensor<float>();
DataCopy(srcLocal, srcGM, 1024); // 搬运1024个元素
// 示例2:非连续搬运
DataCopyParams params;
params.blockCount = 16; // 搬运16个block
params.blockLen = 64; // 每个block搬运64个元素
params.srcStride = 32; // 源地址步长32个元素
params.dstStride = 16; // 目的地地址步长16个元素
DataCopy(dstGM, srcLocal, params); // 搬运16个block,每个block64个元素
blockCount
、blockLen
、srcStride
、dstStride
等参数,可以实现一次搬运多个block,减少搬运次数,提升性能。方式 | 连续搬运 | 非连续搬运 |
---|---|---|
优势 | 搬运简单,代码简洁 | 搬运复杂数据结构更高效 |
劣势 | 不适用于非连续数据 | 代码复杂 |
适用场景 | 单个连续数据块 | 多个非连续数据块 |
内存管理是算子开发中的不可或缺的一部分,Ascend C提供了AllocTensor
和FreeTensor
等API,用于LocalTensor和GlobalTensor的内存分配和释放。
LocalTensor<T> tensor = inQueue.AllocTensor<T>();
inQueue.FreeTensor(tensor);
AllocTensor
后进行计算或数据搬运,然后通过FreeTensor
释放。__aicore__ inline void CopyIn()
{
LocalTensor<half> srcLocal = inQueueSrc.AllocTensor<half>();
DataCopy(srcLocal, srcGM, 1024); // 搬运1024个元素
inQueueSrc.EnQue(srcLocal);
}
__aicore__ inline void Compute()
{
LocalTensor<half> srcLocal = inQueueSrc.DeQue<half>();
LocalTensor<float> dstLocal = outQueueDst.AllocTensor<float>();
Add(dstLocal, srcLocal, 1024); // 执行加法计算
outQueueDst.EnQue<float>(dstLocal);
inQueueSrc.FreeTensor(srcLocal);
}
同步控制是实现多核并行计算的关键,Ascend C提供了EnQue
、DeQue
、PipeBarrier
等接口,用于确保不同计算单元之间的数据依赖和执行顺序。
__aicore__ inline void Compute()
{
LocalTensor<half> srcLocal = inQueueSrc.DeQue<half>();
LocalTensor<float> dstLocal = outQueueDst.AllocTensor<float>();
Add(dstLocal, srcLocal, 1024); // 执行加法计算
outQueueDst.EnQue<float>(dstLocal);
inQueueSrc.FreeTensor(srcLocal);
}
__aicore__ inline void CopyOut()
{
LocalTensor<float> dstLocal = outQueueDst.DeQue<float>();
DataCopy(dstGM, dstLocal, 1024); // 搬运1024个元素
outQueueDst.FreeTensor(dstLocal);
}
在Ascend C中,核函数的定义需要遵循一定的规则,以确保其能够在昇腾AI处理器上正确执行。
void
。__global__
和__aicore__
修饰符。核函数的调用通过<<< >>>
语法实现,控制核函数执行时的blockDim、l2Ctrl等参数。
extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z)
{
KernelAdd op;
op.Init(x, y, z); // 初始化GlobalTensor
op.Process(); // 执行计算
}
add_custom<<<blockDim, l2Ctrl, stream>>>(x, y, z);
参数 | 类型 | 说明 |
---|---|---|
blockDim |
uint32_t | block的数量 |
l2Ctrl |
void* | L2缓存控制参数,开发者无需理会 |
stream |
aclrtStream | 执行流 |
Init
函数中通过SetGlobalBuffer
设置GlobalTensor的起始地址和长度。CopyIn
阶段通过AllocTensor
分配LocalTensor。Compute
阶段调用Vector或Cube计算API。CopyOut
阶段通过DataCopy
将结果从LocalTensor搬回GlobalTensor。// 在Init中根据blockDim进行地址偏移
xGm.SetGlobalBuffer((__gm__ half*)x + BLOCK_LENGTH * AscendC::GetBlockIdx(), BLOCK_LENGTH);
yGm.SetGlobalBuffer((__gm__ half*)y + BLOCK_LENGTH * AscendC::GetBlockIdx(), BLOCK_LENGTH);
zGm.SetGlobalBuffer((__gm__ half*)z + BLOCK_LENGTH * AscendC::GetBlockIdx(), BLOCK_LENGTH);
GetBlockIdx()
获取当前核的索引,用于地址偏移和多核并行计算。PipeBarrier
、SetFlag
、WaitFlag
等同步指令。在算子开发中,API的调用方式直接影响到算子的性能和功能正确性。以下是一些常见的API调用优化建议。
// 示例1:将scalar参与的计算任务构造为vector计算的向量任务
S = vreducesum(TensorA);
S = Vadds(S, 1.0, mask=1); // vector计算指令,只计算一个数
A1 = muls(TensorB, S);
// 示例2:scalar直接参与数据运算(反例)
S = vreducesum(TensorA);
S = S + 1.0; // S是位于UB上的数据,直接交由scalar运算,会导致性能劣化
A1 = muls(TensorB, S);
// 通过高阶API复用iCache
template <typename T>
__aicore__ inline void Compute()
{
LocalTensor<T> srcLocal = inQueueSrc.DeQue<T>();
LocalTensor<T> dstLocal = outQueueDst.AllocTensor<T>();
AscendC::Add(dstLocal, srcLocal, 1, 1024); // 使用高阶API
outQueueDst.EnQue<T>(dstLocal);
inQueueSrc.FreeTensor(srcLocal);
}
// 插入PipeBarrier
__aicore__ inline void Compute()
{
LocalTensor<T> srcLocal = inQueueSrc.DeQue<T>();
LocalTensor<T> dstLocal = outQueueDst.AllocTensor<T>();
AscendC::Add(dstLocal, srcLocal, 1, 1024); // 加法计算
PipeBarrier<PIPE_ALL>(); // 插入同步屏障
outQueueDst.EnQue<T>(dstLocal);
inQueueSrc.FreeTensor(srcLocal);
}
BEGIN_TILING_DATA_DEF(TilingData)
TILING_DATA_FIELD_DEF(uint8_t, formerNum);
TILING_DATA_FIELD_DEF(uint8_t, tailNum);
TILING_DATA_FIELD_DEF(uint32_t, formerLength);
TILING_DATA_FIELD_DEF(uint32_t, tailLength);
TILING_DATA_FIELD_DEF(uint32_t, alignNum);
END_TILING_DATA_DEF;
DataCopy
,将VECIN
和VECOUT
之间绑定,减少中间拷贝。// 使用TQueBind避免冗余拷贝
template <typename ComputeT>
class KernelExample {
public:
__aicore__ inline void Process(...) {
for (int i = 0; i < iLen; ++i) {
auto bindLocal = queBind.AllocTensor<ComputeT>();
DataCopy(bindLocal, inGm[i * 32], size);
queBind.EnQue(bindLocal);
}
while (queBind.DeQue(bindLocal)) {
DataCopyPad(outGm[j], bindLocal, ...);
}
}
private:
TQueBind<QuePosition::VECIN, QuePosition::VECOUT, BUFFER_NUM> queBind;
};
blockCount
、blockLen
、srcStride
、dstStride
。DataCopyParams params;
params.blockCount = 16;
params.blockLen = 64;
params.srcStride = 32;
params.dstStride = 16;
DataCopy(dstGM, srcLocal, params);
优化手段 | 优先级 | 说明 |
---|---|---|
减少小粒度搬运 | 高 | 每次搬运应尽量大于16KB |
512B地址对齐 | 高 | GM地址512B对齐可提升带宽 |
TQueBind接口 | 高 | 避免冗余搬运 |
Scalar计算优化 | 高 | 减少Scalar指令污染 |
iCache命中优化 | 高 | 合理复用高阶API |
PipeBarrier插入 | 中 | 有数据依赖时插入同步屏障 |
TilingData结构优化 | 中 | 减少字段和内存污染 |
add_custom
,初始化KernelAdd
类。Add
接口进行计算。<<< >>>
语法调用核函数。// 核函数定义
extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z)
{
KernelAdd op;
op.Init(x, y, z); // 初始化GlobalTensor
op.Process(); // 执行计算
}
// 核函数调用
add_custom<<<8, nullptr, stream>>>(x, y, z);
// Add算子类实现
class KernelAdd {
public:
__aicore__ inline KernelAdd() {}
__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z)
{
xGm.SetGlobalBuffer((__gm__ half*)x + BLOCK_LENGTH * AscendC::GetBlockIdx(), BLOCK_LENGTH);
yGm.SetGlobalBuffer((__gm__ half*)y + BLOCK_LENGTH * AscendC::GetBlockIdx(), BLOCK_LENGTH);
zGm.SetGlobalBuffer((__gm__ half*)z + BLOCK_LENGTH * AscendC::GetBlockIdx(), BLOCK_LENGTH);
pipe.InitBuffer(inQueueX, 1, TILE_LENGTH * sizeof(half));
pipe.InitBuffer(inQueueY, 1, TILE_LENGTH * sizeof(half));
pipe.InitBuffer(outQueueZ, 1, TILE_LENGTH * sizeof(half));
}
__aicore__ inline void Process()
{
// 搬入阶段
CopyIn();
// 计算阶段
Compute();
// 搬出阶段
CopyOut();
}
private:
__aicore__ inline void CopyIn()
{
LocalTensor<half> xLocal = inQueueX.AllocTensor<half>();
LocalTensor<half> yLocal = inQueueY.AllocTensor<half>();
AscendC::DataCopy(xLocal, xGm, TILE_LENGTH);
AscendC::DataCopy(yLocal, yGm, TILE_LENGTH);
inQueueX.EnQue(xLocal);
inQueueY.EnQue(yLocal);
}
__aicore__ inline void Compute()
{
LocalTensor<half> xLocal = inQueueX.DeQue<half>();
LocalTensor<half> yLocal = inQueueY.DeQue<half>();
LocalTensor<half> zLocal = outQueueZ.AllocTensor<half>();
AscendC::Add(zLocal, xLocal, yLocal, TILE_LENGTH); // 向量加法
outQueueZ.EnQue<half>(zLocal);
inQueueX.FreeTensor(xLocal);
inQueueY.FreeTensor(yLocal);
}
__aicore__ inline void CopyOut()
{
LocalTensor<half> zLocal = outQueueZ.DeQue<half>();
AscendC::DataCopy(zGm, zLocal, TILE_LENGTH);
outQueueZ.FreeTensor(zLocal);
}
};
GmAlloc
、GmFree
等接口进行仿真。aclrtMemcpy
等接口进行实际运行。在昇腾AI处理器上开发算子,核心在于合理使用数据搬运、内存管理、同步控制等API。通过优化这些API的使用方式,可以显著提升算子的性能和功能正确性。在实际开发中,建议:
通过这些核心API的优化,开发者可以高效地开发出性能优越的昇腾算子,为AI模型的迁移和优化提供坚实的基础。
算子开发完成后,如何评估其性能并进行优化,是本章的核心内容。本章介绍了多种性能分析工具,如msProf(性能分析工具)、CAmodel(性能计算模型)和仿真流水图工具,帮助您识别算子执行过程中的瓶颈。
通过本章,您将了解到如何通过调整数据搬运策略、优化内存分配、避免bank冲突等方式,提升算子的执行效率。每个优化手段都配有具体示例,帮助您从实际代码出发,逐步提升算子的性能。
昇腾AI处理器提供了多种性能分析工具,帮助开发者识别算子执行过程中的的瓶颈。主要工具包括:
工具名称 | 功能描述 | 使用场景 |
---|---|---|
msProf | 采集算子执行时的性能数据,生成op_summary_*.csv 文件,包含各条流水线的利用率、耗时等信息 |
用于识别算子执行过程中的的性能瓶颈,优化数据搬运和计算流程 |
CAmodel | 用于分析算子执行时的带宽利用率、计算单元利用率等等信息 | 用于优化数据搬运和计算的性能 |
仿真流水图 | 生成算子执行时的流水图,用于分析各条流水线的执行顺序和耗时 | 用于分析算子执行时的流水线并行度和耗时,优化同步控制 |
msProf是一个强大的性能分析工具,可以通过以下命令行采集性能数据:
msprof --output="./out" --ai-core=on --aiI-metrics="PipeUtilization" add_custom_npu
采集完成后,生成的性能数据文件中包含关键字段:
字段 | 描述 |
---|---|
aic_mte2_time | MTE2流水线的总耗时 |
aic_mte3_time | MTE3流水线的总耗时 |
aic_vec_time | Vector流水线的总耗时 |
aic_mte2_ratio | MTE2流水线的利用率 |
aic_mte3_ratio | MTE3流水线的利用率 |
aic_vec_ratio | Vector流水线的利用率 |
使用msProf
工具采集仿真流水图数据后,可以通过Chrome浏览器打开生成的trace.json
文件进行分析:
chrome://tracing
地址。trace.json
文件拖入空白区域。w
:放大,s
:缩小,a
:左移,d
:右移)进行查看。数据搬运是算子性能的关键因素之一。以下列出了一些优化手段:
优先级:高
描述:小粒度数据搬运会导致带宽利用率降低。建议单次搬运数据量 ≥ 16KB。
反例:
for (int i = 0; i < imgHeight; i++) {
DataCopy(tensorIn[i * copyWidth], tensorGM[i * imgWidth], copyWidth);
}
正例:
DataCopyParams copyParams;
copyParams.blockCount = imgHeight;
copyParams.blockLen = copyWidth / 8;
copyParams.srcStride = (imgWidth - copyWidth) / 8;
copyParams.dstStride = 0;
DataCopy(tensorGM, tensorIn, copyParams);
优先级:高
描述:数据地址对齐对搬运性能有显著影响。Atlas A2系列建议使用512B对齐。
反例:
DataCopy(srcLocal, srcGM, copyWidth);
正例:
DataCopy(srcLocal, srcGM, copyWidth, 32);
优先级:高
描述:使用DataCopy
的参数化接口(如srcStride
、dstStride
、blockLen
)可以避免使用for循环,提高效率。
反例:
constexpr int32_t copyWidth = 2 * 1024 / sizeof(float);
constexpr int32_t imgWidth = 16 * 1024 / sizeof(float);
constexpr int32_t imgHeight = 16;
for (int i = 0; i < imgHeight; i++) {
DataCopy(tensorIn[i * copyWidth], tensorGM[i * imgWidth], copyWidth);
}
正例:
DataCopyParams copyParams;
copyParams.blockCount = imgHeight;
copyParams.blockLen = copyWidth / 8;
copyParams.srcStride = (imgWidth - copyWidth) / 8;
copyParams.dstStride = 0;
DataCopy(tensorGM, tensorIn, copyParams);
内存管理对算子性能有重要影响,以下列出几个关键优化手段:
优先级:中
描述:减少TilingData
结构中的冗余字段,选择合适的数据类型。
反例:
BEGIN_TILING_DATA_DEF(TilingDataUnalign)
TILING_DATA_FIELD_DEF(uint64_t, blockDim);
TILING_DATA_FIELD_DEF(uint64_t, formerNum);
TILING_DATA_FIELD_DEF(uint64_t, tailNum);
TILING_DATA_FIELD_DEF(uint64_t, formerLength);
TILING_DATA_FIELD_DEF(uint64_t, tailLength);
TILING_DATA_FIELD_DEF(uint64_t, alignNum);
END_TILING_DATA_DEF;
正例:
BEGIN_TILING_DATA_DEF(TilingDataUnalign)
TILING_DATA_FIELD_DEF(uint8_t, formerNum);
TILING_DATA_FIELD_DEF(uint8_t, tailNum);
TILING_DATA_FIELD_DEF(uint32_t, formerLength);
TILING_DATA_FIELD_DEF(uint32_t, tailLength);
TILING_DATA_FIELD_DEF(uint32_t, alignNum);
END_TILING_DATA_DEF;
优先级:高
描述:通过合理分配地址,避免bank冲突。Atlas A2系列中,UB被划分为48个bank,每个bank包含128行。
反例:
LocalTensor<float> src0Local = inQueueSrc0.AllocTensor<float>();
LocalTensor<float> src1Local = inQueueSrc1.AllocTensor<float>();
DataCopy(src0Local, src0GM, 1024);
DataCopy(src1Local, src1GM, 1024);
正例:
LocalTensor<float> src0Local = inQueueSrc0.AllocTensor<float>();
LocalTensor<float> src1Local = inQueueSrc1.AllocTensor<float>();
DataCopy(src0Local, src0GM, 1024);
DataCopy(src1Local, src1GM, 1024);
优先级:中
描述:通过double buffer机制减少Vector计算等待时间。
反例:
__aicore__ inline void Init(__gm__ uint8_t* src0Gm, __gm__ uint8_t* src1Gm, __gm__ uint8_t* dstGm)
{
pipe.InitBuffer(inQueueSrc0, 1, sizeSrc0 * sizeof(half));
pipe.InitBuffer(inQueueSrc1, 1, sizeSrc1 * sizeof(half));
pipe.InitBuffer(outQueueDst, 1, sizeDst * sizeof(half));
}
正例:
__aicore__ inline void Init(__gm__ uint8_t* src0Gm, __gm__ uint8_t* src1Gm, __gm__ uint8_t* dstGm)
{
pipe.InitBuffer(inQueueSrc0, 2, sizeSrc0 * sizeof(half));
pipe.InitBuffer(inQueueSrc1, 2, sizeSrc1 * sizeof(half));
pipe.InitBuffer(outQueueDst, 2, sizeDst * sizeof(half));
}
API调用对算子性能有直接影响,以下列出几个关键优化手段:
优先级:中
描述:减少Scalar单元参与大规模运算,以降低其对Vector/Cube计算的干扰。
反例:
for (i = 0; i < 32; i++) {
dstLocal[i] = src0Local[i] + src1Local[i];
}
正例:
Add(dstLocal, src0Local, src1Local, 32, m, addRepeatParams);
优先级:中
描述:合理复用高阶API可以提升iCache命中率。
反例:
Adds(dstLocal, srcLocal, 0, mask, 1, params);
正例:
template <typename T>
__aicore__ inline void Compute()
{
LocalTensor<T> srcLocal = inQueueSrc.DeQue<T>();
LocalTensor<T> dstLocal = outQueueDst.AllocTensor<T>();
AscendC::Add(dstLocal, srcLocal, 1, 1024); // 使用高阶API
outQueueDst.EnQue<T>(dstLocal);
inQueueSrc.FreeTensor(srcLocal);
}
流水线优化是提升算子性能的关键步骤。以下列出几个关键技巧:
优先级:高
描述:在Matmul计算后需要与GM数据进行Add操作时,可以通过AtomicAdd选项实现累加操作。
反例:
DataCopy(local_c, gm_c, c_size);
Add(local_c, local_c, biasLocal, c_size);
DataCopy(gm_c, local_c, c_size);
正例:
DataCopy(local_c, gm_c, c_size);
Add(local_c, local_c, biasLocal, c_size, AtomicAdd);
DataCopy(gm_c, local_c, c_size);
优先级:高
描述:使用Counter模式减少主尾块判断和mask计算。
反例:
AscendC::Add(zLocal, xLocal, yLocal, TILE_LENGTH);
正例:
AscendC::SetMaskCount();
AscendC::SetVectorMask<DTYPE_X, AscendC::MaskMode::COUNTER>(ELE_SIZE);
AscendC::Add(zLocal, xLocal, yLocal, ELE_SIZE);
AscendC::ResetMask();
优先级:高
描述:在MIX场景中,使用异步Compute执行减少Cube与Vector核间交互开销。
反例:
while(mm.template Iterate()){
auto cInUB = qVecIn.AllocTensor<float>();
mm.GetTensorC(cInUB);
qVecIn.EnQue(cInUB);
cInUB = qVecIn.Deque<float>();
auto cOutUB = qVecOut.AllocTensor<float>();
Muls(cOutUB, cInUB, scalar, baseM*baseN);
qVecIn.FreeTensor(cInUB);
...
}
正例:
mm.SetWorkspace(workspace, size);
while(mm.template Iterate<false>()){
auto cInUB = qVecIn.AllocTensor<float>();
mm.GetTensorC(cInUB);
qVecIn.EnQue(cInUB);
cInUB = qVecIn.Deque<float>();
auto cOutUB = qVecOut.AllocTensor<float>();
Muls(cOutUB, cInUB, scalar, baseM*baseN);
qVecIn.FreeTensor(cInUB);
...
}
优化手段 | 优化前耗时 (us) | 优化后耗时 (ub) | 性能提升 |
---|---|---|---|
避免小粒度搬运 | 350 | 300 | 14.3% |
32B对齐优化 | 350 | 280 | 20% |
使用Counter模式 | 350 | 236 | 32.9% |
使用TQueBind优化 | 350 | 281 | 19.7% |
优化手段 | 优化前耗时 (us) | 优化后耗时 (us) | 性能提升 |
---|---|---|---|
L2Cache切分 | 384 | 350 | 8.8% |
32B对齐优化 | 384 | 284 | 26.1% |
使用TilingData优化 | 384 | 344 | 10.4% |
使用Double Buffer优化 | 384 | 304 | 20.9% |
描述:当输入和输出数据量超过L2Cache大小时,使用L2Cache切分策略可以显著提升性能。
优化前后对比:
场景 | 优化前带宽利用率 | 优化后带宽利用率 | 性能提升 |
---|---|---|---|
384MB数据 | 0.8TB/s | 1.6TB/s | 100% |
描述:通过调整地址分配,避免bank冲突。
优化前后对比:
场景 | 优化前bank冲突率 | 优化后bank冲突率 | 性能提升 |
---|---|---|---|
Add算子双输入 | 100% | 0% | 50% |
优化类别 | 关键优化点 | 优先级 |
---|---|---|
数据搬运 | 避免小粒度搬运、32B/512B对齐优化、高效使用搬运API | 高 |
内存管理 | TilingData结构优化、bank冲突避免、double buffer机制 | 中 |
API调用 | Scalar计算精简、iCache利用率提升 | 中 |
流水线优化 | AtomicAdd使能、Counter模式简化、异步Compute执行 | 高 |
优化手段 | 适用场景 | 优先级 |
---|---|---|
DataCopyParams | 非连续数据搬运 | 高 |
L2Cache切分 | 大数据量搬运 | 高 |
bank冲突避免 | 双输入操作 | 高 |
double buffer | Vector计算 | 中 |
Counter模式 | 连续计算 | 高 |
AtomicAdd | 矩阵乘后累加 | 高 |
通过以上优化手段,开发者可以显著提升算子在昇腾AI处理器上的执行效率。每个优化点都有其适用场景和优先级,建议根据实际性能瓶颈选择合适的优化策略。
随着AI模型规模的扩大,单机单卡的训练能力逐渐不够。本章将介绍如何配置多机多卡系统,实现大规模模型的分布式训练。多机多卡训练是昇腾平台实现大规模模型训练的关键场景。本章将从环境准备、脚本适配、集群启动三个层面,介绍如何完成分布式训练系统的搭建。
您将学会如何通过网络配置(如RoCE v2)和软件工具(如HCCL通信库)搭建分布式训练环境。本章还提供了了两种启动方式的示例代码,帮助您顺利启动训练任务。
在实际开发中,开发者需要注意网络带宽、节点IP、昇腾卡数等多个因素,以确保集群的稳定性和训练的高效性。
多机多卡训练依赖高速网络互联。建议使用 RoCE v2 或 InfiniBand 组网,确保网络带宽满足需求。
步骤 | 操作内容 | 命令示例 |
---|---|---|
1 | 关闭防火墙 | Ubuntu: ufw disable Redhat/CentOS: systemctl stop firewalld |
2 | 验证交换机状态 | 使用 ethtool <网口名> 检查网口是否UP |
3 | 配置主机IP | 确保 master_addr 和 master_port 可被集群节点访问,且 IP 与昇腾设备绑定 |
source /usr/local/Ascend/ascend-toolkit/set_env.sh
。昇腾平台支持单机多卡和多机多卡两种模式。开发者需对原始脚本进行以下适配:
os.environ["LOCAL_RANK"]
获取当前节点的卡索引。master_addr
和 master_port
,确保各节点能连接到主节点。torch.distributed.init_process_group(backend="hccl")
初始化HCCL通信组。import os
import torch.distributed as dist
local_rank = int(os.environ["LOCAL_RANK"])
device = torch.device("npu", local_rank)
dist.init_process_group(backend="hccl", rank=local_rank)
model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank])
train_sampler = dist.DistributedSampler(train_data)
train_dataloader = DataLoader(train_data, batch_size=..., sampler=train_sampler)
昇腾平台支持多种命令行启动方式,开发者可根据集群规模选择以下方案:
适用于多机场景,支持灵活配置。
torchrun \
--nnodes=<节点数> \
--nproc_per_node=<每节点卡数> \
--node_rank=<当前节点序号> \
--master_addr=<主节点IP> \
--master_port=<主节点端口> \
ddp_train_script.py
参数 | 作用 | 示例 |
---|---|---|
--nnodes |
集群节点总数 | --nnodes=2 |
--nproc_per_node |
每节点使用的昇腾卡数 | --nproc_per_node=8 |
--node_rank |
当前节点在集群中的序号(0~N-1) | --node_rank=0 |
--master_addr |
主节点IP地址 | --master_addr=192.168.1.100 |
--master_port |
主节点通信端口 | --master_port=12345 |
专为昇腾集群设计,推荐用于多机多卡训练。
export MASTER_IP_ADDR=<主节点IP>
export MASTER_PORT=<主节点端口>
torch_npu_run \
--rdzv_backend=parallel \
--master_addr=$MASTER_IP_ADDR \
--master_port=$MASTER_PORT \
--nnodes=<节点数> \
--node_rank=<当前节点序号> \
--nproc_per_node=<每节点卡数> \
ddp_train_script.py
参数 | torchrun |
torch_npu_run |
特殊说明 |
---|---|---|---|
--nnodes |
支持 | 支持 | 需确保主节点IP可访问 |
--node_rank |
指定当前节点ID | 与 torchrun 一致 |
多节点需唯一 |
--nproc_per_node |
指定每节点卡数 | 支持最大32卡/节点 | 需匹配昇腾设备数量 |
--master_addr |
必填 | 必填 | 主节点IP地址 |
--master_port |
必填 | 必填 | 通常使用 29500 端口 |
Connection refused
。master_addr
和 master_port
指向正确的主节点IP和端口。npu-smi info
确认昇腾设备是否正常识别。DistributedDataParallel
报错 Invalid device index
。device = torch.device("npu", local_rank)
是否正确绑定昇腾卡。dist.init_process_group(backend="hccl")
是否成功初始化。优化点 | 建议 |
---|---|
带宽利用率 | 确保网络带宽为100Gbps或更高,避免成为瓶颈 |
HCCL配置 | 使用 HCCL_TONNector 工具优化通信策略,减少同步开销 |
多卡负载均衡 | 每节点分配的卡数应与昇腾AI处理器物理核数匹配(如Atlas 910为8卡/节点) |
多机多卡训练的核心在于网络稳定性与脚本适配。开发者需注意:
torch_npu_run
启动脚本时,需提前设置 MASTER_IP_ADDR
和 MASTER_PORT
。通过上述步骤,开发者可快速完成昇腾平台的多机多卡训练系统配置,为大规模模型训练提供可靠支持。
算子开发完成后,如何进行调试和验证,是确保其正确性和性能达标的重要环节。本章为您介绍了昇腾算子的调试规则,包括同步插入、偏移地址计算等,帮助您排查潜在问题。
通过ST测试工具(ST测试工具),您可以在不同计算单元上验证算子的功能。本章还提供了了CPU模式与NPU模式的调试方法,帮助您在开发初期快速定位问题。
在昇腾AI处理器上开发算子时,功能调试是确保算子正确执行的关键步骤。以下列出了几个影响功能调试的核心方面及其处理方法:
问题描述:
昇腾AI处理器内部存在多条流水线(如MTE1、MTE2、MTE3、VEC、SC等),不同流水线之间可能存在数据依赖。开发者在编写算子代码时,需确保在数据依赖场景中正确插入同步指令,以避免数据读写冲突和计算错误。
同步机制说明:
注意事项:
SetBlockDim
的设置必须 不大于实际运行核数,否则框架会插入异常同步,导致 Kernel
卡死。PipeBarrier()
来确保流水线间的同步。示例:
LocalTensor<uint8_t> tmpSoftmaxTensor = tmpSoftmaxBuf.Get<uint8_t>(softmaxBufSize);
SoftMax<T, true, true>(dstTensor, expSumTensor, dstMaxTensor, srcTensor, tmpSoftmaxTensor, tiling);
问题描述:
在多核并行计算中,每个核需要处理不同的数据分块。若偏移地址计算不正确,可能导致数据访问越界或读写错误。
处理方法:
GetBlockIdx()
获取当前核的索引。block_idx
与数据长度(如 BLOCK_LENGTH
)结合,计算当前核的数据偏移地址。BLOCK_LENGTH
个元素,当前核的索引为 block_idx
,则偏移地址为 block_idx * BLOCK_LENGTH
。示例代码:
LocalTensor<float> src0Local = inQueueSrc0.AllocTensor<float>();
DataCopy(src0Local, src0Global, 1024);
问题描述:
不同硬件平台对浮点运算的实现方式略有差异,可能导致精度不一致。例如,Vector和Scalar单元的浮点精度不同,或某些指令(如 Exp
)在不同硬件上对舍入规则的处理不同。
处理方法:
float
)作为中间结果,再转换回低精度类型(如 bfloat16
)。Cast
指令来确保数据的精度一致性。示例代码:
Cast(tmp0Tensor, src0Tensor, RoundMode::CAST_NONE, computeSize);
Add(tmp2Tensor, tmp0Tensor, tmp1Tensor, computeSize);
Cast(dstTensor, tmp2Tensor, RoundMode::CAST_FLOOR, computeSize);
昇腾AI处理器的自定义算子开发流程中,ST(Single Test)测试用于验证算子的功能正确性。ST测试的执行流程包括:测试用例定义、执行、报告解读。
使用 msopst
工具生成测试用例,开发者需要定义输入、输出的格式、类型、shape,以及期望的计算结果。
步骤:
编写测试用例定义文件 AddCustom_case.json
,内容如下:
[
{
"case_name": "Test_AddCustom_001",
"op": "AddCustom",
"input_desc": [
{
"format": ["ND"],
"type": ["float16"],
"shape": [8, 2048],
"data_distribute": ["uniform"],
"value_range": [[0.1, 1.0]],
"name": "x"
},
{
"format": ["ND"],
"type": ["float16"],
"shape": [8, 2048],
"data_distribute": ["uniform"],
"value_range": [[0.1, 1.0]],
"name": "y"
}
],
"output_desc": [
{
"format": ["ND"],
"type": ["float16"],
"shape": [8, 2048],
"name": "z"
}
]
}
]
执行ST测试时,需要配置以下环境变量:
环境变量 | 说明 |
---|---|
DDK_PATH |
CANN软件安装路径(如:/usr/local/Ascend/ascend-toolkit ) |
NPU_HOST_LIB |
NPU运行库路径(如:$DDK_PATH/ ) |
执行命令:
./msopst run -i $HOME/AddCustom_st/AddCustom_case.json -soc <soc_version> -out $HOME/
-i
:测试用例定义文件的路径。-soc
:昇腾AI处理器的型号。-out
:测试结果文件的输出路径。执行结果:
若测试成功,控制台输出如下信息:
Test case count: 1
Success count: 1
Failed count: 0
The st report saved in: xxxx/AddCustom_st/20230828202015/st_report.json.
st_report.json
包含了测试用例的执行结果和性能数据。开发者可通过以下字段判断算子功能是否正常:
字段 | 说明 |
---|---|
case_name |
测试用例名称 |
status |
测试状态(SUCCESS 表示通过) |
duration |
测试用例执行时间(单位:微秒) |
output |
输出数据与真值数据的对比结果(Matched 表示一致) |
建议:
st_report.json
文件,查看详细的数据。Chrome
浏览器访问 chrome://tracing
,上传 trace.json
文件进行可视化分析。昇腾平台支持两种调试模式:CPU模式和NPU模式。开发者可以在CPU模式下进行初步调试,确认算子逻辑无误后再迁移至NPU模式进行验证。
关键函数:
示例代码:
uint8_t* x = (uint8_t*)AscendC::GmAlloc(inputByteSize);
ReadFile("./input/input_x.bin", inputByteSize, x, inputByteSize);
ICPU_RUN_KF(add_custom, blockDim, x, y, z);
WriteFile("./output/output_z.bin", z, outputByteSize);
AscendC::GmFree((void*)x);
AscendC::GmFree((void*)y);
AscendC::GmFree((void*)z);
关键函数:
示例代码:
CHECK_ACL(aclrtMemcpy(xDevice, inputByteSize, xHost, inputByteSize, ACL_MEMCPY_HOST_TO_DEVICE));
CHECK_ACL(aclrtMemcpy(yDevice, inputByteSize, yHost, inputByteSize, ACL_MEMCPY_HOST_TO_DEVICE));
add_custom_do(blockDim, nullptr, stream, xDevice, yDevice, zDevice);
CHECK_ACL(aclrtMemcpy(zHost, outputByteSize, zDevice, outputByteSize, ACL_MEMCPY_DEVICE_TO_HOST));
项目 | CPU模式 | NPU模式 |
---|---|---|
调试速度 | 快 | 慢 |
调试工具 | ICPU_RUN_KF |
aclrtMemcpy , aclrtMemcpy |
内存管理 | GmAlloc , GmFree |
aclrtMalloc , aclrtMemcpy |
性能分析 | 仅用于功能验证 | 可用于性能优化 |
在算子开发过程中,开发者可能会遇到以下常见问题。以下是典型问题及其解决方案:
原因:
bfloat16
无法在Vector指令中使用)。解决方案:
float
类型,计算完成后再转换回 bfloat16
。示例:
Cast(tmp0Tensor, src0Tensor, RoundMode::CAST_NONE, computeSize);
Add(tmp2Tensor, tmp0Tensor, tmp1Tensor, computeSize);
Cast(dstTensor, tmp2Tensor, RoundMode::CAST_FLOOR, computeSize);
原因:
DataCopy
时未正确使用 srcStride
和 dstStride
参数,导致内存冲突。解决方案:
TilingData
结构体,减少不必要的字段。DataCopy
:确保 DataCopy
的参数对齐,避免32B/512B对齐问题。示例:
DataCopyParams copyParams;
copyParams.blockCount = imgHeight;
copyParams.blockLen = copyWidth / 8; // 每个DataBlock内有8个float
copyParams.srcStride = (imgWidth - copyWidth) / 8;
copyParams.dstStride = 0;
DataCopy(tensorIn, tensorGM, copyParams);
原因:
解决方案:
DataCopy
的 srcStride
和 dstStride
参数实现非连续搬运。GlobalTensor
地址对齐,提升搬运效率。示例:
GlobalTensor<float> src0Global;
LocalTensor<float> src0Local = inQueueSrc0.AllocTensor<float>();
DataCopy(src0Local, src0Global, { 1, (uint16_t)(count * sizeof(float) / 32), 0, 0 });
工具名称 | 用途 | 使用场景 |
---|---|---|
msopst |
ST测试 | 功能验证 |
msProf |
性能分析 | 优化搬运次数 |
GmAlloc /GmFree |
CPU模式调试 | 初期调试 |
aclrtMemcpy |
NPU模式调试 | 验证输出结果 |
本章为开发者提供了算子调试与验证的完整流程,涵盖了功能调试、ST测试、CPU/NPU调试模式以及常见问题的解决方案。开发者在实际调试过程中,应结合上述步骤,逐步排查问题,确保算子逻辑正确、性能达标。
本章通过具体案例,带您走进昇腾算子开发的实战世界。我们选取了SiLU激活函数作为开发案例,展示了从功能设计到性能优化的完整流程。
此外,我们还分析了如何通过L2Cache切分策略减少Global Memory访问次数,以及如何通过地址偏移解决bank冲突问题。这些案例将帮助您更好地理解昇腾算子的开发思路和优化方向。
SiLU(Sigmoid-weighted Linear Unit)是一种结合Sigmoid函数的激活函数,其数学表达式为:
SiLU(x) = x ∙ σ(x)
其中:
数据搬运与内存管理
DataCopy
接口进行数据搬运Vector计算优化
AscendC::Muls
计算 x ⋅ σ ( x ) x \cdot σ(x) x⋅σ(x)AscendC::Exp
和AscendC::Adds
实现Sigmoid函数代码实现
class KernelSiLU {
public:
__aicore__ inline KernelSiLU() {}
__aicore__ inline void Init(GM_ADDR in, GM_ADDR out, uint32_t inputSize) {
inGm.SetGlobalBuffer((__gm__ half*)in);
outGm.SetGlobalBuffer((__gm__ half*)out);
pipe.InitBuffer(inQueue, 1, inputSize * sizeof(half));
}
__aicore__ inline void Process() {
CopyIn();
Compute();
CopyOut();
}
private:
__aicore__ inline void CopyIn() {
LocalTensor<half> inLocal = inQueue.AllocTensor<half>();
DataCopy(inLocal, inGm, inputSize);
inQueue.EnQue(inLocal);
}
__aicore__ inline void Compute() {
LocalTensor<half> inLocal = inQueue.DeQue<half>();
LocalTensor<half> sigmoidLocal = outQueue.AllocTensor<half>();
AscendC::Muls(sigmoidLocal, inLocal, -1.0f, inputSize); // 计算 -x
AscendC::Exp(sigmoidLocal, sigmoidLocal, inputSize); // 计算 e^(-x)
AscendC::Adds(sigmoidLocal, sigmoidLocal, 1.0f, inputSize); // 计算 1 + e^(-x)
AscendC::Div(outLocal, inLocal, sigmoidLocal, inputSize); // 计算 x / (1 + e^(-x))
inQueue.FreeTensor(inLocal);
outQueue.EnQue(outLocal);
}
__aicore__ inline void CopyOut() {
LocalTensor<half> outLocal = outQueue.DeQue<half>();
DataCopy(outGm, outLocal, inputSize);
outQueue.FreeTensor(outLocal);
}
AscendC::TPipe pipe;
AscendC::TQue<QuePosition::VECIN, 1> inQueue;
AscendC::TQue<QuePosition::VECOUT, 1> outQueue;
AscendC::GlobalTensor<half> inGm, outGm;
uint32_t inputSize;
};
优化前 | 优化后 | 性能提升 |
---|---|---|
标量计算参与运算 | 仅使用Vector计算 | 减少50% Scalar耗时 |
多次小粒度搬运 | 联合搬运减少次数 | 降低30%搬运时间 |
无内存对齐优化 | 32B/512B对齐优化 | 提升25%带宽利用率 |
当输入数据量超过L2Cache容量时(例如384MB输入,L2Cache为192MB),传统搬运会导致频繁访问Global Memory (GM),成为性能瓶颈。优化策略:通过分块减少GM访问次数。
分块逻辑
代码实现
constexpr int32_t TOTAL_LENGTH = 384 * 1024 * 1024 / sizeof(half);
constexpr int32_t TILE_NUM = 2;
constexpr int32_t USE_CORE_NUM = 20;
constexpr int32_t TILE_LENGTH = TOTAL_LENGTH / USE_CORE_NUM / TILE_NUM;
__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling) {
GET_TILING_DATA(tiling_data, tiling);
xGm.SetGlobalBuffer((__gm__ half*)x + TILE_LENGTH * AscendC::GetBlockIdx() * TILE_NUM, TILE_LENGTH * TILE_NUM);
yGm.SetGlobalBuffer((__gm__ half*)y + TILE_LENGTH * AscendC::GetBlockIdx() * TILE_NUM, TILE_LENGTH * TILE_NUM);
pipe.InitBuffer(inQueueX, 2, TILE_LENGTH * sizeof(half));
pipe.InitBuffer(inQueueY, 2, TILE_LENGTH * sizeof(half));
pipe.InitBuffer(outQueueC, 2, TILE_LENGTH * sizeof(half));
}
__aicore__ inline void Process() {
for (int32_t i = 0; i < TILE_NUM; i++) {
CopyIn(i);
Compute(i);
CopyOut(i);
}
}
__aicore__ inline void CopyIn(int32_t i) {
LocalTensor<half> xLocal = inQueueX.AllocTensor<half>();
DataCopy(xLocal, xGm[i * TILE_LENGTH], TILE_LENGTH);
inQueueX.EnQue(xLocal);
}
__aicore__ inline void Compute(int32_t i) {
LocalTensor<half> xLocal = inQueueX.DeQue<half>();
LocalTensor<half> yLocal = inQueueY.DeQue<half>();
LocalTensor<half> cLocal = outQueueC.AllocTensor<half>();
Mmad(cLocal, xLocal, yLocal, mmadParams); // 矩阵乘加Bias
outQueueC.EnQue(cLocal);
inQueueX.FreeTensor(xLocal);
inQueueY.FreeTensor(yLocal);
}
__aicore__ inline void CopyOut(int32_t i) {
LocalTensor<half> cLocal = outQueueC.DeQue<half>();
DataCopy(cGm[i * TILE_LENGTH], cLocal, TILE_LENGTH);
outQueueC.FreeTensor(cLocal);
}
在处理双输入Add算子时,两个输入地址可能落在同一Bank Group内,导致读写冲突。例如:
x
和y
均从地址0x10000
开始,长度8KBy
地址偏移32B(0x10020
),与x
错开地址调整
LocalTensor<float> xLocal = inQueueX.AllocTensor<float>();
LocalTensor<float> yLocal = inQueueY.AllocTensor<float>();
DataCopy(xLocal, xGm, xSize);
DataCopy(yLocal, yGm, ySize);
性能对比
反例 | 正例 | 性能提升 |
---|---|---|
读写冲突导致50%时间浪费 | 无冲突,Vector利用率95% | 减少35%总耗时 |
Bank Group规则
msProf
分析Bank冲突占比算子开发不仅仅是写代码,更需要一套完整的工程化流程。本章介绍昇腾算子开发的工程化关键步骤,涵盖算子工程创建、Tiling实现、编译部署等核心环节,帮助开发者快速构建可维护的算子工程。
通过本章,您将了解到如何打包算子、安装到昇腾系统,并验证其部署效果。工程化流程的规范化将帮助您在大规模开发中提高效率和稳定性。本章将介绍如何利用msopgen(工程生成工具)快速生成算子工程结构,以及如何通过TilingData(分块策略)和CMake(编译配置)实现算子的标准化构建。
msopgen是昇腾官方提供的算子工程生成工具,可自动生成标准化工程框架。支持功能:
[
{
"op": "SiLUNew",
"input_desc": [
{"name": "x", "param_type": "required", "format": ["ND"], "type": ["float16", "float32"]}
],
"output_desc": [
{"name": "y", "param_type": "required", "format": ["ND"], "type": ["float16", "float32"]}
]
}
]
msopgen gen -i <path/to/op_def.json> -c ai_core-<soc_version> -lan cpp
参数 | 说明 | 示例值 |
---|---|---|
|
目标芯片型号(如Ascend910A2) | Ascend910A2 |
-lan cpp |
使用C++编程语言 |
SiLUNew/
├── build.sh # 编译入口脚本
├── framework/ # 框架适配代码
├── op_host/ # Host侧代码(Tiling等)
├── op_kernel/ # Kernel侧代码(核函数实现)
└── scripts/ # 打包脚本
BEGIN_TILING_DATA_DEF(SiLUTileData)
TILING_DATA_FIELD_DEF(uint32_t, tile_length); // 分块长度
TILING_DATA_FIELD_DEF(uint32_t, tile_num); // 分块数量
END_TILING_DATA_DEF;
static ge::graphStatus TilingFunc(gert::TilingContext *context) {
SiLUTileData tiling;
uint32_t total_length = context->GetInputTensor(0)->GetShapeSize();
// 核数设置(示例为24核)
context->SetBlockDim(24);
// 分块策略(总长512时分4块)
tiling.set_tile_length(total_length / 4);
tiling.set_tile_num(4);
// 序列化参数至内存
tiling.SaveToBuffer(...);
return ge::GRAPH_SUCCESS;
}
场景 | 最佳实践 |
---|---|
数据量>内存容量 | 启用L2Cache切分策略 |
矩阵运算 | 优先计算K维度切分 |
不规则形状 | 使用K_MAX_SHAPE_DIM 控制bank冲突 |
# 核心配置文件(CMakeLists.txt)
add_subdirectory(op_host)
add_subdirectory(op_kernel)
include_directories(${ASCENDCL_HOME}/include)
link_directories(${ASCENDCL_HOME}/lib)
add_executable(silu_test main.cpp)
target_link_libraries(silu_test ${ASCENDCL_LIB})
# 编译命令示例
./build.sh -v Ascend910A2 -c debug
参数 | 说明 | 默认值 |
---|---|---|
-v |
目标芯片型号 | Ascend910A2 |
-c |
编译模式(release/debug) | release |
build_out/
├── bin/ # 可执行文件
├── lib/ # 动态库
└── include/ # 接口头文件
# 打包命令(路径替换为实际路径)
python3 setup.py bdist_opp --vendor your_vendor
# 安装到默认路径
./custom_opp_<os>_<arch>.run
# 自定义安装路径
./custom_opp_<os>_<arch>.run --install-path=/opt/opp
# 查看已安装算子
op_info list | grep SiLUNew
# 验证性能
msprof --op SiLUNew --input_shape [1,3,224,224]
// Kernel实现(SiLUKernel.cpp)
class SiLUKernel {
public:
__aicore__ inline void Process() {
for (int i = 0; i < tiling.tile_num; ++i) {
CopyIn(i);
Compute(i);
CopyOut(i);
}
}
private:
__aicore__ inline void Compute(int idx) {
// 使用Vector单元计算x * sigmoid(x)
AscendC::Sigmoid(tmp, srcLocal);
AscendC::Muls(dstLocal, srcLocal, tmp, tiling.tile_length);
}
};
步骤 | 描述 |
---|---|
数据搬运优化 | 使用DataCopy 的block参数减少开销 |
同步控制 | 确保EnQue/DeQue 在正确阶段调用 |
内存分配 | TPipe::InitBuffer 控制队列大小 |
通过以上流程,开发者可快速实现算子从开发到部署的全流程,关键步骤包括工程生成、Tiling策略设计、编译配置及性能验证。建议使用msprof
工具持续监控性能指标,重点优化内存带宽利用率和计算单元利用率。
在算子开发过程中,难免会遇到各种问题。本章为您汇总了常见的错误和解决方案,涵盖精度异常、资源冲突、编译器优化限制等多个方面。
通过本章的指导,您将能够快速排查问题,并根据实际情况进行修复。我们还提供了开发Checklist和性能调优优先级建议,帮助您在开发过程中少走弯路。
现象 | 原因 | 解决方案 |
---|---|---|
计算结果与GPU版本存在差异 | 浮点计算不满足交换律/结合律 | 强制按序计算 对于精度敏感场景,按顺序执行标量运算(如先乘后加) |
某些api导致结果漂移 | 复合指令内部精度舍入 | 拆分复合运算 将 Axp 拆分为独立Mul 和Add 指令 |
bank冲突场景下出现nan | 内存访问冲突导致数据污染 | 调整数据布局 通过 32B/512B对齐 优化访问 |
// 反例:直接修改参数指针
__aicore__ __global__ void FlashAttentionKernel(__gm__ uint8_t* query, __gm__ uint8_t* key, __gm__ uint8_t* tiling_data) {
query = tmpQueryPtr; // ❌ 禁止修改输入参数指针
key = tmpKeyPtr;
}
Cast
操作,优先使用FP32
中间类型再转回目标精度冲突类型 | 检测方法 | 优化手段 |
---|---|---|
bank读写冲突 | 仿真流水图分析 msprof工具检测bank冲突 |
调整地址偏移 通过 GetBlockIdx() 控制不同核间地址分配 |
L2Cache未命中 | msprof带宽分析 查看 aic_mte_ratio 指标 |
数据切片优化 将数据拆分为≤192MB块,利用L2Cache |
double buffer失效 | 实际执行耗时分析 对比串行/并行场景的 aiv_vec_time |
合理配置BUFFER_NUM 在 pipe.InitBuffer() 中设置为2 |
// 反例:bank冲突场景
AscendC::Add(dstLocal, src0Local, src1Local, mask, count, params);
// 两个输入来自同一bank_group时,读取会排队
// 正例:通过地址偏移错开bank
LocalTensor<float> src0 = inQueue0.AllocTensor<float>();
LocalTensor<float> src1 = inQueue1.AllocTensor<float>();
// src0与src1地址间隔≥32B时避免冲突
AscendC::Add(dstLocal, src0, src1, mask, count, params);
限制场景 | 表现 | 规避方法 |
---|---|---|
Scalar常量折叠失效 | 逻辑判断导致性能下降 | 预计算参数 在Host侧完成所有shape相关计算 |
TilingData结构污染 | 地址分配延迟增加 | 精简结构体字段 仅保留必要参数(如blockDim、tileNum) |
Matmul异步调用失败 | Cube与Vector核通信延迟 | 绑定TQue 使用 TQueBind 实现A1→VECIN直通 |
// 反例:TPipe对象内部污染
class KernelExample {
private:
TPipe pipe; // ❌ 导致Scalar指令无法优化
TQue<VECIN> inQueue;
}
// 正例:分离TPipe污染
extern TPipe pipe; // ✅ 推荐全局TPipe对象
class KernelExample {
private:
TQue<VECIN> inQueue;
}
优化项 | 反例耗时 | 正例耗时 | 提升度 |
---|---|---|---|
Tiling结构冗余 | 281us | 236us | 17% |
TPipe污染 | 350us | 280us | 20% |
Matmul AtomicAdd | 154us | 135us | 12.4% |
CANN安装验证
npu-smi info # 获取soc_version
source /usr/local/Ascend/ascend-toolkit/set_env.sh
依赖版本检查
gcc --version # 需≥7.3.0
python3 --version # 需3.7.x~3.11.x
检查点 | 合格标准 | 验证方法 |
---|---|---|
核函数返回类型 | 必须为void |
编译检查 |
GM地址对齐 | 优先512B对齐 | 使用msprof --bandwidth 分析 |
TilingData大小 | ≤64KB | 检查结构体字节数 |
msopst
生成随机数据测试msprof --output ./
采集流水数据,分析op_summary.csv
中各流水利用率msprof --bank conflict
输出冲突比例// ❌ 错误:修改kernel参数指针
__aicore__ __global__ void add_custom(__gm__ uint8_t* x, __gm__ uint8_t* y, __gm__ uint8_t* z) {
x = x + 1024; // 参数地址偏移需通过GlobalTensor内部实现
}
// ❌ 错误:未对齐地址
LocalTensor<half> srcLocal = inQueue.AllocTensor<half>();
DataCopy(srcLocal, srcGM, 1024); // 1024字节未达到512B对齐
// ✅ 正确:使用ShapeInfo优化对齐
constexpr uint32_t ALIGN_NUM = 512 / sizeof(half);
uint32_t totalLengthAligned = ((inputSize + ALIGN_NUM - 1) / ALIGN_NUM) * ALIGN_NUM;
xGm.SetGlobalBuffer((__gm__ half*)x + GetBlockIdx() * totalLengthAligned, totalLengthAligned);
现象 | 原因 | 解决方案 |
---|---|---|
连接失败 | 防火墙未关闭 | 在Ubuntu执行ufw disable |
IP冲突 | master_addr配置错误 | 检查/etc/hosts 文件 |
带宽不足 | 未使用--nproc_per_node |
修改torchrun 脚本参数 |
# ❌ 错误:未指定节点rank
torchrun --nnodes=2 --nproc_per_node=8 ddp_train.py
# ✅ 正确:完整节点配置
export MASTER_PORT=1234
torchrun --nnodes=2 --nproc_per_node=8 --node_rank=0 --master_addr=192.168.1.1 ddp_train.py
级别 | 优化类型 | 典型场景 |
---|---|---|
P0 | 搬运合并 | 非连续数据使用srcStride 参数 |
P1 | bank冲突规避 | UB地址分配时添加32B偏移 |
P2 | iCache复用 | 矩阵乘计算间共享中间buffer |
P3 | 核间负载均衡 | 在分离架构中合理分配AIC/AIV并行度 |
blockNum
与物理核数匹配)错误操作 | 正确操作 |
---|---|
❌ 未指定soc_version | ✅ msopgen gen -i op_def.json -c ai_core-Ascend910B |
❌ 使用默认K_MAX_SHAPE_DIM | ✅ 在包含头文件前定义#define K_MAX_SHAPE_DIM 0 |
# 反例:未绑定环境变量
./build.sh
# 正例:指定安装路径
./custom_opp.run --install-path=/usr/local/Ascend/opp/vendors/custom
source /usr/local/Ascend/opp/vendors/custom/bin/set_env.sh