已合并
[CANNBot]Relu6算子支持Ascend950 AscendC实现 #3794
wangweidong创建于 4月14日
[CANNBot]Relu6算子支持Ascend950 AscendC实现 #3794
已合并
wangweidong创建于 4月14日
11 个文件变更+900-0
@@ -0,0 +1,21 @@
1+# ----------------------------------------------------------------------------------------------------------
2+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+# CANN Open Software License Agreement Version 2.0 (the "License").
5+# Please refer to the License for details. You may not use this file except in compliance with the License.
6+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+# See LICENSE in the root of the software repository for the full text of the License.
9+# ----------------------------------------------------------------------------------------------------------
10+# NOTE: Portions of this code were AI-generated and have been
11+# technically reviewed for functional accuracy and security
12+ 
13+file(GLOB CURRENT_DIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
14+if(NOT ENABLE_TEST)
15+ list(REMOVE_ITEM CURRENT_DIRS tests)
16+endif()
17+foreach(SUB_DIR ${CURRENT_DIRS})
18+ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUB_DIR}/CMakeLists.txt")
19+ add_subdirectory(${SUB_DIR})
20+ endif()
21+endforeach()
@@ -0,0 +1,104 @@
1+# Relu6 算子
2+ 
3+Relu6 激活函数算子的 Ascend C 自定义算子实现,面向 Ascend 950 芯片。
4+ 
5+## 功能简介
6+ 
7+Relu6 算子按元素对输入张量执行 Relu6 激活函数计算,将输入张量中的每个元素值限制在 `[0, 6]` 区间内。
8+ 
9+### 数学公式
10+ 
11+```
12+y = min(max(x, 0), 6)
13+```
14+ 
15+等价于分段函数:
16+ 
17+```
18+y = { 0, if x < 0
19+ { x, if 0 <= x <= 6
20+ { 6, if x > 6
21+```
22+ 
23+## 支持特性
24+ 
25+### 数据类型
26+ 
27+| 数据类型 | C 类型 |
28+|---------|--------|
29+| float16 | half |
30+| float | float |
31+| int32 | int32_t |
32+| bfloat16 | bfloat16_t |
33+ 
34+### Shape 约束
35+ 
36+- 输入 x 和输出 y 的 shape 必须相同
37+- 最高支持 8 维张量
38+- 支持任意维度(1D ~ 8D),使用 FORMAT_ND(任意维度连续排布)
39+ 
40+### 芯片支持
41+ 
42+| 芯片 | 状态 |
43+|-----|------|
44+| Ascend 950PR | 支持 |
45+| Ascend 950DT | 支持 |
46+ 
47+## 工程结构
48+ 
49+```
50+relu6/
51+├── examples/ # 调用示例
52+│ └── test_aclnn_relu6.cpp # C++ 调用示例
53+├── op_host/ # 主机端算子注册代码
54+│ ├── relu6_def.cpp # 算子定义
55+│ └── relu6_tiling.cpp # Tiling 策略
56+├── op_kernel/ # Kernel 实现代码
57+│ ├── relu6.cpp # Kernel 入口
58+│ ├── relu6.h # Kernel 类实现
59+│ ├── relu6_tiling_data.h # TilingData 结构体
60+│ └── relu6_tiling_key.h # TilingKey 模板参数
61+├── tests/ # 测试代码
62+├── CMakeLists.txt # 顶层 CMake 配置
63+└── README.md # 本文件
64+```
65+ 
66+## API 接口
67+ 
68+算子提供两段式 ACLNN 接口:
69+ 
70+### aclnnRelu6GetWorkspaceSize
71+ 
72+计算执行 Relu6 算子所需的 workspace 大小。
73+ 
74+```cpp
75+extern "C" aclnnStatus aclnnRelu6GetWorkspaceSize(
76+ const aclTensor* x, // 输入张量
77+ const aclTensor* y, // 输出张量
78+ uint64_t* workspaceSize, // 输出:workspace 大小
79+ aclOpExecutor** executor); // 输出:执行器指针
80+```
81+ 
82+### aclnnRelu6
83+ 
84+执行 Relu6 算子计算。
85+ 
86+```cpp
87+extern "C" aclnnStatus aclnnRelu6(
88+ void* workspace, // workspace 内存地址
89+ uint64_t workspaceSize, // workspace 大小
90+ aclOpExecutor* executor, // 执行器指针
91+ aclrtStream stream); // 运行流
92+```
93+ 
94+## 实现原理
95+ 
96+Relu6 算子在 AI Core 上通过两次矢量计算实现:
97+ 
98+1. **Maxs 计算**`tmp = max(x, 0)` -- 将小于 0 的值截断为 0
99+2. **Mins 计算**`y = min(tmp, 6)` -- 将大于 6 的值截断为 6
100+ 
101+内部采用多核并行 + UB 缓冲优化策略:
102+- 根据数据量和 AI Core 数量自动切分数据,实现多核并行处理
103+- UB 中使用 3 个 LocalTensor(input、tmp、output),根据 UB 大小动态计算单次循环处理量
104+- LocalTensor 起始地址 32 字节对齐,尾部数据通过 DataCopyPad 自动处理
@@ -0,0 +1,209 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+/**
12+ * NOTE: Portions of this code were AI-generated and have been
13+ * technically reviewed for functional accuracy and security
14+ */
15+ 
16+/**
17+ * @file test_aclnn_relu6.cpp
18+ * @brief Relu6 算子调用示例
19+ *
20+ * Relu6 计算公式:y = min(max(x, 0), 6)
21+ */
22+ 
23+#include <iostream>
24+#include <vector>
25+#include "acl/acl.h"
26+#include "aclnn_relu6.h"
27+ 
28+using DataType = float;
29+ 
30+#define CHECK_RET(cond, return_expr) \
31+ do { \
32+ if (!(cond)) { \
33+ return_expr; \
34+ } \
35+ } while (0)
36+ 
37+#define LOG_PRINT(message, ...) \
38+ do { \
39+ printf(message, ##__VA_ARGS__); \
40+ } while (0)
41+ 
42+int64_t GetShapeSize(const std::vector<int64_t>& shape)
43+{
44+ int64_t shapeSize = 1;
45+ for (auto i : shape) {
46+ shapeSize *= i;
47+ }
48+ return shapeSize;
49+}
50+ 
51+void PrintOutResult(std::vector<int64_t>& shape, void** deviceAddr)
52+{
53+ auto size = GetShapeSize(shape);
54+ std::vector<DataType> resultData(size, 0);
55+ auto ret = aclrtMemcpy(
56+ resultData.data(), resultData.size() * sizeof(resultData[0]), *deviceAddr, size * sizeof(resultData[0]),
57+ ACL_MEMCPY_DEVICE_TO_HOST);
58+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return);
59+ for (int64_t i = 0; i < size; i++) {
60+ LOG_PRINT("relu6 result[%ld] is: %f\n", i, resultData[i]);
61+ }
62+}
63+ 
64+/**
65+ * @brief 在 CPU 上计算 Relu6 参考结果
66+ */
67+void Relu6CpuReference(const DataType* input, DataType* output, size_t size)
68+{
69+ for (size_t i = 0; i < size; ++i) {
70+ float val = input[i];
71+ if (val < 0.0f) {
72+ output[i] = 0.0f;
73+ } else if (val > 6.0f) {
74+ output[i] = 6.0f;
75+ } else {
76+ output[i] = val;
77+ }
78+ }
79+}
80+ 
81+int Init(int32_t deviceId, aclrtStream* stream)
82+{
83+ auto ret = aclInit(nullptr);
84+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
85+ ret = aclrtSetDevice(deviceId);
86+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
87+ ret = aclrtCreateStream(stream);
88+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
89+ return 0;
90+}
91+ 
92+template <typename T>
93+int CreateAclTensor(
94+ const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr,
95+ aclDataType dataType, aclTensor** tensor)
96+{
97+ auto size = GetShapeSize(shape) * sizeof(T);
98+ auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
99+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
100+ ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
101+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);
102+ 
103+ std::vector<int64_t> strides(shape.size(), 1);
104+ for (int64_t i = shape.size() - 2; i >= 0; i--) {
105+ strides[i] = shape[i + 1] * strides[i + 1];
106+ }
107+ 
108+ *tensor = aclCreateTensor(
109+ shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(),
110+ *deviceAddr);
111+ return 0;
112+}
113+ 
114+int main()
115+{
116+ // 1. 调用acl进行device/stream初始化
117+ int32_t deviceId = 0;
118+ aclrtStream stream;
119+ auto ret = Init(deviceId, &stream);
120+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
121+ 
122+ // 2. 构造输入与输出
123+ // 输入 shape: [2, 4],包含典型 Relu6 测试值:负数、0、正数、大于 6 的数
124+ aclTensor* selfX = nullptr;
125+ void* selfXDeviceAddr = nullptr;
126+ std::vector<int64_t> selfXShape = {2, 4};
127+ std::vector<DataType> selfXHostData = {-2.0f, 0.0f, 3.0f, 7.0f, 1.5f, -0.5f, 6.0f, 10.0f};
128+ ret = CreateAclTensor(selfXHostData, selfXShape, &selfXDeviceAddr, aclDataType::ACL_FLOAT, &selfX);
129+ CHECK_RET(ret == ACL_SUCCESS, return ret);
130+ 
131+ aclTensor* out = nullptr;
132+ void* outDeviceAddr = nullptr;
133+ std::vector<int64_t> outShape = {2, 4};
134+ std::vector<DataType> outHostData(8, 0);
135+ ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out);
136+ CHECK_RET(ret == ACL_SUCCESS, return ret);
137+ 
138+ // 计算 CPU 参考结果用于对比
139+ std::vector<DataType> cpuRefOut(8);
140+ Relu6CpuReference(selfXHostData.data(), cpuRefOut.data(), 8);
141+ 
142+ LOG_PRINT("Before GetWorkspaceSize: selfX=%p, out=%p\n", (void*)selfX, (void*)out);
143+ LOG_PRINT("Before GetWorkspaceSize: selfXDeviceAddr=%p, outDeviceAddr=%p\n",
144+ selfXDeviceAddr, outDeviceAddr);
145+ 
146+ // 3. 调用 aclnnRelu6GetWorkspaceSize(第一段接口)
147+ uint64_t workspaceSize = 0;
148+ aclOpExecutor* executor;
149+ ret = aclnnRelu6GetWorkspaceSize(selfX, out, &workspaceSize, &executor);
150+ LOG_PRINT("aclnnRelu6GetWorkspaceSize returned %d, workspaceSize=%llu, executor=%p\n",
151+ ret, (unsigned long long)workspaceSize, (void*)executor);
152+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnRelu6GetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
153+ 
154+ // 根据 workspaceSize 申请 device 内存
155+ void* workspaceAddr = nullptr;
156+ if (workspaceSize > static_cast<uint64_t>(0)) {
157+ ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
158+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
159+ }
160+ 
161+ // 4. 调用 aclnnRelu6(第二段接口)
162+ ret = aclnnRelu6(workspaceAddr, workspaceSize, executor, stream);
163+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnRelu6 failed. ERROR: %d\n", ret); return ret);
164+ 
165+ // 5. 同步等待任务执行结束
166+ ret = aclrtSynchronizeStream(stream);
167+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
168+ 
169+ // 6. 获取输出的值
170+ PrintOutResult(outShape, &outDeviceAddr);
171+ 
172+ // 7. 简单验证
173+ std::vector<DataType> npuOut(8);
174+ ret = aclrtMemcpy(npuOut.data(), npuOut.size() * sizeof(DataType), outDeviceAddr,
175+ 8 * sizeof(DataType), ACL_MEMCPY_DEVICE_TO_HOST);
176+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result for verification failed. ERROR: %d\n", ret); return ret);
177+ 
178+ bool pass = true;
179+ for (size_t i = 0; i < 8; i++) {
180+ float diff = std::abs(npuOut[i] - cpuRefOut[i]);
181+ if (diff > 1e-2f) {
182+ LOG_PRINT("Mismatch at index %ld: NPU=%f, CPU=%f, diff=%f\n", i, npuOut[i], cpuRefOut[i], diff);
183+ pass = false;
184+ }
185+ }
186+ if (pass) {
187+ LOG_PRINT("Verification passed! NPU output matches CPU reference.\n");
188+ } else {
189+ LOG_PRINT("Verification FAILED!\n");
190+ }
191+ 
192+ // 8. 释放 aclTensor
193+ aclDestroyTensor(selfX);
194+ aclDestroyTensor(out);
195+ 
196+ // 9. 释放 device 资源
197+ aclrtFree(selfXDeviceAddr);
198+ aclrtFree(outDeviceAddr);
199+ if (workspaceSize > static_cast<uint64_t>(0)) {
200+ aclrtFree(workspaceAddr);
201+ }
202+ aclrtDestroyStream(stream);
203+ aclrtResetDevice(deviceId);
204+ 
205+ // 10. acl 去初始化
206+ aclFinalize();
207+ 
208+ return pass ? 0 : 1;
209+}
@@ -0,0 +1,13 @@
1+# ----------------------------------------------------------------------------------------------------------
2+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+# CANN Open Software License Agreement Version 2.0 (the "License").
5+# Please refer to the License for details. You may not use this file except in compliance with the License.
6+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+# See LICENSE in the root of the software repository for the full text of the License.
9+# ----------------------------------------------------------------------------------------------------------
10+# NOTE: Portions of this code were AI-generated and have been
11+# technically reviewed for functional accuracy and security
12+ 
13+add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE relu6 ACLNNTYPE aclnn)
陈佳良4月17日

CMakeLists 缺少架构和 tiling 目录参数。虽然只支持 ascend950,但 examples 在 arch35 目录下,建议添加 COMPUTE_UNITTILING_DIR 参数以保持配置一致性。

likedislike
@@ -0,0 +1,58 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+/**
12+ * NOTE: Portions of this code were AI-generated and have been
13+ * technically reviewed for functional accuracy and security
14+ */
15+ 
16+/*!
17+ * \file relu6_def.cpp
18+ * \brief Relu6 算子定义,声明输入输出和算子配置
19+ *
20+ * 算子公式: y = min(max(x, 0), 6)
21+ * 支持数据类型: float16, float, int32, bfloat16
22+ * 目标芯片: Ascend950 (arch35)
23+ */
24+ 
25+#include "register/op_def_registry.h"
26+ 
27+namespace ops {
28+class Relu6 : public OpDef {
29+public:
30+ explicit Relu6(const char* name) : OpDef(name)
31+ {
32+ this->Input("x") // 输入 x 定义
33+ .ParamType(REQUIRED) // 必选输入
34+ .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32, ge::DT_BF16}) // 支持数据类型
35+ .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) // 支持 format 格式
36+ .UnknownShapeFormat({ge::FORMAT_ND}) // 未确定大小 shape 对应 format
37+ .AutoContiguous(); // 内存自动连续化
38+ this->Output("y") // 输出 y 定义
39+ .ParamType(REQUIRED)
40+ .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32, ge::DT_BF16})
41+ .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND})
42+ .UnknownShapeFormat({ge::FORMAT_ND})
43+ .AutoContiguous();
44+ 
45+ // Ascend950 AI Core 配置
46+ OpAICoreConfig aiCoreConfig;
47+ aiCoreConfig.DynamicCompileStaticFlag(true)
48+ .DynamicFormatFlag(false)
49+ .DynamicRankSupportFlag(true)
50+ .DynamicShapeSupportFlag(true)
51+ .NeedCheckSupportFlag(false)
52+ .PrecisionReduceFlag(true)
53+ .ExtendCfgInfo("opFile.value", "relu6"); // 指定 Kernel 入口文件名
54+ this->AICore().AddConfig("ascend950", aiCoreConfig);
55+ }
56+};
57+OP_ADD(Relu6); // 注册算子到算子信息库
58+} // namespace ops
@@ -0,0 +1,175 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+/**
12+ * NOTE: Portions of this code were AI-generated and have been
13+ * technically reviewed for functional accuracy and security
14+ */
15+ 
16+/*!
17+ * \file relu6_tiling.cpp
18+ * \brief Relu6 Tiling 实现(arch35 = Ascend950)
19+ *
20+ * Tiling 策略:
21+ * 1. 获取平台信息(UB 大小、AI Core 数量)
22+ * 2. 获取 shape/dtype 信息
23+ * 3. 多核切分:blockFactor = ceil(totalNum / coreNum)
24+ * 4. UB 切分:ubFactor = floor_align(floor_div(ubSize / typeSize / 3), ubBlockSize)
25+ * Relu6 需要 3 个 LocalTensor: inputLocal, tmpLocal, outputLocal
26+ * 5. 设置 TilingKey(由模板参数选择 dtype)
27+ */
28+ 
29+#include "register/op_def_registry.h"
30+#include "op_common/log/log.h"
31+#include "op_common/op_host/util/math_util.h"
32+#include "op_common/op_host/util/platform_util.h"
33+#include "../op_kernel/relu6_tiling_data.h"
34+#include "../op_kernel/relu6_tiling_key.h"
35+ 
36+namespace optiling {
37+ 
38+using Ops::Base::CeilDiv;
39+using Ops::Base::FloorAlign;
40+using Ops::Base::FloorDiv;
41+using Ops::Base::GetUbBlockSize;
42+ 
43+constexpr uint32_t WS_SYS_SIZE = 0U;
44+// Relu6 需要 3 个 LocalTensor:inputLocal + tmpLocal(Maxs输出)+ outputLocal
45+constexpr int64_t BUFFER_NUM = 3;
46+// 双缓冲阈值:数据量大于此值时启用双缓冲(迭代三扩展)
47+constexpr int64_t MIN_SPLIT_THRESHOLD = 1024;
48+ 
49+static const gert::Shape g_vec_1_shape = {1};
50+ 
51+/// \brief 确保 shape 不为标量(0 维),若为标量则转换为 {1}
52+static inline const gert::Shape EnsureNotScalar(const gert::Shape& in_shape)
53+{
54+ if (in_shape.GetDimNum() == 0) {
55+ return g_vec_1_shape;
56+ }
57+ return in_shape;
58+}
59+ 
60+/// \brief 获取平台信息:UB 大小、AI Core 数量
61+static ge::graphStatus GetPlatformInfo(gert::TilingContext* context, uint64_t& ubSize, int64_t& coreNum)
62+{
63+ fe::PlatFormInfos* platformInfoPtr = context->GetPlatformInfo();
64+ OP_CHECK_NULL_WITH_CONTEXT(context, platformInfoPtr);
65+ auto ascendcPlatform = platform_ascendc::PlatformAscendC(platformInfoPtr);
66+ coreNum = ascendcPlatform.GetCoreNumAiv();
67+ OP_CHECK_IF(coreNum == 0, OP_LOGE(context, "coreNum is 0"), return ge::GRAPH_FAILED);
68+ ascendcPlatform.GetCoreMemSize(platform_ascendc::CoreMemType::UB, ubSize);
69+ OP_CHECK_IF(ubSize == 0, OP_LOGE(context, "ubSize is 0"), return ge::GRAPH_FAILED);
70+ return ge::GRAPH_SUCCESS;
71+}
72+ 
73+/// \brief 获取 shape 和 dtype 信息
74+static ge::graphStatus GetShapeAttrsInfo(gert::TilingContext* context, int64_t& totalNum, ge::DataType& dataType)
75+{
76+ auto inputX = context->GetInputShape(0);
77+ OP_CHECK_NULL_WITH_CONTEXT(context, inputX);
78+ auto inputShapeX = EnsureNotScalar(inputX->GetStorageShape());
79+ auto outY = context->GetOutputShape(0);
80+ OP_CHECK_NULL_WITH_CONTEXT(context, outY);
81+ auto outShapeY = EnsureNotScalar(outY->GetStorageShape());
82+ 
83+ // Shape 校验:输入输出元素数一致
84+ OP_CHECK_IF(
85+ inputShapeX.GetShapeSize() != outShapeY.GetShapeSize(),
86+ OP_LOGE(context, "Relu6: input and output shape size mismatch"), return ge::GRAPH_FAILED);
87+ 
88+ totalNum = inputShapeX.GetShapeSize();
89+ 
90+ // dtype 校验
91+ const std::set<ge::DataType> supportedDtype = {ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32, ge::DT_BF16};
92+ auto inputDesc = context->GetInputDesc(0);
93+ OP_CHECK_NULL_WITH_CONTEXT(context, inputDesc);
94+ dataType = inputDesc->GetDataType();
95+ if (supportedDtype.count(dataType) == 0) {
96+ OP_LOGE(context, "invalid dtype");
97+ return ge::GRAPH_FAILED;
98+ }
99+ return ge::GRAPH_SUCCESS;
100+}
101+ 
102+/// \brief 设置 workspace 大小(Relu6 不需要额外 workspace)
103+static ge::graphStatus GetWorkspaceSize(gert::TilingContext* context)
104+{
105+ size_t* currentWorkspace = context->GetWorkspaceSizes(1);
106+ OP_CHECK_NULL_WITH_CONTEXT(context, currentWorkspace);
107+ currentWorkspace[0] = WS_SYS_SIZE;
108+ return ge::GRAPH_SUCCESS;
109+}
110+ 
111+/// \brief Tiling 分发入口
112+static ge::graphStatus Relu6TilingFunc(gert::TilingContext* context)
113+{
114+ // 1. 获取平台运行信息
115+ uint64_t ubSize;
116+ int64_t coreNum;
117+ OP_CHECK_IF(
118+ GetPlatformInfo(context, ubSize, coreNum) != ge::GRAPH_SUCCESS, OP_LOGE(context, "GetPlatformInfo error"),
119+ return ge::GRAPH_FAILED);
120+ 
121+ // 2. 获取 shape、dtype 信息
122+ int64_t totalNum;
123+ ge::DataType dataType;
124+ OP_CHECK_IF(
125+ GetShapeAttrsInfo(context, totalNum, dataType) != ge::GRAPH_SUCCESS,
126+ OP_LOGE(context, "GetShapeAttrsInfo error"), return ge::GRAPH_FAILED);
127+ 
128+ // 3. 获取 WorkspaceSize
129+ OP_CHECK_IF(
130+ GetWorkspaceSize(context) != ge::GRAPH_SUCCESS, OP_LOGE(context, "GetWorkspaceSize error"),
131+ return ge::GRAPH_FAILED);
132+ 
133+ // 4. 设置 TilingData
134+ Relu6TilingData* tiling = context->GetTilingData<Relu6TilingData>();
135+ OP_CHECK_NULL_WITH_CONTEXT(context, tiling);
136+ OP_CHECK_IF(
137+ memset_s(tiling, sizeof(Relu6TilingData), 0, sizeof(Relu6TilingData)) != EOK,
138+ OP_LOGE(context, "set tiling data error"), return ge::GRAPH_FAILED);
139+ 
140+ // 多核切分:将总元素按核数均分
141+ tiling->totalNum = totalNum;
142+ tiling->blockFactor = CeilDiv(totalNum, coreNum);
143+ int64_t usedCoreNum = CeilDiv(totalNum, tiling->blockFactor);
144+ 
145+ // UB 切分:
146+ // 公式:(UB总大小 / 类型大小) / buffer数量,然后按 UB 块大小对齐
147+ // bufferNum = 3(inputLocal + tmpLocal + outputLocal)
148+ int64_t typeSize = ge::GetSizeByDataType(dataType);
149+ OP_CHECK_IF(typeSize == 0, OP_LOGE(context, "typeSize is 0, invalid dataType"), return ge::GRAPH_FAILED);
150+ int64_t ubBlockSize = GetUbBlockSize(context);
151+ tiling->ubFactor = FloorAlign(FloorDiv(static_cast<int64_t>(ubSize) / typeSize, BUFFER_NUM), ubBlockSize);
152+ 
153+ // 设置 dataType 字段:保存原始 ge::DataType 枚举值,供 Kernel 侧模板参数实例化使用
154+ tiling->dataType = static_cast<int32_t>(dataType);
155+ 
156+ context->SetBlockDim(usedCoreNum);
157+ 
158+ // 5. 设置 TilingKey(dtype 模板参数选择)
159+ uint32_t dType = static_cast<uint32_t>(dataType);
160+ ASCENDC_TPL_SEL_PARAM(context, dType);
161+ return ge::GRAPH_SUCCESS;
162+}
163+ 
164+/// \brief Tiling 解析(Relu6 不需要额外解析)
165+static ge::graphStatus TilingParseForRelu6([[maybe_unused]] gert::TilingParseContext* context)
166+{
167+ return ge::GRAPH_SUCCESS;
168+}
169+ 
170+struct Relu6CompileInfo {}; // 必须定义,入图场景依赖
171+ 
172+// Tiling 注册入口
173+IMPL_OP_OPTILING(Relu6).Tiling(Relu6TilingFunc).TilingParse<Relu6CompileInfo>(TilingParseForRelu6);
174+ 
175+} // namespace optiling
@@ -0,0 +1,38 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+/**
12+ * NOTE: Portions of this code were AI-generated and have been
13+ * technically reviewed for functional accuracy and security
14+ */
15+ 
16+/*!
17+ * \file relu6.cpp
18+ * \brief Relu6 Kernel 入口(arch35 = Ascend950)
19+ *
20+ * 模板参数说明(与 relu6_tiling_key.h 中 ASCENDC_TPL_ARGS_DECL 定义对应):
21+ * - D_T: 数据类型,由 ASCENDC_TPL_DATATYPE_DECL 定义
22+ *
23+ * 迭代一:单核 float16 骨架,D_T 模板自动推导实例化
24+ * 核函数参数顺序(固定):
25+ * GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling
26+ */
27+ 
28+#include "relu6.h"
29+ 
30+template <typename D_T>
31+__global__ __aicore__ void relu6(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling)
32+{
33+ REGISTER_TILING_DEFAULT(Relu6TilingData);
陈佳良4月17日

kernel 入口缺少 ENABLE_PRINTF() 调用。对比 HardShrink(第 34 行)有此调用,建议添加以便支持调试时的 printf 功能。

likedislike
34+ GET_TILING_DATA_WITH_STRUCT(Relu6TilingData, tilingData, tiling);
35+ NsRelu6::Relu6<D_T> op;
36+ op.Init(x, y, &tilingData);
37+ op.Process();
38+}
@@ -0,0 +1,190 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+/**
12+ * NOTE: Portions of this code were AI-generated and have been
13+ * technically reviewed for functional accuracy and security
14+ */
15+ 
16+/*!
17+ * \file relu6.h
18+ * \brief Relu6 Kernel 类实现(arch35 = Ascend950)
19+ *
20+ * 公式: y = min(max(x, 0), 6)
21+ * 迭代一:单核 + 单 dtype(float16)骨架
22+ * 数据流: GM -> UB(inputLocal) -> UB(tmpLocal=Maxs) -> UB(outputLocal=Mins) -> GM
23+ * UB 使用: 3 个 LocalTensor(inputLocal + tmpLocal + outputLocal)
24+ */
25+ 
26+#ifndef RELU6_H
27+#define RELU6_H
28+ 
29+#include "kernel_operator.h"
30+#include "kernel_tiling/kernel_tiling.h"
31+#include "relu6_tiling_data.h"
32+#include "relu6_tiling_key.h"
33+ 
34+namespace NsRelu6 {
35+ 
36+using namespace AscendC;
37+ 
38+/// \brief Relu6 Kernel 类
39+/// \tparam T 数据类型(迭代一使用 half,迭代二扩展为模板参数)
40+template <typename T>
41+class Relu6 {
42+public:
43+ __aicore__ inline Relu6() {}
44+ 
45+ /// \brief 初始化 GlobalBuffer 和 UB Buffer
46+ /// \param x 输入 GM 地址
47+ /// \param y 输出 GM 地址
48+ /// \param tilingData Tiling 参数
49+ __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, const Relu6TilingData* tilingData);
50+ 
51+ /// \brief 执行主循环:CopyIn -> Compute -> CopyOut
52+ __aicore__ inline void Process();
53+ 
54+private:
55+ /// \brief 从 GM 搬运数据到 UB
56+ /// \param progress 当前循环进度
57+ /// \param currentNum 本次搬运元素数
58+ __aicore__ inline void CopyIn(int64_t progress, int64_t currentNum);
59+ 
60+ /// \brief 从 UB 搬运结果到 GM
61+ /// \param progress 当前循环进度
62+ /// \param currentNum 本次搬运元素数
63+ __aicore__ inline void CopyOut(int64_t progress, int64_t currentNum);
64+ 
65+ /// \brief UB 内计算: max(x, 0) -> min(result, 6)
66+ /// \param currentNum 本次计算元素数
67+ __aicore__ inline void Compute(int64_t currentNum);
68+ 
69+private:
70+ TPipe pipe;
71+ TQue<QuePosition::VECIN, 2> inputQueue; // 双缓冲:2 个 input buffer 支持乒乓调度
72+ TQue<QuePosition::VECOUT, 1> outputQueue;
73+ 
74+ GlobalTensor<T> inputGM;
75+ GlobalTensor<T> outputGM;
76+ 
77+ int64_t blockLength_ = 0; // 当前 Core 需处理的元素数
78+ int64_t ubLength_ = 0; // 单次 UB 循环处理的元素数
79+};
80+ 
81+template <typename T>
82+__aicore__ inline void Relu6<T>::Init(GM_ADDR x, GM_ADDR y, const Relu6TilingData* tilingData)
83+{
84+ // 计算当前 AI Core 需要处理的元素数量
85+ int64_t remainderLength = tilingData->totalNum - tilingData->blockFactor * AscendC::GetBlockIdx();
86+ blockLength_ = (remainderLength > tilingData->blockFactor) ? tilingData->blockFactor : remainderLength;
87+ ubLength_ = tilingData->ubFactor;
88+ 
89+ // 设置 Global Buffer,根据 blockIdx 偏移
90+ inputGM.SetGlobalBuffer((__gm__ T*)x + tilingData->blockFactor * AscendC::GetBlockIdx(), blockLength_);
91+ outputGM.SetGlobalBuffer((__gm__ T*)y + tilingData->blockFactor * AscendC::GetBlockIdx(), blockLength_);
92+ 
93+ // 初始化 UB Buffer:每个 LocalTensor 大小为 ubLength_ * sizeof(T)
94+ // 32 字节对齐由 AscendC AllocTensor 自动保证
95+ // inputQueue: 2 个 buffer(双缓冲),支持 CopyIn 与 Compute/CopyOut 流水并行
96+ // outputQueue: 动态管理 2 个 LocalTensor(tmpLocal + outputLocal)
97+ pipe.InitBuffer(inputQueue, 2, ubLength_ * sizeof(T));
98+ pipe.InitBuffer(outputQueue, 2, ubLength_ * sizeof(T));
99+}
100+ 
101+template <typename T>
102+__aicore__ inline void Relu6<T>::CopyIn(int64_t progress, int64_t currentNum)
103+{
104+ AscendC::LocalTensor<T> inputLocal = inputQueue.template AllocTensor<T>();
105+ AscendC::DataCopyParams copyParams;
106+ copyParams.blockCount = 1;
107+ copyParams.blockLen = currentNum * sizeof(T);
108+ copyParams.srcStride = 0;
109+ copyParams.dstStride = 0;
110+ // DataCopyPad 自动处理末尾不足 32 字节对齐的尾部数据
111+ AscendC::DataCopyPad(inputLocal, inputGM[progress * ubLength_], copyParams, {false, 0, 0, 0});
112+ inputQueue.EnQue(inputLocal);
113+}
114+ 
115+template <typename T>
116+__aicore__ inline void Relu6<T>::CopyOut(int64_t progress, int64_t currentNum)
117+{
118+ AscendC::LocalTensor<T> outputLocal = outputQueue.template DeQue<T>();
119+ AscendC::DataCopyParams copyParams;
120+ copyParams.blockCount = 1;
121+ copyParams.blockLen = currentNum * sizeof(T);
122+ copyParams.srcStride = 0;
123+ copyParams.dstStride = 0;
124+ AscendC::DataCopyPad(outputGM[progress * ubLength_], outputLocal, copyParams);
125+ outputQueue.FreeTensor(outputLocal);
126+}
127+ 
128+template <typename T>
129+__aicore__ inline void Relu6<T>::Compute(int64_t currentNum)
130+{
131+ AscendC::LocalTensor<T> inputLocal = inputQueue.template DeQue<T>();
132+ AscendC::LocalTensor<T> outputLocal = outputQueue.template AllocTensor<T>();
133+ 
134+ // Step 1: tmpLocal = max(inputLocal, 0)
135+ // 使用 Maxs API 实现 Relu 的下界
136+ AscendC::LocalTensor<T> tmpLocal = outputQueue.template AllocTensor<T>();
137+ AscendC::Maxs(tmpLocal, inputLocal, static_cast<T>(0), currentNum);
陈佳良4月17日

bf16 类型支持但缺少 Cast 处理。relu6_def.cpp 和 tiling 支持 DT_BF16,但 kernel 直接使用 bf16 进行 Maxs/Mins 计算。对比 HardShrink 算子,bf16 需要先 Cast 到 float 计算以保证精度,建议添加类似处理。

likedislike
138+ 
139+ // Step 2: outputLocal = min(tmpLocal, 6)
140+ // 使用 Mins API 实现 Relu6 的上界
141+ AscendC::Mins(outputLocal, tmpLocal, static_cast<T>(6), currentNum);
142+ 
143+ // 释放 tmpLocal
144+ outputQueue.FreeTensor(tmpLocal);
145+ 
146+ outputQueue.EnQue(outputLocal);
147+ inputQueue.FreeTensor(inputLocal);
148+}
149+ 
150+template <typename T>
151+__aicore__ inline void Relu6<T>::Process()
152+{
153+ int64_t loopCount = (blockLength_ + ubLength_ - 1) / ubLength_;
154+ if (loopCount == 0) {
155+ return;
156+ }
157+ 
158+ // 单循环场景:直接串行执行,无需流水
159+ if (loopCount == 1) {
160+ int64_t currentNum = blockLength_;
161+ CopyIn(0, currentNum);
162+ Compute(currentNum);
163+ CopyOut(0, currentNum);
164+ return;
165+ }
166+ 
167+ // 多循环场景:双缓冲流水调度
168+ // 第 0 轮:仅 CopyIn,启动输入搬运
169+ int64_t currentNum = ubLength_;
170+ CopyIn(0, currentNum);
171+ 
172+ for (int64_t i = 0; i < loopCount; i++) {
173+ // 计算当前轮的元素数
174+ currentNum = (i == (loopCount - 1)) ? (blockLength_ - ubLength_ * i) : ubLength_;
175+ 
176+ // Compute + CopyOut:处理当前轮数据
177+ Compute(currentNum);
178+ CopyOut(i, currentNum);
179+ 
180+ // CopyIn:预取下一轮数据(与当前轮的 Compute/CopyOut 流水并行)
181+ if (i < loopCount - 1) {
182+ int64_t nextNum = ubLength_;
183+ CopyIn(i + 1, nextNum);
184+ }
185+ }
186+}
187+ 
188+} // namespace NsRelu6
189+ 
190+#endif // RELU6_H
@@ -0,0 +1,41 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+/**
12+ * NOTE: Portions of this code were AI-generated and have been
13+ * technically reviewed for functional accuracy and security
14+ */
15+ 
16+/*!
17+ * \file relu6_tiling_data.h
18+ * \brief Relu6 TilingData 结构体定义(arch35 = Ascend950)
19+ *
20+ * 迭代一:仅 float16 单 dtype 骨架
21+ * 迭代二:新增 dataType 字段,支持多 dtype 分发
22+ * 字段说明:
23+ * - totalNum: 输入张量展平后的总元素数量
24+ * - blockFactor: 每个 AI Core 处理的元素数量
25+ * - ubFactor: UB 单次循环处理的元素数量
26+ * - dataType: 数据类型标识(0=float16, 1=float, 2=int32, 3=bfloat16)
27+ */
28+ 
29+#ifndef _RELU6_TILING_DATA_H_
30+#define _RELU6_TILING_DATA_H_
31+ 
32+#include <cstdint>
33+ 
34+struct Relu6TilingData {
35+ int64_t totalNum = 0; // 总元素数量
36+ int64_t blockFactor = 0; // 每个核处理的元素数量
37+ int64_t ubFactor = 0; // 每次 UB 循环处理的元素数量
38+ int32_t dataType = 0; // 数据类型:0=float16, 1=float, 2=int32, 3=bfloat16
39+};
40+ 
41+#endif // _RELU6_TILING_DATA_H_
@@ -0,0 +1,51 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+/**
12+ * NOTE: Portions of this code were AI-generated and have been
13+ * technically reviewed for functional accuracy and security
14+ */
15+ 
16+/*!
17+ * \file relu6_tiling_key.h
18+ * \brief Relu6 TilingKey 模板参数定义(arch35 = Ascend950)
19+ *
20+ * 迭代一:仅 float16 单 dtype 骨架,预留 float/int32/bfloat16 扩展位置
21+ * 模板参数类型参考:
22+ * - DATATYPE: 原生数据类型(C_DT_FLOAT16, C_DT_FLOAT, C_DT_INT32, C_DT_BF16)
23+ * 参考:ascendc/host_api/tiling/template_argument.h
24+ */
25+ 
26+#ifndef __RELU6_TILING_KEY_H__
27+#define __RELU6_TILING_KEY_H__
28+ 
29+#include "ascendc/host_api/tiling/template_argument.h"
30+ 
31+// 迭代一:仅 float16;迭代二扩展全部 4 种 dtype
32+ASCENDC_TPL_ARGS_DECL(Relu6,
33+ ASCENDC_TPL_DATATYPE_DECL(D_T, C_DT_FLOAT16, C_DT_FLOAT, C_DT_INT32, C_DT_BF16, ASCENDC_TPL_INPUT(0))
34+);
35+ 
36+ASCENDC_TPL_SEL(
37+ ASCENDC_TPL_ARGS_SEL(
38+ ASCENDC_TPL_DATATYPE_SEL(D_T, C_DT_FLOAT16)
39+ ),
40+ ASCENDC_TPL_ARGS_SEL(
41+ ASCENDC_TPL_DATATYPE_SEL(D_T, C_DT_FLOAT)
42+ ),
43+ ASCENDC_TPL_ARGS_SEL(
44+ ASCENDC_TPL_DATATYPE_SEL(D_T, C_DT_INT32)
45+ ),
46+ ASCENDC_TPL_ARGS_SEL(
47+ ASCENDC_TPL_DATATYPE_SEL(D_T, C_DT_BF16)
48+ ),
49+);
50+ 
51+#endif // __RELU6_TILING_KEY_H__