已合并
【CANN社区任务】aclblasCcopy算子开发 #1251
【CANN社区任务】aclblasCcopy算子开发 #1251
已合并
mengyijia创建于 7 天前
1 个文件变更+376-0
@@ -0,0 +1,376 @@
1+# aclblasCcopy 算子设计文档
2+ 
3+| 项目 | 内容 |
4+| --- | --- |
5+| 目标硬件 | Ascend 950PR(`ascend950``arch35`) |
6+| 目标软件 | CANN 9.1.0、ops-blas 句柄式 BLAS 接口 |
7+| 实现目录 | `ops-blas/blas/copy/arch35/` |
8+| 测试目录 | `ops-blas/test/copy/ccopy/` |
9+ 
10+# 需求背景(required)
11+ 
12+## 需求来源
13+ 
14+本设计对应 2026 年 8 月社区任务第 44 项“aclblasCcopy 算子开发(950)”。任务要求参考
15+cuBLAS `cublasCcopy` 和 Netlib BLAS `ccopy` 的语义,在 Ascend 950PR 上使用 Ascend C 实现
16+单精度复数向量复制,并将实现与测试工程提交至 ops-blas 仓库。
17+ 
18+算子复用 `include/cann_ops_blas.h` 中已有的 `aclblasCcopy` 声明和
19+`include/cann_ops_blas_common.h` 中的 `aclblasComplex` 类型,不新增 950PR 私有接口。
20+ 
21+## 背景介绍
22+ 
23+### 算子功能
24+ 
25+`aclblasCcopy` 将源向量的 `n` 个逻辑复数元素复制到目标向量。单个元素定义为:
26+ 
27+```cpp
28+typedef struct aclblasComplex {
29+ float real;
30+ float imag;
31+} aclblasComplex;
32+```
33+ 
34+一个 `aclblasComplex` 由相邻的实部和虚部两个 FP32 组成,占 8 B。算子不执行浮点运算或
35+类型转换,按位复制这 8 B 表示。`incx``incy` 是以复数元素为单位的非零步长,可以为正数
36+或负数;目标向量中未被逻辑索引命中的位置必须保持原值。
37+ 
38+### 开发范围
39+ 
40+| 内容 | 路径 | 作用 |
41+| --- | --- | --- |
42+| Host 实现 | `blas/copy/arch35/ccopy_host.cpp` | 参数校验、Tiling、可选 offset 表准备和 Kernel 下发 |
43+| Kernel 实现 | `blas/copy/arch35/ccopy_kernel.cpp` | 连续及离散步长复制 |
44+| Tiling 定义 | `blas/copy/arch35/ccopy_tiling_data.h` | Host 与 Kernel 共享切分参数 |
45+| 测试工程 | `test/copy/ccopy/` | CSV 驱动 GTest、CBLAS 参考结果及验收脚本 |
46+ 
47+# 需求分析(required)
48+ 
49+## 需求描述
50+ 
51+公共接口如下:
52+ 
53+```cpp
54+aclblasStatus_t aclblasCcopy(
55+ aclblasHandle_t handle,
56+ int n,
57+ const aclblasComplex* x,
58+ int incx,
59+ aclblasComplex* y,
60+ int incy);
61+```
62+ 
63+接口参数顺序与 `cublasCcopy` 一致。`x``y` 位于 Device 内存,handle 保存执行 stream。
64+Kernel 在该 stream 上异步下发;调用方在读取 `y` 或释放相关内存前负责同步。
65+ 
66+`n` 是非负逻辑元素数;`x``y` 是 Device 指针;`incx``incy` 是以复数元素为单位的
67+非零步长。具体校验顺序和返回码见 Host 侧设计。`n == 0` 时接口直接返回成功,不访问或校验
68+handle、x、y 和步长。
69+ 
70+## 需求拆解
71+ 
72+1. 支持 COMPLEX64 的按位复制,保持实部、虚部顺序及 y 的非写区域;
73+2. 支持 `incx``incy` 的所有正、负非零组合;
74+3. 连续输入输出采用双缓冲搬运,其他步长采用 Compact 离散搬运;
75+4. 根据运行时 AIV 数量和 UB 容量完成多核与核内切分;
76+5. 使用 CSV、CBLAS 参考结果和 msprof 完成功能、正确性及性能验证。
77+ 
78+# 详细设计(required)
79+ 
80+## 算子分析
81+ 
82+### 数学公式
83+ 
84+接口以缓冲区低地址为基址。对逻辑索引 `i`,其中 `0 <= i < n`,物理索引为:
85+ 
86+$$
87+P(i, inc) =
88+\begin{cases}
89+i \cdot inc, & inc > 0 \\
90+(n - 1 - i) \cdot |inc|, & inc < 0
91+\end{cases}
92+$$
93+ 
94+复制语义为:
95+ 
96+$$
97+y[P(i, incy)].real = x[P(i, incx)].real
98+$$
99+ 
100+$$
101+y[P(i, incy)].imag = x[P(i, incx)].imag
102+$$
103+ 
104+这里的等号表示实部和虚部均按 32 位对象表示逐位复制。`n > 0` 时,x、y 所需的物理跨度分别为
105+`1 + (n - 1) * abs(incx)``1 + (n - 1) * abs(incy)` 个复数元素。
106+ 
107+### 支持数据类型
108+ 
109+算子仅支持 COMPLEX64。每个逻辑元素的物理布局为 `{float real; float imag;}`,大小为 8 B;
110+不支持其他数据类型或类型转换。
111+ 
112+### 支持形状
113+ 
114+x、y 的逻辑形状均为一维 `[n]`,不支持广播、矩阵前导维或额外 Tensor 描述。
115+ 
116+### 计算与带宽特征
117+ 
118+本算子是 AIV-only 数据搬移算子,不使用 Cube。连续路径主要使用 MTE2(GM 到 UB)和
119+MTE3(UB 到 GM);离散且输入输出步长符号不同时,还需要在 UB 中对实部、虚部分别逆序。
120+每个逻辑元素从 GM 读取 8 B、向 GM 写入 8 B,理论最小流量为 16 B/元素,因此大规模连续
121+用例主要受 HBM 带宽和 MTE 流水效率限制。
122+ 
123+## 算子实现
124+ 
125+### 实现方案
126+ 
127+Host 负责参数校验、分核、tileSize 计算及 Kernel 下发;AIV Kernel 根据步长选择连续路径或
128+Compact 离散路径。两条路径共用一个 Kernel 入口和一份 Tiling 数据。
129+ 
130+```text
131+aclblasCcopy
132+ -> 参数校验
133+ -> 查询 AIV 数量和 UB 容量
134+ -> 计算分核、tileSize 和可选 offset 表
135+ -> 在 handle stream 下发 ccopy_kernel
136+ |- 连续双缓冲路径
137+ `- Compact 通用步长路径
138+```
139+ 
140+### Host 侧设计
141+ 
142+#### 参数检查
143+ 
144+| 顺序 | 条件 | 返回值 |
145+| ---: | --- | --- |
146+| 1 | `n < 0` | `ACLBLAS_STATUS_INVALID_VALUE` |
147+| 2 | `n == 0` | `ACLBLAS_STATUS_SUCCESS` |
148+| 3 | `handle == nullptr` | `ACLBLAS_STATUS_HANDLE_IS_NULLPTR` |
149+| 4 | `x == nullptr``y == nullptr` | `ACLBLAS_STATUS_INVALID_VALUE` |
150+| 5 | `incx == 0``incy == 0` | `ACLBLAS_STATUS_INVALID_VALUE` |
151+| 6 | 平台报告的 AIV 核数为 0 | `ACLBLAS_STATUS_EXECUTION_FAILED` |
152+ 
153+Host 不检查 Device 指针的实际分配长度、地址归属和生命周期,这些由调用方保证。
154+ 
155+#### Tiling 数据
156+ 
157+```cpp
158+struct CcopyTilingData {
159+ uint32_t totalN;
160+ uint32_t perCoreN;
161+ uint32_t extraBlockCores;
162+ uint32_t tailElements;
163+ uint32_t tileSize;
164+ int32_t incx;
165+ int32_t incy;
166+};
167+```
168+ 
169+`ELEMENTS_PER_BLOCK` 为 4 个复数,即 32 B,用于计算连续搬运的整块长度。
170+`CORE_ELEMENTS_PER_BLOCK` 为 8 个复数,即 64 B,用于分配各核的逻辑区间。这里保证的是
171+每核逻辑起始偏移按 8 个复数切分。对 `(+1,+1)` 连续路径,这等价于相对 x/y 基址按 64 B
172+粒度切分;只有传入基址本身满足 64 B 对齐时,各核的连续 GM 起始地址才满足 64 B 对齐。
173+该结论不用于描述负步长下的物理低地址起点。
174+ 
175+#### 分核策略
176+ 
177+Host 运行时查询平台 AIV 数量:
178+ 
179+```text
180+numBlocks = min(n, aivCoreNum)
181+```
182+ 
183+Ascend 950PR 自测环境返回 56 个 AIV,因此大规模用例启动 56 个 AIV block;代码不写死核数。
184+`A = 8``B = numBlocks`
185+ 
186+```text
187+rawPerCore = n / B
188+perCoreN = floor(rawPerCore / A) * A
189+leftover = n - perCoreN * B
190+extraBlockCores = leftover / A
191+tailElements = leftover % A
192+```
193+ 
194+`extraBlockCores` 个核各增加 8 个元素,最后一个核承担剩余的 `0..7` 个元素。第 c 个核的
195+逻辑起点和任务量为:
196+ 
197+```text
198+offset(c) = c * perCoreN + min(c, extraBlockCores) * 8
199+count(c) = perCoreN
200+ + (c < extraBlockCores ? 8 : 0)
201+ + (c == B - 1 ? tailElements : 0)
202+```
203+ 
204+该策略避免把不足 8 个元素的尾部拆到多个核。
205+ 
206+#### UB 与 tileSize
207+ 
208+`ubSize` 初始值为 248 KiB;平台管理器有效时,Host 调用 `GetCoreMemSize` 获取实际 UB 容量并
209+覆盖该值。公共接口在平台管理器无效、无法取得 AIV 数量时会先返回失败,因此正常 Tiling 路径
210+使用平台查询值;当前实现假定 `GetCoreMemSize` 查询成功。计算前预留 256 B,tileSize 向下取
211+8 个复数的整数倍。下表中的数值以本次 950PR 环境的 248 KiB UB 为例:
212+ 
213+| Host 判定场景 | UB 估算或命令限制 | tileSize | 248 KiB 下的结果 |
214+| --- | --- | --- | ---: |
215+| `incx == 1 && incy == 1` | 两个 queue 槽位,共 16 B/元素 | `floor((UB-256)/16/8)*8` | 15,856 |
216+| 仅一侧等于 `+1` | queue 16 B、逆序临时数据 8 B、offset 4 B,共 28 B/元素 | `floor((UB-256)/28/8)*8` | 9,056 |
217+| 两侧均不等于 `+1` | Compact 单命令 `blockCount <= 4095` | `floor(4095/8)*8` | 4,088 |
218+ 
219+后两种分类只用于计算安全的 tileSize;Kernel 中所有非 `(+1,+1)` 组合均进入 Compact 路径。
220+15,856、9,056 和 4,088 都是单次 tile 的容量,不是单核总处理量;单核数据超过 tileSize 时
221+会循环处理多个 tile。
222+ 
223+#### 可选 offset workspace 与 Kernel 下发
224+ 
225+当输入、输出步长符号不同时,需要在 tile 内逆序:
226+ 
227+```text
228+needReorder = (incx < 0) XOR (incy < 0)
229+offset[i] = (tileSize - 1 - i) * sizeof(float)
230+```
231+ 
232+Host 优先在 Host 内存中生成 offset 表,再复制到 handle 的有效 Device workspace,所需空间为
233+`tileSize * 4 B`。以本次 248 KiB UB 为例,单边 `+1` 场景需要 36,224 B,两侧均非 `+1` 场景
234+需要 16,352 B;这些数值随平台 UB 和 tileSize 变化,不是实现的固定上限。workspace 不可用或
235+容量不足时,Kernel 在 UB 中生成同一张表。
236+ 
237+连续路径不准备 offset 表。异号步长且 workspace 可用时,Host 先生成表并通过 `aclrtMemcpy`
238+复制到 Device workspace,随后在 `handle->stream` 上异步下发 Kernel。接口不显式调用
239+`aclrtSynchronizeStream``aclrtSynchronizeDevice`;Kernel 执行期错误由调用方后续同步操作暴露。
240+ 
241+### Kernel 侧设计
242+ 
243+#### 初始化与路径选择
244+ 
245+Kernel 标记为 `KERNEL_TYPE_AIV_ONLY`。每个 block 根据 Tiling 计算 `myOffset``myCount`,并用
246+64 位中间值计算物理跨度和 GM 偏移;步长在取绝对值前提升为 `int64_t`,避免 `INT_MIN` 直接取负
247+产生 32 位溢出。
248+ 
249+GM 在 Kernel 中映射为 FP32 视图,每个复数对应相邻两个 float。Kernel 初始化两个槽位的
250+`TQueBind<VECIN,VECOUT>`;非连续路径额外初始化 Gather 临时缓冲区。
251+ 
252+#### 连续路径
253+ 
254+`incx == +1 && incy == +1` 时进入连续路径:
255+ 
256+- 长度为 32 B 整数倍的主体使用 `DataCopy`
257+- 剩余 1 至 3 个复数使用 `DataCopyPad`,只向 y 写回有效字节;
258+- 完整 tile 使用两个 queue 槽位执行 Prime-Pump-Drain,使 tile i 的 MTE2 读取与 tile i-1 的
259+ MTE3 写回重叠;
260+- 完整 tile 写回后,尾 tile 使用 `ContinuousIteration` 处理。
261+ 
262+```text
263+Prime: 读取 tile 0
264+Pump: 读取 tile i || 写回 tile i-1
265+Drain: 写回最后一个完整 tile
266+Tail: 读取尾 tile -> 写回尾 tile
267+```
268+ 
269+该路径没有 Vector Compute 阶段,队列用于管理 MTE2/MTE3 缓冲区所有权和同步。
270+ 
271+#### 离散与负步长路径
272+ 
273+其他步长组合使用 `DataCopyPad<..., PaddingMode::Compact>`。每个 tile 在 UB 中拆成 real 和 imag
274+两个 FP32 平面。每个 Compact block 搬运一个 4 B 分量,相邻 block 之间需要跳过的字节数为:
275+ 
276+```text
277+(2 * abs(inc) - 1) * sizeof(float)
278+```
279+ 
280+因此,相邻同分量起始地址之间的距离为 `2 * abs(inc) * sizeof(float)`
281+ 
282+单个 Compact 命令最多处理 4,088 个逻辑元素;较大的 tile 在一次 `DiscreteIteration` 中拆为
283+多个 Compact batch。写回命令分别设置 real、imag 的目标 stride,只覆盖 y 的有效逻辑位置。
284+ 
285+对逻辑区间 `[elementOffset, elementOffset + dataCount)`,物理低地址起点为:
286+ 
287+```text
288+inc > 0: elementOffset * abs(inc)
289+inc < 0: (totalN - elementOffset - dataCount) * abs(inc)
290+```
291+ 
292+Compact 按物理地址递增方向访问。incx、incy 同号时,输入和输出物理顺序一致;两者异号时,
293+real 和 imag 分别逆序。offset 起点满足 8 元素对齐时,当前 tile 使用 `Gather`;否则整个 tile
294+使用 `GetValue/SetValue` 逐元素处理。MTE2、Vector/Scalar、MTE3 之间通过事件同步。
295+ 
296+#### 按位复制
297+ 
298+Kernel 不执行浮点算术、共轭或类型转换。连续路径按交错 FP32 搬运,离散路径分别搬运 real、imag
299+的 32 位对象表示。
300+ 
301+## 支持硬件
302+ 
303+| 支持的芯片版本 | SoC/架构 | 状态 |
304+| --- | --- | --- |
305+| Ascend 950PR | `ascend950` / `arch35` | 支持 |
306+ 
307+当前设计不声明对其他 SoC、架构或 CANN 版本的新增支持。
308+ 
309+## 算子约束限制
310+ 
311+1. x、y 的实际容量、地址有效性和异步执行期生命周期由调用方保证,Host 不做检查;
312+2. 负步长调用传入物理缓冲区的低地址;
313+3. 部分重叠且映射不同的 x/y 区域不提供 `memmove` 顺序保证。
314+ 
315+# 可维可测分析
316+ 
317+## 精度标准/性能标准
318+ 
319+| 验收项目 | 判定方式 | 标准来源 |
320+| --- | --- | --- |
321+| 正确性 | 通过测试环境链接的 CBLAS `cblas_ccopy` 生成参考结果;完整 y 存储区中 real、imag 分别按 4 B 比较,要求逐位相等(`max_abs_error = 0`),非写区域保持不变 | 任务书 §2.1、§3.2、§3.5 |
322+| 性能 | warmup 后采集大于 50 次 `ccopy_kernel/KERNEL_AIVEC` 设备任务并取平均;当前自测按 `NPU avg_us <= gpu_ms * 1000 / 0.4` 判定 | 配套 README、`gpu_baseline.csv` 和验证脚本 |
323+| 内存 | 任务书未规定硬性阈值;报告实际资源用量。连续路径不使用 workspace,异号路径按 tileSize 使用可选 offset workspace | 任务书 §3.4、§4 |
324+ 
325+本算子不包含数值计算,因此正确性采用比通用 FLOAT32 `rtol/atol` 更严格的逐位比较。
326+ 
327+## 功能与正确性验证
328+ 
329+`arch35/ccopy_test.csv` 驱动 C++ GTest,CPU 侧调用测试环境链接的 `cblas_ccopy` 生成参考结果。
330+测试流程为:
331+ 
332+1. 按 n 和步长计算 x、y 的完整物理跨度,并在前后设置保护区;
333+2. 调用 NPU 接口并将完整 y 分配区复制回 Host;
334+3. 对每个存储位置的 real、imag 分别比较 4 B;
335+4. 对错误用例检查返回码,NullHandle 使用独立测试;
336+5. `verify_accuracy.py` 检查 CSV schema、用例注册集合和 GTest XML,拒绝失败、错误或跳过用例。
337+ 
338+CSV 覆盖小尺寸、尺寸边界、正负步长组合、随机值、全零、正负交替、极值、Inf、NaN、地址偏移、
339+空指针、零步长和负 n。`verify_accuracy.py` 在运行期间生成并核查临时 GTest XML;正式提交时应
340+保存与对应代码版本匹配的终端日志和自测报告,供验收复核。
341+ 
342+## 性能验证
343+ 
344+`verify_performance_msprof.py` 调用 CANN 官方 `msprof`,使用 `--task-time=l0` 采集设备任务,
345+在导出的 `task_time_*.csv` 中筛选:
346+ 
347+```text
348+kernel_name = ccopy_kernel
349+kernel_type = KERNEL_AIVEC
350+```
351+ 
352+`task_time(us)` 表示 Device Kernel 任务耗时,不包含 GTest 数据生成、H2D/D2H、CBLAS 参考结果和
353+结果比较;它也不等同于指令级 on-core 时间。默认每个 case 执行 5 次 warmup 和 55 次有效采样,
354+输出平均值、中位数和 P95。
355+ 
356+配套 README、`ccopy_test.csv``gen_csv.py``gpu_baseline.csv` 使用
357+`n=1,048,576 / 2,097,152 / 4,194,304` 三条典型连续用例,并按 GPU 基线除以 0.4 得到 NPU
358+限值。任务书与配套材料的尺寸不一致,当前自测采用配套材料口径;最终验收口径由任务发布方确认,
359+性能结果以对应代码版本保存的 CSV 和 msprof 记录为准。
360+ 
361+## 兼容性分析
362+ 
363+- 复用已有 `aclblasCcopy` 声明、`aclblasComplex` 类型和错误码,API/ABI 不变;
364+- arch35 实现与其他架构目录隔离;
365+- 沿用 ops-blas 的 handle、stream、workspace、日志和构建机制;
366+- 测试工程沿用 copy 族的 CSV 和 GTest 模式。
367+ 
368+## 参考资料
369+ 
370+1. 随社区任务发放的《aclblasCcopy 算子开发任务书》;
371+2. [cuBLAS Level-1 `cublasCcopy` 接口说明](https://docs.nvidia.com/cuda/cublas/index.html#cublas-t-copy);
372+3. [Netlib BLAS `ccopy.f`](https://www.netlib.org/blas/ccopy.f);
373+4. [ops-blas 开源仓](https://gitcode.com/cann/ops-blas)中的 `include/cann_ops_blas.h`
374+ `include/cann_ops_blas_common.h``blas/copy/arch35/``test/copy/ccopy/`
375+5. [Ascend C 算子开发文档](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/850/opdevg/Ascendcopdevg/atlas_ascendc_map_10_0002.html);
376+6. [Ascend C 基础 API 文档](https://www.hiascend.com/document/detail/zh/canncommercial/850/API/ascendcopapi/atlasascendc_api_07_0003.html)。