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

资讯详情

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

CANN ops-math ReduceStdV2 算子深度解析:基于 aclnnStd 接口的标准差计算与两段式调用实战

CANN ops-math ReduceStdV2 算子深度解析:基于 aclnnStd 接口的标准差计算与两段式调用实战 CANN ops-math ReduceStdV2 算子深度解析基于 aclnnStd 接口的标准差计算与两段式调用实战【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-mathReduceStdV2 是 CANN ops-math 数学算子库中用于计算张量指定维度标准差的归一化统计算子对应 aclnnStd 单算子 API。本文以 math/reduce_std_v2/README.md 为骨架结合 aclnnStd 接口文档 与仓库内的 host/kernel 源码完整讲解其功能定义、参数语义、产品适配、两段式 aclnn 调用流程及底层实现原理读者可据此在 Ascend NPU 上正确使用并二次开发该算子。算子功能与数学定义ReduceStdV2 的核心功能是计算输入张量self在指定维度dim上的标准差其中dim可以是单个维度、维度列表也可以传None表示对所有维度计算。计算公式假设计算维度为 $i$$N$ 为该维度的 shape先求出该维度上的平均值 $\bar{x_{i}}$再按如下公式计算标准差$$ out \sqrt{\frac{1}{max(0, N - \delta N)}\sum_{j0}^{N-1}(self_{ij}-\bar{x_{i}})^2} $$其中$\delta N$ 即参数correction修正值默认语义与 PyTorchtorch.std的correction一致correction1 时对应无偏估计unbiasedTrue分母取 $max(0, N - \delta N)$保证校正后的除数不会为负self_{ij}$表示第 $i$ 维上第 $j$ 个元素$\bar{x_{i}}$ 为该维度的均值。keepdim 的语义当keepdim true时reduce 后保留被计算维度且输出 shape 中该维度值为 1当keepdim false时被计算维度从输出 shape 中移除。仓库中的 shape 推导实现印证了这一规则在 reduce_std_v2_infershape.cpp 中keepdims为 true 时走ReduceDimsWithKeepDims否则走ReduceDimsWithoutKeepDims两种路径分别对应保留为 1与直接删除两种输出形状当传入的axes为空时代码会自动将输入的所有维度依次加入axes列表即全量 reduce这与 README 中dim 为 None 时计算所有维度的语义完全对应。产品支持情况根据 README 中产品支持情况一节各产品线对该算子的支持如下产品是否支持Ascend 950PR/Ascend 950DT√Atlas A3 训练系列产品/Atlas A3 推理系列产品√Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品√Atlas 训练系列产品√注意Atlas 训练系列产品、Atlas 推理系列产品不支持 BFLOAT16其余产品线支持 FLOAT/FLOAT16/BFLOAT16 三种数据类型。这一差异在源码中也有对应实现aclnn_std.cpp中定义了ASCEND910_DTYPE_DTYPE_SUPPORT_LIST {DT_FLOAT, DT_FLOAT16}与ASCEND910B_DTYPE_DTYPE_SUPPORT_LIST {DT_FLOAT, DT_FLOAT16, DT_BF16}两套数据类型支持列表并依据当前平台GetDtypeSupportListV2动态选择校验集合见 aclnn_std.cpp。参数说明ReduceStdV2 算子的完整参数如下表所示源自 README 参数说明参数名输入/输出/属性描述数据类型数据格式self输入待进行 ReduceStdV2 计算的输入 Tensor公式中的 selfFLOAT、FLOAT16、BFLOAT16NDdim输入参与 Reduce 计算的维度公式中的 iINT64NDcorrection输入修正值公式中的 deltaINT32NDkeepdim输入是否在输出张量中保留输入张量的维度BOOLNDout输出ReduceStdV2 计算的出参公式中的 outFLOAT、FLOAT16、BFLOAT16NDdim 的取值范围与约束在 aclnn 接口层dim以aclIntArray*形式传入其使用规则在 aclnnStd.md 中有明确说明参与计算的维度取值范围为 $[-self.dim(), self.dim()-1]$支持负索引负索引从末尾倒数dim数组中的元素不能重复当dim为nullptr或[]空数组时视为计算所有维度。这些规则在 aclnn_std.cpp 的CheckDimValid函数中得到了逐条实现先判断dim是否为nullptr是则直接通过再对每个元素检查是否越界dim[i] selfDimNum || dim[i] -selfDimNum时报错并使用dimMask[64]位掩码机制检测正负索引归一化后的维度是否重复重复即返回ACLNN_ERR_PARAM_INVALID。correction 修正值的边界场景处理correction作为公式中的 $\delta N$直接影响分母 $max(0, N - \delta N)$。仓库在 host 侧对边界场景做了特殊兜底处理见 aclnn_std.cpp当shapeProd 1 shapeProd correction即约简后元素数不超过修正值时直接填充NAN返回当correction 1 shapeProd correction时直接填充INFINITY返回。这两种情况均通过CheckFillScalarShapeStdAndVar以标量填充的方式快速产出结果避免进入 kernel 计算路径。约束说明README 中约束说明一节标注为无即该算子在维度数量支持动态 rank、动态 shape 方面均无额外限制。从 reduce_std_v2_def.cpp 的算子定义看其 AICore 配置显式开启了DynamicCompileStaticFlag(true)、DynamicRankSupportFlag(true)、DynamicShapeSupportFlag(true)并注册了ascend950、ascend350两个平台的 AICore 配置说明该算子支持动态编译、动态 rank 与动态 shape这也与 README无约束的描述相互印证。另外在 aclnn 接口层aclnnStd.md 补充了一条约束aclnnStd 默认确定性实现确定性计算即相同输入在多次执行下结果可复现。调用方式aclnn 两段式接口README 中调用说明一节指出该算子通过aclnn 调用方式aclnnStd接口调用示例见 examples/test_aclnn_std.cpp。两段式 API 概述与 CANN 其他单算子 API 一致aclnnStd 采用两段式接口原文位于 docs/zh/context 目录可参考 docs/zh/context/two_phase_api.md第一段aclnnStdGetWorkspaceSize—— 完成入参校验、构图并计算所需 workspace 大小返回执行器executor第二段aclnnStd—— 在指定 Stream 上真正执行算子计算。函数原型aclnnStatus aclnnStdGetWorkspaceSize( const aclTensor *self, const aclIntArray *dim, const int64_t correction, bool keepdim, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnStd( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream)aclnnStdGetWorkspaceSize 参数明细参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfaclTensor*输入计算公式中的 self-FLOAT、FLOAT16、BFLOAT16ND-√dimaclIntArray*输入公式中的 dim参与计算的维度取值范围为 [-self.dim(), self.dim()-1]且其中的数据不能相同当 dim 为 nullptr 或 [] 时视为计算所有维度----correctionint64_t输入修正值计算公式中的 δN 值-----keepdimbool输入是否在输出张量中保留输入张量的维度-----outaclTensor*输出输出 tensor-FLOAT、FLOAT16、BFLOAT16ND-√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----其中self与out均支持非连续 Tensor√ 标注out支持非连续输出这与接口实现中通过ViewCopy将中间结果写入 out 的机制见下文实现原理直接相关。aclnnStd 参数明细参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnStdGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream第一段接口的校验逻辑与返回码第一段接口完成入参校验出现以下场景时报错返回码说明可参考 docs/zh/context/aclnn_return_code.md返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、out 是空指针时ACLNN_ERR_PARAM_INVALID161002self、dim、out 数据类型不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002dim 数组中的维度超出 self 的维度范围ACLNN_ERR_PARAM_INVALID161002dim 数组中元素重复ACLNN_ERR_PARAM_INVALID161002out 的 shape 与预期不符1. keepdim 为 true 时out.shape ! self.shape(指定维度 dim 设置为 1 的形状)2. keepdim 为 false 时out.shape ! self.shape(去除指定维度 dim 后的形状)这些校验在源码中由CheckParams串联完成依次执行空指针检查CheckNotNull2Tensor→ 数据类型检查CheckDtypeValid→ 维度合法性检查CheckDimValid→ shape 推导匹配检查CheckShape内部先按StdInferShape推导期望的 reduce 后形状再与 out 的 shape 比对见 aclnn_std.cpp。此外接口还会对 0 维标量输入自动做Reshape为 1 维aclnn_std.cpp并将负dim统一转换为非负索引ConvToNotNegDim保证下游 kernel 拿到的是规整后的维度数组。底层实现原理源码级解析整体计算流程在通用平台非 RegBase 平台上aclnnStd 的计算被组合为多算子流水其完整流程在 aclnn_std.cpp 的注释中清晰标注self | Contiguous(workspace_0) // 将输入转为连续内存布局 | ReduceMean(workspace_1) // 先求均值keepdimtrue | ReduceStdWithMean(workspace_2) // 基于均值计算标准差 | Expand(workspace_3) // 将均值广播回输入形状 | Cast(workspace_4) // 转换到输出数据类型 | ViewCopy | result即先Contiguous归一化内存布局 →ReduceMean求均值 →Expand广播均值 →ReduceStdWithMean利用均值完成标准差计算 →Cast转换类型 →ViewCopy拷贝到输出张量兼容非连续输出。每一步的中间张量都会累加计入workspaceSize最终通过uniqueExecutor-GetWorkspaceSize()返回给调用方aclnn_std.cpp。而在 RegBase如 Ascend 950/A3 新架构平台上则直接走aclnnStdV2ImplUnify路径调用 l0op 层聚合算子l0op::ReduceStdV2一步完成 std 与 mean 的输出再经Cast与ViewCopy得到结果避免多算子拼接的开销。算子定义与 Shape 推导算子定义reduce_std_v2_def.cpp 中注册了ReduceStdV2算子输入x、输出std与meanmean 为可选辅助输出属性包括dimListInt、correctionInt、keepdimBool默认 false、is_mean_outBool默认 trueAICore 配置开启动态编译/动态 rank/动态 shape并指向reduce_std_v2_aptkernel 文件。Shape 推导reduce_std_v2_infershape.cpp 中InferShape4ReduceVar依据keepdims选择带/不带 keepdims 的 reduce 推导工具并显式推导mean的 shape与 var 相同因为 GE 框架会给每个输出都申请内存mean 输出 shape 必须一并推导。Kernel 实现reduce_std_v2_apt.cpp 是 AICore kernel 入口按编译期推导的PatternID等模板参数分派到三种实现空 tensor 场景填充 NANReduceVarEmpty、纯搬运场景ReduceVarPureMove与常规 reduce 计算ReduceVarSch内部以提升类型PromoteType保证中间累加精度。对应地reduce_std_v2_tiling_arch35.cpp 通过GEN_REDUCE_STD_V2_TILING_KEY生成包含patternID、loopARCount、loopInnerARCount的 Tiling Key实现 kernel 的多模式静态编译分派。与 torch 语义的对齐验证仓库测试资产 tests/assets/golden.py 提供了该算子的 golden 参考实现内部直接调用torch.std_mean/torch.stddim、correction、keepdim语义一一对应并处理了 BF16 先转 float32 再回写的精度路径说明 ReduceStdV2 的语义与 PyTorchtorch.std完全对齐可作为功能正确性的比对基准。完整调用示例以下示例取自仓库 examples/test_aclnn_std.cpp编译与运行流程参考 docs/zh/context/compile_and_run_sample.md以 shape 为{4, 2}的 float32 输入、dim{0}、correction1、keepdimfalse为例演示完整的 aclnnStd 两段式调用#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_std.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); 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); 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); 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]; } *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初始化根据自己的实际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.构造输入与输出 std::vectorint64_t selfShape {4, 2}; std::vectorint64_t outShape {2}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclIntArray* dim nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {2, 3, 5, 8, 4, 12, 6, 7}; std::vectorfloat outHostData {2, 3, 5, 8}; std::vectorint64_t dimData {0}; bool keepdim false; int64_t correction 1; ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); dim aclCreateIntArray(dimData.data(), 1); CHECK_RET(dim ! nullptr, return ret); ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3.调用CANN算子库API两段式 uint64_t workspaceSize 0; aclOpExecutor* executor; // 第一段获取workspace大小与执行器 ret aclnnStdGetWorkspaceSize(self, dim, correction, keepdim, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnStdGetWorkspaceSize 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); } // 第二段执行计算 ret aclnnStd(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnStd 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(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和aclIntArray aclDestroyTensor(self); aclDestroyIntArray(dim); aclDestroyTensor(out); // 7.释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例中几个关键点值得注意第一段接口返回的workspaceSize可能为 0此时无需申请 workspace 内存if (workspaceSize 0)判断dim通过aclCreateIntArray创建nullptr或空数组均表示对所有维度求标准差out的 shape 必须与keepdim语义一致本示例dim{0}且keepdimfalse故outShape{2}否则第一段接口会返回ACLNN_ERR_PARAM_INVALID执行完第二段接口后必须aclrtSynchronizeStream同步再回拷结果否则可能读到未完成的计算数据。测试与验证仓库在 math/reduce_std_v2/tests 目录下提供了完整的 UT 与 ST 测试支撑UT 用例tests/ut/op_api/test_aclnn_std.cpp 覆盖了多种正常场景例如float32 ND dim0 keepdimtrue输入{2,1,4}、输出{1,1,4}、float32 NCHW dim1 keepdimfalse输入{1,2,4,4}、输出{1,4,4}、float16/BF16 HWCN dim2 keepdimtrue等验证不同数据格式ND/NCHW/NHWC/HWCN、不同dim与keepdim组合下接口返回ACLNN_SUCCESS并可通过Precision设置精度阈值如0.0001进行数值比对Host 侧 UTtests/ut/op_host/test_std_infershape.cpp 与 tests/ut/op_host/arch35/test_std_tiling.cpp 分别验证 shape 推导与 Tiling 逻辑ST 用例tests/st/aclnnStd/atk_aclnnStd.json 与 tests/st/aclnnStd/executor_aclnnStd.py 构成 ATK 单算子自动化测试Golden 基准tests/assets/golden.py 通过torch.std_mean/torch.std生成期望结果作为数值正确性的对照基准。综上ReduceStdV2 算子语义清晰、边界完备既可通过aclnnStd两段式接口在任意受支持产品上直接调用也保留了从算子定义、shape 推导、tiling 到 kernel 分派的完整源码链路便于开发者理解与二次开发。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表