CANN ops-cv MrgbaCustom 算子(aclnnMrgbaCustom)透明度混合 API 开发指南
2026/9/18 12:37:38 网站建设 项目流程

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表示该临时内存的大小。调用顺序上必须严格遵守"先第一段、后第二段"的约束:

  1. 调用aclnnMrgbaCustomGetWorkspaceSize完成入参校验,获取本次调用需要的 workspace 大小以及封装了算子计算流程的执行器executor
  2. 按照返回的workspaceSize在 Device 侧申请 NPU 内存;
  3. 调用aclnnMrgbaCustom(workspace, workspaceSize, executor, stream)执行计算。

需要特别注意的是,第二段接口不能重复调用,即不能出现GetWorkspaceSize → 执行 → 执行这种连续调用两次第二段接口的写法,否则会出现异常。更完整的机制说明可参考 两段式接口。

2.2 aclnnMrgbaCustomGetWorkspaceSize 参数说明

第一段接口的完整参数说明如下:

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续 tensor
rgb(aclTensor*)输入公式中的 rgb-UINT8NDHWC(C=3),与 alpha 满足 broadcast 关系
alpha(aclTensor*)输入公式中的 alpha-UINT8NDHWC(C=1),与 rgb 满足 broadcast 关系
out(aclTensor*)输出输出 tensor-UINT8NDHWC(C=3),与 rgb 的 shape 一致-
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----
executor(aclOpExecutor**)输出返回 op 执行器,包含算子计算流程-----

关于参数中涉及的几个关键点:

  • broadcast 关系rgbalpha之间满足广播语义,即alpha的 C 维(通道维)为 1,计算时会自动扩展为与rgb的 C=3 对齐后再逐元素相乘。广播关系的通用规则说明可参考 broadcast关系。
  • 非连续 tensorrgbalpha允许传入非连续(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_NULLPTR161001rgb、alpha 或 out 是空指针
ACLNN_ERR_PARAM_INVALID161002rgb 和 alpha 的数据类型不在支持的范围之内
ACLNN_ERR_PARAM_INVALID161002rgb 和 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 示例代码要点解读

  1. 资源初始化(固定写法)aclInit → aclrtSetDevice → aclrtCreateStream三步完成 ACL 运行时初始化,其中deviceId需根据实际设备填写。
  2. Tensor 构造CreateAclTensor模板函数完成"申请 Device 内存 → Host 数据拷贝到 Device → 计算连续 tensor 的 strides →aclCreateTensor创建 aclTensor"四个步骤。这里strides的推导方式是"从最后一维往前逐维累乘",即按紧凑连续布局计算各维步长;数据格式固定为ACL_FORMAT_ND
  3. 两段式调用:先调用aclnnMrgbaCustomGetWorkspaceSize拿到workspaceSizeexecutor;若workspaceSize > 0则用aclrtMalloc申请 workspace 内存;再调用aclnnMrgbaCustom执行计算。注意 workspace 内存的申请与释放都必须与返回的workspaceSize严格一致。
  4. 同步与结果回拷aclrtSynchronizeStream确保算子异步任务执行完成后,再通过ACL_MEMCPY_DEVICE_TO_HOST将结果从 Device 拷回 Host 并逐元素打印。
  5. 资源释放:依次释放 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注册算子信息:

  • 输入rgbalphaParamType(REQUIRED)、数据类型DT_UINT8、格式FORMAT_NDAutoContiguous()(自动转换为连续布局);
  • 输出dstDT_UINT8FORMAT_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×31080×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)中完成,与文档公式逐条对应:

  1. 数据搬运(CopyIn):通过DataCopy分别将 alpha 段(长度alphaLen)与 rgb 段(长度3 * alphaLen,即每像素 3 通道)从 Global Memory 拷入 Local Memory 队列;
  2. 类型提升Cast(alphaLocalF16C1, alphaLocal, RoundMode::CAST_NONE, ...)Cast(rgbLocalF16C3, rgbLocal, ...)将 UINT8 数据提升为 FP16,避免整数除法精度损失;
  3. 通道广播BroadCast将 alpha 从[alphaLen, 1]广播为[alphaLen, 3],对应公式中的broadcast(alpha)
  4. 归一化Muls(..., RATIO, ...),其中RATIO = 0.003921568627451f,即精确的1/255浮点表示,对应公式中的alpha/255
  5. 逐元素乘Mul(rgbLocalF16C3, rgbLocalF16C3, alphaBrbaLocalF16C3, ...)完成rgb * (alpha/255)
  6. 结果回写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_replaceblend_images_custom等)的接口文档进行对照参考。

【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询