已开启
Feat: 新增面向arch22的aclblasCaxpy任意incx/incy支持 #334
Feat: 新增面向arch22的aclblasCaxpy任意incx/incy支持 #334
已开启
guodong54_创建于 29 天前
13 个文件变更+2072-376
@@ -0,0 +1,159 @@
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+#ifndef OPS_BLAS_CAXPY_DISPATCH_POLICY_H
12+#define OPS_BLAS_CAXPY_DISPATCH_POLICY_H
13+ 
14+#include <cstdint>
15+ 
16+enum class CaxpyStrideClass : uint32_t {
17+ DENSE = 0,
18+ X_ONLY_POSITIVE = 1,
19+ Y_ONLY_POSITIVE = 2,
20+ DUAL_SYMMETRIC_POSITIVE = 3,
21+ DUAL_ASYMMETRIC_POSITIVE = 4,
22+ NEGATIVE_MIXED = 5,
23+};
24+ 
25+enum class CaxpyKernelVariant : uint32_t {
26+ DENSE_PIPELINED_ATOMIC = 0,
27+ STRIDED_SCALAR = 1,
28+ STRIDED_SHARED_PIPELINED_ATOMIC = 2,
29+ STRIDED_DISJOINT_PIPELINED_ATOMIC = 3,
30+ STRIDED_SHARED_PIPELINED_SCALAR_TAIL = 4,
31+ STRIDED_DISJOINT_PIPELINED_SCALAR_TAIL = 5,
32+};
33+ 
34+constexpr uint64_t CAXPY_STRIDED_SCALAR_MAX_PER_BLOCK = 16;
35+constexpr uint64_t CAXPY_STRIDED_MAX_PER_BLOCK_PER_WAVE = 8192;
36+// Fixed tile capacity used by the disjoint pipeline and as the shared
37+// pipeline's UB buffer allocation size (see CAXPY_STRIDED_TILE_COUNT in
38+// caxpy_kernel.cpp, which must stay equal to this value).
39+constexpr uint64_t CAXPY_STRIDED_TILE_COUNT = 512;
40+// The shared pipeline's lowScratch UB scratch buffer used for the raw span
41+// read is 38 KiB (CAXPY_DENSE_TILE_BYTES in caxpy_kernel.cpp). A span read of
42+// `tile` complex elements at increment `incx` covers
43+// ((tile-1)*|incx|+1) complex elements == ((tile-1)*|incx|+1)*8 bytes of
44+// physical address range, which must fit in that buffer. Solving for the
45+// largest tile <= CAXPY_STRIDED_TILE_COUNT that satisfies this bound gives
46+// the runtime tile capacity the shared pipeline should use for a given incx;
47+// this generalizes the old fixed CAXPY_SHARED_X_SPAN_MAX_INCREMENT=9 cutoff
48+// (which was really just "does tile=512 fit"), letting larger |incx| still
49+// use the faster span-read shared pipeline instead of falling back to
50+// disjoint sparse reads, as long as the shrunk tile stays usable.
51+constexpr uint64_t CAXPY_SHARED_LOW_SCRATCH_BYTES = 38 * 1024;
52+constexpr uint64_t CAXPY_COMPLEX_BYTES = 8;
53+ 
54+// Largest tile (in complex elements, capped at CAXPY_STRIDED_TILE_COUNT) whose
55+// span read at the given |incx| fits in the shared pipeline's lowScratch
56+// buffer. absIncx must be >= 1 (callers only invoke this for direct-strided,
57+// non-dense cases where incx != 0).
58+constexpr uint64_t CaxpySharedTileCapacity(uint64_t absIncx)
59+{
60+ const uint64_t maxSpanComplexElements = CAXPY_SHARED_LOW_SCRATCH_BYTES / CAXPY_COMPLEX_BYTES;
61+ // tile such that (tile-1)*absIncx + 1 <= maxSpanComplexElements
62+ // => tile <= (maxSpanComplexElements - 1) / absIncx + 1
63+ const uint64_t maxTileForIncrement = (maxSpanComplexElements - 1) / absIncx + 1;
64+ return maxTileForIncrement < CAXPY_STRIDED_TILE_COUNT ? maxTileForIncrement : CAXPY_STRIDED_TILE_COUNT;
65+}
66+ 
67+// The shared pipeline's tail-splitting logic (shared_tile_bounds in
68+// caxpy_kernel.cpp) rebalances the last two tiles when the natural final tile
69+// would be shorter than CAXPY_STRIDED_SCALAR_MAX_PER_BLOCK+1
70+// (CAXPY_INTERLEAVE_COUNT+1 in caxpy_kernel.cpp, same constant) elements, by
71+// splitting their combined length roughly in half. For that split to never
72+// itself produce a half shorter than the interleave width, the worst case
73+// (combined length == tileCapacity+1, the smallest total that still triggers
74+// the split branch) must divide into two halves each >=
75+// CAXPY_STRIDED_SCALAR_MAX_PER_BLOCK+1: ceil((tileCapacity+1)/2) and
76+// floor((tileCapacity+1)/2) both >= CAXPY_STRIDED_SCALAR_MAX_PER_BLOCK+1,
77+// which holds starting at tileCapacity == 2*(CAXPY_STRIDED_SCALAR_MAX_PER_BLOCK+1)-1.
78+// A smaller usable-capacity threshold (originally CAXPY_STRIDED_SCALAR_MAX_PER_BLOCK+1
79+// itself) let the split produce a too-short half, caught by the
80+// dynamic_shared_boundary_usable regression test (n=8000, incx=303, tile=17)
81+// only in a Release build where the resulting silent wrong compute went
82+// uncaught -- a shrunk-tile-capacity variant of the same class of bug as the
83+// earlier fixed-tile computeOffset drift.
84+constexpr uint64_t CAXPY_SHARED_MIN_USABLE_TILE_CAPACITY = 2 * (CAXPY_STRIDED_SCALAR_MAX_PER_BLOCK + 1) - 1;
85+ 
86+// CAXPY_SHARED_MIN_USABLE_TILE_CAPACITY only bounds *correctness*: below it
87+// the tail-split can produce too-short tiles. It says nothing about
88+// performance. As |incx| grows, the shrunk tile's span read
89+// ((tile-1)*|incx|+1 complex elements) carries a shrinking fraction of useful
90+// data -- at incx=16 the span is ~6% useful, at incx=100 it is ~1% -- so GM
91+// bandwidth spent reading skipped-over elements eventually exceeds what the
92+// disjoint pipeline's per-element sparse reads cost instead. Measured on
93+// Ascend 910B3 / CANN 9.0 A3, n=1048576 (xlarge, enough tiles per core to
94+// reach steady state and not be dominated by per-tile dispatch overhead):
95+// shared span-read is faster than disjoint through incx<=60, comparable
96+// around incx=60-65, and progressively worse beyond that (roughly +7% at
97+// incx=70, +97% at incx=150 in one measured run). This cutoff is therefore a
98+// measured performance crossover, not a UB-buffer-fit or correctness limit;
99+// re-measure if the UB budget, tile capacity formula, or disjoint pipeline's
100+// per-element cost changes.
101+constexpr uint64_t CAXPY_SHARED_SPAN_PERFORMANCE_MAX_INCREMENT = 60;
102+ 
103+constexpr uint64_t CaxpyFinalWaveCount(uint64_t n, uint32_t numBlocks)
104+{
105+ const uint64_t waveCapacity = static_cast<uint64_t>(numBlocks) * CAXPY_STRIDED_MAX_PER_BLOCK_PER_WAVE;
106+ if (n <= waveCapacity)
107+ return n;
108+ const uint64_t remainder = n % waveCapacity;
109+ return remainder == 0 ? waveCapacity : remainder;
110+}
111+ 
112+// Preconditions are enforced by aclblasCaxpy argument validation and execution
113+// setup: incx and incy are non-zero and are not INT64_MIN; numBlocks is non-zero.
114+constexpr CaxpyStrideClass ClassifyCaxpyStride(int64_t incx, int64_t incy)
115+{
116+ if (incx < 0 || incy < 0)
117+ return CaxpyStrideClass::NEGATIVE_MIXED;
118+ if (incx == 1 && incy == 1)
119+ return CaxpyStrideClass::DENSE;
120+ if (incy == 1)
121+ return CaxpyStrideClass::X_ONLY_POSITIVE;
122+ if (incx == 1)
123+ return CaxpyStrideClass::Y_ONLY_POSITIVE;
124+ if (incx == incy)
125+ return CaxpyStrideClass::DUAL_SYMMETRIC_POSITIVE;
126+ return CaxpyStrideClass::DUAL_ASYMMETRIC_POSITIVE;
127+}
128+ 
129+constexpr uint64_t CaxpyAbsIncrement(int64_t increment)
130+{
131+ return increment > 0 ? static_cast<uint64_t>(increment) : static_cast<uint64_t>(-(increment + 1)) + 1;
132+}
133+ 
134+constexpr bool UseCaxpySharedXSpanPipeline(int64_t incx)
135+{
136+ const uint64_t absIncx = CaxpyAbsIncrement(incx);
137+ return absIncx <= CAXPY_SHARED_SPAN_PERFORMANCE_MAX_INCREMENT &&
138+ CaxpySharedTileCapacity(absIncx) >= CAXPY_SHARED_MIN_USABLE_TILE_CAPACITY;
139+}
140+ 
141+constexpr CaxpyKernelVariant SelectCaxpyKernelVariant(
142+ uint64_t n, int64_t incx, int64_t incy, bool directStrided, uint32_t numBlocks)
143+{
144+ const CaxpyStrideClass strideClass = ClassifyCaxpyStride(incx, incy);
145+ if (strideClass == CaxpyStrideClass::DENSE || !directStrided)
146+ return CaxpyKernelVariant::DENSE_PIPELINED_ATOMIC;
147+ const bool sharedXSpan = UseCaxpySharedXSpanPipeline(incx);
148+ const uint64_t finalWaveCount = CaxpyFinalWaveCount(n, numBlocks);
149+ const uint64_t finalWaveBlocks = finalWaveCount < numBlocks ? finalWaveCount : numBlocks;
150+ const uint64_t minBlockCount = finalWaveCount / finalWaveBlocks;
151+ if (minBlockCount <= CAXPY_STRIDED_SCALAR_MAX_PER_BLOCK)
152+ return n == finalWaveCount ? CaxpyKernelVariant::STRIDED_SCALAR :
153+ (sharedXSpan ? CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_SCALAR_TAIL :
154+ CaxpyKernelVariant::STRIDED_DISJOINT_PIPELINED_SCALAR_TAIL);
155+ return sharedXSpan ? CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC :
156+ CaxpyKernelVariant::STRIDED_DISJOINT_PIPELINED_ATOMIC;
157+}
158+ 
159+#endif // OPS_BLAS_CAXPY_DISPATCH_POLICY_H
@@ -14,6 +14,8 @@
14 */14 */
15 15 
16#include <cstdint>16#include <cstdint>
17+#include <cstddef>
18+#include <limits>
17#include <iostream>19#include <iostream>
18#include <vector>20#include <vector>
19#include <algorithm>21#include <algorithm>
@@ -22,30 +24,58 @@
22#include "cann_ops_blas.h"24#include "cann_ops_blas.h"
23#include "common/helper/aclblas_handle_internal.h"25#include "common/helper/aclblas_handle_internal.h"
24#include "common/helper/host_utils.h"26#include "common/helper/host_utils.h"
27+#include "caxpy_dispatch_policy.h"
28+#include "caxpy_tiling_data.h"
25 29 
26-void caxpy_kernel_do(uint8_t* x, uint8_t* maskBuf, uint8_t* y, uint8_t* workSpace, uint8_t* tilingGm,30+void caxpy_kernel_do(
Z
Zzhuzhiming3 天前

【编码规范 R10】禁止在 Host 里用前向声明引用 kernel_do。

请新增对应的 *_kernel.h,把 kernel_do 声明放到头文件,Host 通过 #include 引入。R10 例外只覆盖 kernel.cpp 里 Ascend C 要求的 extern "C" 入口。

likedislike
27- uint32_t numBlocks, void *stream);31+ uint8_t* x, uint8_t* maskBuf, uint8_t* y, uint8_t* workSpace, uint8_t* tilingGm, uint32_t numBlocks, void* stream);
32+void caxpy_strided_shared_kernel_do(
33+ uint8_t* x, uint8_t* maskBuf, uint8_t* y, uint8_t* workSpace, uint8_t* tilingGm, uint32_t numBlocks, void* stream);
34+void caxpy_strided_disjoint_kernel_do(
35+ uint8_t* x, uint8_t* maskBuf, uint8_t* y, uint8_t* workSpace, uint8_t* tilingGm, uint32_t numBlocks, void* stream);
36+void caxpy_strided_scalar_kernel_do(uint8_t* x, uint8_t* y, uint8_t* tilingGm, uint32_t numBlocks, void* stream);
28 37 
29-constexpr uint32_t COMPLEX_NUM = 2;38+constexpr uint32_t FLOATS_PER_COMPLEX = 2;
30constexpr uint32_t K_FACTOR_4 = 4;39constexpr uint32_t K_FACTOR_4 = 4;
31constexpr uint32_t DEFAULT_VECTOR_NUM = 40;40constexpr uint32_t DEFAULT_VECTOR_NUM = 40;
41+static_assert(
42+ DEFAULT_VECTOR_NUM == CAXPY_MAX_VECTOR_CORES,
43+ "Host block cap must match the tiling struct's per-block array size");
32constexpr uint32_t MAX_DATA_COUNT = 38 * 1024 / sizeof(float);44constexpr uint32_t MAX_DATA_COUNT = 38 * 1024 / sizeof(float);
45+constexpr uint32_t STRIDED_TILE_COMPLEX_COUNT = 512;
46+constexpr uint32_t BLOCKED_FLOATS_PER_COMPLEX = 8;
47+constexpr uint32_t GATHER_PADDING_COUNT = 64;
48+constexpr uint32_t X_NORMALIZE_OFFSET_COUNT =
49+ STRIDED_TILE_COMPLEX_COUNT * BLOCKED_FLOATS_PER_COMPLEX + GATHER_PADDING_COUNT;
50+constexpr uint32_t STORE_OFFSET_COUNT = STRIDED_TILE_COMPLEX_COUNT * FLOATS_PER_COMPLEX + GATHER_PADDING_COUNT;
51+constexpr uint32_t X_NORMALIZE_OFFSET_START = MAX_DATA_COUNT;
52+constexpr uint32_t STORE_OFFSET_START = X_NORMALIZE_OFFSET_START + X_NORMALIZE_OFFSET_COUNT;
53+constexpr uint32_t Y_GATHER_OFFSET_COUNT = STRIDED_TILE_COMPLEX_COUNT * FLOATS_PER_COMPLEX + GATHER_PADDING_COUNT;
54+constexpr uint32_t Y_GATHER_OFFSET_START = STORE_OFFSET_START + STORE_OFFSET_COUNT;
55+constexpr uint32_t DENSE_SWAP_OFFSET_START = Y_GATHER_OFFSET_START + Y_GATHER_OFFSET_COUNT;
56+constexpr uint32_t MASK_DATA_COUNT = DENSE_SWAP_OFFSET_START + MAX_DATA_COUNT;
57+static_assert(STORE_OFFSET_START + STORE_OFFSET_COUNT <= MASK_DATA_COUNT);
58+static_assert(Y_GATHER_OFFSET_START + Y_GATHER_OFFSET_COUNT <= MASK_DATA_COUNT);
59+static_assert(DENSE_SWAP_OFFSET_START + MAX_DATA_COUNT <= MASK_DATA_COUNT);
33 60 
34-struct CaxpyTilingData {61+// CaxpyTilingData is defined once in the shared header and included by both the
35- uint32_t n;62+// host and the device kernel; see caxpy_tiling_data.h. CAXPY_MAX_VECTOR_CORES
36- float alphaReal;63+// from that header equals DEFAULT_VECTOR_NUM below.
37- float alphaImag;
38- uint32_t startOffset[40];
39- uint32_t calNum[40];
40-};
41 64 
42-CaxpyTilingData CalTilingData(uint32_t n, uint32_t vecCoreNum, float alphaReal, float alphaImag)65+CaxpyTilingData CalTilingData(
66+ uint32_t n, uint32_t vecCoreNum, float alphaReal, float alphaImag, int64_t incx, int64_t incy,
67+ uint32_t logicalBase = 0, uint32_t totalN = 0)
43{68{
44- CaxpyTilingData tilingData;69+ CaxpyTilingData tilingData{};
45- tilingData.n = n * COMPLEX_NUM;70+ tilingData.packedFloatCount = n * FLOATS_PER_COMPLEX;
46 tilingData.alphaReal = alphaReal;71 tilingData.alphaReal = alphaReal;
47 tilingData.alphaImag = alphaImag;72 tilingData.alphaImag = alphaImag;
73+ tilingData.totalN = totalN == 0 ? n : totalN;
74+ tilingData.incx = incx;
75+ tilingData.incy = incy;
48 76 
77+ // Guard against division by zero below. Callers currently always pass a
78+ // non-zero count, but this helper stays self-contained.
49 if (vecCoreNum == 0) {79 if (vecCoreNum == 0) {
50 vecCoreNum = 1;80 vecCoreNum = 1;
51 }81 }
@@ -56,98 +86,456 @@ CaxpyTilingData CalTilingData(uint32_t n, uint32_t vecCoreNum, float alphaReal,
56 tilingData.calNum[i] = 0;86 tilingData.calNum[i] = 0;
57 }87 }
58 88 
59- uint32_t rowNumEachCore = n / vecCoreNum;89+ // Distribute n complex elements across vecCoreNum blocks as evenly as
60- uint32_t remainRowNum = n % vecCoreNum;90+ // possible: the first `remainder` blocks take one extra element. When
91+ // n < vecCoreNum, baseComplexPerCore is 0 and only the first n blocks get
92+ // one element each; the trailing blocks keep calNum == 0 and are skipped by
93+ // the device (it guards on calNum > 0), so no special-case branch is needed.
94+ const uint32_t baseComplexPerCore = n / vecCoreNum;
95+ const uint32_t remainder = n % vecCoreNum;
61 96 
62- if (rowNumEachCore == 0) {97+ uint32_t currOffset = logicalBase * FLOATS_PER_COMPLEX;
63- for (uint32_t i = 0; i < remainRowNum; i++) {98+ for (uint32_t i = 0; i < vecCoreNum; i++) {
64- tilingData.calNum[i] = COMPLEX_NUM;99+ const uint32_t complexThisCore = baseComplexPerCore + (i < remainder ? 1 : 0);
65- tilingData.startOffset[i] = i * COMPLEX_NUM;100+ tilingData.calNum[i] = complexThisCore * FLOATS_PER_COMPLEX;
66- }101+ tilingData.startOffset[i] = currOffset;
67- } else {102+ currOffset += complexThisCore * FLOATS_PER_COMPLEX;
68- uint32_t currOffset = 0;
69- uint32_t currNum;
70- for (uint32_t i = 0; i < vecCoreNum; i++) {
71- if (i < remainRowNum) {
72- currNum = rowNumEachCore + 1;
73- } else {
74- currNum = rowNumEachCore;
75- }
76- tilingData.calNum[i] = currNum * COMPLEX_NUM;
77- tilingData.startOffset[i] = currOffset;
78- currOffset += currNum * COMPLEX_NUM;
79- }
80 }103 }
81 104 
82 return tilingData;105 return tilingData;
83}106}
84 107 
85-void GenMaskData(uint32_t* maskData)108+// Magnitude of a BLAS increment. Computed as -(inc+1)+1 rather than -inc so
109+// that INT64_MIN would not overflow -- though callers reject INT64_MIN upstream
110+// (ValidateCaxpyArguments), this keeps the helper correct in isolation.
111+uint64_t AbsIncrement(int64_t increment)
86{112{
87- uint32_t offsetNum = COMPLEX_NUM;113+ return increment > 0 ? static_cast<uint64_t>(increment) : static_cast<uint64_t>(-(increment + 1)) + 1;
88- uint32_t complexCount = MAX_DATA_COUNT / COMPLEX_NUM;114+}
115+ 
116+bool CheckedVectorSpan(int64_t n, int64_t increment, uint64_t& span)
117+{
118+ const uint64_t absIncrement = AbsIncrement(increment);
119+ if (n <= 1) {
120+ span = 1;
121+ return true;
122+ }
123+ const uint64_t logicalDistance = static_cast<uint64_t>(n - 1);
124+ if (logicalDistance > (std::numeric_limits<uint64_t>::max() - 1) / absIncrement)
125+ return false;
126+ span = 1 + logicalDistance * absIncrement;
127+ return span <= std::numeric_limits<size_t>::max() / sizeof(aclblasComplex);
128+}
129+ 
130+// Maps a logical element index (0..n-1) to its physical index in memory. For a
131+// negative increment BLAS walks the vector backwards, so logical 0 sits at the
132+// far end.
133+uint64_t PhysicalIndex(uint64_t logicalIndex, uint64_t n, int64_t increment)
134+{
135+ const uint64_t absIncrement = AbsIncrement(increment);
136+ return increment > 0 ? logicalIndex * absIncrement : (n - 1 - logicalIndex) * absIncrement;
137+}
138+ 
139+// The mask buffer holds precomputed gather/scatter offset tables so the device
140+// kernel never has to derive strided addresses on its hot path. Each Gen*Offsets
141+// routine fills one section (see the *_OFFSET_START layout constants above).
142+ 
143+// Interleaved -> planar: real parts land in the first half, imaginary in the
144+// second, so downstream vector ops see two contiguous real-valued lanes.
145+void GenComputeOffsets(uint32_t* offsets)
146+{
147+ const uint32_t complexCount = MAX_DATA_COUNT / FLOATS_PER_COMPLEX;
89 for (uint32_t i = 0; i < complexCount; i++) {148 for (uint32_t i = 0; i < complexCount; i++) {
90- maskData[offsetNum * i] = K_FACTOR_4 * i;149+ offsets[FLOATS_PER_COMPLEX * i] = K_FACTOR_4 * i;
91- maskData[offsetNum * i + 1] = K_FACTOR_4 * (i + complexCount);150+ offsets[FLOATS_PER_COMPLEX * i + 1] = K_FACTOR_4 * (i + complexCount);
92 }151 }
93}152}
94 153 
95-aclblasStatus_t aclblasCaxpy(154+// Swaps real/imaginary byte positions within each complex element in place.
96- aclblasHandle_t handle, int n, const aclblasComplex* alpha, const aclblasComplex* x, int incx,155+void GenDenseSwapOffsets(uint32_t* offsets)
97- aclblasComplex* y, int incy)
98{156{
99- if (alpha == nullptr) {157+ const uint32_t complexCount = MAX_DATA_COUNT / FLOATS_PER_COMPLEX;
158+ for (uint32_t i = 0; i < complexCount; ++i) {
159+ offsets[FLOATS_PER_COMPLEX * i] = (FLOATS_PER_COMPLEX * i + 1) * sizeof(float);
160+ offsets[FLOATS_PER_COMPLEX * i + 1] = FLOATS_PER_COMPLEX * i * sizeof(float);
161+ }
162+}
163+ 
164+// Gathers strided x into the blocked (8-float-per-complex) working layout. For
165+// increments where the shared pipeline's span read still fits its lowScratch
166+// UB buffer (UseCaxpySharedXSpanPipeline), the span read brings the data in
167+// contiguously and only the blocked layout stride remains; larger increments
168+// fall back to the disjoint pipeline, which reads each element individually
169+// and does not use this span-read addressing at all.
170+void GenXNormalizeOffsets(uint32_t* offsets, uint32_t xIncrement)
171+{
172+ for (uint32_t i = 0; i < STRIDED_TILE_COMPLEX_COUNT; ++i) {
173+ const uint32_t sourceByteOffset =
174+ UseCaxpySharedXSpanPipeline(static_cast<int64_t>(xIncrement)) ?
175+ i * xIncrement * FLOATS_PER_COMPLEX * sizeof(float) :
176+ i * BLOCKED_FLOATS_PER_COMPLEX * sizeof(float);
177+ for (uint32_t lane = 0; lane < BLOCKED_FLOATS_PER_COMPLEX; ++lane) {
178+ offsets[i * BLOCKED_FLOATS_PER_COMPLEX + lane] =
179+ lane == 1 ? sourceByteOffset + sizeof(float) : sourceByteOffset;
180+ }
181+ }
182+}
183+ 
184+// Scatters results from the blocked layout back to packed interleaved complex.
185+void GenStoreOffsets(uint32_t* offsets)
186+{
187+ for (uint32_t i = 0; i < STRIDED_TILE_COMPLEX_COUNT; ++i) {
188+ const uint32_t sourceByteOffset = i * BLOCKED_FLOATS_PER_COMPLEX * sizeof(float);
189+ offsets[i * FLOATS_PER_COMPLEX] = sourceByteOffset;
190+ offsets[i * FLOATS_PER_COMPLEX + 1] = sourceByteOffset + sizeof(float);
191+ }
192+}
193+ 
194+// Gathers strided y (real, imag) pairs for the accumulation step.
195+void GenYGatherOffsets(uint32_t* offsets, uint32_t yIncrement)
196+{
197+ for (uint32_t i = 0; i < STRIDED_TILE_COMPLEX_COUNT; ++i) {
198+ const uint32_t sourceByteOffset = i * yIncrement * FLOATS_PER_COMPLEX * sizeof(float);
199+ offsets[i * FLOATS_PER_COMPLEX] = sourceByteOffset;
200+ offsets[i * FLOATS_PER_COMPLEX + 1] = sourceByteOffset + sizeof(float);
201+ }
202+}
203+ 
204+void GenMaskData(uint32_t* maskData, uint32_t xIncrement, uint32_t yIncrement)
205+{
206+ std::fill(maskData, maskData + MASK_DATA_COUNT, 0);
207+ GenComputeOffsets(maskData);
208+ GenXNormalizeOffsets(maskData + X_NORMALIZE_OFFSET_START, xIncrement);
209+ GenStoreOffsets(maskData + STORE_OFFSET_START);
210+ GenYGatherOffsets(maskData + Y_GATHER_OFFSET_START, yIncrement);
211+ GenDenseSwapOffsets(maskData + DENSE_SWAP_OFFSET_START);
212+}
213+ 
214+aclblasStatus_t GetCaxpyMaskCache(aclblasHandle_t handle, uint64_t absIncx, uint64_t absIncy, uint8_t** maskDevice)
215+{
216+ auto* h = handle;
217+ if (h->caxpy_mask_cache == nullptr) {
218+ void* buffer = nullptr;
219+ const aclError aclRet = aclrtMalloc(&buffer, MASK_DATA_COUNT * sizeof(uint32_t), ACL_MEM_MALLOC_HUGE_FIRST);
220+ CHECK_RET(
221+ aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", aclRet);
222+ return ACLBLAS_STATUS_ALLOC_FAILED);
223+ h->caxpy_mask_cache = buffer;
224+ }
225+ if (h->caxpy_mask_cache_incx != absIncx || h->caxpy_mask_cache_incy != absIncy) {
226+ // Kernels from earlier calls may still be reading the cached mask; drain the
227+ // stream before overwriting it in place.
228+ const aclblasStatus_t syncStatus = SynchronizeHandleStream(h);
229+ CHECK_RET(syncStatus == ACLBLAS_STATUS_SUCCESS, return syncStatus);
230+ std::vector<uint32_t> maskHost(MASK_DATA_COUNT);
231+ GenMaskData(maskHost.data(), static_cast<uint32_t>(absIncx), static_cast<uint32_t>(absIncy));
232+ const aclError aclRet = aclrtMemcpy(
233+ h->caxpy_mask_cache, MASK_DATA_COUNT * sizeof(uint32_t), maskHost.data(),
234+ MASK_DATA_COUNT * sizeof(uint32_t), ACL_MEMCPY_HOST_TO_DEVICE);
235+ CHECK_RET(
236+ aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", aclRet);
237+ return ACLBLAS_STATUS_INTERNAL_ERROR);
238+ h->caxpy_mask_cache_incx = absIncx;
239+ h->caxpy_mask_cache_incy = absIncy;
240+ }
241+ *maskDevice = reinterpret_cast<uint8_t*>(h->caxpy_mask_cache);
242+ return ACLBLAS_STATUS_SUCCESS;
243+}
244+ 
245+struct CaxpyExecution {
246+ int64_t n;
247+ int64_t incx;
248+ int64_t incy;
249+ uint32_t numBlocks;
250+ bool useStridedKernel;
251+ bool packX;
252+ bool packY;
253+ CaxpyKernelVariant kernelVariant;
254+ aclrtStream stream;
255+ uint8_t* tilingDevice;
256+ uint8_t* maskDevice;
257+ CaxpyTilingData tiling;
258+};
259+ 
260+struct CaxpyPackedBuffers {
261+ uint8_t* x = nullptr;
262+ uint8_t* y = nullptr;
263+};
264+ 
265+aclblasStatus_t ValidateCaxpyArguments(
266+ aclblasHandle_t handle, int64_t n, const aclblasComplex* alpha, const aclblasComplex* x, int64_t incx,
267+ const aclblasComplex* y, int64_t incy)
268+{
269+ if (handle == nullptr || alpha == nullptr || n < 0 || incx == 0 || incy == 0)
Z
Zzhuzhiming3 天前

【逻辑】handle == nullptr 应返回 ACLBLAS_STATUS_HANDLE_IS_NULLPTR,不要和 alpha==nullptr / incx==0 一起折成 INVALID_VALUE

同仓其它接口(含本批 aclblasSspr / aclblasChpr)对空 handle 都走 HANDLE_IS_NULLPTR。另外 n==0 建议在校验指针之前早退,与 SSPR 等接口一致,避免 no-op 仍要求 alpha/incx。

likedislike
270+ return ACLBLAS_STATUS_INVALID_VALUE;
Z
Zzhuzhiming2 天前

另返回错误前增加打印, 方便定位

likedislike
271+ if (incx == std::numeric_limits<int64_t>::min() || incy == std::numeric_limits<int64_t>::min()) {
272+ return ACLBLAS_STATUS_INVALID_VALUE;
273+ }
274+ if (n > 0 && (x == nullptr || y == nullptr))
275+ return ACLBLAS_STATUS_INVALID_VALUE;
276+ if (n > std::numeric_limits<uint32_t>::max() / FLOATS_PER_COMPLEX)
277+ return ACLBLAS_STATUS_INVALID_VALUE;
278+ uint64_t span = 0;
279+ if (n > 0 && (!CheckedVectorSpan(n, incx, span) || !CheckedVectorSpan(n, incy, span))) {
100 return ACLBLAS_STATUS_INVALID_VALUE;280 return ACLBLAS_STATUS_INVALID_VALUE;
101 }281 }
102- auto* h = handle;
103- aclrtStream useStream = h->stream;
104- 
105- uint32_t numBlocks = DEFAULT_VECTOR_NUM;
106- 
107- float alphaReal = alpha->real;
108- float alphaImag = alpha->imag;
109- 
110- CaxpyTilingData tiling = CalTilingData(static_cast<uint32_t>(n), numBlocks, alphaReal, alphaImag);
111- 
112- uint32_t maskSize = MAX_DATA_COUNT * sizeof(uint32_t) * COMPLEX_NUM;
113- 
114- uint8_t* maskDevice = nullptr;
115- uint8_t* tilingDevice = nullptr;
116- 
117- uint32_t* maskHost = new uint32_t[MAX_DATA_COUNT * COMPLEX_NUM];
118- GenMaskData(maskHost);
119- 
120- aclError aclRet = aclrtMalloc((void**)&maskDevice, maskSize, ACL_MEM_MALLOC_HUGE_FIRST);
121- CHECK_RET(
122- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", aclRet); delete[] maskHost;
123- return ACLBLAS_STATUS_ALLOC_FAILED);
124- 
125- aclRet = aclrtMalloc((void**)&tilingDevice, sizeof(CaxpyTilingData), ACL_MEM_MALLOC_HUGE_FIRST);
126- CHECK_RET(
127- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", aclRet); aclrtFree(maskDevice);
128- delete[] maskHost; return ACLBLAS_STATUS_ALLOC_FAILED);
129- 
130- aclRet = aclrtMemcpy(maskDevice, maskSize, maskHost, maskSize, ACL_MEMCPY_HOST_TO_DEVICE);
131- CHECK_RET(
132- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", aclRet); aclrtFree(tilingDevice);
133- aclrtFree(maskDevice); delete[] maskHost; return ACLBLAS_STATUS_INTERNAL_ERROR);
134- 
135- aclRet =
136- aclrtMemcpy(tilingDevice, sizeof(CaxpyTilingData), &tiling, sizeof(CaxpyTilingData), ACL_MEMCPY_HOST_TO_DEVICE);
137- CHECK_RET(
138- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", aclRet); aclrtFree(tilingDevice);
139- aclrtFree(maskDevice); delete[] maskHost; return ACLBLAS_STATUS_INTERNAL_ERROR);
140- 
141- caxpy_kernel_do(reinterpret_cast<uint8_t*>(const_cast<aclblasComplex*>(x)), maskDevice, reinterpret_cast<uint8_t*>(y), nullptr,
142- tilingDevice, numBlocks, useStream);
143- aclRet = aclrtSynchronizeStream(useStream);
144- CHECK_RET(
145- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", aclRet); aclrtFree(tilingDevice);
146- aclrtFree(maskDevice); delete[] maskHost; return ACLBLAS_STATUS_INTERNAL_ERROR);
147- 
148- aclrtFree(maskDevice);
149- aclrtFree(tilingDevice);
150- delete[] maskHost;
151- 
152 return ACLBLAS_STATUS_SUCCESS;282 return ACLBLAS_STATUS_SUCCESS;
153-}283+}
284+ 
285+aclblasStatus_t BuildCaxpyExecution(
286+ aclblasHandle_t handle, int64_t n, const aclblasComplex& alpha, int64_t incx, int64_t incy,
287+ CaxpyExecution& execution)
288+{
289+ uint32_t numBlocks = GetAivCoreCount();
290+ CHECK_RET(numBlocks != 0, LOG_PRINT("GetAivCoreCount failed.\n"); return ACLBLAS_STATUS_EXECUTION_FAILED);
291+ numBlocks = std::min(numBlocks, DEFAULT_VECTOR_NUM);
292+ const uint64_t absIncx = AbsIncrement(incx);
293+ const uint64_t absIncy = AbsIncrement(incy);
294+ const uint64_t maxIncrement = std::max(absIncx, absIncy);
295+ const bool incrementFits = maxIncrement <= std::numeric_limits<uint32_t>::max() / sizeof(aclblasComplex);
296+ const bool addressFits =
297+ n <= 1 || (incrementFits && static_cast<uint64_t>(n - 1) <=
298+ std::numeric_limits<uint32_t>::max() / (FLOATS_PER_COMPLEX * maxIncrement));
299+ const bool contiguous = incx == 1 && incy == 1;
300+ const bool useStridedKernel = !contiguous && incrementFits && addressFits;
301+ const CaxpyKernelVariant kernelVariant =
302+ SelectCaxpyKernelVariant(static_cast<uint64_t>(n), incx, incy, useStridedKernel, numBlocks);
303+ auto* h = handle;
304+ CHECK_RET(
305+ sizeof(CaxpyTilingData) <= GetEffectiveWorkspaceSize(h),
306+ LOG_PRINT("workspace need %zu, available %zu\n", sizeof(CaxpyTilingData), GetEffectiveWorkspaceSize(h));
307+ return ACLBLAS_STATUS_EXECUTION_FAILED);
308+ uint8_t* maskDevice = nullptr;
309+ if (kernelVariant != CaxpyKernelVariant::STRIDED_SCALAR) {
310+ const aclblasStatus_t maskStatus = GetCaxpyMaskCache(handle, absIncx, absIncy, &maskDevice);
311+ CHECK_RET(maskStatus == ACLBLAS_STATUS_SUCCESS, return maskStatus);
312+ }
313+ // When a vector is strided but we are not taking the strided kernel path,
314+ // fall back to packing it into a contiguous scratch buffer and running the
315+ // dense kernel. Unit-stride vectors need no packing.
316+ const bool packX = !contiguous && !useStridedKernel && incx != 1;
317+ const bool packY = !contiguous && !useStridedKernel && incy != 1;
318+ execution = {
319+ n,
320+ incx,
321+ incy,
322+ numBlocks,
323+ useStridedKernel,
324+ packX,
325+ packY,
326+ kernelVariant,
327+ h->stream,
328+ reinterpret_cast<uint8_t*>(GetEffectiveWorkspace(h)),
329+ maskDevice,
330+ CalTilingData(
331+ static_cast<uint32_t>(n), numBlocks, alpha.real, alpha.imag, useStridedKernel ? incx : 1,
332+ useStridedKernel ? incy : 1)};
333+ return ACLBLAS_STATUS_SUCCESS;
334+}
335+ 
336+void FreePackedBuffers(CaxpyPackedBuffers& buffers)
337+{
338+ if (buffers.y != nullptr)
339+ aclrtFree(buffers.y);
340+ if (buffers.x != nullptr)
341+ aclrtFree(buffers.x);
342+ buffers = {};
343+}
344+ 
345+aclblasStatus_t AllocatePackedBuffers(const CaxpyExecution& execution, CaxpyPackedBuffers& buffers)
346+{
347+ const size_t bytes = static_cast<size_t>(execution.n) * sizeof(aclblasComplex);
348+ if (execution.packX) {
349+ const aclError result = aclrtMalloc(reinterpret_cast<void**>(&buffers.x), bytes, ACL_MEM_MALLOC_HUGE_FIRST);
350+ CHECK_RET(
351+ result == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", result);
352+ return ACLBLAS_STATUS_ALLOC_FAILED);
353+ }
354+ if (execution.packY) {
355+ const aclError result = aclrtMalloc(reinterpret_cast<void**>(&buffers.y), bytes, ACL_MEM_MALLOC_HUGE_FIRST);
356+ CHECK_RET(
357+ result == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", result); FreePackedBuffers(buffers);
358+ return ACLBLAS_STATUS_ALLOC_FAILED);
359+ }
360+ return ACLBLAS_STATUS_SUCCESS;
361+}
362+ 
363+aclblasStatus_t CopyStridedVector(
364+ uint8_t* packed, uint8_t* strided, uint64_t n, int64_t increment, aclrtStream stream, bool unpack)
365+{
366+ for (uint64_t i = 0; i < n; ++i) {
367+ const size_t packedOffset = static_cast<size_t>(i) * sizeof(aclblasComplex);
368+ const size_t stridedOffset = static_cast<size_t>(PhysicalIndex(i, n, increment)) * sizeof(aclblasComplex);
369+ uint8_t* destination = unpack ? strided + stridedOffset : packed + packedOffset;
370+ uint8_t* source = unpack ? packed + packedOffset : strided + stridedOffset;
371+ const aclError result = aclrtMemcpyAsync(
372+ destination, sizeof(aclblasComplex), source, sizeof(aclblasComplex), ACL_MEMCPY_DEVICE_TO_DEVICE, stream);
373+ CHECK_RET(
374+ result == ACL_SUCCESS, LOG_PRINT("aclrtMemcpyAsync failed. ERROR: %d\n", result);
375+ return ACLBLAS_STATUS_INTERNAL_ERROR);
376+ }
377+ return ACLBLAS_STATUS_SUCCESS;
378+}
379+ 
380+aclblasStatus_t PreparePackedInputs(
381+ const CaxpyExecution& execution, const aclblasComplex* x, aclblasComplex* y, CaxpyPackedBuffers& buffers)
382+{
383+ aclblasStatus_t status = AllocatePackedBuffers(execution, buffers);
384+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, return status);
385+ if (execution.packX) {
386+ status = CopyStridedVector(
387+ buffers.x, const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(x)), execution.n, execution.incx,
388+ execution.stream, false);
389+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, FreePackedBuffers(buffers); return status);
390+ }
391+ if (execution.packY) {
392+ status = CopyStridedVector(
393+ buffers.y, reinterpret_cast<uint8_t*>(y), execution.n, execution.incy, execution.stream, false);
394+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, FreePackedBuffers(buffers); return status);
395+ }
396+ return ACLBLAS_STATUS_SUCCESS;
397+}
398+ 
399+aclblasStatus_t UploadTiling(const CaxpyExecution& execution, const CaxpyTilingData& tiling)
400+{
401+ const aclError result = aclrtMemcpyAsync(
402+ execution.tilingDevice, sizeof(tiling), &tiling, sizeof(tiling), ACL_MEMCPY_HOST_TO_DEVICE, execution.stream);
403+ CHECK_RET(
404+ result == ACL_SUCCESS, LOG_PRINT("aclrtMemcpyAsync failed. ERROR: %d\n", result);
405+ return ACLBLAS_STATUS_INTERNAL_ERROR);
406+ return ACLBLAS_STATUS_SUCCESS;
407+}
408+ 
409+// Builds tiling for a strided launch covering `count` complex elements across
410+// `blocks` blocks. CalTilingData fills the leading word with packedFloatCount,
411+// but the strided kernels expect the active block count there, so we switch the
412+// union to the activeBlocks view (see CaxpyTilingData in caxpy_tiling_data.h).
413+CaxpyTilingData BuildStridedTiling(
414+ const CaxpyExecution& execution, const aclblasComplex& alpha, uint32_t count, uint32_t blocks)
415+{
416+ CaxpyTilingData tiling = CalTilingData(
417+ count, blocks, alpha.real, alpha.imag, execution.incx, execution.incy, 0, count);
418+ tiling.activeBlocks = blocks;
419+ // Only the shared pipeline's run_shared_pipeline/shared_tile_bounds read
420+ // this field; the disjoint and scalar kernel entry points ignore it and
421+ // keep using the fixed CAXPY_STRIDED_TILE_COUNT capacity, so it is safe
422+ // to always populate it here for every strided variant.
423+ tiling.sharedTileCapacity = static_cast<uint32_t>(CaxpySharedTileCapacity(AbsIncrement(execution.incx)));
424+ return tiling;
425+}
426+ 
427+uint64_t CaxpySegmentStorageOffset(uint64_t totalN, uint64_t segmentStart, uint64_t segmentN, int64_t increment)
428+{
429+ const uint64_t absIncrement = AbsIncrement(increment);
430+ return increment > 0 ? segmentStart * absIncrement : (totalN - segmentStart - segmentN) * absIncrement;
431+}
432+ 
433+void LaunchCaxpyStridedPipelined(const CaxpyExecution& execution, uint8_t* x, uint8_t* y, uint32_t blocks)
434+{
435+ if (execution.kernelVariant == CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC ||
436+ execution.kernelVariant == CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_SCALAR_TAIL) {
437+ caxpy_strided_shared_kernel_do(
438+ x, execution.maskDevice, y, nullptr, execution.tilingDevice, blocks, execution.stream);
439+ return;
440+ }
441+ caxpy_strided_disjoint_kernel_do(
442+ x, execution.maskDevice, y, nullptr, execution.tilingDevice, blocks, execution.stream);
443+}
444+ 
445+aclblasStatus_t LaunchCaxpyStridedSegment(
446+ const CaxpyExecution& execution, const aclblasComplex& alpha, const aclblasComplex* x, aclblasComplex* y,
447+ uint64_t segmentStart, uint64_t segmentN, bool scalar)
448+{
449+ const uint32_t blocks = static_cast<uint32_t>(std::min<uint64_t>(execution.numBlocks, segmentN));
450+ CaxpyTilingData tiling = BuildStridedTiling(execution, alpha, static_cast<uint32_t>(segmentN), blocks);
451+ const aclblasStatus_t status = UploadTiling(execution, tiling);
452+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, return status);
453+ 
454+ const uint64_t totalN = static_cast<uint64_t>(execution.n);
455+ const uint64_t xOffset = CaxpySegmentStorageOffset(totalN, segmentStart, segmentN, execution.incx);
456+ const uint64_t yOffset = CaxpySegmentStorageOffset(totalN, segmentStart, segmentN, execution.incy);
457+ auto* segmentX = const_cast<aclblasComplex*>(x + xOffset);
458+ auto* segmentY = y + yOffset;
459+ if (scalar) {
460+ caxpy_strided_scalar_kernel_do(
461+ reinterpret_cast<uint8_t*>(segmentX), reinterpret_cast<uint8_t*>(segmentY), execution.tilingDevice, blocks,
462+ execution.stream);
463+ } else {
464+ LaunchCaxpyStridedPipelined(
465+ execution, reinterpret_cast<uint8_t*>(segmentX), reinterpret_cast<uint8_t*>(segmentY), blocks);
466+ }
467+ return ACLBLAS_STATUS_SUCCESS;
468+}
469+ 
470+aclblasStatus_t LaunchCaxpy(
471+ const CaxpyExecution& execution, const aclblasComplex& alpha, const aclblasComplex* x, aclblasComplex* y,
472+ const CaxpyPackedBuffers& buffers)
473+{
474+ if (execution.useStridedKernel) {
475+ if (execution.kernelVariant == CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_SCALAR_TAIL ||
476+ execution.kernelVariant == CaxpyKernelVariant::STRIDED_DISJOINT_PIPELINED_SCALAR_TAIL) {
477+ const uint64_t tailN = CaxpyFinalWaveCount(static_cast<uint64_t>(execution.n), execution.numBlocks);
478+ const uint64_t prefixN = static_cast<uint64_t>(execution.n) - tailN;
479+ aclblasStatus_t status = LaunchCaxpyStridedSegment(execution, alpha, x, y, 0, prefixN, false);
480+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, return status);
481+ status = LaunchCaxpyStridedSegment(execution, alpha, x, y, prefixN, tailN, true);
482+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, return status);
483+ return ACLBLAS_STATUS_SUCCESS;
484+ }
485+ const uint32_t blocks = static_cast<uint32_t>(std::min<uint64_t>(execution.numBlocks, execution.n));
486+ CaxpyTilingData tiling = BuildStridedTiling(execution, alpha, static_cast<uint32_t>(execution.n), blocks);
487+ const aclblasStatus_t status = UploadTiling(execution, tiling);
488+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, return status);
489+ if (execution.kernelVariant == CaxpyKernelVariant::STRIDED_SCALAR) {
490+ caxpy_strided_scalar_kernel_do(
491+ const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(x)), reinterpret_cast<uint8_t*>(y),
492+ execution.tilingDevice, blocks, execution.stream);
493+ } else {
494+ LaunchCaxpyStridedPipelined(
495+ execution, const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(x)), reinterpret_cast<uint8_t*>(y),
496+ blocks);
497+ }
498+ return ACLBLAS_STATUS_SUCCESS;
499+ }
500+ const aclblasStatus_t status = UploadTiling(execution, execution.tiling);
501+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, return status);
502+ caxpy_kernel_do(
503+ execution.packX ? buffers.x : const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(x)), execution.maskDevice,
504+ execution.packY ? buffers.y : reinterpret_cast<uint8_t*>(y), nullptr, execution.tilingDevice,
505+ execution.numBlocks, execution.stream);
506+ return ACLBLAS_STATUS_SUCCESS;
507+}
508+ 
509+aclblasStatus_t FinishCaxpy(const CaxpyExecution& execution, aclblasComplex* y, CaxpyPackedBuffers& buffers)
510+{
511+ if (execution.packY) {
512+ const aclblasStatus_t status = CopyStridedVector(
513+ buffers.y, reinterpret_cast<uint8_t*>(y), execution.n, execution.incy, execution.stream, true);
514+ CHECK_RET(status == ACLBLAS_STATUS_SUCCESS, FreePackedBuffers(buffers); return status);
515+ }
516+ const aclError result = aclrtSynchronizeStream(execution.stream);
517+ CHECK_RET(
Z
Zzhuzhiming3 天前

【逻辑】公开接口在 FinishCaxpyaclrtSynchronizeStream,把异步下发变成了同步 API。

调用方应通过 handle 上的 stream 自行同步。请去掉接口内同步;若打包回退路径必须等拷贝完成,也只应作用在那条回退上,不要绑死整条 Caxpy。

likedislike
518+ result == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", result);
519+ FreePackedBuffers(buffers); return ACLBLAS_STATUS_INTERNAL_ERROR);
520+ FreePackedBuffers(buffers);
521+ return ACLBLAS_STATUS_SUCCESS;
522+}
523+ 
524+aclblasStatus_t aclblasCaxpy(
525+ aclblasHandle_t handle, int n, const aclblasComplex* alpha, const aclblasComplex* x, int incx, aclblasComplex* y,
526+ int incy)
527+{
528+ const aclblasStatus_t argumentStatus = ValidateCaxpyArguments(handle, n, alpha, x, incx, y, incy);
529+ CHECK_RET(argumentStatus == ACLBLAS_STATUS_SUCCESS, return argumentStatus);
530+ if (n == 0)
531+ return ACLBLAS_STATUS_SUCCESS;
532+ CaxpyExecution execution{};
533+ const aclblasStatus_t buildStatus = BuildCaxpyExecution(handle, n, *alpha, incx, incy, execution);
534+ CHECK_RET(buildStatus == ACLBLAS_STATUS_SUCCESS, return buildStatus);
535+ CaxpyPackedBuffers buffers;
536+ const aclblasStatus_t prepareStatus = PreparePackedInputs(execution, x, y, buffers);
537+ CHECK_RET(prepareStatus == ACLBLAS_STATUS_SUCCESS, return prepareStatus);
538+ const aclblasStatus_t launchStatus = LaunchCaxpy(execution, *alpha, x, y, buffers);
539+ CHECK_RET(launchStatus == ACLBLAS_STATUS_SUCCESS, FreePackedBuffers(buffers); return launchStatus);
540+ return FinishCaxpy(execution, y, buffers);
541+}
@@ -0,0 +1,64 @@
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 <cstddef>
14+#include <cstdint>
15+ 
16+// Single source of truth for the CAXPY host->device tiling block. BOTH the host
17+// (caxpy_host.cpp) and the device kernel (caxpy_kernel.cpp) include this header
18+// and access fields by name. Do not re-declare this layout anywhere else and do
19+// not read it via hard-coded byte offsets -- that is exactly the drift hazard
20+// this shared definition exists to remove.
21+ 
22+constexpr uint32_t CAXPY_MAX_VECTOR_CORES = 40;
23+ 
24+struct CaxpyTilingData {
25+ // Leading word is variant-dependent, so it is exposed through two named
26+ // views over the same storage instead of a single misleadingly-named field:
27+ // - packedFloatCount: dense kernel; number of packed floats to process
28+ // (complex count * 2).
29+ // - activeBlocks: strided kernels; number of blocks that actually run.
30+ // The device kernel selects the view matching the entry point it was
31+ // launched through. Both are uint32_t at offset 0, so the on-wire layout is
32+ // identical to a plain leading uint32_t -- existing binaries are unaffected.
33+ union {
34+ uint32_t packedFloatCount;
35+ uint32_t activeBlocks;
36+ };
37+ float alphaReal;
38+ float alphaImag;
39+ uint32_t startOffset[CAXPY_MAX_VECTOR_CORES]; // per-block start, in floats
40+ uint32_t calNum[CAXPY_MAX_VECTOR_CORES]; // per-block element count, in floats
41+ uint32_t totalN;
42+ int64_t incx;
43+ int64_t incy;
44+ // Shared-pipeline span-read tile capacity, in complex elements. Computed by
45+ // the host from incx so that the span read
46+ // ((sharedTileCapacity-1)*|incx|+1) complex elements always fits in the
47+ // 38 KiB lowScratch UB buffer -- see CaxpySharedTileCapacity in
48+ // caxpy_dispatch_policy.h for the formula. Unused (left at its default of
49+ // CAXPY_STRIDED_TILE_COUNT) by the disjoint and scalar kernel variants,
50+ // which never span-read.
51+ uint32_t sharedTileCapacity;
52+};
53+ 
54+// The device kernel historically read these fields via literal byte offsets.
55+// Pin them so any accidental layout change is caught at compile time on both
56+// sides, and so the values remain greppable next to the struct.
57+static_assert(offsetof(CaxpyTilingData, alphaReal) == 4, "CAXPY tiling layout changed");
58+static_assert(offsetof(CaxpyTilingData, alphaImag) == 8, "CAXPY tiling layout changed");
59+static_assert(offsetof(CaxpyTilingData, startOffset) == 12, "CAXPY tiling layout changed");
60+static_assert(offsetof(CaxpyTilingData, calNum) == 172, "CAXPY tiling layout changed");
61+static_assert(offsetof(CaxpyTilingData, totalN) == 332, "CAXPY tiling layout changed");
62+static_assert(offsetof(CaxpyTilingData, incx) == 336, "CAXPY tiling layout changed");
63+static_assert(offsetof(CaxpyTilingData, incy) == 344, "CAXPY tiling layout changed");
64+static_assert(offsetof(CaxpyTilingData, sharedTileCapacity) == 352, "CAXPY tiling layout changed");
@@ -64,6 +64,13 @@ aclblasStatus_t aclblasDestroy(aclblasHandle_t handle)
64 64 
65 FreeLibraryWorkspace(h);65 FreeLibraryWorkspace(h);
66 66 
67+ if (h->caxpy_mask_cache != nullptr) {
68+ aclrtFree(h->caxpy_mask_cache);
69+ h->caxpy_mask_cache = nullptr;
70+ h->caxpy_mask_cache_incx = 0;
71+ h->caxpy_mask_cache_incy = 0;
72+ }
73+ 
67 h->stream = nullptr;74 h->stream = nullptr;
68 75 
69 delete h;76 delete h;
@@ -55,6 +55,15 @@ struct _aclblas_handle {
55 55 
56 /** Size of the last library-managed workspace buffer (preserved across user switches). */56 /** Size of the last library-managed workspace buffer (preserved across user switches). */
57 size_t library_workspace_size = 0;57 size_t library_workspace_size = 0;
58+ 
59+ /* ========== Cached caxpy gather-offset buffer ========== */
60+ /** Device buffer holding the caxpy gather-offset table; owned by the handle and
61+ * built once per |incx| instead of being re-allocated and re-uploaded per call. */
62+ void* caxpy_mask_cache = nullptr;
63+ /** The |incx| value the cached mask was built for; 0 means the cache content is invalid. */
64+ uint64_t caxpy_mask_cache_incx = 0;
65+ /** The |incy| value the cached mask was built for; 0 means the cache content is invalid. */
66+ uint64_t caxpy_mask_cache_incy = 0;
58};67};
59 68 
60/**69/**
@@ -8,4 +8,14 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------------------------------------9# ----------------------------------------------------------------------------------------------------------
10 10 
11-ops_blas_add_tests(${OPS_BLAS})11+ops_blas_add_gtest_tests(
12+ ${OPS_BLAS}
13+ caxpy_test)
14+ 
15+ops_blas_add_tests(
16+ ${OPS_BLAS}
17+ caxpy_dispatch_policy_test)
18+ 
19+target_include_directories(
20+ caxpy_dispatch_policy_test PRIVATE
21+ ${CMAKE_SOURCE_DIR}/blas/axpy/arch22)
@@ -0,0 +1,121 @@
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 <array>
12+#include <cstdint>
13+#include <iostream>
14+ 
15+#include "caxpy_dispatch_policy.h"
16+ 
17+namespace {
18+struct ClassificationCase {
19+ int64_t incx;
20+ int64_t incy;
21+ CaxpyStrideClass expected;
22+};
23+ 
24+struct PolicyCase {
25+ uint64_t n;
26+ int64_t incx;
27+ int64_t incy;
28+ bool directStrided;
29+ uint32_t numBlocks;
30+ CaxpyKernelVariant expected;
31+};
32+ 
33+ 
34+constexpr std::array<ClassificationCase, 15> CASES{{
35+ {1, 1, CaxpyStrideClass::DENSE},
36+ {2, 1, CaxpyStrideClass::X_ONLY_POSITIVE},
37+ {9, 1, CaxpyStrideClass::X_ONLY_POSITIVE},
38+ {1, 2, CaxpyStrideClass::Y_ONLY_POSITIVE},
39+ {1, 10, CaxpyStrideClass::Y_ONLY_POSITIVE},
40+ {2, 2, CaxpyStrideClass::DUAL_SYMMETRIC_POSITIVE},
41+ {10, 10, CaxpyStrideClass::DUAL_SYMMETRIC_POSITIVE},
42+ {2, 3, CaxpyStrideClass::DUAL_ASYMMETRIC_POSITIVE},
43+ {10, 11, CaxpyStrideClass::DUAL_ASYMMETRIC_POSITIVE},
44+ {-1, 1, CaxpyStrideClass::NEGATIVE_MIXED},
45+ {1, -1, CaxpyStrideClass::NEGATIVE_MIXED},
46+ {-2, 3, CaxpyStrideClass::NEGATIVE_MIXED},
47+ {5, -3, CaxpyStrideClass::NEGATIVE_MIXED},
48+ {-2, -3, CaxpyStrideClass::NEGATIVE_MIXED},
49+ {-10, -10, CaxpyStrideClass::NEGATIVE_MIXED},
50+}};
51+ 
52+constexpr std::array<PolicyCase, 23> POLICY_CASES{{
53+ {1, 1, 1, false, 1, CaxpyKernelVariant::DENSE_PIPELINED_ATOMIC},
54+ {8192, 1, 1, false, 40, CaxpyKernelVariant::DENSE_PIPELINED_ATOMIC},
55+ {640, 2, 1, true, 40, CaxpyKernelVariant::STRIDED_SCALAR},
56+ {640, 1, 3, true, 40, CaxpyKernelVariant::STRIDED_SCALAR},
57+ {640, 5, 5, true, 40, CaxpyKernelVariant::STRIDED_SCALAR},
58+ {640, 2, 3, true, 40, CaxpyKernelVariant::STRIDED_SCALAR},
59+ {640, -2, 3, true, 40, CaxpyKernelVariant::STRIDED_SCALAR},
60+ {641, 5, 1, true, 40, CaxpyKernelVariant::STRIDED_SCALAR},
61+ {679, 5, 1, true, 40, CaxpyKernelVariant::STRIDED_SCALAR},
62+ {17, 65537, -32769, true, 40, CaxpyKernelVariant::STRIDED_SCALAR},
63+ {680, 9, 1, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC},
64+ {680, 1, 9, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC},
65+ {680, 5, 5, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC},
66+ {680, 2, 3, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC},
67+ {680, -2, -3, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC},
68+ // incx=10 now stays on the shared span-read pipeline: it exceeds the old
69+ // fixed CAXPY_SHARED_X_SPAN_MAX_INCREMENT=9 cutoff but is well within
70+ // both the tile-capacity correctness floor and the measured performance
71+ // cap (CAXPY_SHARED_SPAN_PERFORMANCE_MAX_INCREMENT=60). This case used to
72+ // assert STRIDED_DISJOINT_PIPELINED_ATOMIC under the old fixed threshold;
73+ // that expectation is now wrong on purpose -- see caxpy_dispatch_policy.h
74+ // for the crossover measurement this depends on.
75+ {680, 10, 1, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC},
76+ // incx=61 exceeds the performance cap and correctly falls back to
77+ // disjoint even though its shrunk tile (capacity 80) would still be
78+ // usable by the correctness floor alone.
79+ {680, 61, 1, true, 40, CaxpyKernelVariant::STRIDED_DISJOINT_PIPELINED_ATOMIC},
80+ // incx=304 shrinks the tile capacity to 32, below
81+ // CAXPY_SHARED_MIN_USABLE_TILE_CAPACITY (33) -- the correctness floor,
82+ // not the performance cap, is what forces disjoint here. Kept as
83+ // defense-in-depth coverage of that floor in case the performance cap is
84+ // ever raised past this incx.
85+ {680, 304, 1, true, 40, CaxpyKernelVariant::STRIDED_DISJOINT_PIPELINED_ATOMIC},
86+ {327680, 2, 3, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC},
87+ {327681, 2, 3, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_SCALAR_TAIL},
88+ {328359, -2, 3, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_SCALAR_TAIL},
89+ {328360, 2, -3, true, 40, CaxpyKernelVariant::STRIDED_SHARED_PIPELINED_ATOMIC},
90+ {640, 5, 5, false, 40, CaxpyKernelVariant::DENSE_PIPELINED_ATOMIC},
91+}};
92+ 
93+ 
94+} // namespace
95+ 
96+int main()
97+{
98+ bool passed = true;
99+ for (const ClassificationCase& test : CASES) {
100+ const CaxpyStrideClass actual = ClassifyCaxpyStride(test.incx, test.incy);
101+ if (actual == test.expected)
102+ continue;
103+ std::cerr << "classification mismatch: incx=" << test.incx << " incy=" << test.incy
104+ << " expected=" << static_cast<uint32_t>(test.expected)
105+ << " actual=" << static_cast<uint32_t>(actual) << '\n';
106+ passed = false;
107+ }
108+ for (const PolicyCase& test : POLICY_CASES) {
109+ const CaxpyKernelVariant actual =
110+ SelectCaxpyKernelVariant(test.n, test.incx, test.incy, test.directStrided, test.numBlocks);
111+ if (actual == test.expected)
112+ continue;
113+ std::cerr << "policy mismatch: n=" << test.n << " incx=" << test.incx << " incy=" << test.incy
114+ << " direct_strided=" << test.directStrided
115+ << " expected=" << static_cast<uint32_t>(test.expected)
116+ << " actual=" << static_cast<uint32_t>(actual) << '\n';
117+ passed = false;
118+ }
119+ 
120+ return passed ? 0 : 1;
121+}
@@ -0,0 +1,93 @@
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 <cstdint>
14+#include <vector>
15+ 
16+#include "acl/acl.h"
17+#include "cann_ops_blas.h"
18+#include "fill.h"
19+ 
20+static inline bool CaxpyNeedPassThrough(aclblasHandle_t handle, int n)
21+{
22+ return handle == nullptr || n <= 0;
23+}
24+ 
25+static inline aclError CaxpyAllocCopyH2D(void*& dPtr, const void* hPtr, size_t bytes)
26+{
27+ dPtr = nullptr;
28+ if (hPtr == nullptr || bytes == 0) return ACL_SUCCESS;
29+ aclError ret = aclrtMalloc(&dPtr, bytes, ACL_MEM_MALLOC_HUGE_FIRST);
30+ if (ret != ACL_SUCCESS) return ret;
31+ ret = aclrtMemcpy(dPtr, bytes, hPtr, bytes, ACL_MEMCPY_HOST_TO_DEVICE);
32+ if (ret != ACL_SUCCESS) {
33+ aclrtFree(dPtr);
34+ dPtr = nullptr;
35+ }
36+ return ret;
37+}
38+ 
39+static inline void CaxpyFreeAll(void* dX, void* dY)
40+{
41+ if (dX) aclrtFree(dX);
Z
Zzhuzhiming3 天前

【编码规范】if (ptr) aclrtFree(ptr); 必须使用 {}

本仓 .clang-formatAllowShortIfStatementsOnASingleLine: false)以及昇腾 C++ 规范要求 if 必须加大括号。同文件其余单行 if 请一起改。

likedislike
42+ if (dY) aclrtFree(dY);
43+}
44+ 
45+// Physical storage span (in complex elements) covered by a length-n vector
46+// with the given (possibly negative) increment.
47+static inline size_t CaxpySpanElements(int n, int increment)
48+{
49+ if (n <= 0) return 0;
50+ const int64_t absIncrement = increment >= 0 ? static_cast<int64_t>(increment) : -static_cast<int64_t>(increment);
51+ return static_cast<size_t>((static_cast<int64_t>(n) - 1) * absIncrement + 1);
52+}
53+ 
54+inline aclblasStatus_t aclblasCaxpy_npu(
55+ aclblasHandle_t handle, int n, const aclblasComplex* alpha, const aclblasComplex* x, int incx,
56+ aclblasComplex* y, int incy)
57+{
58+ if (CaxpyNeedPassThrough(handle, n)) {
59+ return aclblasCaxpy(handle, n, alpha, x, incx, y, incy);
60+ }
61+ 
62+ const size_t xBytes = CaxpySpanElements(n, incx) * sizeof(aclblasComplex);
63+ const size_t yBytes = CaxpySpanElements(n, incy) * sizeof(aclblasComplex);
64+ 
65+ void* dX = nullptr;
66+ void* dY = nullptr;
67+ 
68+ if (CaxpyAllocCopyH2D(dX, x, xBytes) != ACL_SUCCESS) {
69+ return ACLBLAS_STATUS_ALLOC_FAILED;
70+ }
71+ if (CaxpyAllocCopyH2D(dY, y, yBytes) != ACL_SUCCESS) {
72+ CaxpyFreeAll(dX, dY);
73+ return ACLBLAS_STATUS_ALLOC_FAILED;
74+ }
75+ 
76+ aclblasStatus_t ret = aclblasCaxpy(
77+ handle, n, alpha, static_cast<const aclblasComplex*>(dX), incx, static_cast<aclblasComplex*>(dY), incy);
78+ 
79+ if (aclrtSynchronizeDevice() != ACL_SUCCESS) {
80+ CaxpyFreeAll(dX, dY);
81+ return ACLBLAS_STATUS_INTERNAL_ERROR;
82+ }
83+ 
84+ if (ret == ACLBLAS_STATUS_SUCCESS && y != nullptr && dY != nullptr) {
85+ if (aclrtMemcpy(y, yBytes, dY, yBytes, ACL_MEMCPY_DEVICE_TO_HOST) != ACL_SUCCESS) {
86+ CaxpyFreeAll(dX, dY);
87+ return ACLBLAS_STATUS_INTERNAL_ERROR;
88+ }
89+ }
90+ 
91+ CaxpyFreeAll(dX, dY);
92+ return ret;
93+}
@@ -8,158 +8,111 @@
8 * See LICENSE in the root of the software repository for the full text of the License.8 * See LICENSE in the root of the software repository for the full text of the License.
9 */9 */
10 10 
11-/* !
12- * \file caxpy_test.cpp
13- * \brief Test for aclblasCaxpy interface
14- */
15- 
16-#include <cstdint>
17-#include <iostream>
18#include <vector>11#include <vector>
19-#include <algorithm>
20-#include <iterator>
21-#include "acl/acl.h"
22-#include "cann_ops_blas.h"
23-#include "complex.h"
24 12 
25-#define CHECK_RET(cond, return_expr) \13+#include "verify.h"
26- do { \14+#include "blas_test.h"
27- if (!(cond)) { \15+#include "csv_loader.h"
28- return_expr; \16+#include "fill.h"
29- } \17+#include "caxpy_param.h"
30- } while (0)18+#include "caxpy_golden.h"
19+#include "caxpy_npu_wrapper.h"
31 20 
32-#define LOG_PRINT(message, ...) \21+namespace {
33- do { \
34- printf(message, ##__VA_ARGS__); \
35- } while (0)
36 22 
37-uint32_t VerifyCaxpyResult(std::vector<aclblasComplex>& output, std::vector<aclblasComplex>& golden)23+constexpr aclblasComplex GUARD_VALUE{12345.25F, -54321.5F};
24+constexpr size_t GUARD_COUNT = 64;
25+ 
26+// Deterministic per-index complex value generator: independent of BlasFillMode
27+// so cross-checking against the reference cblas-style AXPY is exact, matching
28+// the layout the legacy hardcoded-array caxpy_test used before the CSV
29+// migration.
30+aclblasComplex MakeStridedValue(size_t index, int32_t salt)
38{31{
39- auto printTensor = [](std::vector<aclblasComplex>& tensor, const char* name) {32+ const float real = 0.125F * static_cast<float>(static_cast<int32_t>(index % 29) - 14 + salt);
40- constexpr size_t maxPrintSize = 10;33+ const float imag = 0.0625F * static_cast<float>(static_cast<int32_t>(index % 23) - 11 - salt);
41- std::cout << name << ": ";34+ return {real, imag};
42- for (size_t i = 0; i < std::min(tensor.size(), maxPrintSize); i++) {
43- std::cout << "(" << tensor[i].real << "," << tensor[i].imag << ") ";
44- }
45- if (tensor.size() > maxPrintSize) {
46- std::cout << "...";
47- }
48- std::cout << std::endl;
49- };
50- printTensor(output, "Output");
51- printTensor(golden, "Golden");
52- constexpr float EPSILON = 1e-3f;
53- for (size_t i = 0; i < output.size(); i++) {
54- float diff = blasComplexAbs(output[i] - golden[i]);
55- if (diff > EPSILON) {
56- std::cout << "[Failed] Caxpy Index " << i << ": output=(" << output[i].real << "," << output[i].imag
57- << ") golden=(" << golden[i].real << "," << golden[i].imag << ") diff=" << diff << std::endl;
58- return 1;
59- }
60- }
61- std::cout << "[Success] Caxpy accuracy verification passed." << std::endl;
62- return 0;
63}35}
64 36 
65-int32_t TestCaxpy(aclblasHandle handle, aclrtStream stream)37+// Build a length-n complex vector (logical, stride-independent index order)
38+// with GUARD_COUNT sentinel padding on both sides so any out-of-bounds
39+// read/write by the operator under test corrupts a detectable guard region.
40+std::vector<aclblasComplex> MakeGuardedVector(int n, int increment, int32_t salt)
66{41{
67- constexpr uint32_t totalLength = 8 * 2048;42+ const size_t span = CaxpySpanElements(n, increment);
68- constexpr aclblasComplex valueX{1.0f, 0.5f};43+ std::vector<aclblasComplex> data(GUARD_COUNT + span + GUARD_COUNT, GUARD_VALUE);
69- constexpr aclblasComplex valueY{2.0f, 1.0f};44+ for (size_t i = 0; i < span; ++i)
70- constexpr aclblasComplex alpha{2.0f, 1.0f};45+ data[GUARD_COUNT + i] = MakeStridedValue(i, salt);
71- std::vector<aclblasComplex> x(totalLength, valueX);46+ return data;
72- std::vector<aclblasComplex> y(totalLength, valueY);
73- int incx = 1;
74- int incy = 1;
75- 
76- aclblasComplex* xDevice = nullptr;
77- aclblasComplex* yDevice = nullptr;
78- size_t xByteSize = totalLength * sizeof(aclblasComplex);
79- size_t yByteSize = totalLength * sizeof(aclblasComplex);
80- aclError aclRet = aclrtMalloc((void**)&xDevice, xByteSize, ACL_MEM_MALLOC_HUGE_FIRST);
81- CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc xDevice failed. ERROR: %d\n", aclRet); return aclRet);
82- aclRet = aclrtMalloc((void**)&yDevice, yByteSize, ACL_MEM_MALLOC_HUGE_FIRST);
83- CHECK_RET(
84- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc yDevice failed. ERROR: %d\n", aclRet); aclrtFree(xDevice);
85- return aclRet);
86- aclRet = aclrtMemcpy(xDevice, xByteSize, x.data(), xByteSize, ACL_MEMCPY_HOST_TO_DEVICE);
87- CHECK_RET(
88- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy xDevice failed. ERROR: %d\n", aclRet); aclrtFree(xDevice);
89- aclrtFree(yDevice); return aclRet);
90- aclRet = aclrtMemcpy(yDevice, yByteSize, y.data(), yByteSize, ACL_MEMCPY_HOST_TO_DEVICE);
91- CHECK_RET(
92- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy yDevice failed. ERROR: %d\n", aclRet); aclrtFree(xDevice);
93- aclrtFree(yDevice); return aclRet);
94- 
95- std::cout << "========== Testing aclblasCaxpy ==========" << std::endl;
96- std::cout << "Formula: y = alpha * x + y" << std::endl;
97- std::cout << "alpha = (" << alpha.real << ", " << alpha.imag << ")" << std::endl;
98- std::cout << "x = (" << valueX.real << ", " << valueX.imag << ") * " << totalLength << std::endl;
99- std::cout << "y = (" << valueY.real << ", " << valueY.imag << ") * " << totalLength << std::endl;
100- 
101- auto ret = aclblasCaxpy(handle, totalLength, &alpha, xDevice, incx, yDevice, incy);
102- CHECK_RET(
103- ret == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasCaxpy failed. ERROR: %d\n", ret); aclrtFree(xDevice);
104- aclrtFree(yDevice); return ret);
105- 
106- aclRet = aclrtSynchronizeStream(stream);
107- CHECK_RET(
108- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", aclRet); aclrtFree(xDevice);
109- aclrtFree(yDevice); return aclRet);
110- aclRet = aclrtMemcpy(y.data(), yByteSize, yDevice, yByteSize, ACL_MEMCPY_DEVICE_TO_HOST);
111- CHECK_RET(
112- aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy y failed. ERROR: %d\n", aclRet); aclrtFree(xDevice);
113- aclrtFree(yDevice); return aclRet);
114- aclrtFree(xDevice);
115- aclrtFree(yDevice);
116- 
117- std::vector<aclblasComplex> golden(totalLength);
118- for (size_t i = 0; i < totalLength; i++) {
119- golden[i] = alpha * x[i] + valueY;
120- }
121- 
122- return VerifyCaxpyResult(y, golden);
123}47}
124 48 
125-int32_t main(int32_t argc, char* argv[])49+} // namespace
126-{
127- int32_t deviceId = 0;
128 50 
129- aclInit(nullptr);51+// ── Test fixture ──────────────────────────────────────────────────────────────
130- aclrtSetDevice(deviceId);52+class CaxpyArch22Test : public BlasTest<CaxpyParam> { };
131 53 
132- aclblasHandle_t handle = nullptr;54+// ── TEST_F: null handle / null alpha (not expressible via CSV) ───────────────
133- auto ret = aclblasCreate(&handle);55+// aclblasCaxpy's ValidateCaxpyArguments treats handle==nullptr and
134- CHECK_RET(ret == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasCreate failed. ERROR: %d\n", ret); return ret);56+// alpha==nullptr identically: ACLBLAS_STATUS_INVALID_VALUE (unlike some other
57+// operators that return ACLBLAS_STATUS_HANDLE_IS_NULLPTR for a null handle).
58+TEST_F(CaxpyArch22Test, NullHandle) {
59+ aclblasComplex alpha{1.0F, 0.0F};
60+ aclblasStatus_t ret = aclblasCaxpy_npu(nullptr, 1, &alpha, nullptr, 1, nullptr, 1);
61+ EXPECT_EQ(static_cast<int>(ret), static_cast<int>(ACLBLAS_STATUS_INVALID_VALUE));
62+}
135 63 
136- aclrtStream stream = nullptr;64+TEST_F(CaxpyArch22Test, NullAlpha) {
137- aclrtCreateStream(&stream);65+ aclblasComplex x{1.0F, 0.0F};
138- ret = aclblasSetStream(handle, stream);66+ aclblasComplex y{1.0F, 0.0F};
139- CHECK_RET(ret == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasSetStream failed. ERROR: %d\n", ret); return ret);67+ aclblasStatus_t ret = aclblasCaxpy_npu(CaxpyArch22Test::handle_, 1, nullptr, &x, 1, &y, 1);
68+ EXPECT_EQ(static_cast<int>(ret), static_cast<int>(ACLBLAS_STATUS_INVALID_VALUE));
69+}
140 70 
141- int32_t result = 0;71+// ── CSV parameterised test suite ─────────────────────────────────────────────
72+INSTANTIATE_TEST_SUITE_P(
73+ Caxpy, CaxpyArch22Test,
74+ ::testing::ValuesIn(GetCasesFromCsv<CaxpyParam>(ReplaceFileExtension2Csv(__FILE__))),
75+ PrintCaseInfoString<CaxpyParam>);
142 76 
143- result = TestCaxpy(handle, stream);77+// ── TEST_P: 5-step CSV-driven flow ───────────────────────────────────────────
144- if (result != 0) {78+TEST_P(CaxpyArch22Test, CsvDriven) {
145- std::cout << "[FAIL] Caxpy test failed" << std::endl;79+ const auto& p = GetParam();
146- aclblasDestroy(handle);80+ const aclblasComplex alpha{p.alphaReal, p.alphaImag};
147- aclrtDestroyStream(stream);
148- aclrtResetDevice(deviceId);
149- aclFinalize();
150- return result;
151- }
152- std::cout << "[PASS] Caxpy test passed" << std::endl;
153 81 
154- aclblasDestroy(handle);82+ // Step 1: Generate host data (guarded x/y storage, deterministic values)
155- aclrtDestroyStream(stream);83+ std::vector<aclblasComplex> xHost;
156- aclrtResetDevice(deviceId);84+ if (p.nullx == 0 && p.n > 0)
157- aclFinalize();85+ xHost = MakeGuardedVector(p.n, p.incx, 3);
86+ std::vector<aclblasComplex> yHost;
87+ if (p.nully == 0 && p.n > 0)
88+ yHost = MakeGuardedVector(p.n, p.incy, -5);
158 89 
159- std::cout << "========================================" << std::endl;90+ const aclblasComplex* xPtr = xHost.empty() ? nullptr : xHost.data() + GUARD_COUNT;
160- std::cout << "Test Summary:" << std::endl;91+ aclblasComplex* yPtr = yHost.empty() ? nullptr : yHost.data() + GUARD_COUNT;
161- std::cout << " Passed: 1 - Caxpy" << std::endl;
162- std::cout << "========================================" << std::endl;
163 92 
164- return 0;93+ // Step 2: Execute on NPU (wrapper handles device memory, nullptr passthrough)
165-}94+ aclblasStatus_t ret = aclblasCaxpy_npu(CaxpyArch22Test::handle_, p.n, &alpha, xPtr, p.incx, yPtr, p.incy);
95+ 
96+ // Step 3: Verify expected return code
97+ EXPECT_EQ(static_cast<int>(ret), static_cast<int>(p.expectResult));
98+ if (p.expectResult != ACLBLAS_STATUS_SUCCESS) return;
99+ if (p.n == 0) return;
100+ 
101+ // Step 4: Compute golden on CPU over the same guarded storage, so any
102+ // stray write into the guard region is caught by the full-buffer compare.
103+ std::vector<aclblasComplex> xGolden = p.nullx == 0 ? MakeGuardedVector(p.n, p.incx, 3) : std::vector<aclblasComplex>{};
104+ std::vector<aclblasComplex> yGolden = p.nully == 0 ? MakeGuardedVector(p.n, p.incy, -5) : std::vector<aclblasComplex>{};
105+ const aclblasComplex* xGoldenPtr = xGolden.empty() ? nullptr : xGolden.data() + GUARD_COUNT;
106+ aclblasComplex* yGoldenPtr = yGolden.empty() ? nullptr : yGolden.data() + GUARD_COUNT;
107+ aclblasStatus_t cpuRet = aclblasCaxpy_cpu(CaxpyArch22Test::handle_, p.n, &alpha, xGoldenPtr, p.incx, yGoldenPtr, p.incy);
108+ EXPECT_EQ(static_cast<int>(cpuRet), static_cast<int>(ACLBLAS_STATUS_SUCCESS));
109+ 
110+ // Step 5: Precision verification, real/imag components split (MERE/MARE).
111+ VerifyConfig cfg;
112+ cfg.mode = PrecisionMode::MERE_MARE;
113+ cfg.mereThreshold = p.mereThreshold;
114+ cfg.mareMultiplier = p.mareMultiplier;
115+ 
116+ EXPECT_TRUE(Verifier::verifyMereMareComplexFloat(
117+ yHost.data(), yGolden.data(), yHost.size(), cfg.mereThreshold, cfg.mareMultiplier, 0.0, p.caseName));
118+}
@@ -0,0 +1,92 @@
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+# Migrated from the legacy hardcoded CAXPY_CASES array (see git history) to
11+# the repository's CSV-driven test convention. n/incx/incy/alpha match the
12+# original cases one-to-one; mere_threshold/mare_multiplier use the FP32
13+# MERE/MARE defaults from getMereThreshold(ACL_FLOAT) (2^-13) x10.
14+case_name,description,n,incx,incy,alpha_real,alpha_imag,nullx,nully,expect_result,mere_threshold,mare_multiplier,random_seed
15+TC_zero,zero,0,1,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
16+TC_one,one,1,1,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
17+TC_lane_2,lane 2,2,1,1,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
18+TC_lane_3,lane 3,3,1,1,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
19+TC_lane_31,lane 31,31,1,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
20+TC_lane_32,lane 32,32,1,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
21+TC_lane_33,lane 33,33,1,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
22+TC_lane_63,lane 63,63,1,1,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
23+TC_lane_64,lane 64,64,1,1,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
24+TC_lane_65,lane 65,65,1,1,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
25+TC_core_39,core 39,39,1,1,0.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
26+TC_core_40,core 40,40,1,1,0.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
27+TC_core_41,core 41,41,1,1,0.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
28+TC_strided_x,strided x,257,2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
29+TC_vector_reorder_tail_1,vector reorder tail 1,1,2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
30+TC_vector_reorder_tail_7,vector reorder tail 7,7,2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
31+TC_vector_reorder_tail_8,vector reorder tail 8,8,2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
32+TC_vector_reorder_tail_9,vector reorder tail 9,9,2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
33+TC_vector_reorder_tile,vector reorder tile,512,2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
34+TC_scalar_block_15,scalar block 15,600,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
35+TC_mixed_scalar_block_15_16,mixed scalar block 15 16,601,-2,3,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
36+TC_scalar_block_16,scalar block 16,640,2,-3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
37+TC_mixed_scalar_block_16_17,mixed scalar block 16 17,641,5,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
38+TC_scalar_upper_bound_16_17,scalar upper bound 16 17,679,-5,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
39+TC_scalar_wide_increment,scalar wide increment,17,65537,-32769,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
40+TC_vector_reorder_negative_tail,vector reorder negative tail,513,-10,-11,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
41+TC_strided_y,strided y,257,1,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
42+TC_strided_xy,strided xy,257,2,3,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
43+TC_stride_5_x,stride 5 x,257,5,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
44+TC_stride_5_y,stride 5 y,257,1,5,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
45+TC_stride_5_9,stride 5 9,257,5,9,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
46+TC_stride_9_x,stride 9 x,257,9,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
47+TC_stride_10_x_fallback,stride 10 x fallback,257,10,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
48+TC_stride_5_multicore,stride 5 multicore,4097,5,5,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
49+TC_shared_raw_inc2_multitile_tail,shared raw inc2 multitile tail,40961,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
50+TC_shared_tail_1,shared tail 1,41000,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
51+TC_shared_tail_8,shared tail 8,41280,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
52+TC_shared_tail_9,shared tail 9,41320,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
53+TC_shared_tail_16,shared tail 16,41600,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
54+TC_shared_tail_17,shared tail 17,41640,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
55+TC_shared_tail_1_incy1,shared tail 1 incy1,41000,2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
56+TC_shared_tail_1_negative,shared tail 1 negative,41000,-2,-3,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
57+TC_shared_tail_multi_4tile,shared tail multi 4tile,61441,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
58+TC_shared_raw_inc5_multitile_tail,shared raw inc5 multitile tail,40961,5,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
59+TC_shared_raw_inc9_multitile_tail,shared raw inc9 multitile tail,40961,9,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
60+TC_negative_x,negative x,257,-2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
61+TC_negative_unit_y,negative unit y,257,2,-1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
62+TC_negative_unit_y_multitile,negative unit y multitile,4097,2,-1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
63+TC_negative_y,negative y,257,1,-3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
64+TC_negative_x_positive_y,negative x positive y,257,-2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
65+TC_positive_x_negative_y,positive x negative y,257,5,-3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
66+TC_negative_xy,negative xy,257,-2,-3,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
67+TC_negative_9_x,negative 9 x,257,-9,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
68+TC_negative_x_multicore,negative x multicore,4097,-2,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
69+TC_negative_y_multicore,negative y multicore,4097,1,-3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
70+TC_opposite_sign_multicore,opposite sign multicore,4097,-2,3,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
71+TC_negative_xy_multicore,negative xy multicore,4097,-2,-3,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
72+TC_wide_stride_multitile,wide stride multitile,40961,10,11,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
73+TC_wide_stride_negative_multitile,wide stride negative multitile,40963,-10,-11,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
74+TC_dynamic_shared_incx16,dynamic shared incx16,40961,16,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
75+TC_dynamic_shared_incx32,dynamic shared incx32,40961,32,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
76+TC_dynamic_shared_incx41,dynamic shared incx41,40961,41,3,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
77+TC_dynamic_shared_boundary_usable,dynamic shared boundary usable,8000,60,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
78+TC_dynamic_shared_boundary_disjoint,dynamic shared boundary disjoint,8000,61,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
79+TC_strided_tile_plus_1,strided tile plus 1,194561,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
80+TC_multiwave_strided,multiwave strided,400000,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
81+TC_multiwave_negative,multiwave negative,400001,-2,-3,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
82+TC_multiwave_3_waves,multiwave 3 waves,700000,1,2,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
83+TC_pipeline_full_wave,pipeline full wave,327680,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
84+TC_hybrid_tail_1,hybrid tail 1,327681,2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
85+TC_hybrid_tail_1_negative_y,hybrid tail 1 negative y,327681,2,-3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
86+TC_hybrid_tail_679_negative_x,hybrid tail 679 negative x,328359,-2,3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
87+TC_hybrid_tail_679_negative_xy,hybrid tail 679 negative xy,328359,-2,-3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
88+TC_pipeline_tail_680_negative_y,pipeline tail 680 negative y,328360,2,-3,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
89+TC_regular,regular,16384,1,1,0.75,-0.25,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
90+TC_tile_minus_1,tile minus 1,194559,1,1,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
91+TC_tile,tile,194560,1,1,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
92+TC_tile_plus_1,tile plus 1,194561,1,1,2.0,1.0,0,0,ACLBLAS_STATUS_SUCCESS,0.0001220703125,10.0,42
@@ -0,0 +1,69 @@
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 <cstdint>
14+ 
15+#include "cann_ops_blas.h"
16+ 
17+inline aclblasStatus_t CaxpyValidateParams(
18+ aclblasHandle_t handle, int n, const aclblasComplex* alpha, const aclblasComplex* x, int incx,
19+ const aclblasComplex* y, int incy)
20+{
21+ if (handle == nullptr || alpha == nullptr) {
22+ return ACLBLAS_STATUS_HANDLE_IS_NULLPTR;
23+ }
24+ if (n < 0 || incx == 0 || incy == 0) {
25+ return ACLBLAS_STATUS_INVALID_VALUE;
26+ }
27+ if (n > 0 && (x == nullptr || y == nullptr)) {
28+ return ACLBLAS_STATUS_INVALID_VALUE;
29+ }
30+ return ACLBLAS_STATUS_SUCCESS;
31+}
32+ 
33+inline aclblasComplex CaxpyComplexMac(aclblasComplex alpha, aclblasComplex x, aclblasComplex y)
34+{
35+ return {alpha.real * x.real - alpha.imag * x.imag + y.real, alpha.real * x.imag + alpha.imag * x.real + y.imag};
36+}
37+ 
38+// Logical-index -> physical storage index for a strided complex vector.
39+// Positive stride reads forward from the base; negative stride is BLAS
40+// convention: the last logical element is physically first.
41+inline int64_t CaxpyStorageIndex(int64_t logicalIndex, int64_t n, int64_t increment)
42+{
43+ const uint64_t absIncrement =
44+ increment > 0 ? static_cast<uint64_t>(increment) : static_cast<uint64_t>(-increment);
45+ return increment > 0 ? logicalIndex * static_cast<int64_t>(absIncrement)
46+ : (n - 1 - logicalIndex) * static_cast<int64_t>(absIncrement);
47+}
48+ 
49+// y = alpha * x + y, complex, arbitrary (possibly negative) incx/incy.
50+// x and y are the caller-owned base storage pointers (not offset by any guard region).
51+inline aclblasStatus_t aclblasCaxpy_cpu(
52+ aclblasHandle_t handle, int n, const aclblasComplex* alpha, const aclblasComplex* x, int incx,
53+ aclblasComplex* y, int incy)
54+{
55+ aclblasStatus_t st = CaxpyValidateParams(handle, n, alpha, x, incx, y, incy);
56+ if (st != ACLBLAS_STATUS_SUCCESS) {
57+ return st;
58+ }
59+ if (n == 0) {
60+ return ACLBLAS_STATUS_SUCCESS;
61+ }
62+ 
63+ for (int64_t i = 0; i < n; i++) {
64+ const int64_t xIdx = CaxpyStorageIndex(i, n, incx);
65+ const int64_t yIdx = CaxpyStorageIndex(i, n, incy);
66+ y[yIdx] = CaxpyComplexMac(*alpha, x[xIdx], y[yIdx]);
67+ }
68+ return ACLBLAS_STATUS_SUCCESS;
69+}
@@ -0,0 +1,36 @@
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 <string>
14+#include "cann_ops_blas.h"
15+#include "csv_loader.h"
16+ 
17+struct CaxpyParam : public BlasTestParamBase {
18+ int n = 0;
19+ int incx = 1;
20+ int incy = 1;
21+ float alphaReal = 1.0f;
22+ float alphaImag = 0.0f;
23+ int nullx = 0;
24+ int nully = 0;
25+ 
26+ CaxpyParam(const csv_map& csv) : BlasTestParamBase(csv)
27+ {
28+ n = parseInt(ReadMap(csv, "n", "0"));
29+ incx = parseInt(ReadMap(csv, "incx", "1"));
30+ incy = parseInt(ReadMap(csv, "incy", "1"));
31+ alphaReal = parseFloat(ReadMap(csv, "alpha_real", "1.0"));
32+ alphaImag = parseFloat(ReadMap(csv, "alpha_imag", "0.0"));
33+ nullx = parseInt(ReadMap(csv, "nullx", "0"));
34+ nully = parseInt(ReadMap(csv, "nully", "0"));
35+ }
36+};