十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CANN ops-math 算子库 aclnnRandperm 接口实战指南:从随机排列原理到两段式 API 调用

CANN ops-math 算子库 aclnnRandperm 接口实战指南:从随机排列原理到两段式 API 调用 CANN ops-math 算子库 aclnnRandperm 接口实战指南从随机排列原理到两段式 API 调用【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-mathaclnnRandperm 是 CANN ops-math 数学算子库中 StatelessRandperm 算子对外暴露的 aclnn 接口用于在 NPU 上生成从 0 到 n-1 的整数随机排列无状态随机排列。本文以 aclnnRandperm.md 为骨架结合仓库中该算子的 op_api、op_host、op_kernel 与示例代码系统讲解函数原型、两段式调用流程、参数约束、底层实现原理及可编译运行的完整样例帮助读者在 Atlas 训练/推理产品上快速落地该算子。功能概述与产品支持情况StatelessRandperm 算子的功能是返回从 0 到 n-1 的整数随机排列。所谓无状态stateless指的是随机序列完全由用户传入的 seed 与 offset 决定不依赖运行时的全局随机状态因此同一组 (seed, offset) 输入总能得到相同的排列结果便于复现实验与单元测试。根据 aclnnRandperm.md 与算子目录下 README.md 的说明各产品系列的支持情况如下产品系列是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 训练系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持注算子目录内 README.md 的产品表格与接口文档存在差异README 中标注 Atlas 200I/500 A2、Atlas 推理系列为支持二者以接口文档 aclnnRandperm.md 为准。实际运行时请以所安装 CANN 版本的配套说明为准。两段式接口与函数原型在 CANN 的 aclnn 算子接口体系中每个算子采用两段式接口调用模式详见 两段式接口说明第一段aclnnRandpermGetWorkspaceSize完成入参校验计算算子执行所需的 workspace 大小并创建包含算子计算流程的执行器aclOpExecutor第二段aclnnRandperm传入 Device 侧申请的 workspace 内存与执行器在指定 Stream 上真正执行计算。两个接口的函数原型如下来自 aclnnRandperm.mdaclnnStatus aclnnRandpermGetWorkspaceSize( int64_t n, int64_t seed, int64_t offset, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)aclnnStatus aclnnRandperm( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)在仓库源码 op_api/aclnn_randperm.cpp 中可以看到两段接口的实现骨架第一段接口先通过OP_CHECK_COMM_INPUT校验输出参数指针随后调用CheckParams完成参数合法性检查再创建OpExecutor并利用l0op::StatelessRandperm构建计算图含ViewCopy节点用于处理输出可能为非连续 tensor 的情况最后通过uniqueExecutor-GetWorkspaceSize()返回 workspace 大小第二段接口则直接调用框架能力CommonOpExecutorRun完成计算。aclnnRandpermGetWorkspaceSize 参数详解第一段接口的完整参数说明如下摘自 aclnnRandperm.md参数名输入/输出描述数据类型数据格式维度(shape)非连续tensorn输入取随机数的上界INT64---seed输入随机数生成器的种子影响生成的随机数序列INT64---offset输入随机数生成器的偏移量影响生成的随机数序列的位置。设置偏移量后生成的随机数序列会从指定位置开始INT64---out输出输出 tensorINT64、INT32、INT16、UINT8、INT8、FLOAT、FLOAT16、DOUBLE、BFLOAT16ND为 [n]√workspaceSize输出返回需要在 Device 侧申请的 workspace 大小----executor输出返回 op 执行器包含了算子计算流程----参数要点说明n生成排列的长度排列元素取自 0 到 n-1为标量 INT64 值。当 n 等于 0 时第一段接口直接返回空 tensor见 op_api/aclnn_randperm.cpp 中if (n 0)分支此时 workspaceSize 为 0。seed / offset均为 INT64 标量共同决定随机数序列。在 kernel 层二者被用于初始化 Philox 伪随机数生成器的 key 与 counter见下文底层实现原理其中 offset 用于跳过随机数序列中的前若干位。out输出 tensorshape 必须为[n]支持多种数据类型详见数据类型约束且支持非连续 tensor——这是通过第一段接口中附加的l0op::ViewCopy节点实现的计算中间结果先写入连续内存再按 out 的实际 strides 拷贝到目标 tensor。第一段接口的返回值与错误码aclnnStatus返回状态码的完整定义参见 aclnn返回码。第一段接口完成入参校验出现如下场景时报错摘自原文档返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 out 是空指针ACLNN_ERR_PARAM_INVALID161002传入的 n 小于 0ACLNN_ERR_PARAM_INVALID161002out 的 shape 不为 n上述校验逻辑在源码 op_api/aclnn_randperm.cpp 的CheckParams中逐一落实CheckNotNull检查 out 空指针CheckShapeValid将 out 的 shape 与[n]比对n 0校验 n 非负CheckDtypeValid则根据当前 NPU 架构校验 out 的数据类型。数据类型约束通用支持类型INT64、INT32、INT16、UINT8、INT8、FLOAT、FLOAT16、DOUBLEAtlas 训练系列产品Ascend 910不支持 BFLOAT16BFLOAT16 仅在支持它的架构如 Atlas A2/A3 训练与推理系列、Ascend 950 系列上可用。该约束与源码中的两张支持列表一致DTYPE_SUPPORT_LIST_DEFAULT不含 BF16而DTYPE_SUPPORT_LIST_2201对应 DAV_2201/DAV_3510 架构在末尾追加了DT_BF16见 op_api/aclnn_randperm.cpp。aclnnRandperm 参数详解第二段接口完成真正的计算任务参数说明如下摘自原文档参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnRandpermGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream使用要点workspaceSize为 0 时无需申请 workspace可传入空指针大于 0 时需通过aclrtMalloc在 Device 侧分配对应大小的内存建议使用ACL_MEM_MALLOC_HUGE_FIRST标志并在计算结束后释放executor必须来自第一段接口的输出二者严格配对使用第二段接口的返回值同样是aclnnStatus具体参见 aclnn返回码。约束说明确定性计算aclnnRandperm 默认采用确定性实现即相同 (seed, offset) 输入在多核/多次运行下产生一致的排列结果。这一点也与算子名中的stateless相呼应相关背景可参考 确定性计算。Ascend 950PR/Ascend 950DT 专属约束INT64、INT32、INT16、UINT8、INT8、FLOAT、FLOAT16、BFLOAT16 类型下n 不得超过 int32 的最大值即 n ≤ 2147483647因为 kernel 内部使用 32 位索引对随机数缓冲区寻址对应canUse32bitIndexing的判断逻辑见 op_kernel/arch35/stateless_randperm.hDOUBLE 类型下当 n 大于 268000000 时有运行超时风险可通过aclrtSetOpExecuteTimeOut设置算子执行超时时间。源码级原理剖析算子定义与 Shape 推导在算子宿主侧op_host/stateless_randperm_def.cpp 通过OpDef注册了 StatelessRandperm 算子三个必选输入 n、seed、offset均为 INT64、ND 格式、ValueDepend(OPTIONAL)表示其值参与 shape 推导一个输出 y以及两个可选属性layout默认 0和dtype默认ge::DT_INT64用于指定输出数据类型。op_host/stateless_randperm_infershape.cpp 实现了 shape 推导由于 n 是标量输入而非 shape 描述推导逻辑通过InputsDataDependency({0})声明对输入 0即 n的数据依赖读取 n 的值后将输出 shape 的第 0 维设为 n从而保证输出 shape 恒为[n]。计算内核Philox 随机数 排序 Fisher-Yates 洗牌在 Ascend 950 系列arch35上算子内核实现了伪随机数生成 → 排序 → Fisher-Yates 洗牌三阶段算法入口为 op_kernel/stateless_randperm_apt.cpp核心流程见 op_kernel/arch35/stateless_randperm.h 的Process()随机数生成PhiloxPhilox内核函数用 Philox 4×32-10 算法为每个元素生成一个随机位串randomBits位写入randWorkSpace_缓冲区。Philox 算法的完整实现ComputeSingleRound、RaiseKey、PhiloxRandom、SkipAhead、RandInit、Rand4等位于 op_kernel/arch35/stateless_randperm_random.h其中RandInit通过SkipAhead_Sequence(subsequence)和SkipAhead(offset)把 seed 子序列号与用户 offset 编码进 counter实现无状态 可复现排序SortSort模板对随机位串排序使得位串相同的元素聚集为连续的岛屿island同时维护元素原始索引indexWorkSpace_。由于相同位串被聚到一起洗牌只需要在 islands 内部进行大幅减少了跨 core 的同步开销Fisher-Yates 洗牌FindAndFisherYares对每个 island 内部的索引执行 Fisher-Yates 原地洗牌从islandSize - 1递减到 1逐个与[0, j]内的随机位置交换使用的随机数依旧来自 Philox 序列确保随机性完全由 (seed, offset) 决定拷贝输出CopyData最后按洗牌后的索引顺序把 0..n-1 写入输出outGm_并完成类型转换由模板参数Ty指定对应 out 的数据类型。AICORE / AICPU 双路径分派在 op_api/stateless_randperm.cpp 中l0op::StatelessRandperm根据当前 NPU 架构与输出数据类型自动选择执行路径当架构为DAV_3510Ascend 950且输出 dtype 在 AICORE 支持列表FLOAT、FLOAT16、INT32、BF16、INT64、INT8、UINT8、INT16内时走StatelessRandpermAiCore的 AICORE kernel否则回退到StatelessRandpermAiCpu的 AICPU kernel。这种双路径设计既保证了 950 系列上的高性能也保证了其他平台的可用性。完整调用示例原文档给出了可直接编译运行的 C 示例仓库中 examples/test_aclnn_randperm.cpp 与之一致。该示例以 n8、seed1234、offset0、输出类型 FLOAT 为例完整展示了 aclnnRandperm 的标准调用流程#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_randperm.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 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::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_RET(ret ACL_SUCCESS, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出需要根据API的接口自定义构造 std::vectorint64_t outShape {8}; void* outDeviceAddr nullptr; aclTensor* out nullptr; std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0}; int64_t n 8; int64_t seed 1234; int64_t offset 0; // 创建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; // 调用aclnnRandperm第一段接口 ret aclnnRandpermGetWorkspaceSize(n, seed, offset, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnRandpermGetWorkspaceSize 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); } // 调用aclnnRandperm第二段接口 ret aclnnRandperm(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnRandperm 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(resultData[0]), 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(out); // 7. 释放device资源 aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }代码整体分为七个阶段其中第 1、4、6、7 步为所有 aclnn 算子的固定写法资源初始化aclInit→aclrtSetDevice→aclrtCreateStream构造输入输出通过aclrtMalloc申请 Device 内存、aclrtMemcpy拷贝 Host 数据并用aclCreateTensor创建 ND 格式的aclTensor两段式调用先调用aclnnRandpermGetWorkspaceSize获取 workspaceSize 与 executor按需申请 workspace 内存后调用aclnnRandperm提交计算同步等待aclrtSynchronizeStream确保任务在 Stream 上执行完毕取回结果用aclrtMemcpyACL_MEMCPY_DEVICE_TO_HOST将排列结果拷回 Host 侧并打印期望看到的是 0~7 的一个随机排列释放 tensoraclDestroyTensor(out)释放资源依次aclrtFree、aclrtDestroyStream、aclrtResetDevice、aclFinalize。示例的编译与执行方法依赖 CANN 工具包中的头文件与链接库需按安装环境配置编译选项与 LD 路径可参考 编译与运行样例。测试与验证仓库为该算子配备了完整的单测与端到端测试可作为功能验证与二次开发的参照Host 侧单测tests/ut/op_host/test_stateless_randperm_infershape.cpp 验证 shape 推导逻辑确认输出 shape 恒为[n]Kernel 侧单测tests/ut/op_kernel/test_stateless_randperm.cpp 直接对 kernel 进行数值验证Tiling 单测tests/ut/op_host/arch35/test_stateless_randperm_tiling.cpp 校验 950 系列的 tiling 参数计算端到端测试tests/st/aclnnRandperm/executor_aclnnRandperm.py 配合atk_aclnnRandperm.json通过算子测试工具ATK在真实设备上执行其参考结果由 tests/assets/golden.py 生成可作为预期输出的权威参照——由于算子为确定性实现golden 结果可精确比对。总结aclnnRandperm 是 CANN ops-math 中实现无状态整数随机排列的 aclnn 接口通过GetWorkspaceSize 执行两段式调用即可在 NPU 上生成 0 到 n-1 的随机排列输出数据类型覆盖整型与浮点型的九种类型并支持非连续 tensor。从源码看其确定性由Philox 伪随机 排序 Fisher-Yates 洗牌的内核算法与 (seed, offset) 的显式编码共同保证并依据架构在 AICORE 与 AICPU 路径间自动分派。结合仓库中的示例与测试开发者可以在 Atlas 训练/推理系列及 Ascend 950 系列产品上快速完成该算子的集成与验证。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表