已合并
feat(arch35): 新增 geam 算子,包含 aclblasSgeam 和 aclblasCgeam 接口 #328
iuyi创建于 10 天前
feat(arch35): 新增 geam 算子,包含 aclblasSgeam 和 aclblasCgeam 接口 #328
已合并
iuyi创建于 10 天前
26 个文件变更+3377-3
Magent/init.sh+0-0
The file is empty
Ablas/geam/README.md+532-0
@@ -0,0 +1,532 @@
1+# Geam算子
2+ 
3+## 算子概述
4+ 
5+Geam(General Matrix Add)算子执行带标量缩放的矩阵加法运算,支持对输入矩阵 A、B 分别施加可选的转置(Transpose)或共轭转置(ConjTrans)操作。数学定义为:
6+ 
7+```txt
8+C[i, j] = alpha * op(A)[i, j] + beta * op(B)[i, j], i ∈ [0, m), j ∈ [0, n)
9+```
10+ 
11+其中 `op(X)` 根据转置参数取 `X`(NoTrans)、`X^T`(Trans)或 `X^H`(ConjTrans,仅复数有意义,浮点数等价于 Trans)。矩阵采用列主序(Column-Major)存储,`X[i, j]` 的物理地址为 `X[i + j * ldX]`
12+ 
13+本算子提供两个接口,覆盖实数与复数两类矩阵加法场景:
14+ 
15+| 接口名 | 功能简述 |
16+|--------|----------|
17+| aclblasSgeam | 单精度浮点 GEAM(General Matrix Add) |
18+| aclblasCgeam | 单精度复数 GEAM(General Matrix Add) |
19+ 
20+## 算子执行接口
21+ 
22+### aclblasSgeam
23+ 
24+#### 产品支持情况
25+ 
26+- Ascend 950PR / Ascend 950DT:支持
27+- Atlas A3 训练系列产品 / Atlas A3 推理系列产品:不支持
28+- Atlas A2 训练系列产品 / Atlas A2 推理系列产品:不支持
29+ 
30+#### 函数原型
31+ 
32+```cpp
33+aclblasStatus_t aclblasSgeam(aclblasHandle_t handle, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n, const float* alpha, const float* A, int lda, const float* beta, const float* B, int ldb, float* C, int ldc)
34+```
35+ 
36+#### 参数说明
37+ 
38+| 参数名 | 输入/输出 | 参数类型 | 说明 |
39+|--------|----------|---------|------|
40+| handle | 输入 | aclblasHandle_t | ops-blas 库上下文句柄,携带 stream,Host 内存 |
41+| transa | 输入 | aclblasOperation_t | 矩阵 A 的转置操作:ACLBLAS_OP_N(不转置)、ACLBLAS_OP_T(转置)、ACLBLAS_OP_C(共轭转置,FP32 实数等价于转置),Host 内存 |
42+| transb | 输入 | aclblasOperation_t | 矩阵 B 的转置操作(同 transa),Host 内存 |
43+| m | 输入 | int | 输出矩阵 C(及 op(A))的行数,m >= 0,Host 内存 |
44+| n | 输入 | int | 输出矩阵 C(及 op(B))的列数,n >= 0,Host 内存 |
45+| alpha | 输入 | const float*(FP32) | A 的缩放因子指针,指向 Host 侧单个标量;不可为 nullptr,Host 内存 |
46+| A | 输入 | const float*(FP32) | 输入矩阵 A,列主序存储;当 *alpha != 0 时不可为 nullptr,Device 内存 |
47+| lda | 输入 | int | 矩阵 A 的主维度,transa=N 时 lda >= max(1, m),transa=T/C 时 lda >= max(1, n),Host 内存 |
48+| beta | 输入 | const float*(FP32) | B 的缩放因子指针,指向 Host 侧单个标量;不可为 nullptr,Host 内存 |
49+| B | 输入 | const float*(FP32) | 输入矩阵 B,列主序存储;当 *beta != 0 时不可为 nullptr,Device 内存 |
50+| ldb | 输入 | int | 矩阵 B 的主维度,transb=N 时 ldb >= max(1, m),transb=T/C 时 ldb >= max(1, n),Host 内存 |
51+| C | 输出 | float*(FP32) | 输出矩阵 C,列主序存储;m > 0 且 n > 0 时不可为 nullptr,Device 内存 |
52+| ldc | 输入 | int | 矩阵 C 的主维度,ldc >= max(1, m),Host 内存 |
53+ 
54+#### 约束说明
55+ 
56+- handle 不能为 nullptr,否则返回 ACLBLAS_STATUS_HANDLE_IS_NULLPTR
57+- transa / transb 必须为 ACLBLAS_OP_N、ACLBLAS_OP_T 或 ACLBLAS_OP_C,否则返回 ACLBLAS_STATUS_INVALID_ENUM
58+- m >= 0,n >= 0,否则返回 ACLBLAS_STATUS_INVALID_VALUE
59+- transa = N 时 lda >= max(1, m);transa = T/C 时 lda >= max(1, n)
60+- transb = N 时 ldb >= max(1, m);transb = T/C 时 ldb >= max(1, n)
61+- ldc >= max(1, m)
62+- alpha 不允许为 nullptr,否则返回 ACLBLAS_STATUS_INVALID_VALUE
63+- beta 不允许为 nullptr,否则返回 ACLBLAS_STATUS_INVALID_VALUE
64+- *alpha == 0 时,A 不被引用,可为 nullptr
65+- *beta == 0 时,B 不被引用,可为 nullptr
66+- *alpha != 0 时,A 不能为 nullptr
67+- *beta != 0 时,B 不能为 nullptr
68+- m > 0 且 n > 0 时,C 不能为 nullptr
69+- 支持 in-place:C == A 时要求 transa == N 且 lda == ldc;C == B 时要求 transb == N 且 ldb == ldc
70+- m == 0 或 n == 0 时直接返回 ACLBLAS_STATUS_SUCCESS,不执行计算
71+ 
72+#### 调用示例
73+ 
74+示例代码如下,仅供参考,具体编译和执行过程请参考[编译与运行样例](https://www.hiascend.com/document/detail/zh/CANN/community/8.2.RC1/quickstart/quickstart_18_0041.html)。
75+ 
76+以下示例演示 NN 模式(NoTrans/NoTrans),取 m=4, n=3, alpha=1.0, beta=0.5,计算 C(4x3) = 1.0 * A(4x3) + 0.5 * B(4x3):
77+ 
78+```cpp
79+#include <cstdio>
80+#include <memory>
81+#include <vector>
82+ 
83+#include "acl/acl.h"
84+#include "cann_ops_blas.h"
85+ 
86+#define CHECK_RET(cond, return_expr) \
87+ do { \
88+ if (!(cond)) { \
89+ return_expr; \
90+ } \
91+ } while (0)
92+ 
93+#define LOG_PRINT(message, ...) \
94+ do { \
95+ printf(message, ##__VA_ARGS__); \
96+ } while (0)
97+ 
98+class AclContext {
99+public:
100+ explicit AclContext(int32_t deviceId) : deviceId_(deviceId) {}
101+ 
102+ ~AclContext()
103+ {
104+ if (stream_ != nullptr) {
105+ aclrtDestroyStream(stream_);
106+ stream_ = nullptr;
107+ }
108+ if (deviceSet_) {
109+ aclrtResetDevice(deviceId_);
110+ deviceSet_ = false;
111+ }
112+ if (aclInited_) {
113+ aclFinalize();
114+ aclInited_ = false;
115+ }
116+ }
117+ 
118+ int Init()
119+ {
120+ auto ret = aclInit(nullptr);
121+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
122+ aclInited_ = true;
123+ 
124+ ret = aclrtSetDevice(deviceId_);
125+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
126+ deviceSet_ = true;
127+ 
128+ ret = aclrtCreateStream(&stream_);
129+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
130+ return ACL_SUCCESS;
131+ }
132+ 
133+ aclrtStream Stream() const { return stream_; }
134+ 
135+private:
136+ int32_t deviceId_;
137+ aclrtStream stream_ = nullptr;
138+ bool aclInited_ = false;
139+ bool deviceSet_ = false;
140+};
141+ 
142+struct AclMemDeleter {
143+ void operator()(void* p) const { aclrtFree(p); }
144+};
145+ 
146+int aclblasSgeamTest(AclContext& ctx)
147+{
148+ aclrtStream stream = ctx.Stream();
149+ 
150+ // 1. 创建 ops-blas 句柄
151+ aclblasHandle_t rawHandle = nullptr;
152+ auto blasRet = aclblasCreate(&rawHandle);
153+ CHECK_RET(blasRet == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasCreate failed. ERROR: %d\n", blasRet);
154+ return blasRet);
155+ std::unique_ptr<void, aclblasStatus_t (*)(void*)> handlePtr(rawHandle, aclblasDestroy);
156+ 
157+ blasRet = aclblasSetStream(static_cast<aclblasHandle_t>(handlePtr.get()), stream);
158+ CHECK_RET(blasRet == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasSetStream failed. ERROR: %d\n", blasRet);
159+ return blasRet);
160+ 
161+ // 2. 准备 Host 数据
162+ // C(4x3) = 1.0 * A(4x3) + 0.5 * B(4x3),NN 模式
163+ int m = 4, n = 3;
164+ int lda = m, ldb = m, ldc = m;
165+ float alpha = 1.0f;
166+ float beta = 0.5f;
167+ 
168+ // A (m=4, n=3, column-major): 列0=[1,2,3,4], 列1=[5,6,7,8], 列2=[9,10,11,12]
169+ std::vector<float> hA = {1.0f, 2.0f, 3.0f, 4.0f,
170+ 5.0f, 6.0f, 7.0f, 8.0f,
171+ 9.0f, 10.0f, 11.0f, 12.0f};
172+ // B (m=4, n=3, column-major): 列0=[2,4,6,8], 列1=[10,12,14,16], 列2=[18,20,22,24]
173+ std::vector<float> hB = {2.0f, 4.0f, 6.0f, 8.0f,
174+ 10.0f, 12.0f, 14.0f, 16.0f,
175+ 18.0f, 20.0f, 22.0f, 24.0f};
176+ 
177+ size_t aBytes = hA.size() * sizeof(float);
178+ size_t bBytes = hB.size() * sizeof(float);
179+ size_t cBytes = static_cast<size_t>(ldc) * static_cast<size_t>(n) * sizeof(float);
180+ 
181+ // 3. 申请 Device 内存并拷贝数据
182+ void* rawA = nullptr;
183+ auto aclRet = aclrtMalloc(&rawA, aBytes, ACL_MEM_MALLOC_HUGE_FIRST);
184+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for A failed. ERROR: %d\n", aclRet); return aclRet);
185+ std::unique_ptr<float, AclMemDeleter> aDevicePtr(static_cast<float*>(rawA));
186+ 
187+ void* rawB = nullptr;
188+ aclRet = aclrtMalloc(&rawB, bBytes, ACL_MEM_MALLOC_HUGE_FIRST);
189+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for B failed. ERROR: %d\n", aclRet); return aclRet);
190+ std::unique_ptr<float, AclMemDeleter> bDevicePtr(static_cast<float*>(rawB));
191+ 
192+ void* rawC = nullptr;
193+ aclRet = aclrtMalloc(&rawC, cBytes, ACL_MEM_MALLOC_HUGE_FIRST);
194+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for C failed. ERROR: %d\n", aclRet); return aclRet);
195+ std::unique_ptr<float, AclMemDeleter> cDevicePtr(static_cast<float*>(rawC));
196+ 
197+ aclRet = aclrtMemcpy(aDevicePtr.get(), aBytes, hA.data(), aBytes, ACL_MEMCPY_HOST_TO_DEVICE);
198+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy for A failed. ERROR: %d\n", aclRet); return aclRet);
199+ 
200+ aclRet = aclrtMemcpy(bDevicePtr.get(), bBytes, hB.data(), bBytes, ACL_MEMCPY_HOST_TO_DEVICE);
201+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy for B failed. ERROR: %d\n", aclRet); return aclRet);
202+ 
203+ // 4. 调用 aclblasSgeam
204+ blasRet = aclblasSgeam(static_cast<aclblasHandle_t>(handlePtr.get()),
205+ ACLBLAS_OP_N, ACLBLAS_OP_N, m, n,
206+ &alpha, aDevicePtr.get(), lda,
207+ &beta, bDevicePtr.get(), ldb,
208+ static_cast<float*>(cDevicePtr.get()), ldc);
209+ CHECK_RET(blasRet == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasSgeam failed. ERROR: %d\n", blasRet);
210+ return blasRet);
211+ 
212+ // 5. 同步等待任务执行结束
213+ aclRet = aclrtSynchronizeStream(stream);
214+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", aclRet); return aclRet);
215+ 
216+ // 6. 将结果从 Device 拷贝回 Host 并打印
217+ // 预期 C = alpha*A + beta*B (列主序)
218+ // 列0=[1+1, 2+2, 3+3, 4+4]=[2,4,6,8]
219+ // 列1=[5+5, 6+6, 7+7, 8+8]=[10,12,14,16]
220+ // 列2=[9+9, 10+10, 11+11, 12+12]=[18,20,22,24]
221+ std::vector<float> hC(static_cast<size_t>(ldc) * static_cast<size_t>(n), 0.0f);
222+ aclRet = aclrtMemcpy(hC.data(), cBytes, cDevicePtr.get(), cBytes, ACL_MEMCPY_DEVICE_TO_HOST);
223+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", aclRet); return aclRet);
224+ 
225+ LOG_PRINT("result C (column-major):\n");
226+ for (int col = 0; col < n; col++) {
227+ for (int row = 0; row < m; row++) {
228+ LOG_PRINT(" C[%d][%d] = %f\n", row, col, hC[static_cast<size_t>(col) * ldc + row]);
229+ }
230+ }
231+ 
232+ return ACL_SUCCESS;
233+}
234+ 
235+int main()
236+{
237+ AclContext ctx(0);
238+ auto ret = ctx.Init();
239+ CHECK_RET(ret == ACL_SUCCESS, return ret);
240+ 
241+ ret = aclblasSgeamTest(ctx);
242+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclblasSgeamTest failed. ERROR: %d\n", ret); return ret);
243+ return 0;
244+}
245+```
246+ 
247+预期输出:
248+ 
249+```
250+result C (column-major):
251+ C[0][0] = 2.000000
252+ C[1][0] = 4.000000
253+ C[2][0] = 6.000000
254+ C[3][0] = 8.000000
255+ C[0][1] = 10.000000
256+ C[1][1] = 12.000000
257+ C[2][1] = 14.000000
258+ C[3][1] = 16.000000
259+ C[0][2] = 18.000000
260+ C[1][2] = 20.000000
261+ C[2][2] = 22.000000
262+ C[3][2] = 24.000000
263+```
264+ 
265+### aclblasCgeam
266+ 
267+#### 产品支持情况
268+ 
269+- Ascend 950PR / Ascend 950DT:支持
270+- Atlas A3 训练系列产品 / Atlas A3 推理系列产品:不支持
271+- Atlas A2 训练系列产品 / Atlas A2 推理系列产品:不支持
272+ 
273+#### 函数原型
274+ 
275+```cpp
276+aclblasStatus_t aclblasCgeam(aclblasHandle_t handle, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n, const aclblasComplex* alpha, const aclblasComplex* A, int lda, const aclblasComplex* beta, const aclblasComplex* B, int ldb, aclblasComplex* C, int ldc)
277+```
278+ 
279+#### 参数说明
280+ 
281+| 参数名 | 输入/输出 | 参数类型 | 说明 |
282+|--------|----------|---------|------|
283+| handle | 输入 | aclblasHandle_t | ops-blas 库上下文句柄,携带 stream,Host 内存 |
284+| transa | 输入 | aclblasOperation_t | 矩阵 A 的转置操作:ACLBLAS_OP_N(不转置)、ACLBLAS_OP_T(转置)、ACLBLAS_OP_C(共轭转置),Host 内存 |
285+| transb | 输入 | aclblasOperation_t | 矩阵 B 的转置操作(同 transa),Host 内存 |
286+| m | 输入 | int | 输出矩阵 C(及 op(A))的行数,m >= 0,Host 内存 |
287+| n | 输入 | int | 输出矩阵 C(及 op(B))的列数,n >= 0,Host 内存 |
288+| alpha | 输入 | const aclblasComplex* | A 的缩放因子指针,指向 Host 侧单个标量;不可为 nullptr,Host 内存 |
289+| A | 输入 | const aclblasComplex* | 输入矩阵 A,列主序存储;当 *alpha != 0 时不可为 nullptr,Device 内存 |
290+| lda | 输入 | int | 矩阵 A 的主维度,transa=N 时 lda >= max(1, m),transa=T/C 时 lda >= max(1, n),Host 内存 |
291+| beta | 输入 | const aclblasComplex* | B 的缩放因子指针,指向 Host 侧单个标量;不可为 nullptr,Host 内存 |
292+| B | 输入 | const aclblasComplex* | 输入矩阵 B,列主序存储;当 *beta != 0 时不可为 nullptr,Device 内存 |
293+| ldb | 输入 | int | 矩阵 B 的主维度,transb=N 时 ldb >= max(1, m),transb=T/C 时 ldb >= max(1, n),Host 内存 |
294+| C | 输出 | aclblasComplex* | 输出矩阵 C,列主序存储;m > 0 且 n > 0 时不可为 nullptr,Device 内存 |
295+| ldc | 输入 | int | 矩阵 C 的主维度,ldc >= max(1, m),Host 内存 |
296+ 
297+#### 约束说明
298+ 
299+- handle 不能为 nullptr,否则返回 ACLBLAS_STATUS_HANDLE_IS_NULLPTR
300+- transa / transb 必须为 ACLBLAS_OP_N、ACLBLAS_OP_T 或 ACLBLAS_OP_C,否则返回 ACLBLAS_STATUS_INVALID_ENUM
301+- m >= 0,n >= 0,否则返回 ACLBLAS_STATUS_INVALID_VALUE
302+- transa = N 时 lda >= max(1, m);transa = T/C 时 lda >= max(1, n)
303+- transb = N 时 ldb >= max(1, m);transb = T/C 时 ldb >= max(1, n)
304+- ldc >= max(1, m)
305+- alpha 不允许为 nullptr,否则返回 ACLBLAS_STATUS_INVALID_VALUE
306+- beta 不允许为 nullptr,否则返回 ACLBLAS_STATUS_INVALID_VALUE
307+- *alpha == 0 时,A 不被引用,可为 nullptr
308+- *beta == 0 时,B 不被引用,可为 nullptr
309+- *alpha != 0 时,A 不能为 nullptr
310+- *beta != 0 时,B 不能为 nullptr
311+- m > 0 且 n > 0 时,C 不能为 nullptr
312+- 支持 in-place:C == A 时要求 transa == N 且 lda == ldc;C == B 时要求 transb == N 且 ldb == ldc
313+- m == 0 或 n == 0 时直接返回 ACLBLAS_STATUS_SUCCESS,不执行计算
314+- ACLBLAS_OP_C 对复数执行共轭转置,实部不变,虚部取反
315+ 
316+#### 调用示例
317+ 
318+示例代码如下,仅供参考,具体编译和执行过程请参考[编译与运行样例](https://www.hiascend.com/document/detail/zh/CANN/community/8.2.RC1/quickstart/quickstart_18_0041.html)。
319+ 
320+以下示例演示 NC 模式(NoTrans/ConjTrans),取 m=2, n=3, alpha=(1+1i), beta=(0.5+0.5i),计算 C(2x3) = (1+i) * A(2x3) + (0.5+0.5i) * B^H(2x3):
321+ 
322+```cpp
323+#include <cstdio>
324+#include <memory>
325+#include <vector>
326+ 
327+#include "acl/acl.h"
328+#include "cann_ops_blas.h"
329+ 
330+#define CHECK_RET(cond, return_expr) \
331+ do { \
332+ if (!(cond)) { \
333+ return_expr; \
334+ } \
335+ } while (0)
336+ 
337+#define LOG_PRINT(message, ...) \
338+ do { \
339+ printf(message, ##__VA_ARGS__); \
340+ } while (0)
341+ 
342+class AclContext {
343+public:
344+ explicit AclContext(int32_t deviceId) : deviceId_(deviceId) {}
345+ 
346+ ~AclContext()
347+ {
348+ if (stream_ != nullptr) {
349+ aclrtDestroyStream(stream_);
350+ stream_ = nullptr;
351+ }
352+ if (deviceSet_) {
353+ aclrtResetDevice(deviceId_);
354+ deviceSet_ = false;
355+ }
356+ if (aclInited_) {
357+ aclFinalize();
358+ aclInited_ = false;
359+ }
360+ }
361+ 
362+ int Init()
363+ {
364+ auto ret = aclInit(nullptr);
365+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
366+ aclInited_ = true;
367+ 
368+ ret = aclrtSetDevice(deviceId_);
369+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
370+ deviceSet_ = true;
371+ 
372+ ret = aclrtCreateStream(&stream_);
373+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
374+ return ACL_SUCCESS;
375+ }
376+ 
377+ aclrtStream Stream() const { return stream_; }
378+ 
379+private:
380+ int32_t deviceId_;
381+ aclrtStream stream_ = nullptr;
382+ bool aclInited_ = false;
383+ bool deviceSet_ = false;
384+};
385+ 
386+struct AclMemDeleter {
387+ void operator()(void* p) const { aclrtFree(p); }
388+};
389+ 
390+int aclblasCgeamTest(AclContext& ctx)
391+{
392+ aclrtStream stream = ctx.Stream();
393+ 
394+ // 1. 创建 ops-blas 句柄
395+ aclblasHandle_t rawHandle = nullptr;
396+ auto blasRet = aclblasCreate(&rawHandle);
397+ CHECK_RET(blasRet == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasCreate failed. ERROR: %d\n", blasRet);
398+ return blasRet);
399+ std::unique_ptr<void, aclblasStatus_t (*)(void*)> handlePtr(rawHandle, aclblasDestroy);
400+ 
401+ blasRet = aclblasSetStream(static_cast<aclblasHandle_t>(handlePtr.get()), stream);
402+ CHECK_RET(blasRet == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasSetStream failed. ERROR: %d\n", blasRet);
403+ return blasRet);
404+ 
405+ // 2. 准备 Host 数据
406+ // C(2x3) = (1+i) * A(2x3) + (0.5+0.5i) * B^H(2x3),NC 模式
407+ int m = 2, n = 3;
408+ int lda = m, ldb = n, ldc = m; // transb=C 时 ldb >= max(1, n=3)
409+ aclblasComplex alpha = {1.0f, 1.0f}; // 1+i
410+ aclblasComplex beta = {0.5f, 0.5f}; // 0.5+0.5i
411+ 
412+ // A (m=2, n=3, column-major): 列0=[(1+i),(2+0i)], 列1=[(3+0i),(4+i)], 列2=[(0+2i),(1+1i)]
413+ std::vector<aclblasComplex> hA = {{{1.0f, 1.0f}, {2.0f, 0.0f},
414+ {3.0f, 0.0f}, {4.0f, 1.0f},
415+ {0.0f, 2.0f}, {1.0f, 1.0f}}};
416+ // B 原始 (n=3, m=2, column-major),转置前 B 形状为 (3,2)
417+ // 列0=[(1+0i),(0+1i),(1+1i)], 列1=[(2+0i),(0+0i),(1+0i)]
418+ // B^H 将 B 共轭转置为 (m=2, n=3)
419+ std::vector<aclblasComplex> hB = {{{1.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f},
420+ {2.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}}};
421+ 
422+ size_t aBytes = hA.size() * sizeof(aclblasComplex);
423+ size_t bBytes = hB.size() * sizeof(aclblasComplex);
424+ size_t cBytes = static_cast<size_t>(ldc) * static_cast<size_t>(n) * sizeof(aclblasComplex);
425+ 
426+ // 3. 申请 Device 内存并拷贝数据
427+ aclblasComplex* rawA = nullptr;
428+ auto aclRet = aclrtMalloc(reinterpret_cast<void**>(&rawA), aBytes, ACL_MEM_MALLOC_HUGE_FIRST);
429+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for A failed. ERROR: %d\n", aclRet); return aclRet);
430+ std::unique_ptr<aclblasComplex, AclMemDeleter> aDevicePtr(rawA);
431+ 
432+ aclblasComplex* rawB = nullptr;
433+ aclRet = aclrtMalloc(reinterpret_cast<void**>(&rawB), bBytes, ACL_MEM_MALLOC_HUGE_FIRST);
434+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for B failed. ERROR: %d\n", aclRet); return aclRet);
435+ std::unique_ptr<aclblasComplex, AclMemDeleter> bDevicePtr(rawB);
436+ 
437+ aclblasComplex* rawC = nullptr;
438+ aclRet = aclrtMalloc(reinterpret_cast<void**>(&rawC), cBytes, ACL_MEM_MALLOC_HUGE_FIRST);
439+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for C failed. ERROR: %d\n", aclRet); return aclRet);
440+ std::unique_ptr<aclblasComplex, AclMemDeleter> cDevicePtr(rawC);
441+ 
442+ aclRet = aclrtMemcpy(aDevicePtr.get(), aBytes, hA.data(), aBytes, ACL_MEMCPY_HOST_TO_DEVICE);
443+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy for A failed. ERROR: %d\n", aclRet); return aclRet);
444+ 
445+ aclRet = aclrtMemcpy(bDevicePtr.get(), bBytes, hB.data(), bBytes, ACL_MEMCPY_HOST_TO_DEVICE);
446+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy for B failed. ERROR: %d\n", aclRet); return aclRet);
447+ 
448+ // 4. 调用 aclblasCgeam
449+ blasRet = aclblasCgeam(static_cast<aclblasHandle_t>(handlePtr.get()),
450+ ACLBLAS_OP_N, ACLBLAS_OP_C, m, n,
451+ &alpha, aDevicePtr.get(), lda,
452+ &beta, bDevicePtr.get(), ldb,
453+ cDevicePtr.get(), ldc);
454+ CHECK_RET(blasRet == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasCgeam failed. ERROR: %d\n", blasRet);
455+ return blasRet);
456+ 
457+ // 5. 同步等待任务执行结束
458+ aclRet = aclrtSynchronizeStream(stream);
459+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", aclRet); return aclRet);
460+ 
461+ // 6. 将结果从 Device 拷贝回 Host 并打印
462+ std::vector<aclblasComplex> hC(static_cast<size_t>(ldc) * static_cast<size_t>(n), {0.0f, 0.0f});
463+ aclRet = aclrtMemcpy(hC.data(), cBytes, cDevicePtr.get(), cBytes, ACL_MEMCPY_DEVICE_TO_HOST);
464+ CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", aclRet); return aclRet);
465+ 
466+ LOG_PRINT("result C (column-major):\n");
467+ for (int col = 0; col < n; col++) {
468+ for (int row = 0; row < m; row++) {
469+ const auto& val = hC[static_cast<size_t>(col) * ldc + row];
470+ LOG_PRINT(" C[%d][%d] = %f + %fi\n", row, col, val.real, val.imag);
471+ }
472+ }
473+ 
474+ return ACL_SUCCESS;
475+}
476+ 
477+int main()
478+{
479+ AclContext ctx(0);
480+ auto ret = ctx.Init();
481+ CHECK_RET(ret == ACL_SUCCESS, return ret);
482+ 
483+ ret = aclblasCgeamTest(ctx);
484+ CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclblasCgeamTest failed. ERROR: %d\n", ret); return ret);
485+ return 0;
486+}
487+```
488+ 
489+预期输出:
490+ 
491+```
492+result C (column-major):
493+ C[0][0] = 0.500000 + 2.500000i
494+ C[1][0] = 2.500000 + 2.500000i
495+ C[0][1] = 3.500000 + 3.500000i
496+ C[1][1] = 4.500000 + 5.500000i
497+ C[0][2] = -1.500000 + 3.000000i
498+ C[1][2] = 1.000000 + 2.000000i
499+```
500+ 
501+## 支持的数据类型
502+ 
503+| 接口 | 数据类型 | 说明 |
504+|------|----------|------|
505+| `aclblasSgeam` | `float` (FP32) | 单精度实数 |
506+| `aclblasCgeam` | `aclblasComplex` | 单精度复数(实部 + 虚部各为 float) |
507+ 
508+## 返回值 / 错误码
509+ 
510+| 返回值 | 含义 |
511+|--------|------|
512+| `ACLBLAS_STATUS_SUCCESS` | 执行成功 |
513+| `ACLBLAS_STATUS_HANDLE_IS_NULLPTR` | handle 为空指针 |
514+| `ACLBLAS_STATUS_INVALID_VALUE` | 维度非法(m < 0、n < 0)、leading dimension 不满足约束、必需指针为空(alpha、beta、C、A/B 在对应标量非零时为 nullptr) |
515+| `ACLBLAS_STATUS_INVALID_ENUM` | transa / transb 取值不在 N/T/C 范围内 |
516+| `ACLBLAS_STATUS_INTERNAL_ERROR` | 内部错误(如获取核心数失败) |
517+ 
518+## 精度标准
519+ 
520+| 数据类型 | rtol | atol | required_matched_ratio | max_abs_error_limit |
521+|----------|------|------|----------------------|-------------------|
522+| `float` (FP32) | 2^-10 (≈ 9.77e-4) | 2^-16 (≈ 1.53e-5) | 0.99 | 1e-2 或 32×ULP |
523+| `aclblasComplex` | 实部 / 虚部分别按 float 标准校验 | 同上 | 同上 | 同上 |
524+ 
525+## 支持芯片
526+ 
527+| 芯片 | 架构 | 支持状态 |
528+|------|------|----------|
529+| Ascend 950PR | arch35 (DAV_3510) | 支持 |
530+| Ascend 950DT | arch35 (DAV_3510) | 支持 |
531+| Atlas A3 | — | 不支持 |
532+| Atlas A2 | — | 不支持 |
Ablas/geam/arch35/cgeam_host.cpp+165-0
@@ -0,0 +1,165 @@
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+ * See LICENSE in the root of the software repository for the full text of the License.
8+ */
9+ 
10+/**
11+ * @file cgeam_host.cpp
12+ * @brief Host-side implementation for Cgeam operator (complex)
13+ * Handles parameter validation, tiling calculation, and kernel launch
14+ */
15+ 
16+#include "cann_ops_blas.h"
17+#include "cgeam_kernel.h"
18+#include "geam_host_common.h"
19+#include "log/log.h"
20+#include "common/helper/aclblas_handle_internal.h"
21+#include "common/helper/host_utils.h"
22+#include "common/helper/kernel_constant.h"
23+#include <algorithm>
24+ 
25+namespace {
26+ 
27+constexpr uint32_t UB_SIZE = 248 * 1024; // 248 KB
28+constexpr uint32_t UB_RESERVE = 256;
29+constexpr uint32_t ALIGN_UNIT = 8; // 32 bytes / sizeof(float)
30+constexpr uint32_t CGEAM_BUFFERS = 12; // 4 TQue with num=2 (A_r*2, A_i*2, C_r*2, C_i*2) + 4 TBuf (B_r, B_i, calcBufR, calcBufI)
31+constexpr uint32_t CGEAM_BUFFERS_BETA_ZERO = 10; // Same minus B_r, B_i (not allocated when beta=0)
32+// Hardware limit for DataCopyPadExt blockCount parameter.
33+// When curM exceeds this, DataCopyPad blockCount exceeds hardware limit
34+// and behavior is undefined. Each complex element maps to one blockCount
35+// unit (real/imag each have curM floats read separately).
36+constexpr uint32_t MAX_BLOCK_COUNT = 4095;
37+ 
38+CgeamTilingData CalCgeamTilingData(
39+ aclblasOperation_t transa, aclblasOperation_t transb, uint32_t m, uint32_t n, float alphaR, float alphaI,
40+ float betaR, float betaI, uint32_t lda, uint32_t ldb, uint32_t ldc, uint32_t alphaIsZero, uint32_t betaIsZero,
41+ uint32_t aivCoreNum)
42+{
43+ CgeamTilingData tiling{};
44+ tiling.m = m;
45+ tiling.n = n;
46+ tiling.lda = lda;
47+ tiling.ldb = ldb;
48+ tiling.ldc = ldc;
49+ tiling.alphaR = alphaR;
50+ tiling.alphaI = alphaI;
51+ tiling.betaR = betaR;
52+ tiling.betaI = betaI;
53+ tiling.alphaIsZero = alphaIsZero;
54+ tiling.betaIsZero = betaIsZero;
55+ tiling.opA = transa;
56+ tiling.opB = transb;
57+ 
58+ // Calculate tileM based on UB capacity, capped to MAX_BLOCK_COUNT
59+ // to avoid triggering the kernel batch-loop error path when curM > 4095.
60+ // For complex with beta!=0: 8 buffers (A_r, A_i, B_r, B_i, C_r, C_i, calcBufR, calcBufI)
61+ // For complex with beta=0: 6 buffers (A_r, A_i, C_r, C_i, calcBufR, calcBufI)
62+ uint32_t buffersPerTile = betaIsZero ? CGEAM_BUFFERS_BETA_ZERO : CGEAM_BUFFERS;
xutianze
xutianzexutianze10 天前

主机按 beta!=0 时 6 个缓冲区(CGEAM_BUFFERS)计算 tileM 容量上限,而 kernel 实际无条件分配 8 个缓冲区(4 个队列 + calcBufBR_/BI_ + 2 个乘法 scratch),容量预算与 kernel 实现不符,当前仅靠 MAX_BLOCK_COUNT=4095 上限恰好不超 UB,上限一旦调整 tileM 将静默超出 UB 容量

likedislike
63+ uint32_t maxTileM = (UB_SIZE - UB_RESERVE) / (buffersPerTile * sizeof(float));
64+ uint32_t tileM = (maxTileM / ALIGN_UNIT) * ALIGN_UNIT;
65+ tileM = std::min(tileM, MAX_BLOCK_COUNT);
66+ tiling.tileM = tileM;
67+ 
68+ // 2D block decomposition: colBlocks x mBlocks
69+ uint32_t colBlocks = std::min(n, aivCoreNum);
70+ if (colBlocks == 0) {
71+ colBlocks = 1;
72+ }
73+ tiling.colBlocks = colBlocks;
74+ tiling.perCoreN = n / colBlocks;
75+ tiling.remainder = n % colBlocks;
76+ 
77+ // Calculate m-blocks
78+ uint32_t totalMTiles = (tiling.tileM > 0) ? ((m + tiling.tileM - 1) / tiling.tileM) : 1;
79+ uint32_t mBlocks = 1;
80+ if (colBlocks < aivCoreNum && totalMTiles > 1) {
81+ uint32_t maxMBlocks = aivCoreNum / colBlocks;
82+ mBlocks = std::min(maxMBlocks, totalMTiles);
83+ }
84+ if (mBlocks == 0) // to suppress 'maybe divide by zero' warning
85+ __builtin_unreachable();
86+ tiling.mBlocks = mBlocks;
87+ tiling.perCoreMTile = totalMTiles / mBlocks;
88+ tiling.mTileRemainder = totalMTiles % mBlocks;
89+ 
90+ return tiling;
91+}
92+ 
93+aclblasStatus_t LaunchCgeamKernel(
94+ aclblasHandle_t handle, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n,
95+ const aclblasComplex* alpha, const aclblasComplex* A, int lda, const aclblasComplex* beta, const aclblasComplex* B,
96+ int ldb, aclblasComplex* C, int ldc)
97+{
98+ // Get core count
99+ uint32_t aivCoreNum = GetAivCoreCount();
100+ if (aivCoreNum == 0) {
101+ OP_LOGE("aclblasCgeam", "Failed to get AIV core count");
102+ return ACLBLAS_STATUS_INTERNAL_ERROR;
103+ }
104+ 
105+ float alphaR = alpha->real;
106+ float alphaI = alpha->imag;
107+ float betaR = beta->real;
108+ float betaI = beta->imag;
109+ uint32_t alphaIsZero = (alphaR == 0.0f && alphaI == 0.0f) ? 1u : 0u;
110+ uint32_t betaIsZero = (betaR == 0.0f && betaI == 0.0f) ? 1u : 0u;
111+ 
112+ // Calculate tiling
113+ CgeamTilingData tiling = CalCgeamTilingData(
114+ transa, transb, static_cast<uint32_t>(m), static_cast<uint32_t>(n), alphaR, alphaI, betaR, betaI,
115+ static_cast<uint32_t>(lda), static_cast<uint32_t>(ldb), static_cast<uint32_t>(ldc), alphaIsZero, betaIsZero,
116+ aivCoreNum);
117+ 
118+ // Calculate total blocks
119+ uint32_t numBlocks = tiling.colBlocks * tiling.mBlocks;
120+ if (numBlocks == 0) {
121+ numBlocks = 1;
122+ }
123+ 
124+ OP_LOGD(
125+ "aclblasCgeam", "Tiling: tileM=%u, colBlocks=%u, mBlocks=%u, totalBlocks=%u", tiling.tileM, tiling.colBlocks,
126+ tiling.mBlocks, numBlocks);
127+ 
128+ // Convert pointers to GM_ADDR (complex data is stored as interleaved float pairs).
129+ // When alpha/beta value is 0, A/B may be nullptr; use C as a dummy valid GM address
130+ // to avoid hardware issues with null descriptors.
131+ // (Kernel skips reading A/B based on alphaIsZero/betaIsZero tiling flags.)
132+ GM_ADDR gmC = reinterpret_cast<GM_ADDR>(C);
133+ GM_ADDR gmA = (A != nullptr) ? reinterpret_cast<GM_ADDR>(const_cast<aclblasComplex*>(A)) : gmC;
J
Jjustsheldon10 天前

同 sgeam_host.cpp,为将 const aclblasComplex* 转为 GM_ADDR 做 const_cast。

likedislike
134+ GM_ADDR gmB = (B != nullptr) ? reinterpret_cast<GM_ADDR>(const_cast<aclblasComplex*>(B)) : gmC;
135+ 
136+ // Launch kernel
137+ cgeam_kernel_do(gmA, gmB, gmC, tiling, numBlocks, handle->stream);
138+ 
139+ return ACLBLAS_STATUS_SUCCESS;
140+}
141+ 
142+} // namespace
143+ 
144+extern "C" aclblasStatus_t aclblasCgeam(
145+ aclblasHandle_t handle, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n,
146+ const aclblasComplex* alpha, const aclblasComplex* A, int lda, const aclblasComplex* beta, const aclblasComplex* B,
147+ int ldb, aclblasComplex* C, int ldc)
148+{
149+ if (handle == nullptr) {
150+ OP_LOGE("aclblasCgeam", "handle is nullptr");
151+ return ACLBLAS_STATUS_HANDLE_IS_NULLPTR;
152+ }
153+ 
154+ aclblasStatus_t status =
155+ ValidateGeamParams("aclblasCgeam", transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc);
156+ if (status != ACLBLAS_STATUS_SUCCESS) {
157+ return status;
158+ }
159+ 
160+ if (m == 0 || n == 0) {
161+ return ACLBLAS_STATUS_SUCCESS;
162+ }
163+ 
164+ return LaunchCgeamKernel(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc);
165+}
Ablas/geam/arch35/cgeam_kernel.cpp+453-0
@@ -0,0 +1,453 @@
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 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+ * See LICENSE in the root of the software repository for the full text of the License.
8+ */
9+ 
10+/**
11+ * @file cgeam_kernel.cpp
12+ * @brief Cgeam kernel implementation (SIMD/membase, arch35)
13+ * Computes: C = alpha * op(A) + beta * op(B) for complex matrices
14+ *
15+ * Architecture: TPipe + TQue<VECIN>/TQue<VECOUT> + TBuf<VECCALC>
16+ * EnQue/DeQue provides automatic MTE <-> Vector synchronization.
17+ *
18+ * Complex representation:
19+ * - Interleaved storage: r0,i0,r1,i1,...,rN,iN
20+ * - Each complex matrix element = 2 adjacent floats (real, imag)
21+ * - Separate real/imag buffers managed through individual TQue/TBuf
22+ *
23+ * Buffer planning (each buffer holds tileM floats):
24+ * - inQueueAR_, inQueueAI_ (VECIN): A tile real/imag, MTE2→V sync via TQue
25+ * - calcBufBR_, calcBufBI_ (VECCALC): B tile real/imag when beta!=0; scratch when beta==0
26+ * - outQueueCR_, outQueueCI_ (VECOUT): C tile real/imag, V→MTE3 sync via TQue
27+ * - calcBufR_, calcBufI_ (VECCALC): Scratch for complex multiply intermediates
28+ *
29+ * Conjugate handling:
30+ * - isConjA: negate A imaginary after DeQue (PipeBarrier<PIPE_V> before compute)
31+ * - isConjB: negate B imaginary after load (PipeBarrier<PIPE_V> before compute)
32+ *
33+ * Single path: one column per iteration (complex interleaved prevents multi-col batching)
34+ * Multi-core decomposition: 2D grid (colBlocks x mBlocks)
35+ */
36+ 
37+#include "kernel_operator.h"
38+#include "cgeam_kernel.h"
39+#include "cgeam_tiling_data.h"
40+ 
41+namespace {
42+ 
43+using namespace AscendC;
44+ 
45+// ============================================================================
46+// CgeamAIV: operator class for Cgeam complex vector kernel
47+// ============================================================================
48+class CgeamAIV {
49+public:
50+ __aicore__ inline CgeamAIV() {}
51+ __aicore__ inline void Init(GM_ADDR A, GM_ADDR B, GM_ADDR C, const CgeamTilingData& tiling, TPipe* pipe);
52+ __aicore__ inline void Process();
53+ 
54+private:
55+ __aicore__ inline void ProcessTileLoop();
56+ __aicore__ inline void ComputeCoreRanges(const CgeamTilingData& tiling);
57+ __aicore__ inline void CopyInCplxA(
58+ uint32_t col, uint32_t rowStart, uint32_t curM, int64_t ntCplxStride, int64_t tCplxStrideA);
59+ __aicore__ inline void CopyInCplxB(
60+ uint32_t col, uint32_t rowStart, uint32_t curM, int64_t ntCplxStride, int64_t tCplxStrideB);
61+ __aicore__ inline void ComputeCplx(uint32_t curM);
62+ __aicore__ inline void CopyOutCplx(uint32_t col, uint32_t rowStart, uint32_t curM, int64_t storeStride);
63+ 
64+ // GM addresses
65+ GlobalTensor<float> aGm_;
66+ GlobalTensor<float> bGm_;
67+ GlobalTensor<float> cGm_;
68+ 
69+ // A input queues (separate real/imag for clean TQue sync)
70+ TQue<TPosition::VECIN, 2> inQueueAR_; // A real tile (EnQue signals MTE2 done)
71+ TQue<TPosition::VECIN, 2> inQueueAI_; // A imag tile (EnQue signals MTE2 done)
72+ 
73+ // B input: loaded via TBuf (same pattern as sgeam)
74+ TBuf<TPosition::VECCALC> calcBufBR_; // B real tile (also serves as scratch when beta==0)
75+ TBuf<TPosition::VECCALC> calcBufBI_; // B imag tile (also serves as scratch when beta==0)
76+ 
77+ // C output queues
78+ TQue<TPosition::VECOUT, 2> outQueueCR_; // C real tile (EnQue signals Vector done)
79+ TQue<TPosition::VECOUT, 2> outQueueCI_; // C imag tile (EnQue signals Vector done)
80+ 
81+ // Scratch for complex multiply
82+ TBuf<TPosition::VECCALC> calcBufR_; // Temp for alpha_i*A or beta_i*B real component
83+ TBuf<TPosition::VECCALC> calcBufI_; // Temp for alpha_i*A or beta_i*B imag component
84+ 
85+ // Tiling fields cached from CgeamTilingData
86+ uint32_t m_;
87+ uint32_t n_;
88+ 
89+ uint32_t alphaIsZero_;
90+ float alphaR_;
91+ float alphaI_;
92+ aclblasOperation_t opA_;
93+ uint32_t lda_;
94+ bool isConjA_;
95+ 
96+ uint32_t betaIsZero_;
97+ float betaR_;
98+ float betaI_;
99+ aclblasOperation_t opB_;
100+ uint32_t ldb_;
101+ bool isConjB_;
102+ 
103+ uint32_t ldc_;
104+ uint32_t tileM_;
105+ 
106+ // Per-core tile ranges
107+ uint32_t colStart_;
108+ uint32_t colEnd_;
109+ uint32_t mTileStart_;
110+ uint32_t mTileEnd_;
111+};
112+ 
113+// ============================================================================
114+// ComputeCoreRanges: decode 2D block index into per-core column and m-tile ranges.
115+// On invalid range, sets colEnd_ or mTileEnd_ to 0 to signal early exit in Process().
116+// ============================================================================
117+__aicore__ inline void CgeamAIV::ComputeCoreRanges(const CgeamTilingData& tiling)
J
Jjustsheldon10 天前

ComputeCoreRanges 的逻辑与 sgeam_kernel.cpp:103 几乎完全相同(仅 TilingData 类型不同),可以考虑模板化或提取到公共头文件以减少重复。sgeam 和 cgeam 的 tiling 计算也有类似重复。

likedislike
118+{
119+ uint32_t blockIdx = GetBlockIdx();
120+ uint32_t mBlocks = tiling.mBlocks;
121+ uint32_t colBlock = blockIdx / mBlocks;
122+ uint32_t mBlock = blockIdx % mBlocks;
123+ 
124+ // Calculate column range
125+ if (colBlock < tiling.remainder) {
126+ colStart_ = colBlock * (tiling.perCoreN + 1);
127+ colEnd_ = colStart_ + tiling.perCoreN + 1;
128+ } else {
129+ colStart_ = colBlock * tiling.perCoreN + tiling.remainder;
130+ colEnd_ = colStart_ + tiling.perCoreN;
131+ }
132+ 
133+ // Calculate m-tile range
134+ if (mBlock < tiling.mTileRemainder) {
135+ mTileStart_ = mBlock * (tiling.perCoreMTile + 1);
136+ mTileEnd_ = mTileStart_ + tiling.perCoreMTile + 1;
137+ } else {
138+ mTileStart_ = mBlock * tiling.perCoreMTile + tiling.mTileRemainder;
139+ mTileEnd_ = mTileStart_ + tiling.perCoreMTile;
140+ }
141+ 
142+ // Validate column range
143+ if (colStart_ >= n_ || colStart_ >= colEnd_) {
144+ colEnd_ = 0; // Signal Process() to return early
145+ return;
146+ }
147+ if (colEnd_ > n_) {
148+ colEnd_ = n_;
149+ }
150+ 
151+ // Validate m-tile range
152+ uint32_t totalMTiles = (m_ + tileM_ - 1) / tileM_;
153+ if (mTileStart_ >= totalMTiles || mTileStart_ >= mTileEnd_) {
154+ mTileEnd_ = 0; // Signal Process() to return early
155+ return;
156+ }
157+ if (mTileEnd_ > totalMTiles) {
158+ mTileEnd_ = totalMTiles;
159+ }
160+}
161+ 
162+// ============================================================================
163+// Init: setup global tensors, compute per-core ranges, allocate UB buffers.
164+// TPipe pointer passed from kernel entry (R3: TPipe must not be a member).
165+// ============================================================================
166+__aicore__ inline void CgeamAIV::Init(GM_ADDR A, GM_ADDR B, GM_ADDR C, const CgeamTilingData& tiling, TPipe* pipe)
167+{
168+ // Cache tiling fields to member variables
169+ m_ = tiling.m;
170+ n_ = tiling.n;
171+ lda_ = tiling.lda;
172+ ldb_ = tiling.ldb;
173+ ldc_ = tiling.ldc;
174+ tileM_ = tiling.tileM;
175+ opA_ = tiling.opA;
176+ opB_ = tiling.opB;
177+ alphaIsZero_ = tiling.alphaIsZero;
178+ betaIsZero_ = tiling.betaIsZero;
179+ alphaR_ = tiling.alphaR;
180+ alphaI_ = tiling.alphaI;
181+ betaR_ = tiling.betaR;
182+ betaI_ = tiling.betaI;
183+ isConjA_ = (opA_ == ACLBLAS_OP_C);
184+ isConjB_ = (opB_ == ACLBLAS_OP_C);
185+ 
186+ // Set GM buffers
187+ aGm_.SetGlobalBuffer(reinterpret_cast<__gm__ float*>(A));
188+ bGm_.SetGlobalBuffer(reinterpret_cast<__gm__ float*>(B));
189+ cGm_.SetGlobalBuffer(reinterpret_cast<__gm__ float*>(C));
190+ 
191+ // Compute per-core column and m-tile ranges
192+ ComputeCoreRanges(tiling);
J
Jjustsheldon10 天前

sgeam Init 在 ComputeCoreRanges 后有 if (colEnd_ == 0 || mTileEnd_ == 0) return; 跳过 InitBuffer,但 cgeam Init 缺少此检查,空闲核仍会分配 UB buffer。建议与 sgeam 保持一致,在 ComputeCoreRanges 后加早退判断。

likedislike
193+ if (colEnd_ == 0 || mTileEnd_ == 0) {
194+ return;
195+ }
196+ 
197+ // Buffer size: tileM floats per buffer
198+ uint32_t bufSize = tileM_ * sizeof(float);
199+ 
200+ // A input queues (double-buffer, num=2 for MTE2<->V pipeline overlap)
201+ pipe->InitBuffer(inQueueAR_, 2, bufSize);
202+ pipe->InitBuffer(inQueueAI_, 2, bufSize);
203+ 
204+ // C output queues (double-buffer, num=2 for V<->MTE3 pipeline overlap)
205+ pipe->InitBuffer(outQueueCR_, 2, bufSize);
206+ pipe->InitBuffer(outQueueCI_, 2, bufSize);
207+ 
208+ // B input (TBuf, allocated when beta != 0)
209+ if (!betaIsZero_) {
210+ pipe->InitBuffer(calcBufBR_, bufSize);
211+ pipe->InitBuffer(calcBufBI_, bufSize);
212+ }
213+ 
214+ // Complex multiply scratch (only needed when alpha != 0)
215+ if (!alphaIsZero_) {
216+ pipe->InitBuffer(calcBufR_, bufSize);
217+ pipe->InitBuffer(calcBufI_, bufSize);
218+ }
219+}
220+ 
221+// ============================================================================
222+// Process: validate ranges, dispatch to tile loop
223+// ============================================================================
224+__aicore__ inline void CgeamAIV::Process()
225+{
226+ if (colStart_ >= colEnd_ || mTileStart_ >= mTileEnd_) {
227+ return;
228+ }
229+ ProcessTileLoop();
230+}
231+ 
232+// ============================================================================
233+// CopyInCplxA: load A real and imag tiles through their respective VECIN queues.
234+// ============================================================================
235+__aicore__ inline void CgeamAIV::CopyInCplxA(
236+ uint32_t col, uint32_t rowStart, uint32_t curM, int64_t ntCplxStride, int64_t tCplxStrideA)
237+{
238+ // CopyIn A real (MTE2)
239+ LocalTensor<float> aR = inQueueAR_.AllocTensor<float>();
240+ if (!alphaIsZero_) {
241+ uint64_t offsetBase;
242+ int64_t stride;
243+ if (opA_ == ACLBLAS_OP_N) {
244+ offsetBase = static_cast<uint64_t>(col) * static_cast<uint64_t>(lda_) + rowStart;
245+ stride = ntCplxStride;
246+ } else {
247+ offsetBase = static_cast<uint64_t>(col) + static_cast<uint64_t>(rowStart) * static_cast<uint64_t>(lda_);
248+ stride = tCplxStrideA;
249+ }
250+ DataCopyExtParams cp{static_cast<uint16_t>(curM), static_cast<uint32_t>(sizeof(float)), stride, 0, 0};
251+ DataCopyPadExtParams<float> np;
252+ DataCopyPad<float, PaddingMode::Compact>(aR, aGm_[2 * offsetBase], cp, np);
253+ }
254+ inQueueAR_.EnQue(aR); // Signal MTE2 done for A real
255+ 
256+ // CopyIn A imag (MTE2)
257+ LocalTensor<float> aI = inQueueAI_.AllocTensor<float>();
258+ if (!alphaIsZero_) {
259+ uint64_t offsetBase;
260+ int64_t stride;
261+ if (opA_ == ACLBLAS_OP_N) {
262+ offsetBase = static_cast<uint64_t>(col) * static_cast<uint64_t>(lda_) + rowStart;
263+ stride = ntCplxStride;
264+ } else {
265+ offsetBase = static_cast<uint64_t>(col) + static_cast<uint64_t>(rowStart) * static_cast<uint64_t>(lda_);
266+ stride = tCplxStrideA;
267+ }
268+ DataCopyExtParams cp{static_cast<uint16_t>(curM), static_cast<uint32_t>(sizeof(float)), stride, 0, 0};
269+ DataCopyPadExtParams<float> np;
270+ DataCopyPad<float, PaddingMode::Compact>(aI, aGm_[2 * offsetBase + 1], cp, np);
271+ }
272+ inQueueAI_.EnQue(aI); // Signal MTE2 done for A imag
273+}
274+ 
275+// ============================================================================
276+// CopyInCplxB: load B real and imag tiles, and apply conjugate if needed.
277+// ============================================================================
278+__aicore__ inline void CgeamAIV::CopyInCplxB(
279+ uint32_t col, uint32_t rowStart, uint32_t curM, int64_t ntCplxStride, int64_t tCplxStrideB)
280+{
281+ if (!betaIsZero_) {
282+ LocalTensor<float> bR = calcBufBR_.Get<float>();
283+ LocalTensor<float> bI = calcBufBI_.Get<float>();
284+ 
285+ uint64_t offsetBase;
286+ int64_t stride;
287+ if (opB_ == ACLBLAS_OP_N) {
288+ offsetBase = static_cast<uint64_t>(col) * static_cast<uint64_t>(ldb_) + rowStart;
289+ stride = ntCplxStride;
290+ } else {
291+ offsetBase = static_cast<uint64_t>(col) + static_cast<uint64_t>(rowStart) * static_cast<uint64_t>(ldb_);
292+ stride = tCplxStrideB;
293+ }
294+ 
295+ DataCopyExtParams cp{static_cast<uint16_t>(curM), static_cast<uint32_t>(sizeof(float)), stride, 0, 0};
296+ DataCopyPadExtParams<float> np;
297+ DataCopyPad<float, PaddingMode::Compact>(bR, bGm_[2 * offsetBase], cp, np);
298+ DataCopyPad<float, PaddingMode::Compact>(bI, bGm_[2 * offsetBase + 1], cp, np);
xutianze
xutianzexutianze10 天前

B 实/虚部经 DataCopyPad 写入 calcBufBR_/calcBufBI_ 后由 ComputeCplx 直接读取,无显式 MTE2→V 同步,且第 297 行共轭取反 Muls 紧跟在异步搬运之后,依赖隐式时序保障(实机大矩阵用例未复现错误),后续调整时易暴露竞争

likedislike
299+ event_t eMte2V_ = static_cast<event_t>(GetTPipePtr()->FetchEventID(HardEvent::MTE2_V));
300+ SetFlag<HardEvent::MTE2_V>(eMte2V_);
301+ WaitFlag<HardEvent::MTE2_V>(eMte2V_);
302+ 
303+ if (isConjB_) {
304+ Muls<float>(bI, bI, -1.0f, curM);
305+ PipeBarrier<PIPE_V>();
306+ }
307+ }
308+}
309+ 
310+// ============================================================================
311+// ComputeCplx: Phase 1 (C = alpha * A) + Phase 2 (C += beta * B) complex multiply.
312+// ============================================================================
313+__aicore__ inline void CgeamAIV::ComputeCplx(uint32_t curM)
314+{
315+ // DeQue A waits for MTE2 -> V sync
316+ LocalTensor<float> aRv = inQueueAR_.DeQue<float>();
317+ LocalTensor<float> aIv = inQueueAI_.DeQue<float>();
318+ 
319+ // Conjugate A: negate imaginary part after DeQue
320+ if (isConjA_) {
321+ Muls<float>(aIv, aIv, -1.0f, curM);
322+ PipeBarrier<PIPE_V>();
323+ }
324+ 
325+ LocalTensor<float> cR = outQueueCR_.AllocTensor<float>();
326+ LocalTensor<float> cI = outQueueCI_.AllocTensor<float>();
327+ 
328+ if (alphaIsZero_) {
329+ // Zero-initialize C (alpha == 0: A not referenced)
330+ Duplicate<float>(cR, 0.0f, curM);
331+ Duplicate<float>(cI, 0.0f, curM);
332+ } else {
333+ // Scratch buffers for complex multiply
334+ LocalTensor<float> tmpR = calcBufR_.Get<float>();
335+ LocalTensor<float> tmpI = calcBufI_.Get<float>();
336+ 
337+ // Phase 1: C = alpha * A (complex multiplication)
338+ // C_r = alphaR * A_r - alphaI * A_i
339+ // C_i = alphaR * A_i + alphaI * A_r
340+ Muls<float>(tmpR, aRv, alphaI_, curM); // tmpR = alphaI * A_r
341+ Muls<float>(tmpI, aIv, alphaI_, curM); // tmpI = alphaI * A_i
342+ Muls<float>(cR, aRv, alphaR_, curM); // cR = alphaR * A_r
343+ Muls<float>(cI, aIv, alphaR_, curM); // cI = alphaR * A_i
344+ Sub<float>(cR, cR, tmpI, curM); // cR -= alphaI * A_i
345+ Add<float>(cI, cI, tmpR, curM); // cI += alphaI * A_r
346+ }
347+ 
348+ if (!betaIsZero_) {
349+ // Phase 2: C += beta * B (complex multiplication)
350+ LocalTensor<float> bR = calcBufBR_.Get<float>();
351+ LocalTensor<float> bI = calcBufBI_.Get<float>();
352+ // When alpha != 0, use calcBufR_/I_ as scratch; when alpha == 0,
353+ // reuse A input buffers (aRv/aIv) as scratch since Phase 1 was skipped.
354+ LocalTensor<float> tmpR = alphaIsZero_ ? aRv : calcBufR_.Get<float>();
355+ LocalTensor<float> tmpI = alphaIsZero_ ? aIv : calcBufI_.Get<float>();
356+ // C_r += betaR * B_r - betaI * B_i
357+ // C_i += betaR * B_i + betaI * B_r
358+ Muls<float>(tmpR, bR, betaR_, curM); // tmpR = betaR * B_r
359+ Add<float>(cR, cR, tmpR, curM); // cR += betaR * B_r
360+ Muls<float>(tmpI, bI, betaR_, curM); // tmpI = betaR * B_i
361+ Add<float>(cI, cI, tmpI, curM); // cI += betaR * B_i
362+ Muls<float>(tmpR, bR, betaI_, curM); // tmpR = betaI * B_r
363+ Add<float>(cI, cI, tmpR, curM); // cI += betaI * B_r
364+ Muls<float>(tmpI, bI, betaI_, curM); // tmpI = betaI * B_i
365+ Sub<float>(cR, cR, tmpI, curM); // cR -= betaI * B_i
366+ }
367+ 
368+ outQueueCR_.EnQue(cR); // Signal Vector done for C real
369+ outQueueCI_.EnQue(cI); // Signal Vector done for C imag
370+ inQueueAR_.FreeTensor(aRv);
371+ inQueueAI_.FreeTensor(aIv);
372+}
373+ 
374+// ============================================================================
375+// CopyOutCplx: store C real and imag tiles to global memory (interleaved).
376+// ============================================================================
377+__aicore__ inline void CgeamAIV::CopyOutCplx(uint32_t col, uint32_t rowStart, uint32_t curM, int64_t storeStride)
378+{
379+ // CopyOut C real (MTE3): DeQue waits for V -> MTE3 sync
380+ LocalTensor<float> cRo = outQueueCR_.DeQue<float>();
381+ {
382+ const uint64_t offsetBase = static_cast<uint64_t>(col) * static_cast<uint64_t>(ldc_) + rowStart;
383+ DataCopyExtParams cp{static_cast<uint16_t>(curM), static_cast<uint32_t>(sizeof(float)), 0, storeStride, 0};
384+ DataCopyPad<float, PaddingMode::Compact>(cGm_[2 * offsetBase], cRo, cp);
385+ }
386+ outQueueCR_.FreeTensor(cRo);
387+ 
388+ // CopyOut C imag (MTE3)
389+ LocalTensor<float> cIo = outQueueCI_.DeQue<float>();
390+ {
391+ const uint64_t offsetBase = static_cast<uint64_t>(col) * static_cast<uint64_t>(ldc_) + rowStart;
392+ DataCopyExtParams cp{static_cast<uint16_t>(curM), static_cast<uint32_t>(sizeof(float)), 0, storeStride, 0};
393+ DataCopyPad<float, PaddingMode::Compact>(cGm_[2 * offsetBase + 1], cIo, cp);
394+ }
395+ outQueueCI_.FreeTensor(cIo);
396+}
397+ 
398+// ============================================================================
399+// ProcessTileLoop: complex geam tile processing
400+// Each tile: CopyIn A -> EnQue -> CopyIn B -> Compute -> EnQue C -> CopyOut C
401+// ============================================================================
402+__aicore__ inline void CgeamAIV::ProcessTileLoop()
403+{
404+ // Pre-compute strides for complex strided access
405+ // NoTrans complex: adjacent r (or i) elements are 2 floats apart → srcStride = sizeof(float)
406+ const int64_t ntCplxStride = static_cast<int64_t>(sizeof(float));
407+ // Trans/ConjTrans complex: each row = ld complex = 2*ld floats → gap = (2*ld-1)*sizeof(float)
408+ const int64_t tCplxStrideA = static_cast<int64_t>(2 * lda_ - 1) * static_cast<int64_t>(sizeof(float));
409+ const int64_t tCplxStrideB = static_cast<int64_t>(2 * ldb_ - 1) * static_cast<int64_t>(sizeof(float));
410+ // Store: interleaved complex write, dstStride gap = sizeof(float)
411+ const int64_t storeStride = static_cast<int64_t>(sizeof(float));
412+ 
413+ for (uint32_t col = colStart_; col < colEnd_; ++col) {
414+ for (uint32_t mTile = mTileStart_; mTile < mTileEnd_; ++mTile) {
415+ const uint32_t rowStart = mTile * tileM_;
416+ uint32_t rowEnd = rowStart + tileM_;
417+ if (rowEnd > m_) {
418+ rowEnd = m_;
419+ }
420+ const uint32_t curM = rowEnd - rowStart;
421+ 
422+ CopyInCplxA(col, rowStart, curM, ntCplxStride, tCplxStrideA);
423+ CopyInCplxB(col, rowStart, curM, ntCplxStride, tCplxStrideB);
424+ ComputeCplx(curM);
J
Jjustsheldon10 天前

calcBufBR_/calcBufBI_ 是 TBuf,CopyInCplxB 通过 DataCopyPad(MTE2)加载 B 的实部/虚部,但 ComputeCplx 中 Vector 读取 bR/bI(第343-344行)时缺少 MTE2→V 同步。当 isConjB_ 为真时,第297行 Muls 读取 bI 也没有 MTE2→V 同步,PipeBarrier<PIPE_V> 只保证 V→V 顺序,不覆盖 MTE2→V。建议在 CopyInCplxB 后、ComputeCplx 前插入 PipeBarrier<PIPE_MTE2_V>。

likedislike
425+ CopyOutCplx(col, rowStart, curM, storeStride);
426+ }
427+ }
428+}
429+ 
430+} // namespace
431+ 
432+// ============================================================================
433+// Kernel entry point
434+// ============================================================================
435+extern "C" __global__ __aicore__ void cgeam_kernel(GM_ADDR A, GM_ADDR B, GM_ADDR C, CgeamTilingData tiling)
436+{
437+ KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY);
438+ 
439+ if (tiling.m == 0 || tiling.n == 0) {
440+ return;
441+ }
442+ 
443+ TPipe pipe;
444+ CgeamAIV op;
445+ op.Init(A, B, C, tiling, &pipe);
446+ op.Process();
447+}
448+ 
449+void cgeam_kernel_do(
450+ GM_ADDR A, GM_ADDR B, GM_ADDR C, const CgeamTilingData& tiling, uint32_t numBlocks, aclrtStream stream)
451+{
452+ cgeam_kernel<<<numBlocks, nullptr, stream>>>(A, B, C, tiling);
453+}
Ablas/geam/arch35/cgeam_kernel.h+37-0
@@ -0,0 +1,37 @@
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 cgeam_kernel.h
13+ * @brief Kernel launcher declaration for Cgeam operator (complex)
14+ */
15+ 
16+#pragma once
17+ 
18+#include <cstdint>
19+#include "acl/acl_base_rt.h"
20+#include "cgeam_tiling_data.h"
21+ 
22+#ifndef GM_ADDR
23+#define GM_ADDR uint8_t*
24+#endif
25+ 
26+/**
27+ * @brief Launch Cgeam kernel asynchronously
28+ *
29+ * @param A GM address of matrix A (complex)
30+ * @param B GM address of matrix B (complex)
31+ * @param C GM address of matrix C (complex, output)
32+ * @param tiling Host-computed tiling data (copied to device by value)
33+ * @param numBlocks Block count for the <<<>>> launch
34+ * @param stream aclrtStream handle
35+ */
36+void cgeam_kernel_do(
37+ GM_ADDR A, GM_ADDR B, GM_ADDR C, const CgeamTilingData& tiling, uint32_t numBlocks, aclrtStream stream);
Ablas/geam/arch35/cgeam_tiling_data.h+44-0
@@ -0,0 +1,44 @@
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 cgeam_tiling_data.h
13+ * @brief Tiling data structure for Cgeam operator (host/device shared)
14+ * C = alpha * op(A) + beta * op(B) (complex)
15+ */
16+ 
17+#pragma once
18+ 
19+#include <cstdint>
20+#include "cann_ops_blas_common.h"
21+ 
22+struct CgeamTilingData {
23+ uint32_t m; // Number of rows in output matrix C
24+ uint32_t n; // Number of columns in output matrix C
25+ float alphaR; // Real part of scalar alpha
26+ float alphaI; // Imaginary part of scalar alpha
27+ uint32_t alphaIsZero; // Optimized flag indicating alpha == 0
28+ aclblasOperation_t opA; // Transpose type for matrix A
29+ uint32_t lda; // Leading dimension of matrix A
30+ float betaR; // Real part of scalar beta
31+ float betaI; // Imaginary part of scalar beta
32+ uint32_t betaIsZero; // Optimized flag indicating beta == 0
33+ aclblasOperation_t opB; // Transpose type for matrix B
34+ uint32_t ldb; // Leading dimension of matrix B
35+ uint32_t ldc; // Leading dimension of matrix C
36+ // 2D multi-core tiling: colBlocks x mBlocks
37+ uint32_t colBlocks; // Number of column blocks
38+ uint32_t perCoreN; // Number of columns processed per core
39+ uint32_t remainder; // Column remainder
40+ uint32_t mBlocks; // Number of row blocks
41+ uint32_t perCoreMTile; // Number of tiles processed per core in m dimension
42+ uint32_t mTileRemainder; // Tile count remainder in m dimension
43+ uint32_t tileM; // Tile size in m dimension (number of elements)
44+};
Ablas/geam/arch35/geam_host_common.h+150-0
@@ -0,0 +1,150 @@
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+#pragma once
11+ 
12+#include "cann_ops_blas_common.h"
13+#include "op_common/log/log.h"
14+ 
15+#include <string>
16+ 
17+inline bool IsGeamScalarZero(const float* p) { return p == nullptr || *p == 0.0f; }
xutianze
xutianzexutianze10 天前

IsGeamScalarZero 将 alpha/beta 空指针视为 0,与仓内 gemm 对空标量指针返回 INVALID_VALUE 的约定不一致,跨算子错误码语义不统一

likedislike
18+ 
19+inline bool IsGeamScalarZero(const aclblasComplex* p) { return p == nullptr || (p->real == 0.0f && p->imag == 0.0f); }
20+ 
21+inline aclblasStatus_t ValidateGeamTranspose(
22+ aclblasOperation_t transa, aclblasOperation_t transb, const std::string& op_name)
23+{
24+ if (transa != ACLBLAS_OP_N && transa != ACLBLAS_OP_T && transa != ACLBLAS_OP_C) {
25+ OP_LOGE(op_name, "Invalid transa=%d (must be N/T/C)", transa);
26+ return ACLBLAS_STATUS_INVALID_ENUM;
27+ }
28+ if (transb != ACLBLAS_OP_N && transb != ACLBLAS_OP_T && transb != ACLBLAS_OP_C) {
29+ OP_LOGE(op_name, "Invalid transb=%d (must be N/T/C)", transb);
30+ return ACLBLAS_STATUS_INVALID_ENUM;
31+ }
32+ return ACLBLAS_STATUS_SUCCESS;
33+}
34+ 
35+inline aclblasStatus_t ValidateGeamLd(
36+ int m, int n, int lda, int ldb, int ldc, aclblasOperation_t transa, aclblasOperation_t transb,
37+ const std::string& op_name)
38+{
39+ if (transa == ACLBLAS_OP_N) {
40+ if (lda < std::max(1, m)) {
41+ OP_LOGE(op_name, "lda=%d must be >= max(1, m=%d) when transa=N", lda, m);
42+ return ACLBLAS_STATUS_INVALID_VALUE;
43+ }
44+ } else {
45+ if (lda < std::max(1, n)) {
46+ OP_LOGE(op_name, "lda=%d must be >= max(1, n=%d) when transa=T/C", lda, n);
47+ return ACLBLAS_STATUS_INVALID_VALUE;
48+ }
49+ }
50+ if (transb == ACLBLAS_OP_N) {
51+ if (ldb < std::max(1, m)) {
52+ OP_LOGE(op_name, "ldb=%d must be >= max(1, m=%d) when transb=N", ldb, m);
53+ return ACLBLAS_STATUS_INVALID_VALUE;
54+ }
55+ } else {
56+ if (ldb < std::max(1, n)) {
57+ OP_LOGE(op_name, "ldb=%d must be >= max(1, n=%d) when transb=T/C", ldb, n);
58+ return ACLBLAS_STATUS_INVALID_VALUE;
59+ }
60+ }
61+ if (ldc < std::max(1, m)) {
62+ OP_LOGE(op_name, "ldc=%d must be >= max(1, m=%d)", ldc, m);
63+ return ACLBLAS_STATUS_INVALID_VALUE;
64+ }
65+ return ACLBLAS_STATUS_SUCCESS;
66+}
67+ 
68+template <class T>
69+inline aclblasStatus_t ValidateGeamPointers(
70+ const T* alpha, const T* A, const T* beta, const T* B, T* C, const std::string& op_name)
71+{
72+ if (alpha == nullptr) {
73+ OP_LOGE(op_name, "alpha pointer is nullptr");
74+ return ACLBLAS_STATUS_INVALID_VALUE;
75+ }
76+ if (beta == nullptr) {
77+ OP_LOGE(op_name, "beta pointer is nullptr");
78+ return ACLBLAS_STATUS_INVALID_VALUE;
79+ }
80+ if (C == nullptr) {
81+ OP_LOGE(op_name, "C pointer is nullptr");
82+ return ACLBLAS_STATUS_INVALID_VALUE;
83+ }
84+ if (!IsGeamScalarZero(alpha) && A == nullptr) {
85+ OP_LOGE(op_name, "A pointer is nullptr (alpha != 0)");
86+ return ACLBLAS_STATUS_INVALID_VALUE;
87+ }
88+ if (!IsGeamScalarZero(beta) && B == nullptr) {
89+ OP_LOGE(op_name, "B pointer is nullptr (beta != 0)");
90+ return ACLBLAS_STATUS_INVALID_VALUE;
91+ }
92+ return ACLBLAS_STATUS_SUCCESS;
93+}
94+ 
95+template <class T>
96+inline aclblasStatus_t ValidateGeamInplace(
97+ const T* A, const T* B, T* C, int lda, int ldb, int ldc, aclblasOperation_t transa, aclblasOperation_t transb,
98+ const std::string& op_name)
99+{
100+ if (A != nullptr && C == A) {
101+ if (transa != ACLBLAS_OP_N) {
102+ OP_LOGE(op_name, "In-place C==A requires transa==N (got %d)", static_cast<int>(transa));
103+ return ACLBLAS_STATUS_INVALID_VALUE;
104+ }
105+ if (lda != ldc) {
106+ OP_LOGE(op_name, "In-place C==A requires lda==ldc (lda=%d, ldc=%d)", lda, ldc);
107+ return ACLBLAS_STATUS_INVALID_VALUE;
108+ }
109+ }
110+ if (B != nullptr && C == B) {
111+ if (transb != ACLBLAS_OP_N) {
112+ OP_LOGE(op_name, "In-place C==B requires transb==N (got %d)", static_cast<int>(transb));
113+ return ACLBLAS_STATUS_INVALID_VALUE;
114+ }
115+ if (ldb != ldc) {
116+ OP_LOGE(op_name, "In-place C==B requires ldb==ldc (ldb=%d, ldc=%d)", ldb, ldc);
117+ return ACLBLAS_STATUS_INVALID_VALUE;
118+ }
119+ }
120+ return ACLBLAS_STATUS_SUCCESS;
121+}
122+ 
123+template <class T>
124+inline aclblasStatus_t ValidateGeamParams(
125+ const std::string& op_name, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n, const T* alpha,
126+ const T* A, int lda, const T* beta, const T* B, int ldb, T* C, int ldc)
127+{
128+ aclblasStatus_t status;
129+ status = ValidateGeamTranspose(transa, transb, op_name);
130+ if (status != ACLBLAS_STATUS_SUCCESS) {
131+ return status;
132+ }
133+ if (m < 0) {
134+ OP_LOGE(op_name, "m=%d must be >= 0", m);
135+ return ACLBLAS_STATUS_INVALID_VALUE;
136+ }
137+ if (n < 0) {
138+ OP_LOGE(op_name, "n=%d must be >= 0", n);
139+ return ACLBLAS_STATUS_INVALID_VALUE;
140+ }
141+ status = ValidateGeamPointers<T>(alpha, A, beta, B, C, op_name);
142+ if (status != ACLBLAS_STATUS_SUCCESS) {
143+ return status;
144+ }
145+ status = ValidateGeamLd(m, n, lda, ldb, ldc, transa, transb, op_name);
146+ if (status != ACLBLAS_STATUS_SUCCESS) {
147+ return status;
148+ }
149+ return ValidateGeamInplace<T>(A, B, C, lda, ldb, ldc, transa, transb, op_name);
150+}
Ablas/geam/arch35/sgeam_host.cpp+158-0
@@ -0,0 +1,158 @@
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+ * See LICENSE in the root of the software repository for the full text of the License.
8+ */
9+ 
10+/**
11+ * @file sgeam_host.cpp
12+ * @brief Host-side implementation for Sgeam operator (float)
13+ * Handles parameter validation, tiling calculation, and kernel launch
14+ */
15+ 
16+#include "cann_ops_blas.h"
17+#include "geam_host_common.h"
18+ 
19+#include "sgeam_kernel.h"
20+#include "log/log.h"
21+#include "common/helper/aclblas_handle_internal.h"
22+#include "common/helper/host_utils.h"
23+#include "common/helper/kernel_constant.h"
24+#include <algorithm>
25+ 
26+namespace {
27+ 
28+constexpr uint32_t UB_SIZE = 248 * 1024; // 248 KB
29+constexpr uint32_t UB_RESERVE = 256;
30+constexpr uint32_t ALIGN_UNIT = 8; // 32 bytes / sizeof(float)
31+constexpr uint32_t SGEAM_BUFFERS = 5; // 2*bufA(TQue num=2) + 2*bufC(TQue num=2) + bufB(TBuf)
32+// Hardware limit for DataCopyPadExt blockCount parameter.
33+// When curM (rows processed per core) exceeds this, DataCopyPad blockCount
34+// exceeds hardware limit and behavior is undefined.
35+constexpr uint32_t MAX_BLOCK_COUNT = 4095;
36+ 
37+SgeamTilingData CalSgeamTilingData(
38+ aclblasOperation_t transa, aclblasOperation_t transb, uint32_t m, uint32_t n, float alpha, float beta, uint32_t lda,
39+ uint32_t ldb, uint32_t ldc, uint32_t aivCoreNum)
40+{
41+ SgeamTilingData tiling{};
42+ tiling.m = m;
43+ tiling.n = n;
44+ tiling.lda = lda;
45+ tiling.ldb = ldb;
46+ tiling.ldc = ldc;
47+ tiling.alpha = alpha;
48+ tiling.beta = beta;
49+ tiling.alphaIsZero = (alpha == 0.0f) ? 1u : 0u;
50+ tiling.betaIsZero = (beta == 0.0f) ? 1u : 0u;
51+ tiling.opA = transa;
52+ tiling.opB = transb;
53+ 
54+ // Calculate tileM based on UB capacity, capped to MAX_BLOCK_COUNT
55+ // to avoid triggering the kernel batch-loop error path when curM > 4095.
56+ uint32_t maxTileM = (UB_SIZE - UB_RESERVE) / (SGEAM_BUFFERS * sizeof(float));
57+ uint32_t tileM = (maxTileM / ALIGN_UNIT) * ALIGN_UNIT;
58+ tileM = std::min(tileM, MAX_BLOCK_COUNT);
59+ tiling.tileM = tileM;
60+ 
61+ // NN multi-column: max columns per inner iteration that fit in UB
62+ // UB budget: SGEAM_BUFFERS × colsIter × tileM × sizeof(float) ≤ UB_SIZE - UB_RESERVE
63+ uint32_t colsIter = (UB_SIZE - UB_RESERVE) / (SGEAM_BUFFERS * static_cast<uint32_t>(sizeof(float)) * tileM);
64+ if (colsIter < 1u) {
65+ colsIter = 1u;
66+ }
67+ tiling.colsIter = colsIter;
68+ 
69+ // 2D block decomposition: colBlocks x mBlocks
70+ uint32_t colBlocks = std::min(n, aivCoreNum);
71+ if (colBlocks == 0) {
72+ colBlocks = 1;
73+ }
74+ tiling.colBlocks = colBlocks;
75+ tiling.perCoreN = n / colBlocks;
76+ tiling.remainder = n % colBlocks;
77+ 
78+ // Calculate m-blocks
79+ uint32_t totalMTiles = (tiling.tileM > 0) ? ((m + tiling.tileM - 1) / tiling.tileM) : 1;
80+ uint32_t mBlocks = 1;
81+ if (colBlocks < aivCoreNum && totalMTiles > 1) {
82+ uint32_t maxMBlocks = aivCoreNum / colBlocks;
83+ mBlocks = std::min(maxMBlocks, totalMTiles);
84+ }
85+ if (mBlocks == 0) // to suppress 'maybe divide by zero' warning
86+ __builtin_unreachable();
87+ tiling.mBlocks = mBlocks;
88+ tiling.perCoreMTile = totalMTiles / mBlocks;
89+ tiling.mTileRemainder = totalMTiles % mBlocks;
90+ 
91+ return tiling;
92+}
93+ 
94+aclblasStatus_t LaunchSgeamKernel(
95+ aclblasHandle_t handle, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n, const float* alpha,
96+ const float* A, int lda, const float* beta, const float* B, int ldb, float* C, int ldc)
97+{
98+ // Get core count
99+ uint32_t aivCoreNum = GetAivCoreCount();
100+ if (aivCoreNum == 0) {
101+ OP_LOGE("aclblasSgeam", "Failed to get AIV core count");
102+ return ACLBLAS_STATUS_INTERNAL_ERROR;
103+ }
104+ 
105+ float alphaVal = *alpha;
106+ float betaVal = *beta;
107+ 
108+ // Calculate tiling
109+ SgeamTilingData tiling = CalSgeamTilingData(
110+ transa, transb, static_cast<uint32_t>(m), static_cast<uint32_t>(n), alphaVal, betaVal, static_cast<uint32_t>(lda),
111+ static_cast<uint32_t>(ldb), static_cast<uint32_t>(ldc), aivCoreNum);
112+ 
113+ // Calculate total blocks
114+ uint32_t numBlocks = tiling.colBlocks * tiling.mBlocks;
115+ if (numBlocks == 0) {
116+ numBlocks = 1;
117+ }
118+ 
119+ OP_LOGD(
120+ "aclblasSgeam", "Tiling: tileM=%u, colBlocks=%u, mBlocks=%u, totalBlocks=%u", tiling.tileM, tiling.colBlocks,
121+ tiling.mBlocks, numBlocks);
122+ 
123+ // Convert pointers to GM_ADDR. When alpha/beta value is 0, A/B may be nullptr;
124+ // use C as a dummy valid GM address to avoid hardware issues with null descriptors.
125+ // (Kernel skips reading A/B based on alphaIsZero/betaIsZero tiling flags.)
126+ GM_ADDR gmC = reinterpret_cast<GM_ADDR>(C);
127+ GM_ADDR gmA = (A != nullptr) ? reinterpret_cast<GM_ADDR>(const_cast<float*>(A)) : gmC;
J
Jjustsheldon10 天前

为将 const float* 转为 GM_ADDR 做 const_cast。kernel 只读 A(alpha 非 0 时),运行时安全,但 const_cast 是代码异味。如果 GM_ADDR 能支持 const 限定会更规范。

likedislike
128+ GM_ADDR gmB = (B != nullptr) ? reinterpret_cast<GM_ADDR>(const_cast<float*>(B)) : gmC;
129+ 
130+ // Launch kernel
131+ sgeam_kernel_do(gmA, gmB, gmC, tiling, numBlocks, handle->stream);
132+ 
133+ return ACLBLAS_STATUS_SUCCESS;
134+}
135+ 
136+} // namespace
137+ 
138+extern "C" aclblasStatus_t aclblasSgeam(
139+ aclblasHandle_t handle, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n, const float* alpha,
140+ const float* A, int lda, const float* beta, const float* B, int ldb, float* C, int ldc)
141+{
142+ if (handle == nullptr) {
143+ OP_LOGE("aclblasSgeam", "handle is nullptr");
144+ return ACLBLAS_STATUS_HANDLE_IS_NULLPTR;
145+ }
146+ 
147+ aclblasStatus_t status =
148+ ValidateGeamParams("aclblasSgeam", transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc);
149+ if (status != ACLBLAS_STATUS_SUCCESS) {
150+ return status;
151+ }
152+ 
153+ if (m == 0 || n == 0) {
154+ return ACLBLAS_STATUS_SUCCESS;
155+ }
156+ 
157+ return LaunchSgeamKernel(handle, transa, transb, m, n, alpha, A, lda, beta, B, ldb, C, ldc);
158+}
Ablas/geam/arch35/sgeam_kernel.cpp+445-0
@@ -0,0 +1,445 @@
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 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+ * See LICENSE in the root of the software repository for the full text of the License.
8+ */
9+ 
10+/**
11+ * @file sgeam_kernel.cpp
12+ * @brief Sgeam kernel implementation (SIMD/membase, arch35)
13+ * Computes: C = alpha * op(A) + beta * op(B)
14+ *
15+ * Architecture: TPipe + TQue<VECIN>/TQue<VECOUT> + TBuf<VECCALC>
16+ * EnQue/DeQue provides automatic MTE <-> Vector synchronization.
17+ *
18+ * Buffer planning:
19+ * - inQueue_ (VECIN): A tile data, managed by TQue for MTE2<->V sync
20+ * - outQueue_ (VECOUT): C tile data, managed by TQue for V<->MTE3 sync
21+ * - calcBuf_ (VECCALC): Scratch for B data when beta != 0; unused otherwise
22+ *
23+ * Two dispatch paths:
24+ * PATH A: NN multi-column (colsIter > 1), strided DataCopyPad for NC columns
25+ * PATH B: Single-column / Trans / ConjTrans
26+ *
27+ * Multi-core decomposition: 2D grid (colBlocks x mBlocks)
28+ */
29+ 
30+#include "kernel_operator.h"
31+#include "sgeam_kernel.h"
32+#include "sgeam_tiling_data.h"
33+ 
34+namespace {
35+ 
36+using namespace AscendC;
37+ 
38+// ============================================================================
39+// SgeamAIV: operator class for Sgeam vector kernel
40+// ============================================================================
41+class SgeamAIV {
42+public:
43+ __aicore__ inline SgeamAIV() {}
44+ __aicore__ inline void Init(GM_ADDR A, GM_ADDR B, GM_ADDR C, const SgeamTilingData& tiling, TPipe* pipe);
45+ __aicore__ inline void Process();
46+ 
47+private:
48+ __aicore__ inline void ProcessPathA();
49+ __aicore__ inline void ProcessPathB();
50+ __aicore__ inline void ComputeCoreRanges(const SgeamTilingData& tiling);
51+ 
52+ // PATH A helpers (NN multi-column)
53+ __aicore__ inline void CopyInA_PathA(uint32_t col, uint32_t rowStart, uint32_t curM, uint32_t actualNC);
54+ __aicore__ inline void CopyInB_PathA(uint32_t col, uint32_t rowStart, uint32_t curM, uint32_t actualNC);
55+ __aicore__ inline void Compute_PathA(uint32_t curM, uint32_t actualNC);
56+ __aicore__ inline void CopyOutC_PathA(uint32_t col, uint32_t rowStart, uint32_t curM, uint32_t actualNC);
57+ 
58+ // PATH B helpers (single-column / Trans / ConjTrans)
59+ __aicore__ inline void CopyInA_PathB(uint32_t col, uint32_t rowStart, uint32_t curM, int64_t srcStrideA);
60+ __aicore__ inline void CopyInB_PathB(uint32_t col, uint32_t rowStart, uint32_t curM, int64_t srcStrideB);
61+ __aicore__ inline void Compute_PathB(uint32_t curM);
62+ __aicore__ inline void CopyOutC_PathB(uint32_t col, uint32_t rowStart, uint32_t curM);
63+ 
64+ // GM addresses
65+ GlobalTensor<float> aGm_;
66+ GlobalTensor<float> bGm_;
67+ GlobalTensor<float> cGm_;
68+ 
69+ // Queues & buffer
70+ TQue<TPosition::VECIN, 2> inQueue_; // MTE2 input for A (EnQue signals MTE2 done)
71+ TQue<TPosition::VECOUT, 2> outQueue_; // Vector output for C (EnQue signals Vector done)
72+ TBuf<TPosition::VECCALC> calcBuf_; // Scratch for B data (no MTE sync needed)
73+ 
74+ // Tiling fields cached from SgeamTilingData
75+ uint32_t m_;
76+ uint32_t n_;
77+ 
78+ uint32_t alphaIsZero_;
79+ float alpha_;
80+ aclblasOperation_t opA_;
81+ uint32_t lda_;
82+ 
83+ uint32_t betaIsZero_;
84+ float beta_;
85+ aclblasOperation_t opB_;
86+ uint32_t ldb_;
87+ 
88+ uint32_t ldc_;
89+ uint32_t tileM_;
90+ uint32_t colsIter_;
91+ 
92+ // Per-core tile ranges
93+ uint32_t colStart_;
94+ uint32_t colEnd_;
95+ uint32_t mTileStart_;
96+ uint32_t mTileEnd_;
97+};
98+ 
99+// ============================================================================
100+// ComputeCoreRanges: decode 2D block index into per-core column and m-tile ranges.
101+// On invalid range, sets colEnd_ or mTileEnd_ to 0 to signal early exit in Process().
102+// ============================================================================
103+__aicore__ inline void SgeamAIV::ComputeCoreRanges(const SgeamTilingData& tiling)
104+{
105+ uint32_t blockIdx = GetBlockIdx();
106+ uint32_t mBlocks = tiling.mBlocks;
107+ uint32_t colBlock = blockIdx / mBlocks;
108+ uint32_t mBlock = blockIdx % mBlocks;
109+ 
110+ // Calculate column range
111+ if (colBlock < tiling.remainder) {
112+ colStart_ = colBlock * (tiling.perCoreN + 1);
113+ colEnd_ = colStart_ + tiling.perCoreN + 1;
114+ } else {
115+ colStart_ = colBlock * tiling.perCoreN + tiling.remainder;
116+ colEnd_ = colStart_ + tiling.perCoreN;
117+ }
118+ 
119+ // Validate column range
120+ if (colStart_ >= n_ || colStart_ >= colEnd_) {
121+ colEnd_ = 0; // Signal Process() to return early
122+ return;
123+ }
124+ if (colEnd_ > n_) {
125+ colEnd_ = n_;
126+ }
127+ 
128+ // Calculate m-tile range
129+ if (mBlock < tiling.mTileRemainder) {
130+ mTileStart_ = mBlock * (tiling.perCoreMTile + 1);
131+ mTileEnd_ = mTileStart_ + tiling.perCoreMTile + 1;
132+ } else {
133+ mTileStart_ = mBlock * tiling.perCoreMTile + tiling.mTileRemainder;
134+ mTileEnd_ = mTileStart_ + tiling.perCoreMTile;
135+ }
136+ 
137+ // Validate m-tile range
138+ uint32_t totalMTiles = (m_ + tileM_ - 1) / tileM_;
139+ if (mTileStart_ >= totalMTiles || mTileStart_ >= mTileEnd_) {
140+ mTileEnd_ = 0; // Signal Process() to return early
141+ return;
142+ }
143+ if (mTileEnd_ > totalMTiles) {
144+ mTileEnd_ = totalMTiles;
145+ }
146+}
147+ 
148+// ============================================================================
149+// Init: setup global tensors, compute per-core ranges, allocate UB buffers.
150+// TPipe pointer passed from kernel entry (R3: TPipe must not be a member).
151+// ============================================================================
152+__aicore__ inline void SgeamAIV::Init(GM_ADDR A, GM_ADDR B, GM_ADDR C, const SgeamTilingData& tiling, TPipe* pipe)
153+{
154+ // Cache tiling fields to member variables
155+ m_ = tiling.m;
156+ n_ = tiling.n;
157+ 
158+ alphaIsZero_ = tiling.alphaIsZero;
159+ opA_ = tiling.opA;
160+ lda_ = tiling.lda;
161+ alpha_ = tiling.alpha;
162+ 
163+ betaIsZero_ = tiling.betaIsZero;
164+ ldb_ = tiling.ldb;
165+ opB_ = tiling.opB;
166+ beta_ = tiling.beta;
167+ 
168+ ldc_ = tiling.ldc;
169+ tileM_ = tiling.tileM;
170+ colsIter_ = tiling.colsIter;
171+ 
172+ // Set GM buffers
173+ aGm_.SetGlobalBuffer(reinterpret_cast<__gm__ float*>(A));
174+ bGm_.SetGlobalBuffer(reinterpret_cast<__gm__ float*>(B));
175+ cGm_.SetGlobalBuffer(reinterpret_cast<__gm__ float*>(C));
176+ 
177+ // Compute per-core column and m-tile ranges
178+ ComputeCoreRanges(tiling);
179+ if (colEnd_ == 0 || mTileEnd_ == 0) {
180+ return;
181+ }
182+ 
183+ // Buffer size: PATH A needs colsIter * tileM elements, PATH B needs tileM elements.
184+ // Allocate the maximum (colsIter >= 1, so colsIter * tileM >= tileM).
185+ uint32_t bufElems = colsIter_ * tileM_;
J
Jjustsheldon10 天前

所有 buffer 按 colsIter*tileM 分配(PathA 需要),但 PathB 只处理单列,用 tileM 即可。PathB 执行时 buffer 大部分空闲。如果 PathB 是常见路径(非 NN 场景),可考虑按路径动态分配。

likedislike
186+ uint32_t bufSize = bufElems * sizeof(float);
187+ 
188+ // Double-buffer queues (num=2) enable MTE2<->Vector pipeline overlap
189+ pipe->InitBuffer(inQueue_, 2, bufSize);
190+ pipe->InitBuffer(outQueue_, 2, bufSize);
191+ pipe->InitBuffer(calcBuf_, bufSize);
192+}
193+ 
194+// ============================================================================
195+// Process: dispatch to PATH A or PATH B based on op and colsIter
196+// ============================================================================
197+__aicore__ inline void SgeamAIV::Process()
198+{
199+ if (colStart_ >= colEnd_ || mTileStart_ >= mTileEnd_) {
200+ return;
201+ }
202+ 
203+ if (opA_ == ACLBLAS_OP_N && opB_ == ACLBLAS_OP_N && colsIter_ > 1) {
204+ ProcessPathA();
205+ } else {
206+ ProcessPathB();
207+ }
208+}
209+ 
210+// ============================================================================
211+// CopyInA_PathA: strided DataCopyPad loads NC columns of A at once.
212+// ============================================================================
213+__aicore__ inline void SgeamAIV::CopyInA_PathA(uint32_t col, uint32_t rowStart, uint32_t curM, uint32_t actualNC)
214+{
215+ LocalTensor<float> bufA = inQueue_.AllocTensor<float>();
216+ if (!alphaIsZero_) {
217+ const uint64_t baseOff = static_cast<uint64_t>(col) * static_cast<uint64_t>(lda_) + rowStart;
218+ const int64_t colGap = static_cast<int64_t>(lda_ - curM) * static_cast<int64_t>(sizeof(float));
219+ DataCopyExtParams cpA{
220+ static_cast<uint16_t>(actualNC), static_cast<uint32_t>(curM * sizeof(float)), colGap, 0, 0};
221+ DataCopyPadExtParams<float> npA;
222+ DataCopyPad<float, PaddingMode::Compact>(bufA, aGm_[baseOff], cpA, npA);
223+ }
224+ inQueue_.EnQue(bufA); // Signal MTE2 done for A
225+}
226+ 
227+// ============================================================================
228+// CopyInB_PathA: strided DataCopyPad loads NC columns of B when beta != 0.
229+// ============================================================================
230+__aicore__ inline void SgeamAIV::CopyInB_PathA(uint32_t col, uint32_t rowStart, uint32_t curM, uint32_t actualNC)
231+{
232+ if (!betaIsZero_) {
233+ LocalTensor<float> ubB = calcBuf_.Get<float>();
234+ const uint64_t baseOff = static_cast<uint64_t>(col) * static_cast<uint64_t>(ldb_) + rowStart;
235+ const int64_t colGap = static_cast<int64_t>(ldb_ - curM) * static_cast<int64_t>(sizeof(float));
236+ DataCopyExtParams cpB{
237+ static_cast<uint16_t>(actualNC), static_cast<uint32_t>(curM * sizeof(float)), colGap, 0, 0};
238+ DataCopyPadExtParams<float> npB;
239+ DataCopyPad<float, PaddingMode::Compact>(ubB, bGm_[baseOff], cpB, npB);
xutianze
xutianzexutianze10 天前

B 数据经 DataCopyPad 写入 TBuf calcBuf_ 后未用 EnQue/DeQue 或 SetFlag/WaitFlag 建立 MTE2→V 同步,向量读取 calcBuf_ 依赖隐式时序保障(实机大矩阵用例未复现错误),后续调整流水或平台时序变化时易引入数据竞争

likedislike
240+ event_t eMte2V_ = static_cast<event_t>(GetTPipePtr()->FetchEventID(HardEvent::MTE2_V));
241+ SetFlag<HardEvent::MTE2_V>(eMte2V_);
242+ WaitFlag<HardEvent::MTE2_V>(eMte2V_);
243+ }
244+}
245+ 
246+// ============================================================================
247+// Compute_PathA: C = alpha*A + beta*B for the multi-column PATH A case.
248+// ============================================================================
249+__aicore__ inline void SgeamAIV::Compute_PathA(uint32_t curM, uint32_t actualNC)
250+{
251+ const uint32_t totalElems = actualNC * curM;
252+ LocalTensor<float> aIn = inQueue_.DeQue<float>();
253+ LocalTensor<float> cOut = outQueue_.AllocTensor<float>();
254+ 
255+ if (alphaIsZero_ && betaIsZero_) {
256+ Duplicate<float>(cOut, 0.0f, totalElems);
257+ } else if (alphaIsZero_) {
258+ LocalTensor<float> ubB = calcBuf_.Get<float>();
259+ Muls<float>(cOut, ubB, beta_, totalElems);
260+ } else if (betaIsZero_) {
261+ Muls<float>(cOut, aIn, alpha_, totalElems);
262+ } else {
263+ LocalTensor<float> ubB = calcBuf_.Get<float>();
264+ Muls<float>(cOut, aIn, alpha_, totalElems);
265+ // Reuse aIn as scratch for beta*B to save a buffer
266+ Muls<float>(aIn, ubB, beta_, totalElems);
J
Jjustsheldon10 天前

直接覆写 aIn(从 inQueue_ DeQue 出来的 A buffer)为 beta*B,然后 Add(cOut, cOut, aIn)。虽然逻辑正确且节省一个 scratch buffer,但覆写输入 buffer 的写法容易让读者困惑。建议加一行注释说明此处是故意复用 aIn 作为临时存储。

likedislike
267+ Add<float>(cOut, cOut, aIn, totalElems);
268+ }
269+ outQueue_.EnQue(cOut); // Signal Vector done
270+ inQueue_.FreeTensor(aIn);
271+}
272+ 
273+// ============================================================================
274+// CopyOutC_PathA: strided DataCopyPad writes NC columns of C.
275+// ============================================================================
276+__aicore__ inline void SgeamAIV::CopyOutC_PathA(uint32_t col, uint32_t rowStart, uint32_t curM, uint32_t actualNC)
277+{
278+ LocalTensor<float> cFinal = outQueue_.DeQue<float>();
279+ const uint64_t baseOff = static_cast<uint64_t>(col) * static_cast<uint64_t>(ldc_) + rowStart;
280+ const int64_t colGap = static_cast<int64_t>(ldc_ - curM) * static_cast<int64_t>(sizeof(float));
281+ DataCopyExtParams cpS{static_cast<uint16_t>(actualNC), static_cast<uint32_t>(curM * sizeof(float)), 0, colGap, 0};
282+ DataCopyPad<float, PaddingMode::Compact>(cGm_[baseOff], cFinal, cpS);
283+ outQueue_.FreeTensor(cFinal);
284+}
285+ 
286+// ============================================================================
287+// PATH A: NN multi-column — strided DataCopyPad loads NC columns at once
288+// Each tile: CopyIn A -> EnQue -> CopyIn B -> Compute -> EnQue C -> CopyOut C
289+// ============================================================================
290+__aicore__ inline void SgeamAIV::ProcessPathA()
291+{
292+ for (uint32_t col = colStart_; col < colEnd_; col += colsIter_) {
293+ const uint32_t actualNC = ((colEnd_ - col) < colsIter_) ? (colEnd_ - col) : colsIter_;
294+ for (uint32_t mTile = mTileStart_; mTile < mTileEnd_; ++mTile) {
295+ const uint32_t rowStart = mTile * tileM_;
296+ uint32_t rowEnd = rowStart + tileM_;
297+ if (rowEnd > m_) {
298+ rowEnd = m_;
299+ }
300+ const uint32_t curM = rowEnd - rowStart;
301+ 
302+ CopyInA_PathA(col, rowStart, curM, actualNC);
303+ CopyInB_PathA(col, rowStart, curM, actualNC);
304+ Compute_PathA(curM, actualNC);
J
Jjustsheldon10 天前

calcBuf_ 是 TBuf,CopyInB_PathA 通过 DataCopyPad(MTE2)将 B 数据搬入 calcBuf_,但 Compute_PathA 中 Vector 直接读取 calcBuf_ 时缺少 MTE2→V 同步。inQueue_ 的 EnQue 只同步了 A 的 MTE2,不覆盖 B 的 DataCopyPad(B 的 MTE2 指令在 EnQue 之后发射)。对照仓库 sdgmm_kernel.cpp:71-73,TBuf+DataCopyPad 后需显式 SetFlag/WaitFlag<MTE2_V> 或 PipeBarrier<PIPE_MTE2_V>。PathB 的 Compute_PathB 存在同样问题。

likedislike
305+ CopyOutC_PathA(col, rowStart, curM, actualNC);
306+ }
307+ }
308+}
309+ 
310+// ============================================================================
311+// CopyInA_PathB: single-shot for NN, strided Compact for T/C (one column).
312+// ============================================================================
313+__aicore__ inline void SgeamAIV::CopyInA_PathB(uint32_t col, uint32_t rowStart, uint32_t curM, int64_t srcStrideA)
314+{
315+ LocalTensor<float> bufA = inQueue_.AllocTensor<float>();
316+ if (!alphaIsZero_) {
317+ if (opA_ == ACLBLAS_OP_N) {
318+ const uint64_t offset = static_cast<uint64_t>(col) * static_cast<uint64_t>(lda_) + rowStart;
319+ DataCopyExtParams cp{1, static_cast<uint32_t>(curM * sizeof(float)), 0, 0, 0};
320+ DataCopyPadExtParams<float> np;
321+ DataCopyPad(bufA, aGm_[offset], cp, np);
322+ } else {
323+ const uint64_t baseOff =
324+ static_cast<uint64_t>(col) + static_cast<uint64_t>(rowStart) * static_cast<uint64_t>(lda_);
325+ DataCopyExtParams cp{static_cast<uint16_t>(curM), static_cast<uint32_t>(sizeof(float)), srcStrideA, 0, 0};
326+ DataCopyPadExtParams<float> np;
327+ DataCopyPad<float, PaddingMode::Compact>(bufA, aGm_[baseOff], cp, np);
328+ }
329+ }
330+ inQueue_.EnQue(bufA); // Signal MTE2 done for A
331+}
332+ 
333+// ============================================================================
334+// CopyInB_PathB: single-shot for NN, strided Compact for T/C (one column).
335+// ============================================================================
336+__aicore__ inline void SgeamAIV::CopyInB_PathB(uint32_t col, uint32_t rowStart, uint32_t curM, int64_t srcStrideB)
337+{
338+ if (!betaIsZero_) {
339+ LocalTensor<float> ubB = calcBuf_.Get<float>();
340+ if (opB_ == ACLBLAS_OP_N) {
341+ const uint64_t offset = static_cast<uint64_t>(col) * static_cast<uint64_t>(ldb_) + rowStart;
342+ DataCopyExtParams cp{1, static_cast<uint32_t>(curM * sizeof(float)), 0, 0, 0};
343+ DataCopyPadExtParams<float> np;
344+ DataCopyPad(ubB, bGm_[offset], cp, np);
345+ } else {
346+ const uint64_t baseOff =
347+ static_cast<uint64_t>(col) + static_cast<uint64_t>(rowStart) * static_cast<uint64_t>(ldb_);
348+ DataCopyExtParams cp{static_cast<uint16_t>(curM), static_cast<uint32_t>(sizeof(float)), srcStrideB, 0, 0};
349+ DataCopyPadExtParams<float> np;
350+ DataCopyPad<float, PaddingMode::Compact>(ubB, bGm_[baseOff], cp, np);
351+ }
352+ event_t eMte2V_ = static_cast<event_t>(GetTPipePtr()->FetchEventID(HardEvent::MTE2_V));
353+ SetFlag<HardEvent::MTE2_V>(eMte2V_);
354+ WaitFlag<HardEvent::MTE2_V>(eMte2V_);
355+ }
356+}
357+ 
358+// ============================================================================
359+// Compute_PathB: C = alpha*A + beta*B for the single-column PATH B case.
360+// ============================================================================
361+__aicore__ inline void SgeamAIV::Compute_PathB(uint32_t curM)
362+{
363+ LocalTensor<float> aIn = inQueue_.DeQue<float>();
364+ LocalTensor<float> cOut = outQueue_.AllocTensor<float>();
365+ 
366+ if (alphaIsZero_ && betaIsZero_) {
367+ Duplicate<float>(cOut, 0.0f, curM);
368+ } else if (alphaIsZero_) {
369+ LocalTensor<float> ubB = calcBuf_.Get<float>();
370+ Muls<float>(cOut, ubB, beta_, curM);
371+ } else if (betaIsZero_) {
372+ Muls<float>(cOut, aIn, alpha_, curM);
373+ } else {
374+ LocalTensor<float> ubB = calcBuf_.Get<float>();
375+ Muls<float>(cOut, aIn, alpha_, curM);
376+ // Reuse aIn as scratch for beta*B to save a buffer
377+ Muls<float>(aIn, ubB, beta_, curM);
378+ Add<float>(cOut, cOut, aIn, curM);
379+ }
380+ outQueue_.EnQue(cOut); // Signal Vector done
381+ inQueue_.FreeTensor(aIn);
382+}
383+ 
384+// ============================================================================
385+// CopyOutC_PathB: single-shot DataCopyPad writes one column of C.
386+// ============================================================================
387+__aicore__ inline void SgeamAIV::CopyOutC_PathB(uint32_t col, uint32_t rowStart, uint32_t curM)
388+{
389+ LocalTensor<float> cFinal = outQueue_.DeQue<float>();
390+ const uint64_t offset = static_cast<uint64_t>(col) * static_cast<uint64_t>(ldc_) + rowStart;
391+ DataCopyExtParams cp{1, static_cast<uint32_t>(curM * sizeof(float)), 0, 0, 0};
392+ DataCopyPad(cGm_[offset], cFinal, cp);
393+ outQueue_.FreeTensor(cFinal);
394+}
395+ 
396+// ============================================================================
397+// PATH B: Single-column / Trans / ConjTrans — one column per iteration
398+// Each tile: CopyIn A -> EnQue -> CopyIn B -> Compute -> EnQue C -> CopyOut C
399+// ============================================================================
400+__aicore__ inline void SgeamAIV::ProcessPathB()
401+{
402+ const int64_t srcStrideA = static_cast<int64_t>(lda_ - 1) * static_cast<int64_t>(sizeof(float));
403+ const int64_t srcStrideB = static_cast<int64_t>(ldb_ - 1) * static_cast<int64_t>(sizeof(float));
404+ 
405+ for (uint32_t col = colStart_; col < colEnd_; ++col) {
406+ for (uint32_t mTile = mTileStart_; mTile < mTileEnd_; ++mTile) {
407+ const uint32_t rowStart = mTile * tileM_;
408+ uint32_t rowEnd = rowStart + tileM_;
409+ if (rowEnd > m_) {
410+ rowEnd = m_;
411+ }
412+ const uint32_t curM = rowEnd - rowStart;
413+ 
414+ CopyInA_PathB(col, rowStart, curM, srcStrideA);
415+ CopyInB_PathB(col, rowStart, curM, srcStrideB);
416+ Compute_PathB(curM);
417+ CopyOutC_PathB(col, rowStart, curM);
418+ }
419+ }
420+}
421+ 
422+} // namespace
423+ 
424+// ============================================================================
425+// Kernel entry point
426+// ============================================================================
427+extern "C" __global__ __aicore__ void sgeam_kernel(GM_ADDR A, GM_ADDR B, GM_ADDR C, SgeamTilingData tiling)
428+{
429+ KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY);
430+ 
431+ if (tiling.m == 0 || tiling.n == 0) {
432+ return;
433+ }
434+ 
435+ TPipe pipe;
436+ SgeamAIV op;
437+ op.Init(A, B, C, tiling, &pipe);
438+ op.Process();
439+}
440+ 
441+void sgeam_kernel_do(
442+ GM_ADDR A, GM_ADDR B, GM_ADDR C, const SgeamTilingData& tiling, uint32_t numBlocks, aclrtStream stream)
443+{
444+ sgeam_kernel<<<numBlocks, nullptr, stream>>>(A, B, C, tiling);
445+}
Ablas/geam/arch35/sgeam_kernel.h+37-0
@@ -0,0 +1,37 @@
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 sgeam_kernel.h
13+ * @brief Kernel launcher declaration for Sgeam operator
14+ */
15+ 
16+#pragma once
17+ 
18+#include <cstdint>
19+#include "acl/acl_base_rt.h"
20+#include "sgeam_tiling_data.h"
21+ 
22+#ifndef GM_ADDR
23+#define GM_ADDR uint8_t*
24+#endif
25+ 
26+/**
27+ * @brief Launch Sgeam kernel asynchronously
28+ *
29+ * @param A GM address of matrix A
30+ * @param B GM address of matrix B
31+ * @param C GM address of matrix C (output)
32+ * @param tiling Host-computed tiling data (copied to device by value)
33+ * @param numBlocks Block count for the <<<>>> launch
34+ * @param stream aclrtStream handle
35+ */
36+void sgeam_kernel_do(
37+ GM_ADDR A, GM_ADDR B, GM_ADDR C, const SgeamTilingData& tiling, uint32_t numBlocks, aclrtStream stream);
Ablas/geam/arch35/sgeam_tiling_data.h+44-0
@@ -0,0 +1,44 @@
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 sgeam_tiling_data.h
13+ * @brief Tiling data structure for Sgeam operator (host/device shared)
14+ * C = alpha * op(A) + beta * op(B)
15+ */
16+ 
17+#pragma once
18+ 
19+#include <cstdint>
20+#include "cann_ops_blas_common.h"
21+ 
22+struct SgeamTilingData {
23+ uint32_t m; // Number of rows in output matrix C
24+ uint32_t n; // Number of columns in output matrix C
25+ float alpha; // Scalar alpha
26+ uint32_t alphaIsZero; // Optimized flag indicating alpha == 0
27+ aclblasOperation_t opA; // Transpose type for matrix A
28+ uint32_t lda; // Leading dimension of matrix A
29+ float beta; // Scalar beta
30+ uint32_t betaIsZero; // Optimized flag indicating beta == 0
31+ aclblasOperation_t opB; // Transpose type for matrix B
32+ uint32_t ldb; // Leading dimension of matrix B
33+ uint32_t ldc; // Leading dimension of matrix C
34+ // 2D multi-core tiling: colBlocks x mBlocks
35+ uint32_t colBlocks; // Number of column blocks
36+ uint32_t perCoreN; // Number of columns processed per core
37+ uint32_t remainder; // Column remainder
38+ uint32_t mBlocks; // Number of row blocks
39+ uint32_t perCoreMTile; // Number of tiles processed per core in m dimension
40+ uint32_t mTileRemainder; // Tile count remainder in m dimension
41+ uint32_t tileM; // Tile size in m dimension (number of elements)
42+ uint32_t colsIter; // NN path: columns processed per inner iteration (multi-column)
43+ uint32_t reserved; // Reserved for 8-byte alignment
44+};
J
Jjustsheldon10 天前

结构体共 19 个 4 字节字段,总大小 76 字节,不是 8 的倍数。cgeam_tiling_data.h 是 80 字节(8 对齐)。建议在末尾补一个 uint32_t 保留字段使大小对齐到 80 字节,与 cgeam 保持一致。

likedislike
Mbuild.sh+0-0
The file is empty
Mcmake/test.cmake+3-3
@@ -186,9 +186,9 @@ function(_ops_blas_register_test_target target link_lib)
186 ${CMAKE_SOURCE_DIR}/test/utils186 ${CMAKE_SOURCE_DIR}/test/utils
187 ${CMAKE_CURRENT_SOURCE_DIR}187 ${CMAKE_CURRENT_SOURCE_DIR}
188 ${CMAKE_SOURCE_DIR}/blas/common/helper188 ${CMAKE_SOURCE_DIR}/blas/common/helper
189- ${ASCEND_CANN_PACKAGE_PATH}/pkg_inc/op_common/ 189+ ${ASCEND_CANN_PACKAGE_PATH}/pkg_inc/op_common/
190- ${ASCEND_CANN_PACKAGE_PATH}/pkg_inc/base/ 190+ ${ASCEND_CANN_PACKAGE_PATH}/pkg_inc/base/
191- ${ASCEND_CANN_PACKAGE_PATH}/pkg_inc/ 191+ ${ASCEND_CANN_PACKAGE_PATH}/pkg_inc/
192 $ENV{LINUX_INCLUDE_PATH}192 $ENV{LINUX_INCLUDE_PATH}
193 ${REFBLAS_INCLUDE_DIR}193 ${REFBLAS_INCLUDE_DIR}
194 )194 )
Mdocs/zh/api_list.md+2-0
@@ -892,6 +892,8 @@ BLAS-like Extension 提供标准 BLAS Level 3 之外的扩展 GEMM 接口(以
892 892 
893| 接口名 | 说明 |893| 接口名 | 说明 |
894|---|---|894|---|---|
895+| [aclblasSgeam](../../blas/geam/README.md) | 单精度矩阵加法:C = alpha * op(A) + beta * op(B) |
896+| [aclblasCgeam](../../blas/geam/README.md) | 复数矩阵加法:C = alpha * op(A) + beta * op(B) |
895| [aclblasGemmEx](../../blas/gemm_ex/README.md) | 通用矩阵乘法扩展接口,支持 A/B/C 独立数据类型 |897| [aclblasGemmEx](../../blas/gemm_ex/README.md) | 通用矩阵乘法扩展接口,支持 A/B/C 独立数据类型 |
896| [aclblasGemmBatchedEx](../../blas/gemm_batched_ex/README.md) | 通用矩阵乘法批量扩展接口 |898| [aclblasGemmBatchedEx](../../blas/gemm_batched_ex/README.md) | 通用矩阵乘法批量扩展接口 |
897| [aclblasGemmGroupedBatchedEx](../../blas/gemm_grouped_batched_ex/README.md) | 通用矩阵乘法分组批量扩展接口 |899| [aclblasGemmGroupedBatchedEx](../../blas/gemm_grouped_batched_ex/README.md) | 通用矩阵乘法分组批量扩展接口 |
Minclude/cann_ops_blas.h+16-0
@@ -478,6 +478,22 @@ aclblasStatus_t aclblasCdgmm(
478 const aclblasComplex* A, int lda, const aclblasComplex* x, int incx,478 const aclblasComplex* A, int lda, const aclblasComplex* x, int incx,
479 aclblasComplex* C, int ldc);479 aclblasComplex* C, int ldc);
480 480 
481+aclblasStatus_t aclblasSgeam(
482+ aclblasHandle_t handle,
483+ aclblasOperation_t transa, aclblasOperation_t transb,
484+ int m, int n,
485+ const float* alpha, const float* A, int lda,
486+ const float* beta, const float* B, int ldb,
487+ float* C, int ldc);
488+ 
489+aclblasStatus_t aclblasCgeam(
490+ aclblasHandle_t handle,
491+ aclblasOperation_t transa, aclblasOperation_t transb,
492+ int m, int n,
493+ const aclblasComplex* alpha, const aclblasComplex* A, int lda,
494+ const aclblasComplex* beta, const aclblasComplex* B, int ldb,
495+ aclblasComplex* C, int ldc);
496+ 
481aclblasStatus_t aclblasSgemm3m(497aclblasStatus_t aclblasSgemm3m(
482 aclblasHandle handle, aclblasOperation_t transA, aclblasOperation_t transB, int m, int n, int k,498 aclblasHandle handle, aclblasOperation_t transA, aclblasOperation_t transB, int m, int n, int k,
483 const float* alpha, const float* A, int lda,499 const float* alpha, const float* A, int lda,
Atest/geam/cgeam/CMakeLists.txt+11-0
@@ -0,0 +1,11 @@
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 FILE 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+ops_blas_add_gtest_tests(${OPS_BLAS} cgeam_test)
Atest/geam/cgeam/arch35/cgeam_npu_wrapper.h+115-0
@@ -0,0 +1,115 @@
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+#pragma once
12+ 
13+#include <algorithm>
14+#include <cstdint>
15+#include <memory>
16+#include <vector>
17+ 
18+#include "acl/acl.h"
19+#include "cann_ops_blas.h"
20+#include "device.h"
21+#include "fill.h"
22+ 
23+inline size_t CgeamBufferBytes(aclblasOperation_t trans, int m, int n, int ld)
24+{
25+ size_t cols = (trans == ACLBLAS_OP_N) ? static_cast<size_t>(n) : static_cast<size_t>(m);
26+ return static_cast<size_t>(ld) * cols * sizeof(aclblasComplex);
27+}
28+ 
29+inline std::unique_ptr<DeviceBuffer> CgeamAllocAndCopy(const aclblasComplex* hostPtr, size_t bytes)
30+{
31+ if (hostPtr == nullptr) {
32+ return nullptr;
33+ }
34+ auto buf = std::make_unique<DeviceBuffer>(bytes);
35+ buf->copyFromHost(hostPtr, bytes);
36+ return buf;
37+}
38+ 
39+inline std::unique_ptr<DeviceBuffer> CgeamAllocOutput(size_t cBytes)
40+{
41+ auto buf = std::make_unique<DeviceBuffer>(cBytes);
42+ std::vector<float> sentinel(cBytes / sizeof(float), kBlasSentinel);
43+ buf->copyFromHost(sentinel.data(), cBytes);
44+ return buf;
45+}
46+ 
47+inline aclblasComplex* CgeamGetCPtr(bool inplaceAC, bool inplaceBC, DeviceBuffer* dA, DeviceBuffer* dB, DeviceBuffer* dC)
48+{
49+ if (inplaceAC) {
50+ return static_cast<aclblasComplex*>(dA->ptr());
51+ }
52+ if (inplaceBC) {
53+ return static_cast<aclblasComplex*>(dB->ptr());
54+ }
55+ return static_cast<aclblasComplex*>(dC->ptr());
56+}
57+ 
58+inline void CgeamCopyBack(bool inplaceAC, bool inplaceBC, DeviceBuffer* dA, DeviceBuffer* dB, DeviceBuffer* dC,
59+ aclblasComplex* C, size_t aBytes, size_t bBytes, size_t cBytes)
60+{
61+ if (inplaceAC && dA) {
62+ dA->copyToHost(C, aBytes);
63+ } else if (inplaceBC && dB) {
64+ dB->copyToHost(C, bBytes);
65+ } else if (dC) {
66+ dC->copyToHost(C, cBytes);
67+ }
68+}
69+ 
70+inline aclblasStatus_t aclblasCgeam_npu(
71+ aclblasHandle_t handle, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n,
72+ const aclblasComplex* alpha, const aclblasComplex* A, int lda,
73+ const aclblasComplex* beta, const aclblasComplex* B, int ldb, aclblasComplex* C, int ldc)
74+{
75+ if (m <= 0 || n <= 0) {
76+ aclblasComplex dummyA = {0.0f, 0.0f};
77+ aclblasComplex dummyB = {0.0f, 0.0f};
78+ aclblasComplex dummyC = {0.0f, 0.0f};
79+ aclblasComplex dummyAlpha = alpha ? *alpha : aclblasComplex{0.0f, 0.0f};
80+ aclblasComplex dummyBeta = beta ? *beta : aclblasComplex{0.0f, 0.0f};
81+ return aclblasCgeam(handle, transa, transb, m, n,
82+ &dummyAlpha, &dummyA, std::max(lda, 1),
83+ &dummyBeta, &dummyB, std::max(ldb, 1),
84+ &dummyC, std::max(ldc, 1));
85+ }
86+ size_t aBytes = CgeamBufferBytes(transa, m, n, lda);
87+ size_t bBytes = CgeamBufferBytes(transb, m, n, ldb);
88+ size_t cBytes = static_cast<size_t>(ldc) * static_cast<size_t>(n) * sizeof(aclblasComplex);
89+ 
90+ bool inplaceAC = (A != nullptr && C == A);
91+ bool inplaceBC = (B != nullptr && C == B);
92+ 
93+ auto dA = CgeamAllocAndCopy(A, aBytes);
94+ auto dB = CgeamAllocAndCopy(B, bBytes);
95+ 
96+ std::unique_ptr<DeviceBuffer> dC;
97+ if (!inplaceAC && !inplaceBC) {
98+ dC = CgeamAllocOutput(cBytes);
99+ }
100+ 
101+ aclblasComplex* dC_ptr = CgeamGetCPtr(inplaceAC, inplaceBC, dA.get(), dB.get(), dC.get());
102+ 
103+ aclblasStatus_t ret = aclblasCgeam(
104+ handle, transa, transb, m, n, alpha,
105+ dA ? static_cast<const aclblasComplex*>(dA->ptr()) : nullptr, lda, beta,
106+ dB ? static_cast<const aclblasComplex*>(dB->ptr()) : nullptr, ldb, dC_ptr, ldc);
107+ 
108+ if (aclrtSynchronizeDevice() != ACL_SUCCESS) {
109+ return ACLBLAS_STATUS_INTERNAL_ERROR;
110+ }
111+ if (ret == ACLBLAS_STATUS_SUCCESS) {
112+ CgeamCopyBack(inplaceAC, inplaceBC, dA.get(), dB.get(), dC.get(), C, aBytes, bBytes, cBytes);
113+ }
114+ return ret;
115+}
Atest/geam/cgeam/arch35/cgeam_test.cpp+383-0
@@ -0,0 +1,383 @@
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+#include <algorithm>
12+#include <cmath>
13+#include <complex>
14+#include <vector>
15+ 
16+#include "verify.h"
17+#include "blas_test.h"
18+#include "csv_loader.h"
19+#include "fill.h"
20+#include "cgeam_param.h"
21+#include "../../geam_golden.h"
22+#include "cgeam_npu_wrapper.h"
23+ 
24+#include "gtest/gtest.h"
25+ 
26+// Generate a column-major complex matrix: m rows, n cols, column stride lda.
27+// Returns interleaved float vector of size lda * n * 2.
28+static inline std::vector<float> makeComplexMatrixCM(int m, int n, int lda, const BlasFillMode& fill, uint32_t seed)
29+{
30+ const size_t storageSize = static_cast<size_t>(lda) * n * 2;
31+ std::vector<float> data(storageSize, 0.0f);
32+ 
33+ if (fill.method == BlasFillMode::M_VALUE) {
34+ for (size_t i = 0; i < storageSize; i++)
35+ data[i] = fill.val1;
36+ return data;
37+ }
38+ 
39+ std::mt19937 rngReal(seed ? seed : 42);
40+ std::mt19937 rngImag((seed ? seed : 42) + 2000);
41+ auto genReal = createGenerator(fill, rngReal);
42+ auto genImag = createGenerator(fill, rngImag);
43+ 
44+ for (int j = 0; j < n; j++) {
45+ for (int i = 0; i < m; i++) {
46+ size_t idx = (static_cast<size_t>(j) * lda + i) * 2;
47+ size_t flatIdx = static_cast<size_t>(j) * m + i;
48+ data[idx] = genReal->at(flatIdx);
49+ data[idx + 1] = genImag->at(flatIdx);
50+ }
51+ }
52+ return data;
53+}
54+ 
55+// -- Test fixture ------------------------------------------------------------
56+class CgeamArch35Test : public BlasTest<CgeamParam> {};
57+ 
58+// Helper: 4x4 complex matrix as interleaved float vector
59+static inline std::vector<float> c4x4(float v = 1.0f) { return std::vector<float>(4 * 4 * 2, v); }
60+ 
61+// -- TEST_F: null handle (not in CSV) ---------------------------------------
62+TEST_F(CgeamArch35Test, NullHandle)
63+{
64+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
65+ auto A = c4x4(), B = c4x4(), C = c4x4(0.0f);
66+ EXPECT_EQ(
67+ aclblasCgeam(
68+ nullptr, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha, reinterpret_cast<const aclblasComplex*>(A.data()), 4,
69+ &beta, reinterpret_cast<const aclblasComplex*>(B.data()), 4, reinterpret_cast<aclblasComplex*>(C.data()),
70+ 4),
71+ ACLBLAS_STATUS_HANDLE_IS_NULLPTR);
72+}
73+ 
74+// -- TEST_F: A=nullptr (host validates and returns error) --------------------
75+TEST_F(CgeamArch35Test, NullA)
76+{
77+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
78+ auto B = c4x4(), C = c4x4(0.0f);
79+ EXPECT_EQ(
80+ aclblasCgeam(
81+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha, nullptr, 4, &beta,
82+ reinterpret_cast<const aclblasComplex*>(B.data()), 4, reinterpret_cast<aclblasComplex*>(C.data()), 4),
83+ ACLBLAS_STATUS_INVALID_VALUE);
84+}
85+ 
86+// -- TEST_F: B=nullptr (host validates and returns error) --------------------
87+TEST_F(CgeamArch35Test, NullB)
88+{
89+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
90+ auto A = c4x4(), C = c4x4(0.0f);
91+ EXPECT_EQ(
92+ aclblasCgeam(
93+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha,
94+ reinterpret_cast<const aclblasComplex*>(A.data()), 4, &beta, nullptr, 4,
95+ reinterpret_cast<aclblasComplex*>(C.data()), 4),
96+ ACLBLAS_STATUS_INVALID_VALUE);
97+}
98+ 
99+// -- TEST_F: C=nullptr (host validates and returns error) --------------------
100+TEST_F(CgeamArch35Test, NullC)
101+{
102+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
103+ auto A = c4x4(), B = c4x4();
104+ EXPECT_EQ(
105+ aclblasCgeam(
106+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha,
107+ reinterpret_cast<const aclblasComplex*>(A.data()), 4, &beta,
108+ reinterpret_cast<const aclblasComplex*>(B.data()), 4, nullptr, 4),
109+ ACLBLAS_STATUS_INVALID_VALUE);
110+}
111+ 
112+// -- TEST_F: alpha=nullptr returns INVALID_VALUE (host validates) ------------
113+TEST_F(CgeamArch35Test, NullAlpha)
114+{
115+ aclblasComplex beta = {1.0f, 0.0f};
116+ auto A = c4x4(2.0f), B = c4x4(3.0f), C = c4x4(kBlasSentinel);
117+ EXPECT_EQ(
118+ aclblasCgeam(
119+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, nullptr,
120+ reinterpret_cast<aclblasComplex*>(A.data()), 4, &beta, reinterpret_cast<aclblasComplex*>(B.data()), 4,
121+ reinterpret_cast<aclblasComplex*>(C.data()), 4),
122+ ACLBLAS_STATUS_INVALID_VALUE);
123+}
124+ 
125+// -- TEST_F: beta=nullptr returns INVALID_VALUE (host validates) -------------
126+TEST_F(CgeamArch35Test, NullBeta)
127+{
128+ aclblasComplex alpha = {1.0f, 0.0f};
129+ auto A = c4x4(2.0f), B = c4x4(3.0f), C = c4x4(kBlasSentinel);
130+ EXPECT_EQ(
131+ aclblasCgeam(
132+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha,
133+ reinterpret_cast<aclblasComplex*>(A.data()), 4, nullptr, reinterpret_cast<aclblasComplex*>(B.data()), 4,
134+ reinterpret_cast<aclblasComplex*>(C.data()), 4),
135+ ACLBLAS_STATUS_INVALID_VALUE);
136+}
137+ 
138+// -- TEST_F: transa invalid 0xFF (host validates) ----------------------------
139+TEST_F(CgeamArch35Test, TransaInvalid)
140+{
141+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
142+ auto A = c4x4(), B = c4x4(), C = c4x4(0.0f);
143+ EXPECT_EQ(
144+ aclblasCgeam(
145+ CgeamArch35Test::handle_, static_cast<aclblasOperation_t>(0xFF), ACLBLAS_OP_N, 4, 4, &alpha,
146+ reinterpret_cast<const aclblasComplex*>(A.data()), 4, &beta,
147+ reinterpret_cast<const aclblasComplex*>(B.data()), 4, reinterpret_cast<aclblasComplex*>(C.data()), 4),
148+ ACLBLAS_STATUS_INVALID_ENUM);
149+}
150+ 
151+// -- TEST_F: transb invalid 0xFF (host validates) ----------------------------
152+TEST_F(CgeamArch35Test, TransbInvalid)
153+{
154+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
155+ auto A = c4x4(), B = c4x4(), C = c4x4(0.0f);
156+ EXPECT_EQ(
157+ aclblasCgeam(
158+ CgeamArch35Test::handle_, ACLBLAS_OP_N, static_cast<aclblasOperation_t>(0xFF), 4, 4, &alpha,
159+ reinterpret_cast<const aclblasComplex*>(A.data()), 4, &beta,
160+ reinterpret_cast<const aclblasComplex*>(B.data()), 4, reinterpret_cast<aclblasComplex*>(C.data()), 4),
161+ ACLBLAS_STATUS_INVALID_ENUM);
162+}
163+ 
164+// -- TEST_F: m<0 (host validates and returns error) -------------------------
165+TEST_F(CgeamArch35Test, MNegative)
166+{
167+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
168+ EXPECT_EQ(
169+ aclblasCgeam(
170+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, -1, 4, &alpha, nullptr, 1, &beta, nullptr, 1, nullptr,
171+ 1),
172+ ACLBLAS_STATUS_INVALID_VALUE);
173+}
174+ 
175+// -- TEST_F: n<0 (host validates and returns error) -------------------------
176+TEST_F(CgeamArch35Test, NNegative)
177+{
178+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
179+ EXPECT_EQ(
180+ aclblasCgeam(
181+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, -1, &alpha, nullptr, 4, &beta, nullptr, 4, nullptr,
182+ 4),
183+ ACLBLAS_STATUS_INVALID_VALUE);
184+}
185+ 
186+// -- TEST_F: lda too small when transa=N (host validates) --------------------
187+TEST_F(CgeamArch35Test, LdaTooSmallN)
188+{
189+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
190+ auto A = c4x4(), B = c4x4(), C = c4x4(0.0f);
191+ EXPECT_EQ(
192+ aclblasCgeam(
193+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha,
194+ reinterpret_cast<const aclblasComplex*>(A.data()), 3, &beta,
195+ reinterpret_cast<const aclblasComplex*>(B.data()), 4, reinterpret_cast<aclblasComplex*>(C.data()), 4),
196+ ACLBLAS_STATUS_INVALID_VALUE);
197+}
198+ 
199+// -- TEST_F: ldc too small (host validates and returns error) ----------------
200+TEST_F(CgeamArch35Test, LdcTooSmall)
201+{
202+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
203+ std::vector<float> A(8 * 4 * 2, 1.0f), B(8 * 4 * 2, 1.0f), C(8 * 4 * 2, 0.0f);
204+ EXPECT_EQ(
205+ aclblasCgeam(
206+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 8, 4, &alpha,
207+ reinterpret_cast<const aclblasComplex*>(A.data()), 8, &beta,
208+ reinterpret_cast<const aclblasComplex*>(B.data()), 8, reinterpret_cast<aclblasComplex*>(C.data()), 7),
209+ ACLBLAS_STATUS_INVALID_VALUE);
210+}
211+ 
212+// -- TEST_F: in-place C==A with transa!=N (host validates) -------------------
213+TEST_F(CgeamArch35Test, InplaceATransInvalid)
214+{
215+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
216+ std::vector<float> A(8 * 8 * 2, 1.0f), B(8 * 8 * 2, 1.0f);
217+ EXPECT_EQ(
218+ aclblasCgeam(
219+ CgeamArch35Test::handle_, ACLBLAS_OP_T, ACLBLAS_OP_N, 8, 8, &alpha,
220+ reinterpret_cast<const aclblasComplex*>(A.data()), 8, &beta,
221+ reinterpret_cast<const aclblasComplex*>(B.data()), 8, reinterpret_cast<aclblasComplex*>(A.data()), 8),
222+ ACLBLAS_STATUS_INVALID_VALUE);
223+}
224+ 
225+// -- TEST_F: in-place C==B with transb!=N (host validates) -------------------
226+TEST_F(CgeamArch35Test, InplaceBTransInvalid)
227+{
228+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
229+ std::vector<float> A(8 * 8 * 2, 1.0f), B(8 * 8 * 2, 1.0f);
230+ EXPECT_EQ(
231+ aclblasCgeam(
232+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_T, 8, 8, &alpha,
233+ reinterpret_cast<const aclblasComplex*>(A.data()), 8, &beta,
234+ reinterpret_cast<const aclblasComplex*>(B.data()), 8, reinterpret_cast<aclblasComplex*>(B.data()), 8),
235+ ACLBLAS_STATUS_INVALID_VALUE);
236+}
237+ 
238+// -- TEST_F: in-place C==A with lda!=ldc (host validates) --------------------
239+TEST_F(CgeamArch35Test, InplaceALdaNeLdc)
240+{
241+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
242+ std::vector<float> A(10 * 8 * 2, 1.0f), B(10 * 8 * 2, 1.0f);
243+ EXPECT_EQ(
244+ aclblasCgeam(
245+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 8, 8, &alpha,
246+ reinterpret_cast<const aclblasComplex*>(A.data()), 10, &beta,
247+ reinterpret_cast<const aclblasComplex*>(B.data()), 8, reinterpret_cast<aclblasComplex*>(A.data()), 8),
248+ ACLBLAS_STATUS_INVALID_VALUE);
249+}
250+ 
251+// -- TEST_F: in-place C==B with ldb!=ldc (host validates) --------------------
252+TEST_F(CgeamArch35Test, InplaceBLdbNeLdc)
253+{
254+ aclblasComplex alpha = {1.0f, 0.0f}, beta = {1.0f, 0.0f};
255+ std::vector<float> A(10 * 8 * 2, 1.0f), B(10 * 8 * 2, 1.0f);
256+ EXPECT_EQ(
257+ aclblasCgeam(
258+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 8, 8, &alpha,
259+ reinterpret_cast<const aclblasComplex*>(A.data()), 8, &beta,
260+ reinterpret_cast<const aclblasComplex*>(B.data()), 10, reinterpret_cast<aclblasComplex*>(B.data()), 8),
261+ ACLBLAS_STATUS_INVALID_VALUE);
262+}
263+ 
264+// -- TEST_F: alpha=nullptr with A=nullptr returns INVALID_VALUE -------------
265+TEST_F(CgeamArch35Test, NullAlphaAllowsNullA)
266+{
267+ aclblasComplex beta = {1.0f, 0.0f};
268+ auto B = c4x4(), C = c4x4(kBlasSentinel);
269+ EXPECT_EQ(
270+ aclblasCgeam(
271+ CgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, nullptr, nullptr, 4, &beta,
272+ reinterpret_cast<aclblasComplex*>(B.data()), 4, reinterpret_cast<aclblasComplex*>(C.data()), 4),
273+ ACLBLAS_STATUS_INVALID_VALUE);
274+}
275+ 
276+// -- CSV parameterised test suite -------------------------------------------
277+INSTANTIATE_TEST_SUITE_P(
278+ Cgeam, CgeamArch35Test, ::testing::ValuesIn(GetCasesFromCsv<CgeamParam>(ReplaceFileExtension2Csv(__FILE__))),
279+ PrintCaseInfoString<CgeamParam>);
280+ 
281+// -- Helper: generate A or B input complex matrix -----------------------------
282+static std::vector<float> CgeamGenerateInput(const CgeamParam& p, bool isA, int extraSeed)
283+{
284+ int nullFlag = isA ? p.nullA : p.nullB;
285+ if (nullFlag != 0 || p.m <= 0 || p.n <= 0) {
286+ return {};
287+ }
288+ aclblasOperation_t trans = isA ? p.transa : p.transb;
289+ int ld = isA ? p.lda : p.ldb;
290+ const BlasFillMode& fill = isA ? p.aFill : p.bFill;
291+ int rows = (trans == ACLBLAS_OP_N) ? p.m : p.n;
292+ int cols = (trans == ACLBLAS_OP_N) ? p.n : p.m;
293+ return makeComplexMatrixCM(rows, cols, ld, fill, p.randomSeed + extraSeed);
294+}
295+ 
296+// -- Helper: prepare C buffer (in-place aware, complex) -----------------------
297+static aclblasComplex* CgeamPrepareC(const CgeamParam& p, const aclblasComplex* aPtr,
298+ const aclblasComplex* bPtr, std::vector<float>& cHost)
299+{
300+ if (p.inplace == 1 && aPtr) {
301+ return const_cast<aclblasComplex*>(aPtr);
302+ }
303+ if (p.inplace == 2 && bPtr) {
304+ return const_cast<aclblasComplex*>(bPtr);
305+ }
306+ if (p.nullC == 0 && p.m > 0 && p.n > 0) {
307+ cHost.assign(static_cast<size_t>(p.ldc) * static_cast<size_t>(p.n) * 2, kBlasSentinel);
308+ }
309+ return cHost.empty() ? nullptr : reinterpret_cast<aclblasComplex*>(cHost.data());
310+}
311+ 
312+// -- Helper: compute golden reference (complex) --------------------------------
313+static std::vector<float> CgeamComputeGolden(
314+ const CgeamParam& p, aclblasComplex alpha, aclblasComplex beta,
315+ const aclblasComplex* aPtr, const aclblasComplex* bPtr, aclblasComplex* cPtr,
316+ const std::vector<float>& aHost, const std::vector<float>& bHost, const std::vector<float>& cHost)
317+{
318+ std::vector<float> goldenC;
319+ if (p.expectResult != ACLBLAS_STATUS_SUCCESS || p.m <= 0 || p.n <= 0 || !cPtr) {
320+ return goldenC;
321+ }
322+ size_t goldenSize = cHost.empty() ? (p.inplace == 1 ? aHost.size() :
323+ p.inplace == 2 ? bHost.size() : 0) :
324+ cHost.size();
325+ goldenC.assign(goldenSize, kBlasSentinel);
326+ aclblasGeam_cpu<std::complex<float>>(
327+ p.transa, p.transb, static_cast<std::size_t>(p.m), static_cast<std::size_t>(p.n),
328+ *reinterpret_cast<const std::complex<float>*>(&alpha), reinterpret_cast<const std::complex<float>*>(aPtr),
329+ static_cast<std::size_t>(p.lda), *reinterpret_cast<const std::complex<float>*>(&beta),
330+ reinterpret_cast<const std::complex<float>*>(bPtr), static_cast<std::size_t>(p.ldb),
331+ reinterpret_cast<std::complex<float>*>(goldenC.data()), static_cast<std::size_t>(p.ldc));
332+ return goldenC;
333+}
334+ 
335+// -- Helper: verify output precision (complex) ---------------------------------
336+static void CgeamVerifyOutput(
337+ const CgeamParam& p, const std::vector<float>& goldenC,
338+ const std::vector<float>& aHost, const std::vector<float>& bHost, const std::vector<float>& cHost)
339+{
340+ VerifyConfig cfg;
341+ cfg.mode = PrecisionMode::MERE_MARE;
342+ cfg.mereThreshold = p.mereThreshold;
343+ cfg.mareMultiplier = p.mareMultiplier;
344+ const float* outPtr = nullptr;
345+ size_t outSize = 0;
346+ if (p.inplace == 1) {
347+ outPtr = aHost.data();
348+ outSize = aHost.size();
349+ } else if (p.inplace == 2) {
350+ outPtr = bHost.data();
351+ outSize = bHost.size();
352+ } else {
353+ outPtr = cHost.data();
354+ outSize = cHost.size();
355+ }
356+ if (outSize > 0) {
357+ EXPECT_TRUE(Verifier::verifyVector(outPtr, goldenC.data(), outSize, 1, cfg, p.caseName));
358+ }
359+}
360+ 
361+// -- TEST_P: 5-step CSV-driven flow -----------------------------------------
362+TEST_P(CgeamArch35Test, CsvDriven)
363+{
364+ const auto& p = GetParam();
365+ aclblasComplex alpha = {p.alphaFill.val1, p.alphaFill.val2};
366+ aclblasComplex beta = {p.betaFill.val1, p.betaFill.val2};
367+ 
368+ std::vector<float> aHost = CgeamGenerateInput(p, true, 0);
369+ std::vector<float> bHost = CgeamGenerateInput(p, false, 1);
370+ const aclblasComplex* aPtr = aHost.empty() ? nullptr : reinterpret_cast<const aclblasComplex*>(aHost.data());
371+ const aclblasComplex* bPtr = bHost.empty() ? nullptr : reinterpret_cast<const aclblasComplex*>(bHost.data());
372+ 
373+ std::vector<float> cHost;
374+ aclblasComplex* cPtr = CgeamPrepareC(p, aPtr, bPtr, cHost);
375+ 
376+ std::vector<float> goldenC = CgeamComputeGolden(p, alpha, beta, aPtr, bPtr, cPtr, aHost, bHost, cHost);
377+ 
378+ aclblasStatus_t ret = aclblasCgeam_npu(
379+ CgeamArch35Test::handle_, p.transa, p.transb, p.m, p.n, &alpha, aPtr, p.lda, &beta, bPtr, p.ldb, cPtr, p.ldc);
380+ EXPECT_EQ(ret, ACLBLAS_STATUS_SUCCESS);
381+ 
382+ CgeamVerifyOutput(p, goldenC, aHost, bHost, cHost);
383+}
Atest/geam/cgeam/arch35/cgeam_test.csv+40-0
@@ -0,0 +1,40 @@
1+case_name,description,transa,transb,m,n,lda,ldb,ldc,alpha_fill,beta_fill,a_fill,b_fill,nullA,nullB,nullC,mere_threshold,mare_multiplier,random_seed,inplace
2+TC_CL0_01,NN 2x2 basic complex,N,N,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
3+TC_CL0_02,NT 2x2 basic complex,N,T,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
4+TC_CL0_03,NC 2x2 basic complex,N,C,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
5+TC_CL0_04,TN 2x2 basic complex,T,N,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
6+TC_CL0_05,TT 2x2 basic complex,T,T,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
7+TC_CL0_06,TC 2x2 basic complex,T,C,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
8+TC_CL0_07,CN 2x2 basic complex,C,N,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
9+TC_CL0_08,CT 2x2 basic complex,C,T,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
10+TC_CL0_09,CC 2x2 basic complex,C,C,2,2,2,2,2,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
11+TC_CL0_10,TC 4x4 conj vs trans distinction,T,C,4,4,4,4,4,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
12+TC_CL0_11,CT 4x4 conj vs trans distinction,C,T,4,4,4,4,4,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
13+TC_CL0_12,CC 4x4 dual conjugate,C,C,4,4,4,4,4,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
14+TC_CL0_13,alpha=(0.5 0.5) complex scaling,N,N,4,4,4,4,4,VALUE_NORM_0.5_0.5,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
15+TC_CL0_14,beta=(0 1) pure imaginary,N,N,4,4,4,4,4,VALUE_NORM_1_0,VALUE_NORM_0_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
16+TC_CL0_15,alpha=(0 0) beta=(1 1),N,N,4,4,4,4,4,VALUE_NORM_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
17+TC_CL0_16,NN 4x4 alpha=0 beta=1 short-circuit,N,N,4,4,4,4,4,VALUE_NORM_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
18+TC_CL0_17,NN 4x4 alpha=1 beta=0 short-circuit,N,N,4,4,4,4,4,VALUE_NORM_1_0,VALUE_NORM_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
19+TC_CL0_21,m=0 early return complex,N,N,0,4,1,1,1,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0,0,42,0
20+TC_CL0_22,n=0 early return complex,N,N,4,0,4,4,4,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0,0,42,0
21+TC_CL1_01,m=1 n=8 single-row NN complex,N,N,1,8,1,1,1,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
22+TC_CL1_02,m=8 n=1 single-col NN complex,N,N,8,1,8,8,8,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
23+TC_CL1_03,m=1024 n=1024 large data NN complex,N,N,1024,1024,1024,1024,1024,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_5_5,RANDOM_5_5,0,0,0,0.0001220703125,10.0,42,0
24+TC_CL1_04,ld padding +1 all matrices NN complex,N,N,8,8,9,9,9,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
25+TC_CL1_05,alpha=(0 1) pure-imag scaling CN complex,C,N,4,4,4,4,4,VALUE_NORM_0_1,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
26+TC_CL1_06,beta=(0.5 0.5) complex scaling NN,N,N,8,8,8,8,8,VALUE_NORM_1_0,VALUE_NORM_0.5_0.5,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
27+TC_CL1_07,conj correctness CN beta=0 isolate A,C,N,4,4,4,4,4,VALUE_NORM_1_0,VALUE_NORM_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
28+TC_CL1_08,dual conjugate CC complex m=n=8,C,C,8,8,8,8,8,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
29+TC_CL1_09,alpha=INF real part complex NN,N,N,4,4,4,4,4,VALUE_NORM_INF,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
30+TC_CL1_11,m=127 n=63 non-power2 TT complex,T,T,127,63,63,63,127,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
31+TC_CL1_12,m=8 n=16 non-square NN complex,N,N,8,16,8,8,8,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
32+TC_CL1_13,m=5000 n=1 NN complex batch loop,N,N,5000,1,5000,5000,5000,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
33+TC_CL1_14,in-place C==A transa=N lda=ldc complex,N,N,8,8,8,8,8,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,1
34+TC_CL1_15,in-place C==B transb=N ldb=ldc complex,N,N,8,8,8,8,8,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,2
35+TC_CL1_18,in-place C==A large N=64 complex,N,N,64,64,64,64,64,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,1
36+TC_CL1_19,in-place C==A alpha=0 beta=0 complex,N,N,8,8,8,8,8,VALUE_NORM_0,VALUE_NORM_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,1
37+TC_CL1_20,in-place C==A large N=128 complex,N,N,128,128,128,128,128,VALUE_NORM_1_0,VALUE_NORM_1_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,1
38+TC_CL1_21,alpha=0 A=nullptr NN complex,N,N,8,8,8,8,8,VALUE_NORM_0,VALUE_NORM_1_0,NULLPTR,RANDOM_1_1,1,0,0,0.0001220703125,10.0,42,0
39+TC_CL1_22,alpha=0 beta=0 both nullptr NN complex,N,N,8,8,8,8,8,VALUE_NORM_0,VALUE_NORM_0,NULLPTR,NULLPTR,1,1,0,0.0001220703125,10.0,42,0
40+TC_CL1_23,beta=0 B=nullptr alpha=1 NN complex,N,N,8,8,8,8,8,VALUE_NORM_1_0,VALUE_NORM_0,RANDOM_1_1,NULLPTR,0,1,0,0.0001220703125,10.0,42,0
Atest/geam/cgeam/cgeam_param.h+54-0
@@ -0,0 +1,54 @@
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+#pragma once
12+ 
13+#include <algorithm>
14+#include <string>
15+#include "cann_ops_blas.h"
16+#include "csv_loader.h"
17+ 
18+struct CgeamParam : public BlasTestParamBase {
19+ aclblasOperation_t transa = ACLBLAS_OP_N;
20+ aclblasOperation_t transb = ACLBLAS_OP_N;
21+ int m = 0;
22+ int n = 0;
23+ int lda = 0;
24+ int ldb = 0;
25+ int ldc = 0;
26+ // alpha/beta encoded as BlasFillMode: val1=real, val2=imag
27+ BlasFillMode alphaFill = BlasFillMode("VALUE_NORM_1");
28+ BlasFillMode betaFill = BlasFillMode("VALUE_NORM_1");
29+ BlasFillMode aFill = BlasFillMode("RANDOM_1_1");
30+ BlasFillMode bFill = BlasFillMode("RANDOM_1_1");
31+ int nullA = 0;
32+ int nullB = 0;
33+ int nullC = 0;
34+ int inplace = 0; // 0=out-of-place, 1=C==A, 2=C==B
35+ 
36+ CgeamParam(const csv_map& csv) : BlasTestParamBase(csv)
37+ {
38+ transa = parseOpTrans(ReadMap(csv, "transa", "N"));
39+ transb = parseOpTrans(ReadMap(csv, "transb", "N"));
40+ m = parseInt(ReadMap(csv, "m", "0"));
41+ n = parseInt(ReadMap(csv, "n", "0"));
42+ lda = parseInt(ReadMap(csv, "lda", std::to_string((transa == ACLBLAS_OP_N) ? std::max(1, m) : std::max(1, n))));
43+ ldb = parseInt(ReadMap(csv, "ldb", std::to_string((transb == ACLBLAS_OP_N) ? std::max(1, m) : std::max(1, n))));
44+ ldc = parseInt(ReadMap(csv, "ldc", std::to_string(std::max(1, m))));
45+ alphaFill = BlasFillMode(ReadMap(csv, "alpha_fill", "VALUE_NORM_1"));
46+ betaFill = BlasFillMode(ReadMap(csv, "beta_fill", "VALUE_NORM_1"));
47+ aFill = BlasFillMode(ReadMap(csv, "a_fill", "RANDOM_1_1"));
48+ bFill = BlasFillMode(ReadMap(csv, "b_fill", "RANDOM_1_1"));
49+ nullA = parseInt(ReadMap(csv, "nullA", "0"));
50+ nullB = parseInt(ReadMap(csv, "nullB", "0"));
51+ nullC = parseInt(ReadMap(csv, "nullC", "0"));
52+ inplace = parseInt(ReadMap(csv, "inplace", "0"));
53+ }
54+};
Atest/geam/geam_golden.h+74-0
@@ -0,0 +1,74 @@
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 the License for the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, 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+#pragma once
12+ 
13+#include <complex>
14+#include <cstddef>
15+#include "cann_ops_blas.h"
16+ 
17+// todo: 优化 cmake 让 family/dtype/arch/test.cpp 可以直接找到 family/golden.h
18+ 
19+// std::conj(float) 返回 complex<float> , complex 不能自动转换成 float
20+// 这里定义一个直接返回 float 的 conj
21+template <class T>
22+constexpr T conjugate(T x)
23+{
24+ return x;
25+}
26+template <class T>
27+constexpr std::complex<T> conjugate(std::complex<T> x)
28+{
29+ return std::conj(x);
30+}
31+ 
32+static_assert(conjugate(1.0f) == 1.0f);
33+// std::conj is constexpr since c++20
34+// using namespace std::complex_literals;
35+// add this code if upgrade to c++20 `static_assert(conjugate(1.0if) == -1.0if);`
36+ 
37+template <class T>
38+inline void aclblasGeam_cpu(
39+ aclblasOperation_t transa, aclblasOperation_t transb, std::size_t m, std::size_t n, T alpha, const T* A,
40+ std::size_t lda, T beta, const T* B, std::size_t ldb, T* C, std::size_t ldc)
41+{
42+ for (std::size_t j = 0; j < n; j++) {
43+ for (std::size_t i = 0; i < m; i++) {
44+ // maybe A == C or B == C
45+ T cij = 0;
46+ if (alpha != 0.0f) {
47+ // ignore nan in A, when alpha == 0
48+ T aij;
49+ if (transa == ACLBLAS_OP_N) {
50+ aij = A[i + j * lda];
51+ } else {
52+ aij = A[j + i * lda];
53+ }
54+ if (transa == ACLBLAS_OP_C) {
55+ aij = conjugate(aij);
56+ }
57+ cij += alpha * aij;
58+ }
59+ if (beta != 0.0f) {
60+ T bij;
61+ if (transb == ACLBLAS_OP_N) {
62+ bij = B[i + j * ldb];
63+ } else {
64+ bij = B[j + i * ldb];
65+ }
66+ if (transb == ACLBLAS_OP_C) {
67+ bij = conjugate(bij);
68+ }
69+ cij += beta * bij;
70+ }
71+ C[i + j * ldc] = cij;
72+ }
73+ }
74+}
Atest/geam/sgeam/CMakeLists.txt+11-0
@@ -0,0 +1,11 @@
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 FILE 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+ops_blas_add_gtest_tests(${OPS_BLAS} sgeam_test)
Atest/geam/sgeam/arch35/sgeam_npu_wrapper.h+113-0
@@ -0,0 +1,113 @@
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+#pragma once
12+ 
13+#include <algorithm>
14+#include <cstdint>
15+#include <memory>
16+#include <vector>
17+ 
18+#include "acl/acl.h"
19+#include "cann_ops_blas.h"
20+#include "device.h"
21+#include "fill.h"
22+ 
23+inline size_t SgeamBufferBytes(aclblasOperation_t trans, int m, int n, int ld)
24+{
25+ size_t cols = (trans == ACLBLAS_OP_N) ? static_cast<size_t>(n) : static_cast<size_t>(m);
26+ return static_cast<size_t>(ld) * cols * sizeof(float);
27+}
28+ 
29+inline std::unique_ptr<DeviceBuffer> SgeamAllocAndCopy(const float* hostPtr, size_t bytes)
30+{
31+ if (hostPtr == nullptr) {
32+ return nullptr;
33+ }
34+ auto buf = std::make_unique<DeviceBuffer>(bytes);
35+ buf->copyFromHost(hostPtr, bytes);
36+ return buf;
37+}
38+ 
39+inline std::unique_ptr<DeviceBuffer> SgeamAllocOutput(size_t cBytes)
40+{
41+ auto buf = std::make_unique<DeviceBuffer>(cBytes);
42+ std::vector<float> sentinel(cBytes / sizeof(float), kBlasSentinel);
43+ buf->copyFromHost(sentinel.data(), cBytes);
44+ return buf;
45+}
46+ 
47+inline float* SgeamGetCPtr(bool inplaceAC, bool inplaceBC, DeviceBuffer* dA, DeviceBuffer* dB, DeviceBuffer* dC)
48+{
49+ if (inplaceAC) {
50+ return static_cast<float*>(dA->ptr());
51+ }
52+ if (inplaceBC) {
53+ return static_cast<float*>(dB->ptr());
54+ }
55+ return static_cast<float*>(dC->ptr());
56+}
57+ 
58+inline void SgeamCopyBack(bool inplaceAC, bool inplaceBC, DeviceBuffer* dA, DeviceBuffer* dB, DeviceBuffer* dC,
59+ float* C, size_t aBytes, size_t bBytes, size_t cBytes)
60+{
61+ if (inplaceAC && dA) {
62+ dA->copyToHost(C, aBytes);
63+ } else if (inplaceBC && dB) {
64+ dB->copyToHost(C, bBytes);
65+ } else if (dC) {
66+ dC->copyToHost(C, cBytes);
67+ }
68+}
69+ 
70+inline aclblasStatus_t aclblasSgeam_npu(
71+ aclblasHandle_t handle, aclblasOperation_t transa, aclblasOperation_t transb, int m, int n,
72+ const float* alpha, const float* A, int lda,
73+ const float* beta, const float* B, int ldb, float* C, int ldc)
74+{
75+ if (m <= 0 || n <= 0) {
76+ float dummyA = 0.0f, dummyB = 0.0f, dummyC = 0.0f;
77+ float dummyAlpha = alpha ? *alpha : 0.0f;
78+ float dummyBeta = beta ? *beta : 0.0f;
79+ return aclblasSgeam(handle, transa, transb, m, n,
80+ &dummyAlpha, &dummyA, std::max(lda, 1),
81+ &dummyBeta, &dummyB, std::max(ldb, 1),
82+ &dummyC, std::max(ldc, 1));
83+ }
84+ size_t aBytes = SgeamBufferBytes(transa, m, n, lda);
85+ size_t bBytes = SgeamBufferBytes(transb, m, n, ldb);
86+ size_t cBytes = static_cast<size_t>(ldc) * static_cast<size_t>(n) * sizeof(float);
87+ 
88+ bool inplaceAC = (A != nullptr && C == A);
89+ bool inplaceBC = (B != nullptr && C == B);
90+ 
91+ auto dA = SgeamAllocAndCopy(A, aBytes);
92+ auto dB = SgeamAllocAndCopy(B, bBytes);
93+ 
94+ std::unique_ptr<DeviceBuffer> dC;
95+ if (!inplaceAC && !inplaceBC) {
96+ dC = SgeamAllocOutput(cBytes);
97+ }
98+ 
99+ float* dC_ptr = SgeamGetCPtr(inplaceAC, inplaceBC, dA.get(), dB.get(), dC.get());
100+ 
101+ aclblasStatus_t ret = aclblasSgeam(
102+ handle, transa, transb, m, n, alpha,
103+ dA ? static_cast<const float*>(dA->ptr()) : nullptr, lda, beta,
104+ dB ? static_cast<const float*>(dB->ptr()) : nullptr, ldb, dC_ptr, ldc);
105+ 
106+ if (aclrtSynchronizeDevice() != ACL_SUCCESS) {
107+ return ACLBLAS_STATUS_INTERNAL_ERROR;
108+ }
109+ if (ret == ACLBLAS_STATUS_SUCCESS) {
110+ SgeamCopyBack(inplaceAC, inplaceBC, dA.get(), dB.get(), dC.get(), C, aBytes, bBytes, cBytes);
111+ }
112+ return ret;
113+}
Atest/geam/sgeam/arch35/sgeam_test.cpp+366-0
@@ -0,0 +1,366 @@
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+#include <algorithm>
12+#include <vector>
13+ 
14+#include "verify.h"
15+#include "blas_test.h"
16+#include "csv_loader.h"
17+#include "fill.h"
18+#include "sgeam_param.h"
19+#include "../../geam_golden.h"
20+#include "sgeam_npu_wrapper.h"
21+ 
22+#include "gtest/gtest.h"
23+ 
24+// -- Test fixture ------------------------------------------------------------
25+class SgeamArch35Test : public BlasTest<SgeamParam> {};
26+ 
27+// -- TEST_F: null handle (not in CSV) ---------------------------------------
28+TEST_F(SgeamArch35Test, NullHandle)
29+{
30+ float alpha = 1.0f, beta = 1.0f;
31+ std::vector<float> A(4 * 4, 1.0f);
32+ std::vector<float> B(4 * 4, 1.0f);
33+ std::vector<float> C(4 * 4, 0.0f);
34+ EXPECT_EQ(
35+ aclblasSgeam(nullptr, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha, A.data(), 4, &beta, B.data(), 4, C.data(), 4),
36+ ACLBLAS_STATUS_HANDLE_IS_NULLPTR);
37+}
38+ 
39+// -- TEST_F: A=nullptr (host validates and returns error) --------------------
40+TEST_F(SgeamArch35Test, NullA)
41+{
42+ float alpha = 1.0f, beta = 1.0f;
43+ std::vector<float> B(4 * 4, 1.0f);
44+ std::vector<float> C(4 * 4, 0.0f);
45+ EXPECT_EQ(
46+ aclblasSgeam(
47+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha, nullptr, 4, &beta, B.data(), 4,
48+ C.data(), 4),
49+ ACLBLAS_STATUS_INVALID_VALUE);
50+}
51+ 
52+// -- TEST_F: B=nullptr (host validates and returns error) --------------------
53+TEST_F(SgeamArch35Test, NullB)
54+{
55+ float alpha = 1.0f, beta = 1.0f;
56+ std::vector<float> A(4 * 4, 1.0f);
57+ std::vector<float> C(4 * 4, 0.0f);
58+ EXPECT_EQ(
59+ aclblasSgeam(
60+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha, A.data(), 4, &beta, nullptr, 4,
61+ C.data(), 4),
62+ ACLBLAS_STATUS_INVALID_VALUE);
63+}
64+ 
65+// -- TEST_F: C=nullptr (host validates and returns error) --------------------
66+TEST_F(SgeamArch35Test, NullC)
67+{
68+ float alpha = 1.0f, beta = 1.0f;
69+ std::vector<float> A(4 * 4, 1.0f);
70+ std::vector<float> B(4 * 4, 1.0f);
71+ EXPECT_EQ(
72+ aclblasSgeam(
73+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha, A.data(), 4, &beta, B.data(), 4,
74+ nullptr, 4),
75+ ACLBLAS_STATUS_INVALID_VALUE);
76+}
77+ 
78+// -- TEST_F: alpha=nullptr returns INVALID_VALUE (host validates) ------------
79+TEST_F(SgeamArch35Test, NullAlpha)
80+{
81+ float beta = 1.0f;
82+ std::vector<float> A(4 * 4, 2.0f);
83+ std::vector<float> B(4 * 4, 3.0f);
84+ std::vector<float> C(4 * 4, kBlasSentinel);
85+ EXPECT_EQ(
86+ aclblasSgeam(
87+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, nullptr, A.data(), 4, &beta, B.data(), 4,
88+ C.data(), 4),
89+ ACLBLAS_STATUS_INVALID_VALUE);
90+}
91+ 
92+// -- TEST_F: beta=nullptr returns INVALID_VALUE (host validates) -------------
93+TEST_F(SgeamArch35Test, NullBeta)
94+{
95+ float alpha = 1.0f;
96+ std::vector<float> A(4 * 4, 2.0f);
97+ std::vector<float> B(4 * 4, 3.0f);
98+ std::vector<float> C(4 * 4, kBlasSentinel);
99+ EXPECT_EQ(
100+ aclblasSgeam(
101+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha, A.data(), 4, nullptr, B.data(), 4,
102+ C.data(), 4),
103+ ACLBLAS_STATUS_INVALID_VALUE);
104+}
105+ 
106+// -- TEST_F: transa invalid 0xFF (host validates) ----------------------------
107+TEST_F(SgeamArch35Test, TransaInvalid)
108+{
109+ float alpha = 1.0f, beta = 1.0f;
110+ std::vector<float> A(4 * 4, 1.0f);
111+ std::vector<float> B(4 * 4, 1.0f);
112+ std::vector<float> C(4 * 4, 0.0f);
113+ EXPECT_EQ(
114+ aclblasSgeam(
115+ SgeamArch35Test::handle_, static_cast<aclblasOperation_t>(0xFF), ACLBLAS_OP_N, 4, 4, &alpha, A.data(), 4,
116+ &beta, B.data(), 4, C.data(), 4),
117+ ACLBLAS_STATUS_INVALID_ENUM);
118+}
119+ 
120+// -- TEST_F: transb invalid 0xFF (host validates) ----------------------------
121+TEST_F(SgeamArch35Test, TransbInvalid)
122+{
123+ float alpha = 1.0f, beta = 1.0f;
124+ std::vector<float> A(4 * 4, 1.0f);
125+ std::vector<float> B(4 * 4, 1.0f);
126+ std::vector<float> C(4 * 4, 0.0f);
127+ EXPECT_EQ(
128+ aclblasSgeam(
129+ SgeamArch35Test::handle_, ACLBLAS_OP_N, static_cast<aclblasOperation_t>(0xFF), 4, 4, &alpha, A.data(), 4,
130+ &beta, B.data(), 4, C.data(), 4),
131+ ACLBLAS_STATUS_INVALID_ENUM);
132+}
133+ 
134+// -- TEST_F: m<0 (host validates and returns error) -------------------------
135+TEST_F(SgeamArch35Test, MNegative)
136+{
137+ float alpha = 1.0f, beta = 1.0f;
138+ EXPECT_EQ(
139+ aclblasSgeam(
140+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, -1, 4, &alpha, nullptr, 1, &beta, nullptr, 1, nullptr,
141+ 1),
142+ ACLBLAS_STATUS_INVALID_VALUE);
143+}
144+ 
145+// -- TEST_F: n<0 (host validates and returns error) -------------------------
146+TEST_F(SgeamArch35Test, NNegative)
147+{
148+ float alpha = 1.0f, beta = 1.0f;
149+ EXPECT_EQ(
150+ aclblasSgeam(
151+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, -1, &alpha, nullptr, 4, &beta, nullptr, 4, nullptr,
152+ 4),
153+ ACLBLAS_STATUS_INVALID_VALUE);
154+}
155+ 
156+// -- TEST_F: lda too small when transa=N (host validates) --------------------
157+TEST_F(SgeamArch35Test, LdaTooSmallN)
158+{
159+ float alpha = 1.0f, beta = 1.0f;
160+ std::vector<float> A(4 * 4, 1.0f);
161+ std::vector<float> B(4 * 4, 1.0f);
162+ std::vector<float> C(4 * 4, 0.0f);
163+ EXPECT_EQ(
164+ aclblasSgeam(
165+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, &alpha, A.data(), 3, &beta, B.data(), 4,
166+ C.data(), 4),
167+ ACLBLAS_STATUS_INVALID_VALUE);
168+}
169+ 
170+// -- TEST_F: lda too small when transa=T (lda<n required) -------------------
171+TEST_F(SgeamArch35Test, LdaTooSmallT)
172+{
173+ float alpha = 1.0f, beta = 1.0f;
174+ std::vector<float> A(4 * 8, 1.0f);
175+ std::vector<float> B(4 * 8, 1.0f);
176+ std::vector<float> C(4 * 8, 0.0f);
177+ EXPECT_EQ(
178+ aclblasSgeam(
179+ SgeamArch35Test::handle_, ACLBLAS_OP_T, ACLBLAS_OP_N, 4, 8, &alpha, A.data(), 7, &beta, B.data(), 8,
180+ C.data(), 4),
181+ ACLBLAS_STATUS_INVALID_VALUE);
182+}
183+ 
184+// -- TEST_F: ldc too small (ldc < m) (host validates) -----------------------
185+TEST_F(SgeamArch35Test, LdcTooSmall)
186+{
187+ float alpha = 1.0f, beta = 1.0f;
188+ std::vector<float> A(8 * 4, 1.0f);
189+ std::vector<float> B(8 * 4, 1.0f);
190+ std::vector<float> C(8 * 4, 0.0f);
191+ EXPECT_EQ(
192+ aclblasSgeam(
193+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 8, 4, &alpha, A.data(), 8, &beta, B.data(), 8,
194+ C.data(), 7),
195+ ACLBLAS_STATUS_INVALID_VALUE);
196+}
197+ 
198+// -- TEST_F: in-place C==A with transa!=N (host validates) -------------------
199+TEST_F(SgeamArch35Test, InplaceATransInvalid)
200+{
201+ float alpha = 1.0f, beta = 1.0f;
202+ std::vector<float> A(8 * 8, 1.0f);
203+ std::vector<float> B(8 * 8, 1.0f);
204+ EXPECT_EQ(
205+ aclblasSgeam(
206+ SgeamArch35Test::handle_, ACLBLAS_OP_T, ACLBLAS_OP_N, 8, 8, &alpha, A.data(), 8, &beta, B.data(), 8,
207+ A.data(), 8),
208+ ACLBLAS_STATUS_INVALID_VALUE);
209+}
210+ 
211+// -- TEST_F: in-place C==B with transb!=N (host validates) -------------------
212+TEST_F(SgeamArch35Test, InplaceBTransInvalid)
213+{
214+ float alpha = 1.0f, beta = 1.0f;
215+ std::vector<float> A(8 * 8, 1.0f);
216+ std::vector<float> B(8 * 8, 1.0f);
217+ EXPECT_EQ(
218+ aclblasSgeam(
219+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_T, 8, 8, &alpha, A.data(), 8, &beta, B.data(), 8,
220+ B.data(), 8),
221+ ACLBLAS_STATUS_INVALID_VALUE);
222+}
223+ 
224+// -- TEST_F: in-place C==A with lda!=ldc (host validates) --------------------
225+TEST_F(SgeamArch35Test, InplaceALdaNeLdc)
226+{
227+ float alpha = 1.0f, beta = 1.0f;
228+ std::vector<float> A(10 * 8, 1.0f);
229+ std::vector<float> B(10 * 8, 1.0f);
230+ EXPECT_EQ(
231+ aclblasSgeam(
232+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 8, 8, &alpha, A.data(), 10, &beta, B.data(), 8,
233+ A.data(), 8),
234+ ACLBLAS_STATUS_INVALID_VALUE);
235+}
236+ 
237+// -- TEST_F: in-place C==B with ldb!=ldc (host validates) --------------------
238+TEST_F(SgeamArch35Test, InplaceBLdbNeLdc)
239+{
240+ float alpha = 1.0f, beta = 1.0f;
241+ std::vector<float> A(10 * 8, 1.0f);
242+ std::vector<float> B(10 * 8, 1.0f);
243+ EXPECT_EQ(
244+ aclblasSgeam(
245+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 8, 8, &alpha, A.data(), 8, &beta, B.data(), 10,
246+ B.data(), 8),
247+ ACLBLAS_STATUS_INVALID_VALUE);
248+}
249+ 
250+// -- TEST_F: alpha=nullptr with A=nullptr returns INVALID_VALUE -------------
251+TEST_F(SgeamArch35Test, NullAlphaAllowsNullA)
252+{
253+ float beta = 1.0f;
254+ std::vector<float> B(4 * 4, 1.0f);
255+ std::vector<float> C(4 * 4, kBlasSentinel);
256+ EXPECT_EQ(
257+ aclblasSgeam(
258+ SgeamArch35Test::handle_, ACLBLAS_OP_N, ACLBLAS_OP_N, 4, 4, nullptr, nullptr, 4, &beta, B.data(), 4,
259+ C.data(), 4),
260+ ACLBLAS_STATUS_INVALID_VALUE);
261+}
262+ 
263+// -- CSV parameterised test suite -------------------------------------------
264+INSTANTIATE_TEST_SUITE_P(
265+ Sgeam, SgeamArch35Test, ::testing::ValuesIn(GetCasesFromCsv<SgeamParam>(ReplaceFileExtension2Csv(__FILE__))),
266+ PrintCaseInfoString<SgeamParam>);
267+ 
268+// -- Helper: generate A or B input matrix ---------------------------------------
269+static std::vector<float> SgeamGenerateInput(const SgeamParam& p, bool isA, float extraSeed)
270+{
271+ int nullFlag = isA ? p.nullA : p.nullB;
272+ if (nullFlag != 0 || p.m <= 0 || p.n <= 0) {
273+ return {};
274+ }
275+ aclblasOperation_t trans = isA ? p.transa : p.transb;
276+ int ld = isA ? p.lda : p.ldb;
277+ const BlasFillMode& fill = isA ? p.aFill : p.bFill;
278+ int rows = (trans == ACLBLAS_OP_N) ? p.m : p.n;
279+ int cols = (trans == ACLBLAS_OP_N) ? p.n : p.m;
280+ return makeBlasMatrix(rows, cols, ld, fill, p.randomSeed + static_cast<uint32_t>(extraSeed));
281+}
282+ 
283+// -- Helper: prepare C buffer (in-place aware) --------------------------------
284+static float* SgeamPrepareC(const SgeamParam& p, const float* aPtr, const float* bPtr, std::vector<float>& cHost)
285+{
286+ if (p.inplace == 1 && aPtr) {
287+ return const_cast<float*>(aPtr);
288+ }
289+ if (p.inplace == 2 && bPtr) {
290+ return const_cast<float*>(bPtr);
291+ }
292+ if (p.nullC == 0 && p.m > 0 && p.n > 0) {
293+ cHost.assign(static_cast<size_t>(p.ldc) * static_cast<size_t>(p.n), kBlasSentinel);
294+ }
295+ return cHost.empty() ? nullptr : cHost.data();
296+}
297+ 
298+// -- Helper: compute golden reference ------------------------------------------
299+static std::vector<float> SgeamComputeGolden(
300+ const SgeamParam& p, float alpha, float beta, const float* aPtr, const float* bPtr, float* cPtr,
301+ const std::vector<float>& aHost, const std::vector<float>& bHost, const std::vector<float>& cHost)
302+{
303+ std::vector<float> goldenC;
304+ if (p.expectResult != ACLBLAS_STATUS_SUCCESS || p.m <= 0 || p.n <= 0 || !cPtr) {
305+ return goldenC;
306+ }
307+ size_t goldenSize = cHost.empty() ? (p.inplace == 1 ? aHost.size() :
308+ p.inplace == 2 ? bHost.size() : 0) :
309+ cHost.size();
310+ goldenC.assign(goldenSize, kBlasSentinel);
311+ aclblasGeam_cpu<float>(
312+ p.transa, p.transb, static_cast<std::size_t>(p.m), static_cast<std::size_t>(p.n), alpha, aPtr,
313+ static_cast<std::size_t>(p.lda), beta, bPtr, static_cast<std::size_t>(p.ldb), goldenC.data(),
314+ static_cast<std::size_t>(p.ldc));
315+ return goldenC;
316+}
317+ 
318+// -- Helper: verify output precision ------------------------------------------
319+static void SgeamVerifyOutput(
320+ const SgeamParam& p, const std::vector<float>& goldenC,
321+ const std::vector<float>& aHost, const std::vector<float>& bHost, const std::vector<float>& cHost)
322+{
323+ VerifyConfig cfg;
324+ cfg.mode = PrecisionMode::MERE_MARE;
325+ cfg.mereThreshold = p.mereThreshold;
326+ cfg.mareMultiplier = p.mareMultiplier;
327+ const float* outPtr = nullptr;
328+ size_t outSize = 0;
329+ if (p.inplace == 1) {
330+ outPtr = aHost.data();
331+ outSize = aHost.size();
332+ } else if (p.inplace == 2) {
333+ outPtr = bHost.data();
334+ outSize = bHost.size();
335+ } else {
336+ outPtr = cHost.data();
337+ outSize = cHost.size();
338+ }
339+ if (outSize > 0) {
340+ EXPECT_TRUE(Verifier::verifyVector(outPtr, goldenC.data(), outSize, 1, cfg, p.caseName));
341+ }
342+}
343+ 
344+// -- TEST_P: 5-step CSV-driven flow -----------------------------------------
345+TEST_P(SgeamArch35Test, CsvDriven)
346+{
347+ const auto& p = GetParam();
348+ float alpha = p.alphaFill.val1;
349+ float beta = p.betaFill.val1;
350+ 
351+ std::vector<float> aHost = SgeamGenerateInput(p, true, 0);
352+ std::vector<float> bHost = SgeamGenerateInput(p, false, 1);
353+ const float* aPtr = aHost.empty() ? nullptr : aHost.data();
354+ const float* bPtr = bHost.empty() ? nullptr : bHost.data();
355+ 
356+ std::vector<float> cHost;
357+ float* cPtr = SgeamPrepareC(p, aPtr, bPtr, cHost);
358+ 
359+ std::vector<float> goldenC = SgeamComputeGolden(p, alpha, beta, aPtr, bPtr, cPtr, aHost, bHost, cHost);
360+ 
361+ aclblasStatus_t ret = aclblasSgeam_npu(
362+ SgeamArch35Test::handle_, p.transa, p.transb, p.m, p.n, &alpha, aPtr, p.lda, &beta, bPtr, p.ldb, cPtr, p.ldc);
363+ EXPECT_EQ(static_cast<int>(ret), ACLBLAS_STATUS_SUCCESS);
364+ 
365+ SgeamVerifyOutput(p, goldenC, aHost, bHost, cHost);
366+}
Atest/geam/sgeam/arch35/sgeam_test.csv+71-0
@@ -0,0 +1,71 @@
1+case_name,description,transa,transb,m,n,lda,ldb,ldc,alpha_fill,beta_fill,a_fill,b_fill,nullA,nullB,nullC,mere_threshold,mare_multiplier,random_seed,inplace
2+TC_L0_01,NN 2x2 basic,N,N,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
3+TC_L0_02,NT 2x2 basic,N,T,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
4+TC_L0_03,NC 2x2 basic,N,C,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
5+TC_L0_04,TN 2x2 basic,T,N,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
6+TC_L0_05,TT 2x2 basic,T,T,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
7+TC_L0_06,TC 2x2 basic,T,C,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
8+TC_L0_07,CN 2x2 basic,C,N,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
9+TC_L0_08,CT 2x2 basic,C,T,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
10+TC_L0_09,CC 2x2 basic,C,C,2,2,2,2,2,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
11+TC_L0_10,NN 4x4 basic,N,N,4,4,4,4,4,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
12+TC_L0_11,NN 1x1 minimal,N,N,1,1,1,1,1,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
13+TC_L0_12,m=0 early return,N,N,0,4,1,1,1,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0,0,42,0
14+TC_L0_13,n=0 early return,N,N,4,0,4,4,4,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0,0,42,0
15+TC_L0_14,alpha=0 beta=1,N,N,4,4,4,4,4,VALUE_NORM_0,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
16+TC_L0_15,alpha=1 beta=0 (short-circuit),N,N,4,4,4,4,4,VALUE_NORM_1,VALUE_NORM_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
17+TC_L0_16,alpha=-1 beta=-1,N,N,4,4,4,4,4,VALUE_NORM_N1,VALUE_NORM_N1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
18+TC_L0_25,T minimal ld (lda>=n),T,T,4,8,8,8,4,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
19+TC_L0_28,alpha=0 beta=0 both short-circuit,N,N,4,4,4,4,4,VALUE_NORM_0,VALUE_NORM_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
20+TC_L0_29,alpha=0.5 beta=2,N,N,4,4,4,4,4,VALUE_NORM_0.5,VALUE_NORM_2,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
21+TC_L0_30,NN m>n tall matrix,N,N,16,4,16,16,16,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
22+TC_L0_31,TT m>n tall matrix,T,T,16,4,4,4,16,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
23+TC_L0_32,NN m<n wide matrix,N,N,4,16,4,4,4,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
24+TC_L1_01,m=1 n=8 single-row NN,N,N,1,8,1,1,1,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
25+TC_L1_02,m=1 n=8 single-row NT,N,T,1,8,1,8,1,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
26+TC_L1_03,m=1 n=8 single-row NC,N,C,1,8,1,8,1,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
27+TC_L1_04,m=1 n=8 single-row TN,T,N,1,8,8,1,1,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
28+TC_L1_05,m=1 n=8 single-row TT,T,T,1,8,8,8,1,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
29+TC_L1_06,m=1 n=8 single-row CN,C,N,1,8,8,1,1,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
30+TC_L1_07,m=8 n=1 single-col NN,N,N,8,1,8,8,8,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
31+TC_L1_08,m=8 n=1 single-col TN,T,N,8,1,1,8,8,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
32+TC_L1_09,m=8 n=1 single-col CN,C,N,8,1,1,8,8,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
33+TC_L1_10,m=8 n=16 non-square NN,N,N,8,16,8,8,8,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
34+TC_L1_11,m=8 n=16 non-square TT,T,T,8,16,16,16,8,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
35+TC_L1_12,m=16 n=8 non-square NN,N,N,16,8,16,16,16,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
36+TC_L1_13,m=16 n=8 non-square TT,T,T,16,8,8,8,16,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
37+TC_L1_14,m=16 n=8 non-square CC,C,C,16,8,8,8,16,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
38+TC_L1_15,m=64 n=64 shape gradient NN,N,N,64,64,64,64,64,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
39+TC_L1_16,m=64 n=64 shape gradient TT,T,T,64,64,64,64,64,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
40+TC_L1_17,m=128 n=128 tail-m-tile NN,N,N,128,128,128,128,128,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
41+TC_L1_18,m=128 n=128 tail-m-tile CC,C,C,128,128,128,128,128,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
42+TC_L1_19,m=256 n=256 shape gradient NN,N,N,256,256,256,256,256,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
43+TC_L1_20,m=1024 n=1024 large data NN,N,N,1024,1024,1024,1024,1024,VALUE_NORM_1,VALUE_NORM_1,RANDOM_5_5,RANDOM_5_5,0,0,0,0.0001220703125,10.0,42,0
44+TC_L1_21,m=1024 n=1024 large data TT,T,T,1024,1024,1024,1024,1024,VALUE_NORM_1,VALUE_NORM_1,RANDOM_5_5,RANDOM_5_5,0,0,0,0.0001220703125,10.0,42,0
45+TC_L1_22,m=1024 n=1024 large data CC,C,C,1024,1024,1024,1024,1024,VALUE_NORM_1,VALUE_NORM_1,RANDOM_5_5,RANDOM_5_5,0,0,0,0.0001220703125,10.0,42,0
46+TC_L1_23,ld padding +1 all matrices NN,N,N,8,8,9,9,9,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
47+TC_L1_24,ld padding +32 all matrices NN,N,N,8,8,40,40,40,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
48+TC_L1_25,ld padding T mode lda=n*2,T,T,4,8,16,8,4,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
49+TC_L1_26,large ld padding 32 all NN,N,N,4,4,32,32,32,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
50+TC_L1_27,alpha=+INF beta=1,N,N,4,4,4,4,4,VALUE_NORM_INF,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
51+TC_L1_28,alpha=-INF beta=1,N,N,4,4,4,4,4,VALUE_NORM_NINF,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
52+TC_L1_29,alpha=NAN beta=1 NAN propagation,N,N,4,4,4,4,4,VALUE_NORM_NAN,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
53+TC_L1_30,beta=+INF alpha=1 INF propagation,N,N,4,4,4,4,4,VALUE_NORM_1,VALUE_NORM_INF,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
54+TC_L1_31,beta=NAN alpha=1 NAN propagation,N,N,4,4,4,4,4,VALUE_NORM_1,VALUE_NORM_NAN,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
55+TC_L1_32,m=127 n=63 non-power2 TT,T,T,127,63,63,63,127,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
56+TC_L1_33,m=33 n=255 non-power2 NN,N,N,33,255,33,33,33,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
57+TC_L1_34,alpha=0 beta=0.5 beta-only output TT,T,T,4,4,4,4,4,VALUE_NORM_0,VALUE_NORM_0.5,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
58+TC_L1_35,alpha=2 beta=-1 differential NN,N,N,8,8,8,8,8,VALUE_NORM_2,VALUE_NORM_N1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
59+TC_L1_36,A all zero B random NN,N,N,8,8,8,8,8,VALUE_NORM_1,VALUE_NORM_1,VALUE_NORM_0,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
60+TC_L1_37,B all zero A random NN,N,N,8,8,8,8,8,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,VALUE_NORM_0,0,0,0,0.0001220703125,10.0,42,0
61+TC_L1_38,INDEX fill equidistant NN,N,N,8,8,8,8,8,VALUE_NORM_1,VALUE_NORM_1,INDEX,INDEX,0,0,0,0.0001220703125,10.0,42,0
62+TC_L1_39,INDEX_ALTER fill NN,N,N,8,8,8,8,8,VALUE_NORM_1,VALUE_NORM_1,INDEX_ALTER,INDEX_ALTER,0,0,0,0.0001220703125,10.0,42,0
63+TC_L1_40,m=5000 n=1 TT batch loop curM>4095,T,T,5000,1,1,1,5000,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,0
64+TC_L1_41,in-place C==A transa=N lda=ldc,N,N,8,8,8,8,8,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,1
65+TC_L1_42,in-place C==B transb=N ldb=ldc,N,N,8,8,8,8,8,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,2
66+TC_L1_47,in-place C==A alpha=0 beta=0,N,N,8,8,8,8,8,VALUE_NORM_0,VALUE_NORM_0,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,1
67+TC_L1_48,in-place C==A large N=128,N,N,128,128,128,128,128,VALUE_NORM_1,VALUE_NORM_1,RANDOM_1_1,RANDOM_1_1,0,0,0,0.0001220703125,10.0,42,1
68+TC_L1_49,alpha=0 A=nullptr NN,N,N,8,8,8,8,8,VALUE_NORM_0,VALUE_NORM_1,NULLPTR,RANDOM_1_1,1,0,0,0.0001220703125,10.0,42,0
69+TC_L1_50,alpha=0 A=nullptr TT,T,T,16,4,4,4,16,VALUE_NORM_0,VALUE_NORM_1,NULLPTR,RANDOM_1_1,1,0,0,0.0001220703125,10.0,42,0
70+TC_L1_51,alpha=0 beta=0 both nullptr NN,N,N,8,8,8,8,8,VALUE_NORM_0,VALUE_NORM_0,NULLPTR,NULLPTR,1,1,0,0.0001220703125,10.0,42,0
71+TC_L1_53,beta=0 B=nullptr alpha=1 NN,N,N,8,8,8,8,8,VALUE_NORM_1,VALUE_NORM_0,RANDOM_1_1,NULLPTR,0,1,0,0.0001220703125,10.0,42,0
Atest/geam/sgeam/sgeam_param.h+53-0
@@ -0,0 +1,53 @@
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+#pragma once
12+ 
13+#include <algorithm>
14+#include <string>
15+#include "cann_ops_blas.h"
16+#include "csv_loader.h"
17+ 
18+struct SgeamParam : public BlasTestParamBase {
19+ aclblasOperation_t transa = ACLBLAS_OP_N;
20+ aclblasOperation_t transb = ACLBLAS_OP_N;
21+ int m = 0;
22+ int n = 0;
23+ int lda = 0;
24+ int ldb = 0;
25+ int ldc = 0;
26+ BlasFillMode alphaFill = BlasFillMode("VALUE_NORM_1");
27+ BlasFillMode betaFill = BlasFillMode("VALUE_NORM_1");
28+ BlasFillMode aFill = BlasFillMode("RANDOM_1_1");
29+ BlasFillMode bFill = BlasFillMode("RANDOM_1_1");
30+ int nullA = 0;
31+ int nullB = 0;
32+ int nullC = 0;
33+ int inplace = 0; // 0=out-of-place, 1=C==A, 2=C==B
34+ 
35+ SgeamParam(const csv_map& csv) : BlasTestParamBase(csv)
36+ {
37+ transa = parseOpTrans(ReadMap(csv, "transa", "N"));
38+ transb = parseOpTrans(ReadMap(csv, "transb", "N"));
39+ m = parseInt(ReadMap(csv, "m", "0"));
40+ n = parseInt(ReadMap(csv, "n", "0"));
41+ lda = parseInt(ReadMap(csv, "lda", std::to_string((transa == ACLBLAS_OP_N) ? std::max(1, m) : std::max(1, n))));
42+ ldb = parseInt(ReadMap(csv, "ldb", std::to_string((transb == ACLBLAS_OP_N) ? std::max(1, m) : std::max(1, n))));
43+ ldc = parseInt(ReadMap(csv, "ldc", std::to_string(std::max(1, m))));
44+ alphaFill = BlasFillMode(ReadMap(csv, "alpha_fill", "VALUE_NORM_1"));
45+ betaFill = BlasFillMode(ReadMap(csv, "beta_fill", "VALUE_NORM_1"));
46+ aFill = BlasFillMode(ReadMap(csv, "a_fill", "RANDOM_1_1"));
47+ bFill = BlasFillMode(ReadMap(csv, "b_fill", "RANDOM_1_1"));
48+ nullA = parseInt(ReadMap(csv, "nullA", "0"));
49+ nullB = parseInt(ReadMap(csv, "nullB", "0"));
50+ nullC = parseInt(ReadMap(csv, "nullC", "0"));
51+ inplace = parseInt(ReadMap(csv, "inplace", "0"));
52+ }
53+};