CANN ops-math 算子指南:aclnnReflectionPad2d 反射填充接口的完整使用与源码解析
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
本篇技术指南以 CANN ops-math 开源仓库中 aclnnReflectionPad2d 接口文档 为核心,系统讲解该算子的产品支持范围、功能语义、两段式接口定义、参数约束、错误码与完整可运行的 C++ 调用示例,并结合仓库内的 op_api 实现、op_host 定义 与 ST 测试用例 深入剖析其底层调用链与校验逻辑。读者读完可直接在 Ascend NPU 上编写、编译并运行 aclnnReflectionPad2d,并掌握如何利用仓库源码排查参数与性能问题。
一、产品支持情况
aclnnReflectionPad2d 是 CANN ops-math 提供的数学基础算子库接口,位于 conversion/mirror_pad 目录下。不同硬件产品对该接口的支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | 支持 |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 支持 |
| Atlas 训练系列产品 | 支持 |
从 op_host 算子定义 可以看到,底层 MirrorPad 算子的 AICore 配置仅注册了ascend950与ascend350两个芯片型号,与上表中 950 系列及 Atlas A3/A2 等产品线对应。
二、功能说明与计算语义
2.1 接口功能
aclnnReflectionPad2d 的功能是:使用输入边界的反射填充输入 tensor,即在输入 tensor 的最后两维边界外侧,以"镜像反射"的方式复制数据,填充后得到更大尺寸的输出 tensor。它对应 PyTorch 中torch.nn.ReflectionPad2d的语义,仓库的 ST 测试正是用torch.nn.ReflectionPad2d作为基准生成 golden 数据(见 executor_aclnnReflectionPad2d.py)。
2.2 计算示例
原始文档给出的示例(padding 为[2,2,2,2],即左右各填充 2 列、上下各填充 2 行):
输入tensor([[[[0,1,2], [3,4,5], [6,7,8]]]]) padding([2,2,2,2]) 输出为([[[[8,7,6,7,8,7,6], [5,4,3,4,5,4,3], [2,1,0,1,2,1,0], [5,4,3,4,5,4,3], [8,7,6,7,8,7,6], [5,4,3,4,5,4,3], [2,1,0,1,2,1,0]]]])注意这里采用的是REFLECT(反射)模式:镜像时不包含边界本身。例如最左侧的填充列[8,7,6]取自原矩阵第一列的逆序(不含边界元素 0 本身);最上方的填充行[8,7,6,7,8,7,6]同样由第一行数据反射得到。与之相对,MirrorPad 算子还支持 SYMMETRIC(对称)模式(镜像时包含边界本身),二者区别可参见 mirror_pad README 中的对比示例。
2.3 与底层 MirrorPad 算子的关系
aclnnReflectionPad2d 是面向用户的aclnn 层接口,其底层计算单元是 MirrorPad 算子。在 op_api 实现 中可以看到:
- 输入 tensor 先被转为连续内存(
l0op::Contiguous); - 对于非复数类型,调用
ProcessMirrorPad,最终通过l0op::MirrorPad(selfContiguous, paddingsTensor, REFLECTION_MODE, executor)执行计算,其中模式常量REFLECTION_MODE = "REFLECT"定义在 reflection_pad_common.h; - 对于COMPLEX64/COMPLEX128 复数类型,则走
ProcessPadV3路径,借助 PadV3 算子(reflect 模式)完成填充,因为 MirrorPad 本身不支持复数。
三、函数原型:两段式接口
每个 aclnn 算子都采用 两段式接口 设计:先调用aclnnReflectionPad2dGetWorkspaceSize获取计算所需的 workspace 大小以及封装了算子计算流程的执行器(executor),再调用aclnnReflectionPad2d真正执行计算。
aclnnStatus aclnnReflectionPad2dGetWorkspaceSize( const aclTensor *self, const aclIntArray *padding, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnReflectionPad2d( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream)两段式接口的声明定义于 aclnn_reflection_pad2d.h,采用extern "C"导出,编译时需包含头文件aclnnop/aclnn_reflection_pad2d.h。
四、aclnnReflectionPad2dGetWorkspaceSize 参数说明
第一段接口完成入参校验并构建执行器,各参数含义如下:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| self(aclTensor*) | 输入 | 待填充的原输入数据 | 维度支持三维或四维 | BOOL、INT8、UINT8、INT16、UINT16、FLOAT16、BFLOAT16、INT32、UINT32、FLOAT32、INT64、UINT64、DOUBLE、COMPLEX64、COMPLEX128、HIFLOAT8、FLOAT8_E5M2、FLOAT8_E4M3FN、FLOAT8_E8M0 | ND | 3-4 | √ |
| padding(aclIntArray*) | 输入 | 输入中需要填充的大小 | 长度为4,数值依次代表左右上下需要填充的值。padding前两个数值需小于self最后一维度的数值,后两个数值需小于self倒数第二维度的数值 | INT64 | ND | - | √ |
| out(aclTensor*) | 输出 | Device侧的aclTensor | 维度与self一致,out倒数第二维度的数值等于self倒数第二维度的数值加padding后两个值,out最后一维度的数值等于self最后一维度的数值加padding前两个值 | 与 self 相同 | ND | 3-4 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在Device侧申请的workspace大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回op执行器,包含了算子计算流程 | - | - | - | - | - |
4.1 关键参数解读
- padding 的排列顺序:长度为 4 的数组
[left, right, top, bottom],前两个值(索引 0、1)作用于最后一维(左右),后两个值(索引 2、3)作用于倒数第二维(上下)。这一点在 CheckShape 源码 中有直接印证:padding[0]、padding[1]与self最后一维比较,padding[2]、padding[3]与倒数第二维比较。 - out 的 shape 推导:
out的后两维必须严格等于self对应维度加 padding 之和,即out[H] = self[H] + padding[2] + padding[3],out[W] = self[W] + padding[0] + padding[1]。若用户提供的 out shape 不匹配,接口会直接返回ACLNN_ERR_PARAM_INVALID。 - REFLECT 模式边界约束:反射模式不复制边界本身,因此 padding 值必须严格小于对应维度的尺寸(而不是小于等于),否则没有可反射的数据。
4.2 平台相关的数据类型差异
原始文档对数据类型支持做了平台区分,需要在具体硬件上确认:
- Atlas A3 训练/推理系列、Atlas A2 训练/推理系列:不支持 UINT16、UINT32、UINT64、HIFLOAT8、FLOAT8_E5M2、FLOAT8_E4M3FN、FLOAT8_E8M0。
- Atlas 推理系列、Atlas 训练系列:不支持 BFLOAT16、UINT16、UINT32、UINT64、COMPLEX64、COMPLEX128、HIFLOAT8、FLOAT8_E5M2、FLOAT8_E4M3FN、FLOAT8_E8M0。
这一点与源码中的平台分支完全对应:aclnn_reflection_pad2d.cpp 定义了ASCEND910_DTYPE_DTYPE_SUPPORT_LIST(含 FLOAT/INT32/INT64/FLOAT16/INT16/DOUBLE/INT8/UINT8/BOOL)、ASCEND910B_DTYPE_DTYPE_SUPPORT_LIST(额外支持 BF16/COMPLEX64/COMPLEX128)与REGBASE_DTYPE_DTYPE_SUPPORT_LIST(全类型列表),GetDtypeSupportList()依据当前 SoC 版本与是否 RegBase 平台动态选择校验列表。
五、返回值与错误码
第一段接口返回aclnnStatus状态码,具体定义参见 aclnn返回码。aclnnReflectionPad2dGetWorkspaceSize完成入参校验,出现以下场景时报错:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | Tensor为空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self、padding和out的数据类型或数据格式不在支持的范围之内。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self、padding和out的输入shape在支持范围之外。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self为空tensor且存在非第一维度的值为0。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | 三维self不支持为空tensor。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | padding的数值大于等于self对应维度的值。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | out后两维度的值不等于self后两维度的值加对应padding。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | out的shape与实际输出shape不匹配。 |
这些校验逻辑均可在 CheckParams 系列函数 中一一对应找到:空指针检查(CheckNotNull)、dtype 检查(CheckDtypeValid,并要求 self 与 out 类型一致)、格式检查(CheckFormat,要求 self 与 out 的 format 相同)、shape 检查(CheckShape)。空 tensor 的特殊处理位于 GetWorkspaceSize 主体:三维空 tensor 直接报错;四维空 tensor 仅允许第 0 维(batch)为 0,其余维度必须非零。
六、aclnnReflectionPad2d 参数说明
第二段接口真正触发 NPU 上的计算,参数含义如下:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在Device侧申请的workspace内存地址。 |
| workspaceSize | 输入 | 在Device侧申请的workspace大小,由第一段接口aclnnReflectionPad2dGetWorkspaceSize获取。 |
| executor | 输入 | op执行器,包含了算子计算流程。 |
| stream | 输入 | 指定执行任务的Stream。 |
从源码看,第二段接口的实现非常简洁,仅调用框架的公共执行入口完成计算(见 aclnn_reflection_pad2d.cpp):
aclnnStatus aclnnReflectionPad2d(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream) { L2_DFX_PHASE_2(aclnnReflectionPad2d); // 固定写法,调用框架能力,完成计算 return CommonOpExecutorRun(workspace, workspaceSize, executor, stream); }该接口同样返回aclnnStatus,错误码参见 aclnn返回码。
七、约束说明
- 确定性计算:aclnnReflectionPad2d 默认采用确定性实现,即相同输入在多次运行中产出相同结果。关于确定性计算的通用约束可参考 determinism_compute。
- 执行超时风险:如果计算量过大,可能导致算子执行超时(以 aicore error 类型报错,errorStr 为
timeout or trap error)。原始文档明确指出的触发场景为:最后 2 轴合轴小于 16,而前面的轴合轴超大。在实际业务中,若输入 shape 呈现"前面维度极大、后两维很小"的特征且 padding 较大,应关注该超时风险,必要时调整输入排布或拆分计算。
八、完整调用示例
以下代码来自原始文档(与仓库中的 examples/test_aclnn_reflection_pad_2d.cpp 同一套模式),演示了从资源初始化、tensor 构造、两段式接口调用到结果回收的完整流程。具体编译与运行方法请参考 编译与运行样例。
#include "acl/acl.h" #include "aclnnop/aclnn_reflection_pad2d.h" #include <iostream> #include <vector> #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 shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } 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 API手册 // 根据自己的实际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> selfShape = {1, 1, 2, 2}; std::vector<int64_t> outShape = {1, 1, 4, 4}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclIntArray* padding = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {1, 2, 3, 4}; std::vector<int64_t> paddingData = {1, 1, 1, 1}; std::vector<float> outHostData = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建padding aclIntArray padding = aclCreateIntArray(paddingData.data(), 4); CHECK_RET(padding != nullptr, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3.调用CANN算子库API,需要修改为具体的API uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnReflectionPad2d第一段接口 ret = aclnnReflectionPad2dGetWorkspaceSize(self, padding, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnReflectionPad2dGetWorkspaceSize 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;); } // 调用aclnnReflectionPad2d第二段接口 ret = aclnnReflectionPad2d(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnReflectionPad2d 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(outShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(float), 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: %f\n", i, resultData[i]); } // 6.释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyIntArray(padding); aclDestroyTensor(out); // 7.释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例要点拆解
- 初始化:
aclInit→aclrtSetDevice→aclrtCreateStream是固定套路,deviceId 需按实际环境填写。 - tensor 构造:示例输入为
selfShape = {1,1,2,2}、padding = {1,1,1,1},则输出 shape 应为{1,1,4,4},即 H、W 各加 2。CreateAclTensor模板函数内部计算连续 tensor 的 strides 并调用aclCreateTensor创建 ND 格式的 aclTensor。 - 两段式调用:第一段返回
workspaceSize与executor;当workspaceSize > 0时必须用aclrtMalloc在 Device 侧申请对应大小的内存,再传入第二段执行。 - 同步与取数:
aclrtSynchronizeStream确保任务完成,再通过aclrtMemcpy将结果从 Device 拷回 Host 并逐元素打印。 - 资源回收:
aclDestroyTensor/aclDestroyIntArray释放 acl 对象,aclrtFree释放 Device 内存,最后销毁 stream、复位设备并aclFinalize。
九、源码级实现原理
9.1 op_api 层的完整调用链
以非复数输入为例,aclnnReflectionPad2dGetWorkspaceSize内部的执行流(见 aclnn_reflection_pad2d.cpp)为:
- 创建 executor,执行四类参数检查(空指针、dtype、format、shape);
- 处理空 tensor 的合法/非法分支;
l0op::Contiguous将非连续输入转为连续内存(接口支持非连续 tensor 的体现);ProcessMirrorPad将 padding 转换为[dim, 2]形状的 paddings tensor 后调用l0op::MirrorPad(mode 固定为REFLECT);CheckShapeAndScalarSame校验中间结果 shape 与用户 out 一致;l0op::ViewCopy将连续结果拷回用户提供的 out(若 out 本身非连续则完成视图转换);- 通过
uniqueExecutor->GetWorkspaceSize()汇总 workspace 需求并返回。
其中 padding 到 paddings tensor 的转换细节见 reflection_pad_common.h:GetPaddingTensor会把 4 元组 padding 从后向前依次展开成dim * 2的数组(例如对 4 维输入展开为[0,0, top,bottom, left,right]),再经executor->ConvertToTensor转为 INT64 类型 tensor。对复数类型则走ProcessPadV3,先UnsqueezeNd在 0 维插入一维,用 PadV3 的"reflect"模式填充后再SqueezeNd还原维度。
9.2 op_host 层的算子定义与 shape 推导
底层 MirrorPad 算子的定义在 mirror_pad_def.cpp:
- 输入
x支持INT64/INT32/UINT32/FLOAT/FLOAT16/DOUBLE/BF16/INT16/UINT16/INT8/UINT8/BOOL/HIFLOAT8/FLOAT8_E5M2/FLOAT8_E8M0/FLOAT8_E4M3FN,格式固定 ND; - 输入
paddings支持 INT64/INT32,且声明为ValueDepend(OPTIONAL)(即 shape 推导依赖其取值); - 属性
mode固定为字符串"REFLECT"; - AICore 配置开启动态编译、动态 rank 与动态 shape 支持,kernel 文件为
mirror_pad_apt。
shape 推导逻辑在 mirror_pad_infershape.cpp:通过InputsDataDependency({PAD_IN_IDX_PADDINGS})声明输出 shape 依赖 paddings 数据,并复用 pad_v3 目录下的公共推导函数InferShapeForPadWithPaddingTensor计算输出维度。
9.3 测试用例验证
仓库为 aclnnReflectionPad2d 提供了完善的 ST(系统测试)与 UT(单元测试)用例:
- atk_aclnnReflectionPad2d.json 覆盖了 fp16、fp32、bf16、int8、int16、int32、int64 多种 dtype,三维与四维 shape,以及大量"边界用例"(
is_boundary: true),padding 取值范围覆盖左右上下各方向的典型值与极值; - executor_aclnnReflectionPad2d.py 在 CPU 侧使用
torch.nn.ReflectionPad2d作为基准实现,计算结果与 NPU 侧输出进行 md5 级精度比对; - test_aclnn_reflection_pad2d.cpp 为 op_api 单元测试,test_mirror_pad_infershape.cpp 覆盖 host 侧 shape 推导。
十、常见问题与排查建议
- 返回 161002 参数非法:优先检查 padding 长度是否为 4、padding 各值是否小于对应 self 维度(REFLECT 模式必须严格小于)、out 后两维是否等于
self 维度 + 对应 padding、self 与 out 的 dtype/format 是否一致。 - 返回 161001 空指针:self、padding、out 三者任一为空指针都会触发,检查 aclTensor/aclIntArray 是否创建成功。
- 空 tensor 报错:三维输入不允许为空 tensor;四维输入允许 batch(第 0 维)为 0,但其余维度不得为 0。
- aicore timeout 报错(errorStr 为 timeout or trap error):多发生在"后两维合轴 < 16、前序维度合轴超大"的计算场景,需评估输入 shape 与 padding 规模。
- 非连续 tensor:接口声明支持非连续 tensor,第一段内部会自动
Contiguous归一后再计算、再ViewCopy回 out,无需用户手动转连续。
通过本文对接口定义、参数约束、错误码、调用示例及底层源码的综合梳理,开发者可以在 CANN ops-math 中正确使用 aclnnReflectionPad2d,并借助 源码目录、测试目录 与 mirror_pad README 进一步深入定制与排障。
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考