尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CANN ops-cv 中 aclnnUpsampleTrilinear3d 三线性插值上采样算子详解

CANN ops-cv 中 aclnnUpsampleTrilinear3d 三线性插值上采样算子详解 CANN ops-cv 中 aclnnUpsampleTrilinear3d 三线性插值上采样算子详解【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv本文围绕 CANN ops-cv 算子库中的aclnnUpsampleTrilinear3d接口展开系统讲解三线性插值上采样的数学原理含角对齐/边对齐两种坐标映射公式、两段式接口的函数原型与参数约束、平台相关的数据类型支持矩阵以及完整可编译调用的示例代码。读完本文你既能按接口规范正确接入该算子也能从 ops-cv 仓库源码层面理解参数校验链、缩放系数解析与 NPU 核内 tiling 的实现细节。算子功能与产品支持情况ResizeUpsampleTrilinear算子对由多个输入通道组成的输入信号应用三线性插值算法进行上采样其 aclnn 接口为aclnnUpsampleTrilinear3d。算子实现位于 image/resize_upsample_trilinear 目录接口头文件见 aclnn_upsample_trilinear_3d.h。产品支持情况详见 docs/aclnnUpsampleTrilinear3d.md 与 算子README产品是否支持Ascend 950PR/Ascend 950DT√Atlas A3 训练系列产品/Atlas A3 推理系列产品√Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品310P√Atlas 训练系列产品√各平台数据类型支持存在差异接口实现会按当前 NPU 架构做校验Atlas 推理系列产品310PDAV_2002入参self与出参out仅支持 FLOAT32、FLOAT16且不支持 inf、-inf 输入Atlas 训练系列产品DAV_1xxx 系入参self和出参out支持 FLOAT32、FLOAT16、DOUBLEAscend 950PR/950DTDAV_3510入参self和出参out不支持 DOUBLE即支持 FLOAT32、FLOAT16、BFLOAT16。这一点可以在接口实现源码中得到印证aclnn_upsample_trilinear_3d.cpp 中按架构分别维护了三份类型支持列表——通用平台DTYPE_SUPPORT_LISTFLOAT/FLOAT16/DOUBLE/BF16、310P 的ASCEND310P_DTYPE_SUPPORT_LIST仅 FLOAT16/FLOAT和 A5 系列的A5_DTYPE_SUPPORT_LISTFLOAT/FLOAT16/BF16并在CheckDtypeValid中依据GetCurrentPlatformInfo().GetCurNpuArch()分流校验。计算公式角对齐与边对齐算子的核心算法逻辑分三步将目标图像的每一个点映射回原图得到一个带小数点的坐标根据这个浮点数坐标计算前后相邻的原始图像的点分别计算相邻点到对应目标点的权重按照权重相乘累加即可得到目标点值。缩放方式分为角对齐和边对齐角对齐alignCornerstrue表示按照原始图片左上角像素中心点对齐边对齐alignCornersfalse表示按照原始图片左上角顶点及两条边对齐二者在计算缩放系数和坐标位置时存在差异。对于一个三维插值点 $(N, C, D, H, W)$缩放系数公式为$$ scale_d \begin{cases} (self.D-1) / (outputSize[0]-1) alignCornerstrue \ 1 / scales_d alignCornersfalsescales_d0\ self.D / outputSize[0] alignCornersfalse \end{cases} $$$$ scale_h \begin{cases} (self.H-1) / (outputSize[1]-1) alignCornerstrue \ 1 / scales_h alignCornersfalsescales_h0\ self.H / outputSize[1] alignCornersfalse \end{cases} $$$$ scale_w \begin{cases} (self.W-1) / (outputSize[2]-1) alignCornerstrue \ 1 / scales_w alignCornersfalsescales_w0\ self.W / outputSize[2] alignCornersfalse \end{cases} $$对于 output 某个方向上的点 $p(x,y,z)$映射回原始图像中的点记为 $q(x,y,z)$则有$$ x \begin{cases} x * scale_d alignCornerstrue \ MAX(0,{(x0.5)*scale_d-0.5}) alignCornersfalse \end{cases} $$$$ y \begin{cases} y * scale_h alignCornerstrue \ MAX(0,{(y0.5)*scale_h-0.5}) alignCornersfalse \end{cases} $$$$ z \begin{cases} z * scale_w alignCornerstrue \ MAX(0,{(z0.5)*scale_w-0.5}) alignCornersfalse \end{cases} $$记$$ x_{0} int(x),\quad x_{1} int(x)1,\quad \lambda_{0} x_{1}-x,\quad \lambda_{1} 1-\lambda_{0} $$$$ y_{0} int(y),\quad y_{1} int(y)1,\quad \lambda_{b0} y_{1}-y,\quad \lambda_{b1} 1-\lambda_{b0} $$$$ z_{0} int(z),\quad z_{1} int(z)1,\quad \lambda_{c0} z_{1}-z,\quad \lambda_{c1} 1-\lambda_{c0} $$则三线性插值结果为 8 个相邻体素按权重乘积累加$$ V(p_{x, y, z}) V(p_{x0,y0,z0})\lambda_0\lambda_{b0}\lambda_{c0} V(p_{x0,y0,z1})\lambda_0\lambda_{b0}\lambda_{c1} V(p_{x0,y1,z0})\lambda_0\lambda_{b1}\lambda_{c0} V(p_{x0,y1,z1})\lambda_0\lambda_{b1}\lambda_{c1} V(p_{x1,y0,z0})\lambda_1\lambda_{b0}\lambda_{c0} V(p_{x1,y0,z1})\lambda_1\lambda_{b0}\lambda_{c1} V(p_{x1,y1,z0})\lambda_1\lambda_{b1}\lambda_{c0} V(p_{x1,y1,z1})\lambda_1\lambda_{b1}\lambda_{c1} $$从源码结构看这段公式的“坐标映射”逻辑在接口层由AsComputeScale/ComputeScales两个函数实现见 aclnn_upsample_trilinear_3d.cpp当alignCorners为 true 且输出尺寸大于 1 时返回(inputSize - 1) / (outputSize - 1)否则若用户显式传入的scales 0则直接使用该值否则退化为inputSize / outputSize与文档公式中的三个分支一一对应。函数原型两段式接口每个 aclnn 算子分为两段式接口必须先调用aclnnUpsampleTrilinear3dGetWorkspaceSize接口获取计算所需 workspace 大小以及包含算子计算流程的执行器executor再调用aclnnUpsampleTrilinear3d接口执行计算。aclnnStatus aclnnUpsampleTrilinear3dGetWorkspaceSize( const aclTensor *self, const aclIntArray *outputSize, bool alignCorners, double scalesD, double scalesH, double scalesW, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnUpsampleTrilinear3d( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)两段式调用模型的含义与注意事项详见 two_phase_api.md第一段接口完成入参校验计算本次调用所需的 workspace 大小并生成包含完整计算流程的aclOpExecutor执行器第二段接口携带第一段生成的 executor 与申请好的 workspace 执行计算。第二段接口不能重复调用即GetWorkspaceSize之后调用两次aclnnUpsampleTrilinear3d会出现异常。aclnnUpsampleTrilinear3dGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入表示进行上采样的输入张量对应公式中的self不支持空Tensor当数据格式为ND时默认按照NCDHW格式处理shape的C轴、D轴、H轴、W轴的取值大于0FLOAT32、FLOAT16、BFLOAT16、DOUBLENCDHW、NDHWC、ND5√outputSizeaclIntArray*输入表示出参out在 D、H 和 W 维度上的空间大小对应公式中的outputSizesize为3且各元素均大于零INT64---alignCornersbool输入表示是否对齐角像素点对应公式中的alignCorners如果为true则输入和输出张量的角像素点会被对齐否则不对齐----scalesDdouble输入表示输出out的 depth 维度乘数对应公式中的scales_d取值小于等于50----scalesHdouble输入表示输出out的 height 维度乘数对应公式中的scales_h取值小于等于50----scalesWdouble输入表示输出out的 width 维度乘数对应公式中的scales_w取值小于等于50----outaclTensor*输出表示采样后的输出张量不支持空Tensor数据类型和数据格式与入参self保持一致shape的N轴、C轴与入参self保持一致FLOAT32、FLOAT16、BFLOAT16、DOUBLENCDHW、NDHWC、ND5√workspaceSizeuint64_t*输出返回需要在Device侧申请的workspace大小-----executoraclOpExecutor**输出返回op执行器包含了算子计算流程-----返回值与错误码返回aclnnStatus状态码具体参见 aclnn返回码。第一段接口完成入参校验出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、outputSize 或 out 是空指针时ACLNN_ERR_PARAM_INVALID161002self 的数据类型不在支持的范围之内或 self 与 out 数据类型不同ACLNN_ERR_PARAM_INVALID161002self 的 shape 不是 5 维ACLNN_ERR_PARAM_INVALID161002outputSize 的 size 不等于 3ACLNN_ERR_PARAM_INVALID161002self 在 C、D、H、W 维度上的 size 不大于 0ACLNN_ERR_PARAM_INVALID161002outputSize 的某个元素值不大于 0这些错误分支与源码中的校验函数严格对应。aclnn_upsample_trilinear_3d.cpp 的CheckParams按序执行五步校验CheckNotNullself / outputSize / out 空指针检查对应ACLNN_ERR_PARAM_NULLPTRCheckDtypeValid数据类型白名单校验及 self/out 类型一致性检查CheckShape5 维校验UPSAMPLE_DIM_LIMIT、outputSize 必须为 3 个元素、存储格式必须为 NCDHW/NDHWC/NDCheckInputElementC/D/H/W 各维度大于 0并校验 out 的期望 shape——NCDHW 格式下为{inputN, outC, outD, outH, outW}NDHWC 格式下为{inputN, outD, outH, outW, outC}见 源码第 127-131 行CheckUplimit各维度不超过INT32_MAX上界。第一段接口的执行流程从源码结构看GetWorkspaceSize并非只做参数校验它还完成了完整的计算图构建源码第 386-445 行若self为空 Tensor直接返回workspaceSize 0先对非连续输入执行l0op::Contiguous使其连续化通过AllocScalesArrays将scalesD/H/W与其倒数castScales1/scales写入浮点数组供 kernel 使用——当任一 scale 不大于 0 时两个数组均为空通过ResolveScales按alignCorners规则解析出 kernel 实际使用的每轴缩放系数按平台分派执行路径310P 路径CheckIsPlatform310p且 scale ≤ 50NCDHW 输入先 Transpose 为 DHWNC、执行UpsampleTrilinear3dNcdhw再转回NDHWC 输入同理经 NDHWC→DHWNC→NDHWC 两次 Transpose见 Run310pNcdhw / Run310pNdhwc通用路径NDHWC 输入先 Transpose 到 NCDHW 再计算并转回NCDHW 输入直接计算。在 Ascend 950DAV_3510架构上若输入输出格式一致且 out 为连续张量CanDirectWriteOutput判定成立时可直接写输出、省去一次 ViewCopy最后由uniqueExecutor-GetWorkspaceSize()汇总出 workspace 大小并释放执行器。第二段aclnnUpsampleTrilinear3d则直接委托给CommonOpExecutorRun按第一段构建好的执行器运行源码第 447-452 行。aclnnUpsampleTrilinear3d第二段接口参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口aclnnUpsampleTrilinear3dGetWorkspaceSize获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回aclnnStatus状态码具体参见 aclnn返回码。约束说明调用前请确认以下约束均与 aclnn_upsample_trilinear_3d.cpp 及 tiling 实现中的检查一致1. 缩放倍数上限输入数据缩放场景缩小倍数必须小于等于 50即$$ 输入shape的深度D / outputSize_D \le 50 $$$$ 输入shape的高度H / outputSize_H \le 50 $$$$ 输入shape的宽度W / outputSize_W \le 50 $$源码中该限制以MAX_SUPPORT_SCALE 50.0常量落地接口层 CheckScales 与 tiling 层 CheckScales 均对scalesW/H/D做 ≤ 50 的校验。2. shape 约束针对参数self、out每个维度的取值小于等于 2^20参数out的 N 轴和 C 轴与self保持一致内存占用需小于 60GB计算公式$$ N * C * (self_D * self_H * self_W out_D * out_H * out_W self_D * self_H * out_W self_D * out_H * out_W) * sizeof(dtype) 60 * 1024 * 1024 * 1024 $$其中 N、C 分别代表输入和输出的 N 轴、C 轴dtype 代表输入张量的数据类型$N \times C \times self_D \times self_H 2^{31}$$out_W \times out_H 2^{31}$。3. scales 与 outputSize 的一致性当输入scalesD、scalesH、scalesW取值均大于 0 时参数self、outputSize、scalesD、scalesH、scalesW需满足$$ outputSize_D floor(self_D * scalesD) $$$$ outputSize_H floor(self_H * scalesH) $$$$ outputSize_W floor(self_W * scalesW) $$4. 确定性计算aclnnUpsampleTrilinear3d默认确定性实现。调用示例仓库提供了与文档一致的完整示例 test_aclnn_upsample_trilinear3d.cpp其完整流程如下具体编译和执行过程请参考 编译与运行样例#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_upsample_trilinear_3d.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::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_NCDHW, 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, 2}; std::vectorint64_t outShape {1, 1, 4, 4, 4}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {1, 2, 3, 4, 5, 6, 7, 8}; const size_t kSize64 64U; std::vectorfloat outHostData(kSize64, 0); std::vectorint64_t outputSizeData {4, 4, 4}; bool alignCorners false; double scalesD 0.0; double scalesH 0.0; double scalesW 0.0; // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); const aclIntArray* outputSize aclCreateIntArray(outputSizeData.data(), outputSizeData.size()); CHECK_RET(outputSize ! nullptr, return ACL_ERROR_INTERNAL_ERROR); // 3. 调用CANN算子库API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnUpsampleTrilinear3d第一段接口 ret aclnnUpsampleTrilinear3dGetWorkspaceSize(self, outputSize, alignCorners, scalesD, scalesH, scalesW, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnUpsampleTrilinear3dGetWorkspaceSize 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); } // 调用aclnnUpsampleTrilinear3d第二段接口 ret aclnnUpsampleTrilinear3d(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnUpsampleTrilinear3d 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侧 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 aclDestroyTensor(self); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例以 shape 为{1, 1, 2, 2, 2}的 FLOAT32 NCDHW 张量为输入指定outputSize {4, 4, 4}、alignCorners false、scalesD/H/W 0即由输入/输出尺寸自动推导缩放系数scale inputSize / outputSize 0.5完成 D/H/W 三个方向各 2 倍的上采样。注意scales传 0 时接口按outputSize自动计算缩放比若显式传入 scales则需满足outputSize_i floor(self_i * scales_i)的一致性约束。源码纵深算子定义、Tiling 与核实现算子注册定义算子定义见 resize_upsample_trilinear_def.cpp声明了input/output两个必填张量FLOAT16/FLOAT/BF16以及四个可选属性属性类型默认值output_sizeLISTINT空align_cornersBOOLfalsescales_d / scales_h / scales_wFLOAT空AICore 配置注册了ascend910b、ascend910_93、ascend950regbase 动态编译路径绑定resize_upsample_trilinear_apt和ascend310p仅 FLOAT16/FLOAT四套平台配置与前述产品支持矩阵相符。Tiling按方向分治的 Cube 矩阵乘方案host 侧 tiling 实现在 resize_upsample_trilinear_tiling.cpp从源码结构看三线性插值在核内被分解为W、H、D 三个方向串行的矩阵乘Matmul三个方向各自生成一份TCubeTilingmatmul_tiling_w/h/d其中每方向的滑动窗口大小SLIDE_SIZE 16K 维取CEIL(SLIDE_SIZE * scale) 2预留冗余防止越界见 SetTCubeTilingW/H/D核数分配按“每个方向沿输出维度切 slide16 元素/核”的均分逻辑计算calcIndxPerCore对 W/H/D 分别求解最终取三个方向所需核数的最大值作为实际使用的 AICore 数量源码第 649-652 行当某方向 scale 为 1.0 时该方向退化为透传、仅需 1 个核workspace 大小由三部分构成W 方向右乘临时结果、H 方向左乘临时结果scale 为 1 的方向不占空间以及三个方向系数矩阵ratio matrix按最大者共享的空间见 GetWorkSpaceSize最终再叠加 32MB 预留空间通过SetTilingKey将数据类型映射为 kernel 分支标识FP161000、BF162000、FP323000。kernel 入口 resize_upsample_trilinear.cpp 据此按 tiling key 实例化KernelUpsampleTrilinearhalf/bfloat16_t/float并用REGIST_MATMUL_OBJ注册 W/H/D 三个方向的 Matmul 对象后执行InitProcess310P 平台__CCE_AICORE__ 200/300走 resize_upsample_trilinear_310p.h 的KernelUpsampleTrilinear310p分支Ascend 950 则使用 op_kernel/arch35 下的 SIMD/SIMT 实现头文件。测试与验证算子配套了多层级测试可作为接入后自验证的参考接口 UTtest_aclnn_upsample_trilinear_3d.cpptiling UTtest_resize_upsample_trilinear_tiling.cpp核精度测试test_resize_upsample_trilinear.cpp通用平台与 test_resize_upsample_trilinear_310p.cpp310P配套 gen_data.py / compare_data.py 数据生成与比对脚本ST 测试executor_aclnnUpsampleTrilinear3d.py 及其用例配置 atk_aclnnUpsampleTrilinear3d.json。小结aclnnUpsampleTrilinear3d提供了面向 5 维张量NCDHW/NDHWC/ND的三线性插值上采样能力支持角对齐与边对齐两种坐标映射、显式 scales 与 outputSize 双模式指定缩放并默认确定性计算。接入时的关键要点严格按两段式接口调用第二段接口不可重复执行注意平台差异310P 仅支持 FLOAT32/FLOAT16 且不支持 inf/-inf950 系列不支持 DOUBLE满足 scale ≤ 50、shape 与 60GB 内存、$2^{31}$ 乘积上界等约束显式传入 scales 时保证outputSize floor(self × scales)一致性。进一步阅读可参考 算子README、docs/aclnnUpsampleTrilinear3d.md 与 两段式接口说明。【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表