已合并
test: 新增AddLayerNormQuant算子aclnn调用测试示例 #7978
test: 新增AddLayerNormQuant算子aclnn调用测试示例 #7978
已合并
rk创建于 7月27日
共 1 个文件变更+328-0
@@ -0,0 +1,328 @@
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+ * \file test_aclnn_add_layer_norm_quant.cpp
13+ * \brief
14+ */
15+ 
16+#include <iostream>
17+#include <vector>
18+#include <cstring>
19+#include "acl/acl.h"
20+#include "aclnnop/aclnn_add_layer_norm_quant.h"
21+ 
22+#define CHECK_RET(cond, return_expr) \
23+ do { \
24+ if (!(cond)) { \
25+ return_expr; \
26+ } \
27+ } while (0)
28+ 
29+#define LOG_PRINT(message, ...) \
30+ do { \
31+ printf(message, ##__VA_ARGS__); \
32+ } while (0)
33+ 
34+int64_t GetShapeSize(const std::vector<int64_t>& shape)
35+{
36+ int64_t shapeSize = 1;
37+ for (auto i : shape) {
38+ shapeSize *= i;
39+ }
40+ return shapeSize;
41+}
42+ 
43+int Init(int32_t deviceId, aclrtStream* stream)
44+{
45+ // 固定写法,资源初始化
46+ auto ret = aclInit(nullptr);
47+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
48+ ret = aclrtSetDevice(deviceId);
49+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevicefailed. ERROR: %d\n", ret); return ret);
50+ ret = aclrtCreateStream(stream);
51+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
52+ return 0;
53+}
54+ 
55+bool CheckHardwareSupport()
56+{
57+ const char* socName = aclrtGetSocName();
58+ if (socName == nullptr) {
59+ LOG_PRINT("Warning: Cannot get SOC name, skip hardware check\n");
60+ return true;
61+ }
62+ 
63+ LOG_PRINT("Current SOC: %s\n", socName);
64+ 
65+ if (strstr(socName, "Ascend950") != nullptr || strstr(socName, "ascend950") != nullptr) {
66+ return true;
67+ }
68+ 
69+ LOG_PRINT("Warning: This operator only supports Ascend950, current SOC '%s' is not supported. Skip test.\n",
70+ socName);
71+ return false;
72+}
73+ 
74+void Finalize(int32_t deviceId, aclrtStream stream)
75+{
76+ (void)aclrtDestroyStream(stream);
77+ (void)aclrtResetDevice(deviceId);
78+ (void)aclFinalize();
79+}
80+ 
81+template <typename T>
82+int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr,
83+ aclDataType dataType, aclTensor** tensor)
84+{
85+ auto size = GetShapeSize(shape) * sizeof(T);
86+ // 调用aclrtMalloc申请device侧内存
87+ auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
88+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
89+ // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上
90+ ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
91+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);
atomgit-bot
atomgit-botatomgit-bot7月27日

🟡 Medium Priority

在 CreateAclTensor 函数中,第60行通过 aclrtMalloc 在 device 侧申请内存,第63行调用 aclrtMemcpy 拷贝数据。若 aclrtMemcpy 失败,第64行的 CHECK_RET 宏直接 return ret,此前在第60行申请的 device 内存(*deviceAddr)未被释放,造成 device 侧内存泄漏。

触发条件:aclrtMemcpy 调用失败(如 device 内存访问异常)。

建议:在 CHECK_RET 触发返回前先释放 *deviceAddr,例如将 CHECK_RET 改为:if (ret != ACL_SUCCESS) { aclrtFree(*deviceAddr); LOG_PRINT(...); return ret; }

likedislike
不准确?
rk
7月28日 评论:
92+ 
93+ // 计算连续tensor的strides
94+ std::vector<int64_t> strides(shape.size(), 1);
95+ for (int64_t i = shape.size() - 2; i >= 0; i--) {
96+ strides[i] = shape[i + 1] * strides[i + 1];
97+ }
98+ 
99+ // 调用aclCreateTensor接口创建aclTensor
100+ *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND,
101+ shape.data(), shape.size(), *deviceAddr);
102+ CHECK_RET(*tensor != nullptr, LOG_PRINT("aclCreateTensor failed.\n"); aclrtFree(*deviceAddr); return -1);
103+ return 0;
atomgit-bot
atomgit-botatomgit-bot7月27日

🟠 High Priority

在 CreateAclTensor 函数(第73-75行)中,aclCreateTensor 的返回值直接赋给 *tensor,未做空指针检查,且函数无条件 return 0(成功)。

受影响的行为/契约:若 aclCreateTensor 因参数非法或资源不足等原因返回 nullptr,*tensor 被置为空。调用方(如第150行 CHECK_RET(ret == ACL_SUCCESS, return ret))检测到 ret == 0 后继续执行,将空 aclTensor* 传递给第184行的 aclnnAddLayerNormQuantGetWorkspaceSize,该接口内对输入 tensor 的解引用将导致空指针解引用崩溃。

触发条件:aclCreateTensor 失败(通常发生在 device 内存不足或 shape/dataType 参数组合不合法时)。

建议:在 aclCreateTensor 调用后增加空指针检查,若返回 nullptr 则先释放 deviceAddr 指向的内存,再返回错误码。例如:if (*tensor == nullptr) { aclrtFree(*deviceAddr); return -1; }

likedislike
不准确?
rk
7月28日 评论:
104+}
105+ 
106+int main()
107+{
108+ // 1. (固定写法)device/stream初始化,参考acl API手册
109+ // 根据自己的实际device填写deviceId
110+ int32_t deviceId = 0;
111+ aclrtStream stream;
112+ auto ret = Init(deviceId, &stream);
113+ // check根据自己的需要处理
114+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
115+ 
116+ if (!CheckHardwareSupport()) {
117+ LOG_PRINT("\n=== Test SKIPPED (hardware not supported) ===\n");
118+ Finalize(deviceId, stream);
119+ return 0;
120+ }
121+ 
122+ // 2. 构造输入与输出,需要根据API的接口自定义构造
123+ float eps = 1e-6;
124+ bool additionalOut = true;
125+ bool divMode = true;
126+ const char* quantMode = "static";
127+ 
128+ std::vector<int64_t> xShape = {8, 16};
129+ std::vector<int64_t> gammaShape = {16};
130+ 
131+ std::vector<int64_t> reduceShape = {8};
132+ 
133+ void* x1DeviceAddr = nullptr;
134+ void* x2DeviceAddr = nullptr;
135+ void* betaDeviceAddr = nullptr;
136+ void* gammaDeviceAddr = nullptr;
137+ void* biasDeviceAddr = nullptr;
138+ void* s1DeviceAddr = nullptr;
139+ void* s2DeviceAddr = nullptr;
140+ 
141+ // 输出 Device地址
142+ void* y1DeviceAddr = nullptr;
143+ void* y2DeviceAddr = nullptr;
144+ void* xDeviceAddr = nullptr;
145+ void* outScales1DeviceAddr = nullptr;
146+ void* outScales2DeviceAddr = nullptr;
147+ 
148+ aclTensor* x1 = nullptr;
149+ aclTensor* x2 = nullptr;
150+ aclTensor* beta = nullptr;
151+ aclTensor* gamma = nullptr;
152+ aclTensor* bias = nullptr;
153+ aclTensor* s1 = nullptr;
154+ aclTensor* s2 = nullptr;
155+ 
156+ // aclTensor
157+ aclTensor* y1 = nullptr;
158+ aclTensor* y2 = nullptr;
159+ aclTensor* x = nullptr;
160+ aclTensor* outScales1 = nullptr;
161+ aclTensor* outScales2 = nullptr;
162+ 
163+ int64_t xShapeSize = GetShapeSize(xShape);
164+ int64_t gammaShapeSize = GetShapeSize(gammaShape);
165+ int64_t reduceShapeSize = GetShapeSize(reduceShape);
166+ 
167+ std::vector<float> x1HostData(xShapeSize, 0x3A);
168+ std::vector<float> x2HostData(xShapeSize, 0x3A);
169+ std::vector<float> gammaHostData(gammaShapeSize, 0x3A);
170+ std::vector<float> betaHostData(gammaShapeSize, 0x3A);
171+ std::vector<float> biasHostData(gammaShapeSize, 0x3A);
172+ 
173+ std::vector<float> s1HostData(gammaShapeSize, 0x3A);
174+ std::vector<float> s2HostData(gammaShapeSize, 0x3A);
175+ 
176+ // HostData
177+ std::vector<int8_t> y1HostData(xShapeSize, 0);
178+ std::vector<int8_t> y2HostData(xShapeSize, 0);
179+ std::vector<float> xHostData(xShapeSize, 0);
180+ std::vector<float> outScales1HostData(reduceShapeSize, 0);
181+ std::vector<float> outScales2HostData(reduceShapeSize, 0);
182+ 
183+ // 创建self aclTensor
184+ ret = CreateAclTensor(x1HostData, xShape, &x1DeviceAddr, aclDataType::ACL_FLOAT, &x1);
185+ CHECK_RET(ret == ACL_SUCCESS, return ret);
186+ ret = CreateAclTensor(x2HostData, xShape, &x2DeviceAddr, aclDataType::ACL_FLOAT, &x2);
187+ CHECK_RET(ret == ACL_SUCCESS, return ret);
188+ ret = CreateAclTensor(gammaHostData, gammaShape, &gammaDeviceAddr, aclDataType::ACL_FLOAT, &gamma);
189+ CHECK_RET(ret == ACL_SUCCESS, return ret);
190+ ret = CreateAclTensor(betaHostData, gammaShape, &betaDeviceAddr, aclDataType::ACL_FLOAT, &beta);
191+ CHECK_RET(ret == ACL_SUCCESS, return ret);
192+ ret = CreateAclTensor(biasHostData, gammaShape, &biasDeviceAddr, aclDataType::ACL_FLOAT, &bias);
193+ CHECK_RET(ret == ACL_SUCCESS, return ret);
194+ ret = CreateAclTensor(s1HostData, gammaShape, &s1DeviceAddr, aclDataType::ACL_FLOAT, &s1);
195+ CHECK_RET(ret == ACL_SUCCESS, return ret);
196+ ret = CreateAclTensor(s2HostData, gammaShape, &s2DeviceAddr, aclDataType::ACL_FLOAT, &s2);
197+ CHECK_RET(ret == ACL_SUCCESS, return ret);
198+ 
199+ // 创建不带 bias 的 aclTensor
200+ ret = CreateAclTensor(y1HostData, xShape, &y1DeviceAddr, aclDataType::ACL_INT8, &y1);
201+ CHECK_RET(ret == ACL_SUCCESS, return ret);
202+ ret = CreateAclTensor(y2HostData, xShape, &y2DeviceAddr, aclDataType::ACL_INT8, &y2);
203+ CHECK_RET(ret == ACL_SUCCESS, return ret);
204+ ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT, &x);
205+ CHECK_RET(ret == ACL_SUCCESS, return ret);
206+ ret = CreateAclTensor(outScales1HostData, reduceShape, &outScales1DeviceAddr, aclDataType::ACL_FLOAT, &outScales1);
207+ CHECK_RET(ret == ACL_SUCCESS, return ret);
208+ ret = CreateAclTensor(outScales2HostData, reduceShape, &outScales2DeviceAddr, aclDataType::ACL_FLOAT, &outScales2);
209+ CHECK_RET(ret == ACL_SUCCESS, return ret);
210+ 
211+ // aclnnAddLayerNormQuant接口调用示例
212+ // 3. 调用CANN算子库API,需要修改为具体的Api名称
213+ 
214+ // 3.1 示例
215+ // 调用aclnnAddLayerNormQuant第一段接口
216+ uint64_t workspaceSize = 0;
217+ aclOpExecutor* executor;
218+ ret = aclnnAddLayerNormQuantGetWorkspaceSize(x1, x2, gamma, beta, bias, s1, s2, nullptr, nullptr, quantMode, eps,
219+ additionalOut, divMode, y1, y2, x, outScales1, outScales2,
220+ &workspaceSize, &executor);
221+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAddLayerNormQuantGetWorkspaceSize failed. ERROR: %d\n", ret);
222+ return ret);
223+ 
224+ // 根据第一段接口计算出的workspaceSize申请device内存
225+ void* workspaceAddr = nullptr;
226+ if (workspaceSize > 0) {
227+ ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
228+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret;);
229+ }
230+ // 调用aclnnAddLayerNormQuant第二段接口
231+ ret = aclnnAddLayerNormQuant(workspaceAddr, workspaceSize, executor, stream);
232+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAddLayerNormQuant failed. ERROR: %d\n", ret); return ret);
atomgit-bot
atomgit-botatomgit-bot7月27日

🟡 Medium Priority

在 main 函数中,第150-198行通过 CreateAclTensor 逐步创建 tensor 和申请 device 内存。若任一 CHECK_RET 触发 return ret 提前退出(如第151行、第167行等),则此前已成功创建的 aclTensor*(如 x1、x2、gamma 等)和已申请的 device 内存(x1DeviceAddr 等)、以及 Init 中创建的 stream 均不会被释放。此外,第194行 workspace 分配失败时也同样泄露之前的资源。

虽然作为测试程序进程退出后操作系统会回收 host 内存,但 NPU device 侧内存通常不会随进程退出自动回收,导致 device 内存泄漏,影响后续测试或同一设备上其他进程的可用内存。

触发条件:任意一个 CreateAclTensor 调用或 workspace 分配失败。

建议:采用 RAII 封装(如自定义 Deleter 配合 unique_ptr)管理 device 内存和 aclTensor,或使用 goto 跳转到统一的清理标签。由于涉及整个函数结构的重构,不提供精确替换代码。

likedislike
不准确?
rk
7月28日 评论:
233+ 
234+ // 4. (固定写法)同步等待任务执行结束
235+ ret = aclrtSynchronizeStream(stream);
236+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
237+ 
238+ // 5. 获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改
239+ 
240+ auto y1Size = GetShapeSize(xShape);
241+ std::vector<int8_t> resultDataY1(y1Size, 0);
242+ ret = aclrtMemcpy(resultDataY1.data(), resultDataY1.size() * sizeof(resultDataY1[0]), y1DeviceAddr,
243+ y1Size * sizeof(resultDataY1[0]), ACL_MEMCPY_DEVICE_TO_HOST);
244+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from Deviceto host failed. ERROR: %d\n", ret); return ret);
245+ LOG_PRINT("==== AddLayerNormQuant y1 output\n");
246+ for (int64_t i = 0; i < y1Size; i++) {
247+ LOG_PRINT("result[%ld] is: %d\n", i, resultDataY1[i]);
248+ }
249+ 
250+ auto y2Size = GetShapeSize(xShape);
251+ std::vector<int8_t> resultDataY2(y2Size, 0);
252+ ret = aclrtMemcpy(resultDataY2.data(), resultDataY2.size() * sizeof(resultDataY2[0]), y2DeviceAddr,
253+ y2Size * sizeof(resultDataY2[0]), ACL_MEMCPY_DEVICE_TO_HOST);
254+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from Deviceto host failed. ERROR: %d\n", ret); return ret);
255+ LOG_PRINT("==== AddLayerNormQuant y2 output\n");
256+ for (int64_t i = 0; i < y2Size; i++) {
257+ LOG_PRINT("result[%ld] is: %d\n", i, resultDataY2[i]);
258+ }
259+ 
260+ auto xSize = GetShapeSize(xShape);
261+ std::vector<float> resultDataX(xSize, 0);
262+ ret = aclrtMemcpy(resultDataX.data(), resultDataX.size() * sizeof(resultDataX[0]), xDeviceAddr,
263+ xSize * sizeof(resultDataX[0]), ACL_MEMCPY_DEVICE_TO_HOST);
264+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from Deviceto host failed. ERROR: %d\n", ret); return ret);
265+ LOG_PRINT("==== AddLayerNormQuant x output\n");
266+ for (int64_t i = 0; i < xSize; i++) {
267+ LOG_PRINT("result[%ld] is: %f\n", i, resultDataX[i]);
268+ }
269+ 
270+ auto outScale1Size = GetShapeSize(reduceShape);
271+ std::vector<float> resultDataOutScale1(outScale1Size, 0);
272+ ret = aclrtMemcpy(resultDataOutScale1.data(), resultDataOutScale1.size() * sizeof(resultDataOutScale1[0]),
273+ outScales1DeviceAddr, outScale1Size * sizeof(resultDataOutScale1[0]), ACL_MEMCPY_DEVICE_TO_HOST);
274+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from Deviceto host failed. ERROR: %d\n", ret); return ret);
275+ LOG_PRINT("==== AddLayerNormQuant outScale1 output\n");
276+ for (int64_t i = 0; i < outScale1Size; i++) {
277+ LOG_PRINT("result[%ld] is: %f\n", i, resultDataOutScale1[i]);
278+ }
279+ 
280+ auto outScale2Size = GetShapeSize(reduceShape);
281+ std::vector<float> resultDataOutScale2(outScale2Size, 0);
282+ ret = aclrtMemcpy(resultDataOutScale2.data(), resultDataOutScale2.size() * sizeof(resultDataOutScale2[0]),
283+ outScales2DeviceAddr, outScale2Size * sizeof(resultDataOutScale2[0]), ACL_MEMCPY_DEVICE_TO_HOST);
284+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from Deviceto host failed. ERROR: %d\n", ret); return ret);
285+ LOG_PRINT("==== AddLayerNormQuant outScale2 output\n");
286+ for (int64_t i = 0; i < outScale2Size; i++) {
287+ LOG_PRINT("result[%ld] is: %f\n", i, resultDataOutScale2[i]);
288+ }
289+ 
290+ // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改
291+ aclDestroyTensor(x1);
292+ aclDestroyTensor(x2);
293+ aclDestroyTensor(beta);
294+ aclDestroyTensor(gamma);
295+ aclDestroyTensor(bias);
296+ aclDestroyTensor(s1);
297+ aclDestroyTensor(s2);
298+ 
299+ aclDestroyTensor(y1);
300+ aclDestroyTensor(y2);
301+ aclDestroyTensor(x);
302+ aclDestroyTensor(outScales1);
303+ aclDestroyTensor(outScales2);
304+ 
305+ // 7. 释放device资源,需要根据具体API的接口定义修改
306+ aclrtFree(x1DeviceAddr);
307+ aclrtFree(x2DeviceAddr);
308+ aclrtFree(gammaDeviceAddr);
309+ aclrtFree(betaDeviceAddr);
310+ aclrtFree(biasDeviceAddr);
311+ aclrtFree(s1DeviceAddr);
312+ aclrtFree(s2DeviceAddr);
313+ 
314+ aclrtFree(y1DeviceAddr);
315+ aclrtFree(y2DeviceAddr);
316+ aclrtFree(xDeviceAddr);
317+ aclrtFree(outScales1DeviceAddr);
318+ aclrtFree(outScales2DeviceAddr);
319+ 
320+ if (workspaceSize > 0) {
321+ aclrtFree(workspaceAddr);
322+ }
323+ 
324+ aclrtDestroyStream(stream);
325+ aclrtResetDevice(deviceId);
326+ aclFinalize();
327+ return 0;
328+}