免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

CANN ops-math 算子指南:aclnnReflectionPad2d 反射填充接口的完整使用与源码解析

CANN ops-math 算子指南:aclnnReflectionPad2d 反射填充接口的完整使用与源码解析 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)非连续TensorselfaclTensor*输入待填充的原输入数据维度支持三维或四维BOOL、INT8、UINT8、INT16、UINT16、FLOAT16、BFLOAT16、INT32、UINT32、FLOAT32、INT64、UINT64、DOUBLE、COMPLEX64、COMPLEX128、HIFLOAT8、FLOAT8_E5M2、FLOAT8_E4M3FN、FLOAT8_E8M0ND3-4√paddingaclIntArray*输入输入中需要填充的大小长度为4数值依次代表左右上下需要填充的值。padding前两个数值需小于self最后一维度的数值后两个数值需小于self倒数第二维度的数值INT64ND-√outaclTensor*输出Device侧的aclTensor维度与self一致out倒数第二维度的数值等于self倒数第二维度的数值加padding后两个值out最后一维度的数值等于self最后一维度的数值加padding前两个值与 self 相同ND3-4√workspaceSizeuint64_t*输出返回需要在Device侧申请的workspace大小-----executoraclOpExecutor**输出返回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_NULLPTR161001Tensor为空指针。ACLNN_ERR_PARAM_INVALID161002self、padding和out的数据类型或数据格式不在支持的范围之内。ACLNN_ERR_PARAM_INVALID161002self、padding和out的输入shape在支持范围之外。ACLNN_ERR_PARAM_INVALID161002self为空tensor且存在非第一维度的值为0。ACLNN_ERR_PARAM_INVALID161002三维self不支持为空tensor。ACLNN_ERR_PARAM_INVALID161002padding的数值大于等于self对应维度的值。ACLNN_ERR_PARAM_INVALID161002out后两维度的值不等于self后两维度的值加对应padding。ACLNN_ERR_PARAM_INVALID161002out的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.cppaclnnStatus 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::vectorint64_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::vectorT hostData, const std::vectorint64_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::vectorint64_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::vectorint64_t selfShape {1, 1, 2, 2}; std::vectorint64_t outShape {1, 1, 4, 4}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclIntArray* padding nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {1, 2, 3, 4}; std::vectorint64_t paddingData {1, 1, 1, 1}; std::vectorfloat 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::vectorfloat 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::MirrorPadmode 固定为REFLECTCheckShapeAndScalarSame校验中间结果 shape 与用户 out 一致l0op::ViewCopy将连续结果拷回用户提供的 out若 out 本身非连续则完成视图转换通过uniqueExecutor-GetWorkspaceSize()汇总 workspace 需求并返回。其中 padding 到 paddings tensor 的转换细节见 reflection_pad_common.hGetPaddingTensor会把 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固定为字符串REFLECTAICore 配置开启动态编译、动态 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: truepadding 取值范围覆盖左右上下各方向的典型值与极值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),仅供参考
返回列表