前言
本期我们梳理了算子开发的完整开发链路,吃透了「Host 主机调度 + AICore Kernel 核内流水线」标准化开发框架,进阶学习双输入二元逐元素算子,先从最简 HelloWorld 熟悉设备调度基础流程,再完整落地逐元素加法算子 AddCustom,拆解整套通用开发模板;同时配套 SubCustom 填空实战作业,并附上可直接编译运行的完整参考源码。 整篇内容使用开发教程的写作逻辑,不单纯堆砌代码,每段配套底层原理注释、高频疑问解答,拆解分片、内存拷贝、流水线双缓冲、Host/Device 内存交互核心逻辑,新手也能看懂底层设计思路,吃透 "为什么这么写",而不是死记硬背代码模板。
一、开发前置认知
所有 Ascend C 自定义算子遵循统一分层架构:
- Kernel 设备侧 :运行在昇腾 AICore,负责数据分片搬运、本地缓存流水线、内置算子计算,分为
Init/Process/CopyIn/Compute/CopyOut标准五段式结构; - Host 主机侧:运行在 CPU,负责 ACL 设备初始化、Host/Device 内存分配、双向数据拷贝、核函数异步启动、流同步、资源释放;
- 统一流水线设计:采用双缓冲
BUFFER_NUM=2实现计算与数据拷贝并行,隐藏 IO 耗时,提升硬件利用率; - 统一验证体系:CPU 标准真值函数 + 批量打印校验函数,快速验证算子精度。
本次算子统一规范:
- 数据类型:输入输出均为
float - 张量 Shape:固定
(8, 2048),一维展开总长度8*2048 - 数据布局:ND 标准稠密布局
- 并行策略:启动 8 个 Block 均分全部数据,单核内部再细分多块 tile 流水线处理
二、开篇入门:HelloWorld 极简核函数(通用调度底座)
正式开发算子前,先通过极简示例掌握昇腾程序固定执行流程,所有加减乘除、激活算子都会复用这套 Host 基础逻辑:初始化 ACL、创建设备流、启动核函数、同步等待、释放全部资源。 这段代码多 Block 打印日志,直观理解 AICore 多核心并行特性。
#include "acl/acl.h"
#include "kernel_operator.h"
using namespace AscendC;
// Kernel全局核函数:运行在AICore设备端
__global__ __aicore__ void hi_ascend(){
// 打印当前块索引、总块数量,验证多核心并行拆分效果
printf("Block[%lu/%lu]: Hi Ascend\n",GetBlockIdx(),GetBlockNum());
}
// Host主机主程序入口
int32_t main(int argc ,char const *argv[]){
// 1. ACL框架全局初始化
aclInit(nullptr);
int32_t deviceId=0;
aclrtSetDevice(deviceId);
aclrtStream stream=nullptr;
aclrtCreateStream(&stream);
// 2. 启动4个Block并行执行核任务
constexpr uint32_t blockDim=4;
hi_ascend<<<blockDim,nullptr,stream>>>();
// 3. 流同步阻塞等待所有计算完成,再释放资源
aclrtSynchronizeStream(stream);
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
return 0;
}
基础流程总结
全系列算子通用执行链路: 初始化设备 → 创建异步任务流 <<<>>> 启动核函数 → stream同步等待计算结束 → 销毁流/重置设备/释放ACL 后续 Add、Sub 算子仅在此基础上增加张量内存分配、分片参数传递、GM 与 LocalTensor 数据搬运、核内数学计算模块。
三、核心实战:逐元素加法算子 AddCustom 端到端完整实现
逐元素加法是深度学习最基础二元算子,输入两个同 shape 张量 x、y,输出 z=x+y。我们完整实现 Kernel 设备侧 + Host 主机侧代码,拆解每一块的设计目的。
3.1 全局常量与通用头文件
头文件区分 Host 侧 ACL 接口、Kernel 侧算子接口;全局常量控制流水线缓存配置,和 Sigmoid 算子保持统一规范。
#include <cstdint>
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
// Host主机ACL运行时接口
#include "acl/acl.h"
// AICore核内计算底层API
#include "kernel_operator.h"
using namespace AscendC;
using namespace std;
// 流水线核心配置:双缓冲并行
constexpr uint32_t BUFFER_NUM=2; // 队列缓存张量数量,一边拷贝一边计算
constexpr uint32_t QUEUE_DEPTH=2; // TQue队列深度,控制异步读写容量
3.2 Tiling 分片参数结构体
Host 通过结构体把全局数据长度、单核分块数量传递到 Kernel,统一管理分片逻辑,后续修改并行粒度只需要调整入参,不用改动核内代码。
struct AddCustomTilingData{
uint32_t totalLength; // 全部输入张量一维总长度
uint32_t tileNum; // 单个AICore内部细分小分块数量
};
3.3 KernelAdd 核计算封装类(标准五段式架构)
面向对象封装所有核内逻辑,解耦内存初始化、数据迁入、计算、结果写出,是 Ascend C 自定义算子标准范式,和 SigmoidKernel 类结构完全对齐。
class KernelAdd{
public:
// 空构造
__aicore__ inline KernelAdd(){}
/// @brief Init初始化:分片长度计算 + GlobalTensor全局内存绑定 + 流水线队列内存分配
/// @param x/y/z 输入输出全局内存地址
/// @param totalLength 张量总长度
/// @param tileNum 单核细分块数量
__aicore__ inline void Init(GM_ADDR x,GM_ADDR y,GM_ADDR z,uint32_t totalLength,uint32_t tileNum);
/// @brief Process流水线总调度入口:循环执行CopyIn -> Compute -> CopyOut
__aicore__ inline void Process();
private:
// 子功能私有方法:数据迁入、核内加法计算、结果回写
__aicore__ inline void CopyIn(int32_t progress);
__aicore__ inline void Compute(int32_t progress);
__aicore__ inline void CopyOut(int32_t progress);
private:
Tpipe pipe; // 流水线内存管理器
TQue<TPosition::VECIN,QUEUE_DEPTH> inQueueX,inQueueY; // 两路输入向量队列
TQue<TPosition::VECOUT,QUEUE_DEPTH> outQueueZ; // 输出向量队列
GlobalTensor<float> xGm,yGm,zGm; // 绑定设备全局GM内存张量
uint32_t blockLength; // 单个Block(AICore)独占处理数据长度
uint32_t tileNum; // 单核内部细分块数
uint32_t tileLength; // 单个小分块数据长度
};
3.4 Init 初始化函数详解
核心完成两件事:数据均匀分片、绑定当前 Block 专属内存区间、为输入输出队列分配 Local 本地缓存字节空间。
__aicore__ inline void KernelAdd::Init(GM_ADDR x,GM_ADDR y,GM_ADDR z,uint32_t totalLength,uint32_t tileNum){
// 1. 均分全局数据:总长度 / 并行Block数量,每个核心只处理自己对应的区间
this->blockLength=totalLength/AscendC::GetBlockNum();
this->tileNum=tileNum;
// 细分小块长度,适配AICore有限本地缓存,BUFFER_NUM双缓冲拆分
this->tileLength=this->blockLength/tileNum/BUFFER_NUM;
// 2. 绑定当前Block专属GM内存,GetBlockIdx获取当前核心编号,多核心数据互不重叠
xGm.SetGlobalBuffer((__gm__ float *)x + this->blockLength*GetBlockIdx(),this->blockLength);
yGm.SetGlobalBuffer((__gm__ float *)y +this->blockLength*GetBlockIdx(),this->blockLength);
zGm.SetGlobalBuffer((__gm__ float *)z+this->blockLength*GetBlockIdx(),this->blockLength);
// 3. 流水线队列分配内存,入参BUFFER_NUM代表双缓冲,size按字节计算
pipe.InitBuffer(inQueueX,BUFFER_NUM,this->tileLength*sizeof(float));
pipe.InitBuffer(inQueueY,BUFFER_NUM,this->tileLength*sizeof(float));
pipe.InitBuffer(outQueueZ,BUFFER_NUM,this->tileLength*sizeof(float));
}
高频疑问:InitBuffer 第二个参数为什么是 BUFFER_NUM?
BUFFER_NUM 代表队列内部缓存的 LocalTensor 数量,取值 2 实现双缓冲流水线:一块本地缓存正在执行计算,另一块同步从 GM 搬运下一批数据,掩盖内存拷贝延迟,最大化 AICore 算力利用率;单输入 / 双输入算子统一配置 BUFFER_NUM=2,架构通用。
3.5 Process 流水线循环调度
总循环次数 = 单核分块数 × 双缓冲数量,循环依次执行「载入数据 - 计算 - 写出结果」完整链路。
__aicore__ inline void KernelAdd::Process(){
int32_t loopCount=this->tileNum*BUFFER_NUM;
for(int32_t i=0;i<loopCount;i++){
CopyIn(i); // GM全局内存 → AICore Local本地内存
Compute(i); // 本地张量逐元素加法计算
CopyOut(i); // Local计算结果 → GM全局输出内存
}
}
3.6 CopyIn / Compute / CopyOut 三段核心逻辑
// CopyIn:从全局内存搬运单块数据到本地输入队列
__aicore__ inline void KernelAdd::CopyIn(int32_t progress){
LocalTensor<float> xLocal=inQueueX.AllocTensor<float>();
LocalTensor<float> yLocal=inQueueY.AllocTensor<float>();
DataCopy(xLocal,xGm[progress*this->tileLength],this->tileLength);
DataCopy(yLocal,yGm[progress*this->tileLength],this->tileLength);
inQueueX.EnQue(xLocal);
inQueueY.EnQue(yLocal);
}
// Compute:取出两路本地张量,调用AscendC内置Add逐元素求和
__aicore__ inline void KernelAdd::Compute(int32_t progress){
LocalTensor<float> xLocal=inQueueX.DeQue<float>();
LocalTensor<float> yLocal=inQueueY.DeQue<float>();
LocalTensor<float> zLocal=outQueueZ.AllocTensor<float>();
Add(zLocal,xLocal,yLocal,this->tileLength);
outQueueZ.EnQue<float>(zLocal);
// 释放无用本地张量,节省L0/L1缓存
inQueueX.FreeTensor(xLocal);
inQueueY.FreeTensor(yLocal);
}
// CopyOut:计算完成的本地张量写回设备全局输出内存
__aicore__ inline void KernelAdd::CopyOut(int32_t progress){
LocalTensor<float> zLocal=outQueueZ.DeQue<float>();
DataCopy(zGm[progress*tileLength],zLocal,tileLength);
outQueueZ.FreeTensor(zLocal);
}
3.7 全局核函数入口
Host 端 <<<>>> 调度的唯一入口,声明任务类型为纯 AI 计算,实例化 KernelAdd 对象并启动流水线。
__global__ __aicore__ void add_custom(GM_ADDR x,GM_ADDR y,GM_ADDR z,AddCustomTilingData tiling){
KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY);
KernelAdd op;
op.Init(x,y,z,tiling.totalLength,tiling.tileNum);
op.Process();
}
3.8 Host 侧 kernel_add 调度函数(内存交互核心)
Host 侧完整链路:初始化设备 → 分配锁页 Host 内存、设备 GM 显存 → CPU 数据拷贝至芯片显存 → 异步启动核函数 → 同步等待 → 结果拷贝回 CPU 内存 → 统一释放全部资源。 配套解释高频疑问:totalByteSize、uint8_t 强转、Host/Device 内存区分、aclrtMemcpy 参数规范。
vector<float> kernel_add(vector<float> &x,vector<float> &y){
constexpr uint32_t blockDim=8;
uint32_t totalLength=x.size();
// totalByteSize:内存拷贝API仅识别字节流,总字节=元素个数×单float4字节
size_t totalByteSize=totalLength*sizeof(float);
int32_t deviceId=0;
aclrtStream stream=nullptr;
AddCustomTilingData tiling={totalLength,8};
// uint8_t* 强转原因:ACL内存接口底层操作字节流,不感知float/int类型,统一用1字节指针管理整块内存
uint8_t *xHost=reinterpret_cast<uint8_t *>(x.data());
uint8_t *yHost=reinterpret_cast<uint8_t *>(y.data());
uint8_t *zHost=nullptr,*xDevice=nullptr,*yDevice=nullptr,*zDevice=nullptr;
aclInit(nullptr);
aclrtSetDevice(deviceId);
aclrtCreateStream(&stream);
// 分配CPU锁页Host内存、昇腾设备全局显存
aclrtMallocHost((void**)(&zHost),totalByteSize);
aclrtMalloc((void **)&xDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);
aclrtMalloc((void **)&yDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);
aclrtMalloc((void **)&zDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);
/*
aclrtMemcpy 4参标准同步接口规范:dst, src, size, kind
参数顺序口诀:目标,来源,字节长度,拷贝方向
禁止错误写法:dst, size, src, size, kind(偏移参数与长度混淆,会内存越界崩溃)
*/
aclrtMemcpy(xDevice, xHost, totalByteSize, ACL_MEMCPY_HOST_TO_DEVICE);
aclrtMemcpy(yDevice, yHost, totalByteSize, ACL_MEMCPY_HOST_TO_DEVICE);
// 异步启动自定义加法核算子
add_custom<<<blockDim, nullptr, stream>>>(xDevice, yDevice, zDevice, tiling);
aclrtSynchronizeStream(stream);
// 设备显存计算结果拷贝回CPU主机内存
aclrtMemcpy(zHost, zDevice, totalByteSize, ACL_MEMCPY_DEVICE_TO_HOST);
std::vector<float> z((float *)zHost, (float *)(zHost + totalByteSize));
// Host与Device内存物理隔离,必须统一释放防止内存泄漏
aclrtFree(xDevice);
aclrtFree(yDevice);
aclrtFree(zDevice);
aclrtFreeHost(zHost);
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
return z;
}
核心疑问统一解答
- totalByteSize 作用:aclrtMemcpy 底层操作字节,无 float 类型概念,必须计算整块内存总字节数作为拷贝长度;
- uint8_t 指针强转:统一字节流操作,适配 ACL 底层内存接口,避免类型截断;
- Host/Device 内存区分:Host=CPU 内存,仅 CPU 读写;Device = 昇腾芯片显存,仅 AICore 访问;两者物理隔离,必须通过 aclrtMemcpy 完成数据交换,不能直接跨端指针访问;
- aclrtMemcpy 参数 :日常单块张量使用 4 参同步接口,顺序
dst,src,size,kind;7 参异步接口才携带偏移量,不可混淆参数位置。
3.9 通用校验函数 + 测试主程序
和 Sigmoid 教程复用同一套校验逻辑,打印前 20 个数值对比算子输出与 CPU 标准真值,快速定位精度问题。
// 通用精度验证函数
uint32_t VerifyResult(std::vector<float> &output, std::vector<float> &golden)
{
auto printTensor = [](std::vector<float> &tensor, const char *name) {
constexpr size_t maxPrintSize = 20;
std::cout << name << ": ";
std::copy(tensor.begin(), tensor.begin() + std::min(tensor.size(), maxPrintSize),
std::ostream_iterator<float>(std::cout, " "));
if (tensor.size() > maxPrintSize) std::cout << "...";
std::cout << std::endl;
};
printTensor(output, "Output");
printTensor(golden, "Golden");
if (std::equal(golden.begin(), golden.end(), output.begin())) {
std::cout << "[Success] Case accuracy is verification passed." << std::endl;
return 0;
} else {
std::cout << "[Failed] Case accuracy is verification failed!" << std::endl;
return 1;
}
}
// 主测试入口
int32_t main(int32_t argc, char *argv[])
{
constexpr uint32_t totalLength = 8 * 2048;
constexpr float valueX = 1.2f;
constexpr float valueY = 2.3f;
// 构造固定shape测试张量
std::vector<float> x(totalLength, valueX);
std::vector<float> y(totalLength, valueY);
// 调用昇腾自定义加法算子
std::vector<float> output = kernel_add(x, y);
// CPU标准真值 z = x + y
std::vector<float> golden(totalLength, valueX + valueY);
return VerifyResult(output, golden);
}
四、课后实战:SubCustom 逐元素减法算子(填空练习框架)
掌握加法算子后,减法算子属于同架构复刻拓展,整体分片、流水线、内存交互逻辑完全不变,仅将 Compute 内部Add底层算子替换为Sub,输入输出张量数量、缓存配置、Host 调度逻辑全部复用。 下方保留填空练习框架,标注// 请补充......,适合手动填空巩固开发流程。
实战需求
-
数据类型:float
-
Shape:(8,2048),输入输出同 shape
-
数据布局:ND 稠密布局
-
计算公式:z = x - y
#include
#include
#include
#include
#include
#include "acl/acl.h"
#include "kernel_operator.h"
using namespace AscendC;
using namespace std;// 全局流水线配置,与加法、Sigmoid算子统一
constexpr uint32_t BUFFER_NUM = 2;
constexpr uint32_t QUEUE_DEPTH = 2;
constexpr uint32_t TILE_NUM=8;
constexpr uint32_t BLOCK_NUM=8;// 减法分片参数结构体
struct SubCustomTilingData
{
uint32_t totalLength;
uint32_t tileNum;
};// 减法核计算封装类
class KernelSub {
public:
aicore inline KernelSub(){}
aicore inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t totalLength, uint32_t tileNum)
{
// 请补充......
}
aicore inline void Process()
{
// 请补充......
}private:
aicore inline void CopyIn(int32_t progress)
{
// 请补充......
}
aicore inline void Compute(int32_t progress)
{
// 请补充......
}
aicore inline void CopyOut(int32_t progress)
{
// 请补充......
}private:
// 请补充......
Tpipe pipe;
TQueAscendC::TPosition::VECIN,QUEUE_DEPTH inQueueX,inQueueY;
TQueTPosition::VECOUT,QUEUE_DEPTH outQueueZ;
GlobalTensorxGm,yGm,zGm;
uint32_t blockLength,tileNum,tileLength;
};// 减法全局核函数入口
global aicore void sub_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, SubCustomTilingData tiling)
{
// 请补充......
}// Host侧减法调度封装
std::vectorkernel_sub(std::vector &x, std::vector &y)
{
// 请补充......
}// 通用验证函数,与加法算子共用
uint32_t VerifyResult(std::vector&output, std::vector &golden)
{
auto printTensor = [](std::vector&tensor, const char *name) {
constexpr size_t maxPrintSize = 20;
std::cout << name << ": ";
std::copy(tensor.begin(), tensor.begin() + std::min(tensor.size(), maxPrintSize),
std::ostream_iterator(std::cout, " "));
if (tensor.size() > maxPrintSize) {
std::cout << "...";
}
std::cout << std::endl;
};
printTensor(output, "Output");
printTensor(golden, "Golden");
if (std::equal(golden.begin(), golden.end(), output.begin())) {
std::cout << "[Success] Case accuracy is verification passed." << std::endl;
return 0;
} else {
std::cout << "[Failed] Case accuracy is verification failed!" << std::endl;
return 1;
}
return 0;
}// 减法测试主程序
int32_t main(int32_t argc, char *argv[])
{
constexpr uint32_t totalLength = 8 * 2048;
constexpr float valueX = 1.2f;
constexpr float valueY = 2.3f;
std::vectorx(totalLength, valueX);
std::vectory(totalLength, valueY); // 请补充...... std::vector<float> output=kernel_sub(x,y); std::vector<float> golden(totalLength, valueX - valueY); return VerifyResult(output, golden);}
SubCustom 完整可运行参考实现(填空对照源码)
下方为对齐 AddCustom 架构、修复全部语法、参数错误的完整版代码,写完填空后可直接对照自查、编译运行。
#include <cstdint>
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include "acl/acl.h"
#include "kernel_operator.h"
using namespace AscendC;
using namespace std;
constexpr uint32_t BUFFER_NUM = 2;
constexpr uint32_t QUEUE_DEPTH = 2;
constexpr uint32_t TILE_NUM = 8;
constexpr uint32_t BLOCK_NUM = 8;
struct SubCustomTilingData
{
uint32_t totalLength;
uint32_t tileNum;
};
class KernelSub {
public:
__aicore__ inline KernelSub(){}
__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t totalLength, uint32_t tileNum)
{
this->blockLength = totalLength / AscendC::GetBlockNum();
this->tileNum = tileNum;
this->tileLength = this->blockLength / tileNum / BUFFER_NUM;
xGm.SetGlobalBuffer((__gm__ float *)x + this->blockLength * GetBlockIdx(), this->blockLength);
yGm.SetGlobalBuffer((__gm__ float *)y + this->blockLength * GetBlockIdx(), this->blockLength);
zGm.SetGlobalBuffer((__gm__ float *)z + this->blockLength * GetBlockIdx(), this->blockLength);
pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(float));
pipe.InitBuffer(inQueueY, BUFFER_NUM, this->tileLength * sizeof(float));
pipe.InitBuffer(outQueueZ, BUFFER_NUM, this->tileLength * sizeof(float));
}
__aicore__ inline void Process()
{
int32_t loopCount = tileNum * BUFFER_NUM;
for (int32_t i = 0; i < loopCount; i++)
{
CopyIn(i);
Compute(i);
CopyOut(i);
}
}
private:
__aicore__ inline void CopyIn(int32_t progress)
{
LocalTensor<float> xLocal = inQueueX.AllocTensor<float>();
LocalTensor<float> yLocal = inQueueY.AllocTensor<float>();
DataCopy(xLocal, xGm[progress * tileLength], tileLength);
DataCopy(yLocal, yGm[progress * tileLength], tileLength);
inQueueX.EnQue(xLocal);
inQueueY.EnQue(yLocal);
}
__aicore__ inline void Compute(int32_t progress)
{
LocalTensor<float> xLocal = inQueueX.DeQue<float>();
LocalTensor<float> yLocal = inQueueY.DeQue<float>();
LocalTensor<float> zLocal = outQueueZ.AllocTensor<float>();
// 逐元素减法核心接口 z = x - y
Sub(zLocal, xLocal, yLocal, tileLength);
outQueueZ.EnQue<float>(zLocal);
inQueueX.FreeTensor(xLocal);
inQueueY.FreeTensor(yLocal);
}
__aicore__ inline void CopyOut(int32_t progress)
{
LocalTensor<float> zLocal = outQueueZ.DeQue<float>();
DataCopy(zGm[progress * tileLength], zLocal, tileLength);
outQueueZ.FreeTensor(zLocal);
}
private:
Tpipe pipe;
TQue<AscendC::TPosition::VECIN, QUEUE_DEPTH> inQueueX, inQueueY;
TQue<TPosition::VECOUT, QUEUE_DEPTH> outQueueZ;
GlobalTensor<float> xGm, yGm, zGm;
uint32_t blockLength, tileNum, tileLength;
};
__global__ __aicore__ void sub_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, SubCustomTilingData tiling)
{
KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY);
KernelSub op;
op.Init(x, y, z, tiling.totalLength, tiling.tileNum);
op.Process();
}
std::vector<float> kernel_sub(std::vector<float> &x, std::vector<float> &y)
{
constexpr uint32_t blockDim = BLOCK_NUM;
uint32_t totalLength = x.size();
size_t totalByteSize = totalLength * sizeof(float);
int32_t deviceId = 0;
aclrtStream stream = nullptr;
SubCustomTilingData tiling = {totalLength, TILE_NUM};
uint8_t *xHost = reinterpret_cast<uint8_t*>(x.data());
uint8_t *yHost = reinterpret_cast<uint8_t*>(y.data());
uint8_t *zHost = nullptr;
uint8_t *xDevice = nullptr;
uint8_t *yDevice = nullptr;
uint8_t *zDevice = nullptr;
aclInit(nullptr);
aclrtSetDevice(deviceId);
aclrtCreateStream(&stream);
aclrtMallocHost((void**)(&zHost), totalByteSize);
aclrtMalloc((void**)&xDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);
aclrtMalloc((void**)&yDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);
aclrtMalloc((void**)&zDevice, totalByteSize, ACL_MEM_MALLOC_HUGE_FIRST);
aclrtMemcpy(xDevice, xHost, totalByteSize, ACL_MEMCPY_HOST_TO_DEVICE);
aclrtMemcpy(yDevice, yHost, totalByteSize, ACL_MEMCPY_HOST_TO_DEVICE);
sub_custom<<<blockDim, nullptr, stream>>>(xDevice, yDevice, zDevice, tiling);
aclrtSynchronizeStream(stream);
aclrtMemcpy(zHost, zDevice, totalByteSize, ACL_MEMCPY_DEVICE_TO_HOST);
std::vector<float> z((float*)zHost, (float*)(zHost + totalByteSize));
aclrtFree(xDevice);
aclrtFree(yDevice);
aclrtFree(zDevice);
aclrtFreeHost(zHost);
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
return z;
}
uint32_t VerifyResult(std::vector<float> &output, std::vector<float> &golden)
{
auto printTensor = [](std::vector<float> &tensor, const char *name) {
constexpr size_t maxPrintSize = 20;
std::cout << name << ": ";
std::copy(tensor.begin(), tensor.begin() + std::min(tensor.size(), maxPrintSize),
std::ostream_iterator<float>(std::cout, " "));
if (tensor.size() > maxPrintSize) {
std::cout << "...";
}
std::cout << std::endl;
};
printTensor(output, "Output");
printTensor(golden, "Golden");
if (std::equal(golden.begin(), golden.end(), output.begin())) {
std::cout << "[Success] Case accuracy is verification passed." << std::endl;
return 0;
} else {
std::cout << "[Failed] Case accuracy is verification failed!" << std::endl;
return 1;
}
return 0;
}
int32_t main(int32_t argc, char *argv[])
{
constexpr uint32_t totalLength = 8 * 2048;
constexpr float valueX = 1.2f;
constexpr float valueY = 2.3f;
std::vector<float> x(totalLength, valueX);
std::vector<float> y(totalLength, valueY);
std::vector<float> output = kernel_sub(x, y);
std::vector<float> golden(totalLength, valueX - valueY);
return VerifyResult(output, golden);
}
五、学习总结 & 下期预告
本期核心知识点梳理
- 统一开发模板固化 :和 Sigmoid 单输入算子对齐架构,双输入二元算子依旧遵循
Init分片 → Process流水线 → CopyIn/Compute/CopyOut标准五段式开发范式; - 流水线双缓冲底层原理:BUFFER_NUM=2 实现计算与数据拷贝并行,解决 AICore 本地缓存不足、IO 阻塞问题;
- Host-Device 完整内存链路:锁页内存 / 显存分配、4 参标准 aclrtMemcpy 规范、Host 与 Device 内存物理隔离核心概念;
- 二元算子复用逻辑:Add、Sub 算子 90% 代码完全通用,仅替换 Compute 内部底层数学 API,拓展性极强;
- 标准化验证体系:统一通用 VerifyResult 校验函数,CPU 生成标准真值快速校验算子精度。
下期拓展预告
当前已掌握双输入二元加法 AddCustom、减法 SubCustom,下一期将基于同一套通用模板完整实现**单输入激活算子 SigmoidCustom,**并逐步拓展DivCustom 逐元素除法算子,完善加减除基础二元算子体系,横向对比三类二元算子代码差异,彻底吃透 Ascend C 逐元素算子通用开发思维,后续可快速拓展 Relu、Tanh、Mul 等各类算子。