CANN ops-cv MrgbaCustom 算子(aclnnMrgbaCustom)透明度混合 API 开发指南
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
本文围绕 CANN ops-cv 仓库中的objdetect/mrgba_custom算子模块,系统讲解其 aclnn 两段式接口aclnnMrgbaCustomGetWorkspaceSize/aclnnMrgbaCustom的数学原理、函数原型、参数约束、错误码语义以及完整可运行的调用示例,并结合算子定义、shape 推导、tiling 与 AscendC 内核源码剖析其底层实现机制。读完本文,你将能够基于 CANN 单算子 API 在 Atlas 推理系列产品上正确编写并运行 MrgbaCustom 透明度乘法算子,并具备将任意 RGB 图片与 alpha 透明度张量合成为带透明通道图像的实际编码能力。
一、算子功能与产品支持情况
1.1 功能说明
aclnnMrgbaCustom用于完成张量rgb和张量alpha的透明度乘法计算,其计算公式为:
$$ out = rgb \times \frac{broadcast(alpha)}{255} $$
其中alpha会广播(broadcast)到与rgb相同的 shape 后参与逐元素乘法。该算子的典型应用场景是:假设rgb是一张三通道彩色图片(shape 为 HWC,C=3),alpha是其对应的透明度(单通道,C=1),使用该算子后即可将原图片生成一张带透明度的三通道图片——每个像素点的 RGB 数值乘以该像素的归一化透明度(0~1 之间),实现"给图像添加透明度"的视觉效果。
算子原型注释(见 mrgba_custom_proto.h)对算子的描述即为 "Give transparency to the image",输入输出均为DT_UINT8类型的张量。
1.2 产品支持情况
根据 aclnnMrgbaCustom.md 与 README.md 的说明,该算子当前的产品支持矩阵如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR / Ascend 950DT | 不支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 不支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 不支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 支持 |
| Atlas 训练系列产品 | 不支持 |
从源码层面看,这一支持范围与算子注册的硬件配置一致:mrgba_custom_def.cpp 中通过this->AICore().AddConfig("ascend310p")仅为ascend310p(Atlas 推理系列产品对应的昇腾 310P 芯片)注册了 AICore 配置。
二、两段式接口机制与函数原型
2.1 两段式接口机制
在 CANN 单算子 API(aclnn 接口)的调用体系中,每个算子通常采用"两段式接口"设计,MrgbaCustom算子同样遵循该模式。所谓两段式,指的是先调用以GetWorkspaceSize结尾的第一段接口,再调用与算子同名的第二段接口:
// 第一段:计算 workspace 大小并创建执行器 aclnnStatus aclnnMrgbaCustomGetWorkspaceSize( const aclTensor* rgb, const aclTensor* alpha, const aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor); // 第二段:执行算子计算 aclnnStatus aclnnMrgbaCustom( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream);其中,workspace 是指除输入/输出张量外,算子在 NPU 上完成计算所需的临时内存,workspaceSize表示该临时内存的大小。调用顺序上必须严格遵守"先第一段、后第二段"的约束:
- 调用
aclnnMrgbaCustomGetWorkspaceSize完成入参校验,获取本次调用需要的 workspace 大小以及封装了算子计算流程的执行器executor; - 按照返回的
workspaceSize在 Device 侧申请 NPU 内存; - 调用
aclnnMrgbaCustom(workspace, workspaceSize, executor, stream)执行计算。
需要特别注意的是,第二段接口不能重复调用,即不能出现GetWorkspaceSize → 执行 → 执行这种连续调用两次第二段接口的写法,否则会出现异常。更完整的机制说明可参考 两段式接口。
2.2 aclnnMrgbaCustomGetWorkspaceSize 参数说明
第一段接口的完整参数说明如下:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续 tensor |
|---|---|---|---|---|---|---|---|
| rgb(aclTensor*) | 输入 | 公式中的 rgb | - | UINT8 | ND | HWC(C=3),与 alpha 满足 broadcast 关系 | √ |
| alpha(aclTensor*) | 输入 | 公式中的 alpha | - | UINT8 | ND | HWC(C=1),与 rgb 满足 broadcast 关系 | √ |
| out(aclTensor*) | 输出 | 输出 tensor | - | UINT8 | ND | HWC(C=3),与 rgb 的 shape 一致 | - |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含算子计算流程 | - | - | - | - | - |
关于参数中涉及的几个关键点:
- broadcast 关系:
rgb与alpha之间满足广播语义,即alpha的 C 维(通道维)为 1,计算时会自动扩展为与rgb的 C=3 对齐后再逐元素相乘。广播关系的通用规则说明可参考 broadcast关系。 - 非连续 tensor:
rgb与alpha允许传入非连续(stride 非紧凑)的 tensor;而输出out要求与rgb的 shape 完全一致。需要留意的是,README.md 的算子参数说明中提及"只支持连续 Tensor",实际使用时建议优先保证输入为连续内存布局,避免踩坑。 - 数据类型:三个张量均为
UINT8(即aclDataType::ACL_UINT8),数据格式均为 ND(aclFormat::ACL_FORMAT_ND)。这一点与算子定义文件中的.DataType({ge::DT_UINT8}).Format({ge::FORMAT_ND})注册完全对应。
2.3 返回值与错误码语义
第一段接口(及第二段接口)的返回值类型均为aclnnStatus,返回状态码的具体含义可参见 aclnn返回码。
第一段接口会完成入参校验,出现以下场景时会返回对应错误:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | rgb、alpha 或 out 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | rgb 和 alpha 的数据类型不在支持的范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | rgb 和 alpha 的 shape 不满足 HWC(C=3)和 HWC(C=1)的要求 |
其中161001(空指针)与161002(参数非法)两类错误覆盖了空指针、数据类型越界、shape 不满足 HWC 通道约束三类典型入参问题,开发时可通过这两个错误码快速定位问题来源。
2.4 aclnnMrgbaCustom 参数说明
第二段接口的参数说明如下:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnMrgbaCustomGetWorkspaceSize 获取 |
| executor | 输入 | op 执行器,包含算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
三、约束说明
- 确定性计算:
aclnnMrgbaCustom默认为确定性实现,即相同输入在同一硬件环境下多次执行得到完全一致的结果,不会引入随机性。
四、调用示例:完整的 C++ 样例
以下示例代码摘自算子文档(与仓库 examples/test_aclnn_mrgba_custom.cpp 中提供的可编译样例一致),展示了从设备初始化、构造 aclTensor、调用两段式接口、同步等待到资源释放的完整流程。具体编译与运行方式请参考 编译与运行样例。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_mrgba_custom.h" #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vector<int64_t> &shape) { int64_t shapeSize = 1; for (auto i: shape) { shapeSize *= i; } return shapeSize; } int Init(int32_t deviceId, aclrtStream *stream) { // 固定写法,资源初始化 auto ret = aclInit(nullptr); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret); ret = aclrtSetDevice(deviceId); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret); ret = aclrtCreateStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret); return 0; } template<typename T> int CreateAclTensor(const std::vector <T> &hostData, const std::vector <int64_t> &shape, void **deviceAddr, aclDataType dataType, aclTensor **tensor) { auto size = GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); // 计算连续tensor的strides std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1. (固定写法)device/stream初始化,参考acl对外接口列表 // 根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); // check根据自己的需要处理 CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2.构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> rgbShape = {4, 3}; std::vector<int64_t> alphaShape = {4, 1}; std::vector<int64_t> dstShape = {4, 3}; void *rgbDeviceAddr = nullptr; void *alphaDeviceAddr = nullptr; void *dstDeviceAddr = nullptr; aclTensor *rgb = nullptr; aclTensor *alpha = nullptr; aclTensor *dst = nullptr; std::vector<uint8_t> rgbHostData = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120}; std::vector<uint8_t> alphaHostData = {255, 255, 255, 255}; std::vector<uint8_t> dstHostData = {1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 创建rgb aclTensor ret = CreateAclTensor(rgbHostData, rgbShape, &rgbDeviceAddr, aclDataType::ACL_UINT8, &rgb); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建alpha aclTensor ret = CreateAclTensor(alphaHostData, alphaShape, &alphaDeviceAddr, aclDataType::ACL_UINT8, &alpha); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建dst aclTensor ret = CreateAclTensor(dstHostData, dstShape, &dstDeviceAddr, aclDataType::ACL_UINT8, &dst); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API uint64_t workspaceSize = 0; aclOpExecutor *executor; // 调用aclnnMrgba第一段接口 ret = aclnnMrgbaCustomGetWorkspaceSize(rgb, alpha, dst, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnMrgbaCustomGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void *workspaceAddr = nullptr; if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); } // 调用aclnnMrgba第二段接口 ret = aclnnMrgbaCustom(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnMrgbaCustom failed. ERROR: %d\n", ret); return ret); // 4. (固定写法)同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. 获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改 auto size = GetShapeSize(dstShape); std::vector<uint8_t> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), dstDeviceAddr, size * sizeof(uint8_t), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < size; i++) { LOG_PRINT("result[%ld] is: %u\n", i, resultData[i]); } // 6. 释放aclTensor aclDestroyTensor(rgb); aclDestroyTensor(alpha); aclDestroyTensor(dst); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(rgbDeviceAddr); aclrtFree(alphaDeviceAddr); aclrtFree(dstDeviceAddr); if(workspaceSize > 0){ aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }4.1 示例代码要点解读
- 资源初始化(固定写法):
aclInit → aclrtSetDevice → aclrtCreateStream三步完成 ACL 运行时初始化,其中deviceId需根据实际设备填写。 - Tensor 构造:
CreateAclTensor模板函数完成"申请 Device 内存 → Host 数据拷贝到 Device → 计算连续 tensor 的 strides →aclCreateTensor创建 aclTensor"四个步骤。这里strides的推导方式是"从最后一维往前逐维累乘",即按紧凑连续布局计算各维步长;数据格式固定为ACL_FORMAT_ND。 - 两段式调用:先调用
aclnnMrgbaCustomGetWorkspaceSize拿到workspaceSize与executor;若workspaceSize > 0则用aclrtMalloc申请 workspace 内存;再调用aclnnMrgbaCustom执行计算。注意 workspace 内存的申请与释放都必须与返回的workspaceSize严格一致。 - 同步与结果回拷:
aclrtSynchronizeStream确保算子异步任务执行完成后,再通过ACL_MEMCPY_DEVICE_TO_HOST将结果从 Device 拷回 Host 并逐元素打印。 - 资源释放:依次释放 aclTensor(
aclDestroyTensor)、Device 内存(aclrtFree)、Stream(aclrtDestroyStream)、设备(aclrtResetDevice)并aclFinalize收尾。
以示例数据验证算子功能:rgb = {10,20,30, 40,50,60, ...},alpha = {255,255,255,255},则out = rgb * 255/255 = rgb,即透明度为 255(完全不透明)的像素保持原色不变。
五、源码级原理剖析
以下从算子注册、shape 推导、tiling 计算与 AscendC 内核实现四个维度剖析MrgbaCustom的底层实现,帮助你理解 aclnn 接口背后完整的算子运行链路。
5.1 算子定义(OpDef)
mrgba_custom_def.cpp 通过OpDef注册算子信息:
- 输入
rgb、alpha:ParamType(REQUIRED)、数据类型DT_UINT8、格式FORMAT_ND、AutoContiguous()(自动转换为连续布局); - 输出
dst:DT_UINT8、FORMAT_ND; - 硬件配置:
AICore().AddConfig("ascend310p")。
该定义与图侧算子原型 mrgba_custom_proto.h(REG_OP(MrgbaCustom))一一对应,两者共同构成了算子的"身份信息"。
5.2 shape 推导(InferShape)
mrgba_custom_infershape.cpp 中实现了输出 shape 与数据类型的推导逻辑:
InferShape4MrgbaCustom:输出dst_shape直接取输入rgb_shape,即*dst_shape = *rgb_shape,印证了文档中"out 与 rgb 的 shape 一致"的约束;InferDataTypeForMrgbaCustom:输出数据类型直接继承输入 0(rgb)的数据类型。
对应的单测用例 test_mrgba_custom_infershape.cpp 覆盖了480×640×3与1080×1920×3两种典型图像分辨率,验证输出 shape 均正确推导为与 rgb 一致。
5.3 Tiling 计算
Tiling(数据切分)阶段负责把全量数据切分为可被 NPU 各核并行处理的子任务。mrgba_custom_tiling.cpp 的实现要点:
- 读取 alpha 输入张量的总元素数
totalLength = tensorY->GetShapeSize(),写入 tiling 数据的alphaLen字段(tiling 数据结构定义见 mrgba_custom_tiling.h,仅含一个uint32_t alphaLen字段); - 设置
BLOCK_DIM = 8,即算子按 8 个核并行执行; - 将 tiling 数据序列化写入 raw tiling buffer,供内核侧读取。
对应单测 test_mrgba_custom_tiling.cpp 验证:对于480×640×1的 alpha,tiling 数据首字段值应为480*640*1 = 307200,且算子不需要额外 workspace(expectWorkspaces为空),这也解释了示例中workspaceSize通常为 0 的现象。
5.4 AscendC 内核实现
mrgba_custom.cpp 是算子的 AscendC 向量内核实现,核心计算在KernelMrgba::CalcForAlign32(L51-L99)中完成,与文档公式逐条对应:
- 数据搬运(CopyIn):通过
DataCopy分别将 alpha 段(长度alphaLen)与 rgb 段(长度3 * alphaLen,即每像素 3 通道)从 Global Memory 拷入 Local Memory 队列; - 类型提升:
Cast(alphaLocalF16C1, alphaLocal, RoundMode::CAST_NONE, ...)与Cast(rgbLocalF16C3, rgbLocal, ...)将 UINT8 数据提升为 FP16,避免整数除法精度损失; - 通道广播:
BroadCast将 alpha 从[alphaLen, 1]广播为[alphaLen, 3],对应公式中的broadcast(alpha); - 归一化:
Muls(..., RATIO, ...),其中RATIO = 0.003921568627451f,即精确的1/255浮点表示,对应公式中的alpha/255; - 逐元素乘:
Mul(rgbLocalF16C3, rgbLocalF16C3, alphaBrbaLocalF16C3, ...)完成rgb * (alpha/255); - 结果回写:
Cast(dstLocal, rgbLocalF16C3, RoundMode::CAST_FLOOR, ...)使用向下取整模式将 FP16 结果转回 UINT8,再DataCopy拷出到 Global Memory。
入口函数mrgba_custom(L127-L132)通过VectorScheduler(复用自background_replace算子的向量调度器)按核切分数据,配合 tiling 中的alphaLen完成多核并行处理。
5.5 算子二进制编译配置
在 op_host/config/ascend310p/ 目录下存放着内核二进制编译相关的配置:
- mrgba_custom_binary.json:声明算子二进制
MrgbaCustom_uint8对应的输入输出(rgb/alpha/dst,dtype 均为 uint8,format 均为 ND,shape 为-2即动态 rank); - mrgba_custom_simplified_key.ini:为
MrgbaCustom配置--simplified_key_mode编译选项值为default=0,用于指定 opc 工具编译二进制 kernel 时的简化 key 模式。
六、总结
aclnnMrgbaCustom是 CANN ops-cv 提供的一个面向图像透明度合成的单算子 API,其计算逻辑简洁(out = rgb * alpha/255,alpha 广播),但在工程实现上完整覆盖了算子注册、shape 推导、tiling 切分、AscendC 向量计算、二进制编译配置与两段式 aclnn 接口封装的全链路。开发者只需掌握本文介绍的两段式调用模式(GetWorkspaceSize → 申请 workspace → 执行 → 同步 → 释放),即可将MrgbaCustom集成进自己的图像处理流水线中;如需在更多 CANN 产品上使用,可结合 算子支持情况 与仓库内其他图像算子(如background_replace、blend_images_custom等)的接口文档进行对照参考。
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考