
CANN ops-math 算子 aclnnClamp 接口使用指南ClipByValueV2 两段式 API 解析与实战【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math本指南以 CANN 开源数学算子库 ops-math 中 conversion/clip_by_value_v2 模块的 aclnnClamp 接口文档 为主体系统讲解元素裁剪算子 ClipByValueV2 的 aclnnClamp/aclnnClampGetWorkspaceSize 两段式调用流程、参数与平台约束、返回值错误码并结合仓库源码与测试用例深入其底层实现。读者读完可独立完成 aclnnClamp 接口的代码编写、编译运行与结果校验并理解接口背后的算子图构建逻辑。功能说明与计算公式aclnnClamp 接口对应的底层算子是 ClipByValueV2其核心功能是将输入张量的所有元素限制在[min, max]范围内元素大于上界max时被裁剪为max小于下界min时被裁剪为min位于区间内的元素保持不变。如果min为None则没有下限等价于-inf如果max为None则没有上限等价于inf。计算公式逐元素$$ {y}{i} max(min({{x}{i}},{max_value}{i}),{min_value}{i}) $$其中x为输入张量min_value为下界max_value为上界。该公式在 op_api/aclnn_clamp.cpp 中由l0op::ClipByValueV2计算节点实际执行与 README.md 中算子功能描述完全一致。ClipByValueV2 是神经网络训练中的高频算子常用于梯度裁剪gradient clipping、数值稳定性保护等场景。产品支持情况根据接口文档aclnnClamp 在不同硬件平台上的支持情况如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品支持Atlas 训练系列产品支持与 README.md 中算子级产品支持表一致。从算子注册信息看AICore 侧在 clip_by_value_v2_def.cpp 中为ascend950与ascend350两个芯片配置注册了计算内核并启用了DynamicShapeSupportFlag(true)动态 Shape与DynamicRankSupportFlag(true)动态 Rank能力同时 op_kernel_aicpu/clip_by_value_v2_aicpu_def.cpp 提供了 AICPU 侧实现支持包括复数COMPLEX64/COMPLEX128、量化类型在内的更宽泛数据类型集合可见该算子具备 AICore/AICPU 双后端能力。两段式接口调用模型aclnnClamp 采用 CANN 算子库统一的**两段式接口two-phase API**设计相关机制详见 docs/zh/context/two_phase_api.md。整个调用流程必须先调用第一段接口aclnnClampGetWorkspaceSize获取计算所需 workspace 大小以及包含了算子计算流程的执行器再调用第二段接口aclnnClamp执行计算。两段接口的函数原型如下aclnnStatus aclnnClampGetWorkspaceSize( const aclTensor *self, const aclScalar *clipValueMin, const aclScalar *clipValueMax, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnClamp( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)接口声明位于 op_api/aclnn_clamp.h标注domain aclnn_math属于数学类基础算子库 API。aclnnClampGetWorkspaceSize 第一段接口详解参数说明参数名输入/输出描述使用说明数据类型数据格式维度shape非连续 TensorselfaclTensor*输入输入 Tensor需要进行限制的张量即公式中的 x_i-FLOAT16、FLOAT、FLOAT64、INT8、UINT8、INT16、INT32、INT64、BOOL、BFLOAT16ND1-8√clipValueMinaclScalar*输入输入 Scalar对 self 的下界进行限制即公式中的 min_value_i数据类型与 self 需满足数据类型转换规则参见 互转换关系FLOAT16、FLOAT、FLOAT64、INT8、UINT8、INT16、INT32、INT64、BOOL、BFLOAT16---clipValueMaxaclScalar*输入输入 Scalar对 self 的上界进行限制即公式中的 max_value_i数据类型与 self 需满足数据类型转换规则参见 互转换关系FLOAT16、FLOAT、FLOAT64、INT8、UINT8、INT16、INT32、INT64、BOOL、BFLOAT16---outaclTensor*输出输出 tensorshape 和 self 保持一致-FLOAT16、FLOAT、FLOAT64、INT8、UINT8、INT16、INT32、INT64、BOOL、BFLOAT16ND与 self 保持一致√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----需要特别说明的约束输入支持非连续 Tensor数据格式 ND、维度 1-8 维接口内部会通过Contiguous节点对非连续输入做连续性规整见aclnnClampCommon中的l0op::Contiguous(self, ...)调用。clipValueMin与clipValueMax均允许传nullptr但不能同时为空——源码CheckNotNullop_api/aclnn_clamp.cpp中明确At least one of min or max must not be None。这也对应接口文档中如果 min 为 None 则没有下限如果 max 为 None 则没有上限的语义。各平台数据类型限制Atlas 训练系列产品、Atlas 推理系列产品910/310p 平台self 和 out 的数据类型不支持 BOOL、BFLOAT16clipValueMin 和 clipValueMax 的数据类型不支持 BFLOAT16。Atlas A2 训练系列产品/Atlas A2 推理系列产品、Atlas A3 训练系列产品/Atlas A3 推理系列产品self 和 out 的数据类型不支持 BOOL。Ascend 950PR/Ascend 950DTself、clipValueMin 和 clipValueMax 数据类型需满足数据类型推导规则参见 TensorScalar 互推导关系out 的数据类型需要是 self、clipValueMin、clipValueMax 推导之后可转换的数据类型self、clipValueMin、clipValueMax 和 out 的数据类型不支持 BOOL。这些限制在源码中得到印证Ascend910_dtype_support_listFLOAT16、FLOAT、INT32、INT64、INT16、INT8、UINT8、DOUBLE与Ascend910B_dtype_support_list在上述基础上追加 BF16分别对应不同架构的 dtype 支持列表GetDtypeSupportList()依据GetCurNpuArch()动态选择。返回值与入参校验第一段接口返回aclnnStatus状态码具体可参考 aclnn 返回码。第一段接口完成入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、out 其中一个为空指针或者 max、min 全为空指针ACLNN_ERR_PARAM_INVALID161002self、out 的数据类型和数据格式不在支持的范围之内这两类错误与 UT 测试用例一一对应见 tests/ut/op_api/test_aclnn_clamp.cppcase_null分别传入nullptr的 self、out、以及 min/max 全空均断言返回ACLNN_ERR_PARAM_NULLPTRcase_unsupport_shapeself 与 out shape 不一致且格式 NCHW断言返回ACLNN_ERR_PARAM_INVALIDcase_9dim9 维输入超出 1-8 维限制断言返回ACLNN_ERR_PARAM_INVALIDcase_self_uncast_outself 为 FLOAT 而 out 为 INT64self 无法转换为 out 类型断言返回ACLNN_ERR_PARAM_INVALID。对应地源码CheckShape使用OP_CHECK_SHAPE_NOT_EQUAL(self, out)校验 shape 一致、OP_CHECK_MAX_DIM(self, MAX_SUPPORT_DIMS_NUMS)校验最大维度为 8CheckDtypeValid校验 dtype 在支持列表内非寄存器架构下还会调用CheckSelfCanCastOut校验CanCast(self-GetDataType(), out-GetDataType())。aclnnClamp 第二段接口详解参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnClampGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream第二段接口同样返回aclnnStatus状态码。执行时需保证 workspace 内存已按第一段返回的大小在 Device 侧申请完成若workspaceSize 0则无需申请并且传入的 stream 需与执行上下文匹配。源码层面aclnnClamp通过CommonOpExecutorRun(workspace, workspaceSize, executor, stream)完成真正的算子下发执行见 op_api/aclnn_clamp.cpp。约束说明确定性计算aclnnClamp 默认为确定性实现即相同输入在相同环境下多次执行结果完全一致相关概念可参考 确定性计算说明。调用示例示例代码来自接口文档与仓库 examples/test_aclnn_clamp.cpp 保持一致编译和执行整体流程请参考 编译与运行样例。示例以 shape 为{4, 2}的 FLOAT 张量{0, 1, 0, 3, 0, 5, 0, 7}为例下界 min2、上界 max5裁剪后预期结果为{2, 2, 2, 3, 2, 5, 2, 5}。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_clamp.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); aclFinalize(); return ret); ret aclrtCreateStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtCreateStream failed. ERROR: %d\n, ret); aclrtResetDevice(deviceId); aclFinalize(); 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 PrepareInputAndOutput( std::vectorint64_t shape, void** selfDeviceAddr, aclTensor** self, aclScalar** max, aclScalar** min, void** outDeviceAddr, aclTensor** out) { int8_t max_v 5; int8_t min_v 2; std::vectorint8_t selfHostData {0, 1, 0, 3, 0, 5, 0, 7}; std::vectorint8_t outHostData {0, 0, 0, 0, 0, 0, 0, 0}; // 创建self aclTensor auto ret CreateAclTensor(selfHostData, shape, selfDeviceAddr, aclDataType::ACL_INT8, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建max *max aclCreateScalar(max_v, aclDataType::ACL_INT8); CHECK_RET(*max ! nullptr, return ret); // 创建min *min aclCreateScalar(min_v, aclDataType::ACL_INT8); CHECK_RET(*min ! nullptr, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, shape, outDeviceAddr, aclDataType::ACL_INT8, out); CHECK_RET(ret ACL_SUCCESS, return ret); return ACL_SUCCESS; } void ReleaseTensorAndScalar(aclTensor* self, aclScalar* max, aclScalar* min, aclTensor* out) { aclDestroyTensor(self); aclDestroyScalar(max); aclDestroyScalar(min); aclDestroyTensor(out); } void ReleaseDevice( void* selfDeviceAddr, void* outDeviceAddr, uint64_t workspaceSize, void* workspaceAddr, aclrtStream stream, int32_t deviceId) { aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } int main() { // 1.固定写法device/stream初始化参考 // 根据自己的实际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 shape {4, 2}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclScalar* max nullptr; aclScalar* min nullptr; aclTensor* out nullptr; float max_v 5; float min_v 2; std::vectorfloat selfHostData {0, 1, 0, 3, 0, 5, 0, 7}; std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0}; // 创建self aclTensor ret CreateAclTensor(selfHostData, shape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建max max aclCreateScalar(max_v, aclDataType::ACL_FLOAT); CHECK_RET(max ! nullptr, return ret); // 创建min min aclCreateScalar(min_v, aclDataType::ACL_FLOAT); CHECK_RET(min ! nullptr, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, shape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3.调用CANN算子库API需要修改为具体的API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnClamp第一段接口 ret aclnnClampGetWorkspaceSize(self, min, max, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnClampGetWorkspaceSize 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;); } // 调用aclnnClamp第二段接口 ret aclnnClamp(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnClamp 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(shape); 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的接口定义修改 ReleaseTensorAndScalar(self, max, min, out); // 7.释放device资源 ReleaseDevice(selfDeviceAddr, outDeviceAddr, workspaceSize, workspaceAddr, stream, deviceId); return 0; }示例代码的执行步骤可归纳为七步① 初始化 ACL 资源aclInit/aclrtSetDevice/aclrtCreateStream→ ② 构造输入输出aclCreateTensor 创建 self/outaclCreateScalar 创建 min/max→ ③ 调用两段式 API先 GetWorkspaceSize 再按需申请 workspace 后执行→ ④ 同步等待aclrtSynchronizeStream→ ⑤ 拷贝结果回 HostaclrtMemcpy DEVICE_TO_HOST→ ⑥ 释放 Tensor/Scalar → ⑦ 释放 Device 资源。其中CreateAclTensor中 strides 的计算保证创建的是连续 ND 张量workspaceSize 0时无需调用aclrtMalloc申请 workspace释放阶段也做了同样的判空保护。源码级实现原理计算图构建链路第一段接口aclnnClampGetWorkspaceSize的核心逻辑集中在aclnnClampCommonop_api/aclnn_clamp.cpp其执行流程为创建执行器CREATE_EXECUTOR()创建aclOpExecutor入参校验CheckParams依次执行空指针检查、dtype 支持列表检查、shape 一致性检查空张量短路若self-IsEmpty()直接返回workspaceSize 0与执行器不做任何计算类型提升寄存器架构如 950下通过ClampPromoteType结合 Scalar 的默认类型做 Promote 推导并校验promoteType可转换为 out 的类型非寄存器架构下直接以 out 的类型作为 promote 类型并校验CanCast(self, out)上下界归一化NormalizeMaxScalar/NormalizeMinScalar处理传nullptr的情况——此时会按数据类型生成极端值填充。例如 FLOAT 用 ±infINT32 用INT_MAX/INT_MININT64 用±9223372036854775807/8INT8 用 ±127 等这正是文档min/max 为 None 则无下/上限的底层实现构图依次构建Contiguous连续性规整→Cast类型提升→ScalarToTensorScalar 转 Tensor→l0op::ClipByValueV2核心裁剪计算→ViewCopy结果写回 out的计算图节点寄存器架构额外插入Cast回写与ViewCopy返回 workspace 大小uniqueExecutor-GetWorkspaceSize()汇总图中各节点所需 workspace随后将执行器通过ReleaseTo(executor)交给用户。内核实现AICore 内核位于 op_kernel/arch35/clip_by_value_v2.cpp采用BroadcastSchschMode, OpDag sch(tiling); sch.Process(x, clipValueMin, clipValueMax, y);的模板化调度方式即基于atvoss/broadcast/broadcast_sch.h的广播调度框架执行天然支持 min/max 与 self 之间的广播语义Tensor 变体接口依赖此能力算子 IR 与 DAG 结构定义见 op_graph/clip_by_value_v2_proto.h。host 侧 infershape 在 clip_by_value_v2_infershape.cpp 中复用InferShape4Broadcast(context, INPUT_NUM_THREE)完成三输入广播推导与公式y_i max(min(x_i, max_i), min_i)的逐元素语义对应。测试验证除前述 UT 用例tests/ut/op_api/test_aclnn_clamp.cpp覆盖空指针、异常 shape、不可转换 dtype 等错误路径外ST 用例 tests/st/aclnnClamp/atk_aclnnClamp.json 覆盖了 int8/uint8/int16/int32/int64/fp16/fp32/fp64 多种数据类型、1~4 维多种 shape含大 shape 如[128, 360232]、[32, 20, 56, 56]的裁剪正确性验证精度标准采用 md5 对比对应执行脚本为 executor_aclnnClamp.py。此外算子级 golden 参考实现位于 tests/assets/golden.py可用于对照验证裁剪语义。更多相关接口aclnnClamp 是 ClipByValueV2 算子族中的一员同模块还提供以下变体接口声明均位于 op_api/aclnn_clamp.haclnnClampMin仅保留下界裁剪y_i max(x_i, min)aclnnClampMax 与 aclnnInplaceClampMax仅保留上界裁剪y_i min(x_i, max)Inplace 版本输出复用输入内存aclnnClampTensormin/max 以 Tensor而非 Scalar形式传入支持广播aclnnClampMinTensor 与 aclnnInplaceClampMinTensor、aclnnClampMaxTensor 与 aclnnInplaceClampMaxTensorTensor 形式的单边裁剪及 Inplace 版本aclnnHardtanh 与 aclnnInplaceHardtanhHardtanh 激活同样基于裁剪语义。这些接口在 op_api/aclnn_clamp.cpp 中均复用aclnnClampCommon/aclnnClampTensorCommon两个公共实现仅对缺失的 min/max 参数补nullptr因此本文的调用方式、错误码与平台约束分析对算子族内其他接口同样适用。更多调用方式aclnn 调用、图模式调用可参见模块 README.md。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考