已合并
【CANN社区任务】aclnnBernoulli算子设计文档 #671
【CANN社区任务】aclnnBernoulli算子设计文档 #671
已合并
hzw_rpap创建于 7月25日
1 个文件变更+540-0
@@ -0,0 +1,540 @@
1+# aclnnBernoulli 算子内存优化设计文档
2+ 
3+## 1. 需求背景(required)
4+ 
5+### 1.1 需求来源
6+ 
7+- 任务名称:`7月社区任务-aclnnBernoulli算子开发`
8+- 任务依据:[aclnnBernoulli 任务书](../../../../docs/202607/aclnnBernoulli_task_doc.md)
9+- 目标产品:Atlas A2/A3 训练系列产品
10+- 软件版本:CANN 8.5.0 及以上
11+- 开发语言:Ascend C
12+ 
13+本任务要求在不改变 `aclnnBernoulli` 对外功能的前提下,优化 A2/A3
14+标量概率路径的设备内存占用,使其与等价 GPU 调用的内存差距小于 5%,
15+同时保证性能不低于原实现。
16+ 
17+### 1.2 算子功能
18+ 
19+`aclnnBernoulli` 根据 `self` 的 shape、dtype 和布局生成独立 Bernoulli
20+样本。`self` 的数值不参与计算,随机流由 `seed``offset` 决定。
21+ 
22+对每个逻辑位置 `i`
23+ 
24+```text
25+out[i] ∈ {0, 1}
26+P(out[i] = 1) = prob
27+```
28+ 
29+接口同时支持 out-of-place 和 inplace 调用。输出为浮点、整数或 `BOOL`
30+时,均写入对应 dtype 可精确表示的 0 或 1。
31+ 
32+### 1.3 接口能力与约束
33+ 
34+| 参数 | 类型 | 支持范围 | 约束 |
35+| --- | --- | --- | --- |
36+| `self` | `aclTensor*` | FP16、FP32、FP64、BF16、UINT8、INT8、INT16、INT32、INT64、BOOL | ND;0~8 维;可为空、连续或非连续 |
37+| `prob` | `aclScalar*` | FP16、FP32、FP64、BF16 | 有限值,且 `0 <= prob <= 1` |
38+| `seed` | `int64_t` | INT64 | 用于确定随机流 |
39+| `offset` | `int64_t` | INT64 | `offset % 4 == 0` |
40+| `out` | `aclTensor*` | 与 `self` 相同 | shape、dtype 与 `self` 一致;可为非连续 Tensor |
41+ 
42+本次优化针对标量 `prob` 接口。Tensor-probability 接口不进入自定义融合
43+路径,继续沿用原有实现。
44+ 
45+### 1.4 原实现与问题分析
46+ 
47+CANN 8.5.0 中 A2/A3 标量概率的一般路径位于:
48+ 
49+```text
50+random/dsa_gen_bit_mask/op_host/op_api/aclnn_bernoulli.cpp
51+```
52+ 
53+其执行链路为:
54+ 
55+```text
56+Contiguous(self)
57+ -> DSAGenBitMask
58+ -> Fill(ones)
59+ -> DropoutDoMask
60+ -> Cast(target dtype)
61+ -> ViewCopy(out)
62+```
63+ 
64+设输出元素数为 `N`,目标 dtype 字节数为 `B`,DSA 生成的 packed mask
65+大小为:
66+ 
67+```text
68+M = ceil(N / 128) * 16 = align_up(N, 128) / 8
69+```
70+ 
71+原链路除 packed mask 外,还会产生全 shape 的 ones、mask 展开结果以及
72+部分 dtype 下的 Cast 结果。主要逻辑中间值如下:
73+ 
74+| 中间值 | shape / dtype | 逻辑字节数 |
75+| --- | --- | ---: |
76+| packed mask | `uint8[M]` | `M` |
77+| `inputOnes` | 全 shape,FP16 或 FP32 | `N×2``N×4` |
78+| `doMaskOut` | 与 `inputOnes` 相同 | `N×2``N×4` |
79+| Cast 结果 | 目标 dtype | 最多 `N×B` |
80+| 非连续输入的连续副本 | 目标 dtype | `N×B` |
81+ 
82+执行器可能复用不同逻辑节点的存储,因此不能把表中各项直接相加作为峰值;
83+但这些随 `N` 线性增长的中间 Tensor 会显著增加 workspace 和 GM 流量。
84+以 A2/A3、shape `[4096,4096]``prob=0.5` 为例,原 ACLNN workspace
85+在两个平台上的结果一致:
86+ 
87+| dtype | 原 workspace |
88+| --- | ---: |
89+| FP16 / BF16 | 35,652,608 B |
90+| FP32 | 69,207,040 B |
91+| INT64 / FP64 | 69,207,552 B |
92+ 
93+因此,本任务的核心不是更换随机数算法,而是在保留 DSA 随机流的同时,
94+消除 `Fill + DropoutDoMask + Cast` 引入的全 shape 中间值。
95+ 
96+## 2. 需求分析(required)
97+ 
98+### 2.1 设计目标
99+ 
100+1. 保持公共 ACLNN 签名、两阶段调用方式和返回语义不变。
101+2. 覆盖任务书要求的全部 dtype、0~8 维、空 Tensor、连续与非连续布局。
102+3. 保留 `prob=0``prob=1` 快路径及 `seed/offset` 随机状态语义。
103+4. 连续 dense 输出不再申请随 shape 增长的全量中间 Tensor。
104+5. 非连续 Tensor、非零 storage offset 和特殊 storage/view 关系不得越界写。
105+6. A2 与 A3 使用同一份 Ascend C 源码,硬件资源通过 Tiling 运行时查询。
106+7. 新实现性能不低于原实现,并按等价调用协议完成 NPU/GPU 内存对比。
107+ 
108+### 2.2 外部依赖
109+ 
110+本方案不引入新的第三方组件,复用 CANN 已有能力:
111+ 
112+| 依赖 | 用途 |
113+| --- | --- |
114+| `DSAGenBitMask` | 生成与原实现一致的 packed 随机 mask |
115+| Ascend C Vector API | 在 UB 内完成 mask 展开、Select、Cast 和 Gather |
116+| `ZerosLike` / `OnesLike` | 处理 `prob=0/1` 精确快路径 |
117+| `ViewCopy` | 将连续临时结果写回非连续逻辑 view |
118+| `PlatformAscendC` | 查询 UB 容量和 Vector Core 数量 |
119+ 
120+### 2.3 方案比较
121+ 
122+| 方案 | 内存效果 | 随机语义风险 | 实现风险 | 结论 |
123+| --- | --- | --- | --- | --- |
124+| 仅对旧 `Fill/DropoutDoMask` 做存储别名 | 仍保留多 Kernel 和部分全量中间值 | 低 | dtype、别名和非连续布局约束复杂 | 不采用 |
125+| 单 Kernel 自行实现 RNG 和写出 | 可完全融合 | 高,需要重新证明 seed/offset 消耗和统计质量 | A2/A3 无可直接复用的 Philox API | 不采用 |
126+| 保留 DSA,新增 packed mask 展开 Kernel | 连续路径无全 shape 临时值 | 低,可保持原 DSA 随机流 | 需要证明同址展开安全 | 采用 |
127+ 
128+最终方案为:
129+ 
130+```text
131+DSAGenBitMask(mask aliases dense out)
132+ -> BernoulliMask(high-to-low waves)
133+ -> out
134+```
135+ 
136+对于不满足同址条件的输出,采用独立 packed mask 和连续输出临时值作为
137+fallback,以保证通用布局语义。
138+ 
139+## 3. 详细设计(required)
140+ 
141+### 3.1 总体设计
142+ 
143+`BernoulliMask` 是新增的 Ascend C L0 算子。它不读取 `self` 的数据,
144+只读取 DSA 生成的 packed mask,并在 UB 内将每个 bit 展开为目标 dtype
145+的 0/1。
146+ 
147+根据输出布局和容量,Host 侧选择三条路径:
148+ 
149+```text
150+ +--> prob=0/1: ZerosLike / OnesLike
151+ |
152+aclnnBernoulli ----------+--> 一般概率 + 可安全同址
153+ | DSA(mask写入out起始区)
154+ | -> BernoulliMask原地展开
155+ |
156+ +--> 一般概率 + 不可同址
157+ 独立mask -> 连续结果
158+ -> ViewCopy(out)
159+```
160+ 
161+### 3.2 Host 侧设计
162+ 
163+#### 3.2.1 参数校验与特殊路径
164+ 
165+Phase 1 按以下顺序处理:
166+ 
167+1. 校验 `self``prob``out``workspaceSize``executor` 非空。
168+2. 校验 `self/out` 的 dtype、shape 和 format,并限制 rank 不超过 8。
169+3. 校验 `prob` 为有限值且位于 `[0,1]`
170+4. 校验 `offset % 4 == 0`
171+5. 空 Tensor 返回 `workspaceSize=0`,不下发 Kernel。
172+6. `prob=0` 调用 `ZerosLike``prob=1` 调用 `OnesLike`
173+7. 其他概率进入 DSA + `BernoulliMask` 路径。
174+ 
175+#### 3.2.2 Dense alias 判定
176+ 
177+仅当以下条件全部满足时,packed mask 才允许与用户 `out` 共用存储:
178+ 
179+1. `out` 为标准行主序连续布局,stride 与 shape 完全匹配;
180+2. view offset 和 storage offset 均为 0;
181+3. 逻辑 view 恰好覆盖整个底层 storage,即二者元素数相等;
182+4. 输出容量满足 `N×B >= M`
183+5. Tensor format 不是私有格式。
184+ 
185+第 3 条用于保护“连续小 view + 更大 backing storage”的场景。即使该
186+view 本身连续,也不能让原地 Kernel 写入 view 之外的 guard 区域。
187+ 
188+满足条件时,Host 在 `out` 起始地址上建立 `UINT8[M]` view,作为
189+`DSAGenBitMask` 的输出。由于 mask 和最终输出属于同一 GM allocation,
190+连续 dense 路径无需独立 packed-mask Tensor。
191+ 
192+由于 DSA mask 按 128 bit 对齐,极小 Tensor 可能不满足容量条件。对于
193+1/2/4/8 字节输出,允许同址的最小元素数分别为 16/8/4/2;更小输入走
194+独立 mask fallback。
195+ 
196+#### 3.2.3 Fallback 与 FP64 非连续写回
197+ 
198+下列场景使用保守 fallback:
199+ 
200+- 非连续输出;
201+- view/storage offset 非零;
202+- 逻辑 view 未覆盖完整 storage;
203+- 极小 Tensor 容量不足;
204+- 不支持直接解释的私有格式直接在参数校验阶段拒绝。
205+ 
206+fallback 路径为:
207+ 
208+```text
209+DSAGenBitMask(independent mask)
210+ -> BernoulliMask(continuous temporary)
211+ -> ViewCopy(out)
212+```
213+ 
214+A2 的 `ViewCopy` 路径未直接声明 FP64 支持。对于非连续 FP64 输出,Host
215+为源和目标建立保持 shape、stride 和 offset 的 INT64 bit-view,再执行
216+`ViewCopy`。该过程只搬运 64-bit 位模式,不进行数值转换。
217+ 
218+#### 3.2.4 架构分流
219+ 
220+A2/A3 对应的 DAV_2201 路径进入本次 DSA 融合实现。DAV_3510 及其他
221+非目标架构继续沿用 `StatelessBernoulli` 路径,避免改变非目标产品行为。
222+ 
223+### 3.3 Tiling 设计
224+ 
225+#### 3.3.1 TilingData
226+ 
227+TilingData 只传递 Kernel 必需的信息:
228+ 
229+```text
230+totalElements
231+elementsPerCore
232+tileElements
233+maskAliasesOut
234+```
235+ 
236+Host 通过 `PlatformAscendC` 动态获取 UB 大小和 Vector Core 数,不硬编码
237+A2/A3 的资源参数。
238+ 
239+#### 3.3.2 分块与分核
240+ 
241+设输出元素宽度为 `B`,预留 UB 为 8 KiB。Host 以 4-byte work buffer
242+和保守的 1-byte mask 开销估算单元素空间:
243+ 
244+```text
245+bytesPerElement = B + 4 + 1
246+tileElements = align_down((UB - 8 KiB) / bytesPerElement, 256)
247+tileElements = min(tileElements, 16384)
248+blockDim = min(vectorCoreNum, ceil(totalElements / tileElements))
249+elementsPerCore = align_up(ceil(totalElements / blockDim), 256)
250+```
251+ 
252+小 shape 自动减少核数;大 shape 在 A2 上使用 40 个 Vector Core。
253+fallback 路径中,各核处理互不重叠的连续元素区间。
254+ 
255+`maskAliasesOut=1` 时,Host 设置 `SetScheduleMode(1)`。该模式保证
256+所有已启动核心以一致的同步轮次执行高到低 wave,避免核间提前覆盖尚未
257+读取的 mask。
258+ 
259+#### 3.3.3 TilingKey
260+ 
261+| Key | 输出 dtype | Kernel 实例 |
262+| ---: | --- | --- |
263+| 1 | FP16 | `half` |
264+| 2 | FP32 | `float` |
265+| 3 | FP64 | `uint64_t` 位模式,Select + Gather |
266+| 4 | UINT8 / BOOL | `uint8_t` |
267+| 5 | INT8 | `int8_t` |
268+| 6 | INT16 | `int16_t` |
269+| 7 | INT32 | `int32_t` |
270+| 8 | INT64 | `int64_t` |
271+| 9 | BF16 | `bfloat16_t` |
272+ 
273+### 3.4 Kernel 侧设计
274+ 
275+#### 3.4.1 单 tile 执行流程
276+ 
277+每个 tile 依次执行:
278+ 
279+1. 使用 `DataCopyPad``ceil(count/8)` 字节 packed mask 搬入 UB;
280+2. 使用 `Select` 将 mask bit 展开为 FP16 或 FP32 的 0/1;
281+3. 浮点结果直接写出,整数和 BF16 在 UB 内 Cast;
282+4. FP64 使用 Select + Gather 组装 IEEE-754 64-bit 位模式;
283+5. 仅搬出 `count×sizeof(T)` 个有效字节,不写对齐 padding。
284+ 
285+各 dtype 的计算路径如下:
286+ 
287+| 类别 | dtype | UB 中间类型 | 写出策略 |
288+| --- | --- | --- | --- |
289+| 16-bit 浮点 | FP16 | FP16 | Select 后直接写出 |
290+| 32-bit 浮点 | FP32 | FP32 | Select 后直接写出 |
291+| 64-bit 浮点 | FP64 | 两个 FP32 word | Select high word,Gather 交织 |
292+| 8-bit / BOOL | INT8、UINT8、BOOL | FP16 | Select 后 Cast |
293+| 16-bit 整数 | INT16 | FP16 | `CAST_RINT` |
294+| 32/64-bit 整数 | INT32、INT64 | FP32 | `CAST_TRUNC` |
295+| BF16 | BF16 | FP32 | `CAST_RINT` |
296+ 
297+#### 3.4.2 同址展开安全性
298+ 
299+packed mask 初始位于 `out` 的低地址区域。如果从低地址开始展开,输出
300+可能覆盖后续尚未读取的 mask。为此,alias 路径按高地址到低地址分 wave
301+处理。
302+ 
303+设尚未展开区间为 `[0, end)`,输出元素宽度为 `d` 字节,本轮起点为:
304+ 
305+```text
306+start = align_up(ceil(ceil(end / 8) / d), 256)
307+```
308+ 
309+此时满足:
310+ 
311+```text
312+d * start >= ceil(end / 8)
313+```
314+ 
315+因此 `[start,end)` 的输出写区域不会覆盖 `[0,ceil(end/8))` 中仍需读取
316+的 mask。所有核心完成当前 wave 后执行 `SyncAll`,再处理更低地址区间。
317+最后不超过 256 个元素的前缀由 core 0 先完整搬入 UB,再写回输出。
318+ 
319+所有已启动核心必须参与相同次数的 `SyncAll`;即使某个核心在当前 wave
320+没有有效输出,也不能提前返回。
321+ 
322+#### 3.4.3 UB 预算
323+ 
324+Kernel 采用单缓冲:
325+ 
326+```text
327+maskQueue = align_up(ceil(tileElements / 8), 32)
328+outQueue = align_up(tileElements * B, 32)
329+workBuf = align_up(tileElements * 4, 32)
330+UB_total = maskQueue + outQueue + workBuf
331+```
332+ 
333+以 A2 的 192 KiB UB 为例:
334+ 
335+| dtype 宽度 | tileElements | maskQueue | outQueue | workBuf | 总计 |
336+| ---: | ---: | ---: | ---: | ---: | ---: |
337+| 1 B | 16,384 | 2,048 B | 16,384 B | 65,536 B | 83,968 B |
338+| 2 B | 16,384 | 2,048 B | 32,768 B | 65,536 B | 100,352 B |
339+| 4 B | 16,384 | 2,048 B | 65,536 B | 65,536 B | 133,120 B |
340+| 8 B | 14,336 | 1,792 B | 114,688 B | 57,344 B | 173,824 B |
341+ 
342+各路径均低于 UB 容量,并保留 8 KiB 预算。A3 使用相同公式按运行时资源
343+重新计算。当前版本不启用双缓冲,因为其会缩小单 tile,且现有 Profile
344+未显示足以抵消该开销的收益。
345+ 
346+### 3.5 内存与性能分析
347+ 
348+#### 3.5.1 内存模型
349+ 
350+| 指标 | 原实现 | 新实现(连续 dense) |
351+| --- | --- | --- |
352+| 主要链路 | DSA + Fill + DropoutDoMask + Cast + ViewCopy | DSA + BernoulliMask |
353+| 全 shape GM 临时值 | 至少一个,随 dtype 和规划变化 | 0 |
354+| packed mask | 独立分配 | 复用 `out` 起始存储 |
355+| 全 shape 写出 | 多次 | 一次 `N×B` |
356+| 非连续输出 | 连续化后多级中间值 | 一个连续结果 + ViewCopy |
357+ 
358+A2 和 A3 在 `[4096,4096]` 一般概率路径上的 workspace 结果一致:
359+ 
360+| dtype | 原 workspace | 新 workspace | 减少比例 |
361+| --- | ---: | ---: | ---: |
362+| FP16 / BF16 | 35,652,608 B | 1,024 B | 99.997% |
363+| FP32 | 69,207,040 B | 1,024 B | 99.999% |
364+| INT64 / FP64 | 69,207,552 B | 1,024 B | 99.999% |
365+ 
366+workspace 结果用于证明算子内部随 shape 增长的临时值已被消除;最终
367+NPU/GPU 内存一致性仍以等价框架调用的进程峰值为准,不能用 workspace
368+降幅替代。
369+ 
370+#### 3.5.2 测试对象与证据绑定
371+ 
372+A2/A3 实测使用同一源码版本,结果分别绑定目标平台包和实际加载的
373+custom DSO:
374+ 
375+| 对象 | 环境 | 版本标识 | 主证据 |
376+| --- | --- | --- | --- |
377+| 源码 | `experimental/random/bernoulli_mask` | commit `295d1e7fe793d3e5477d00cd1af86ebcea770f1d` | 算子 tree `db92e5b6d2999518723f231804d8c8a6202bacad` |
378+| A2 | Ascend 910B4,CANN 8.5.2 | package SHA-256 `06e71eca975f1ac8735352071e9984a35891ebceb1b6a3304b0d442ea111419a` | `results/*/20260725-p0-guard-hardening-final-a2-*` |
379+| A3 | Ascend910_9362,CANN 8.5.2,构建目标 `ascend910_93` | package SHA-256 `05040ef871a62bff8d66a43a098460fbfcd0990a5a24586dcd8e7b50d1d42029` | `results/a3/a3-final-007/` |
380+| GPU | NVIDIA H800 MIG | B011~B014,各 20 个隔离进程 | `results/gpu/20260725-h800-mig-torch-bernoulli/` |
381+ 
382+#### 3.5.3 A2/A3 与 GPU 内存对比
383+ 
384+三个平台采用相同 `[8192,8192]` shape、dtype、`prob=0.5`、inplace
385+`Tensor.bernoulli_` 调用层级和隔离进程协议。A2、A3 各完成 direct
386+ACLNN old/new 160 条和 torch-npu old/new 160 条记录;GPU 完成 4 个
387+case、每 case 20 条记录。下表为中位 total peak:
388+ 
389+| Case | dtype | 指标 | H800 MIG | A2 | A2/GPU 差距 | A3 | A3/GPU 差距 |
390+| --- | --- | --- | ---: | ---: | ---: | ---: | ---: |
391+| B011 | FP32 | allocated | 268,435,456 B | 268,437,504 B | 0.000763% | 268,437,504 B | 0.000763% |
392+| B011 | FP32 | reserved | 268,435,456 B | 272,629,760 B | 1.5625% | 272,629,760 B | 1.5625% |
393+| B012 | FP16 | allocated | 134,217,728 B | 134,219,776 B | 0.001526% | 134,219,776 B | 0.001526% |
394+| B012 | FP16 | reserved | 134,217,728 B | 138,412,032 B | 3.1250% | 138,412,032 B | 3.1250% |
395+| B013 | INT64 | allocated | 536,870,912 B | 536,872,960 B | 0.000381% | 536,872,960 B | 0.000381% |
396+| B013 | INT64 | reserved | 536,870,912 B | 541,065,216 B | 0.7813% | 541,065,216 B | 0.7813% |
397+| B014 | BF16 | allocated | 134,217,728 B | 134,219,776 B | 0.001526% | 134,219,776 B | 0.001526% |
398+| B014 | BF16 | reserved | 134,217,728 B | 138,412,032 B | 3.1250% | 138,412,032 B | 3.1250% |
399+ 
400+A2、A3 的四个 case 在 total peak allocated 和 reserved 两种可定义
401+口径下均小于 5%。同一 inplace 协议下,A2/A3 的 incremental peak
402+allocated 中位数为 1,536 B、reserved 为 2,097,152 B,GPU 两项均为
403+0 B,因此增量百分比的分母为 0。报告保留原始字节数并将百分比标记为
404+`undefined`,不将其改写为 0%。
405+ 
406+#### 3.5.4 A2/A3 性能结果
407+ 
408+两个平台均对 5 种 dtype 的 old/new 各执行 20 个隔离进程;每个进程
409+预热 10 次,再进行 100 次 device event 计时。每个平台 200/200 条记录
410+成功,且每组 old/new 输出 hash 一致。
411+ 
412+**A2 性能:**
413+ 
414+| dtype | 原/新 P50 (ms) | 原/新 P90 (ms) | P50 改善 |
415+| --- | ---: | ---: | ---: |
416+| FP16 | 0.236425 / 0.221500 | 0.241940 / 0.227110 | 6.313% |
417+| FP32 | 0.396570 / 0.229040 | 0.405220 / 0.233360 | 42.245% |
418+| BF16 | 0.256160 / 0.228795 | 0.264550 / 0.234860 | 10.683% |
419+| INT64 | 0.517965 / 0.379865 | 0.524110 / 0.385890 | 26.662% |
420+| FP64 | 34.954804 / 0.393395 | 35.015261 / 0.400220 | 98.875% |
421+ 
422+**A3 性能:**
423+ 
424+| dtype | 原/新 P50 (ms) | 原/新 P90 (ms) | P50 改善 |
425+| --- | ---: | ---: | ---: |
426+| FP16 | 0.210415 / 0.191985 | 0.213310 / 0.197960 | 8.759% |
427+| FP32 | 0.287395 / 0.200915 | 0.290620 / 0.207110 | 30.091% |
428+| BF16 | 0.226400 / 0.201440 | 0.229470 / 0.207100 | 11.025% |
429+| INT64 | 0.341265 / 0.264750 | 0.343050 / 0.271180 | 22.421% |
430+| FP64 | 34.992384 / 0.341385 | 35.031490 / 0.346200 | 99.024% |
431+ 
432+A2/A3 的 P50、P90 均未出现性能回退。FP64 的主要收益来自 UB 内
433+Select + Gather,避免原路径的低效 64-bit 标量构造和多级中间值。
434+ 
435+### 3.6 支持硬件
436+ 
437+| 芯片版本 | 支持情况 | 验证情况 |
438+| --- | :---: | --- |
439+| Atlas 800I/T A2,`ascend910b` | √ | 已完成核心验收矩阵、TTK 和 Sanitizer |
440+| Atlas A3 训练系列,`ascend910_93` | √ | 已完成同等的核心验收矩阵、TTK 和 Sanitizer |
441+ 
442+### 3.7 算子约束与设计边界
443+ 
444+1. 不新增用户可见约束,公共 shape、dtype、layout 和随机参数语义保持不变。
445+2. 本次融合仅替换 A2/A3 标量概率的一般概率路径。
446+3. Tensor-probability 接口继续沿用原路径。
447+4. 非连续或不满足完整 storage 覆盖条件的输出需要连续临时值和 ViewCopy。
448+5. `offset` 按任务书要求校验为 4 的倍数。
449+ 
450+## 4. 特性交叉分析
451+ 
452+| 交叉场景 | 风险 | 设计处理 |
453+| --- | --- | --- |
454+| out-of-place × 连续 dense | packed mask 额外分配导致峰值增长 | mask 复用 `out` 起始存储 |
455+| inplace × 连续 dense | mask 与最终输出同址覆盖 | 高到低 wave + `SyncAll` |
456+| 连续 view × 更大 backing storage | Kernel 可能写到逻辑 view 外 | 仅当 view 元素数等于 storage 元素数时允许 alias |
457+| 非连续 × 非零 offset | 物理地址与逻辑顺序不一致 | 连续临时结果 + ViewCopy |
458+| FP64 × 非连续 | ViewCopy 不直接声明 DOUBLE 支持 | INT64 bit-view 搬运位模式 |
459+| 极小 Tensor × mask 对齐 | 输出容量小于 16-byte mask | 独立 mask fallback |
460+| 空 Tensor × Kernel 调度 | 无效下发或除零 | Phase 1 直接返回 workspace 0 |
461+| `prob=0/1` × 随机路径 | 不必要的 DSA 和同步开销 | ZerosLike / OnesLike 快路径 |
462+| A2 × A3 | UB 和核数不同 | `PlatformAscendC` 运行时查询 |
463+ 
464+## 5. 可维可测分析
465+ 
466+### 5.1 验收标准
467+ 
468+| 验收项 | 标准 | 验证方法 |
469+| --- | --- | --- |
470+| 功能 | 覆盖全部 dtype、rank 0~8、空/非连续/inplace 和边界参数 | ACLNN 功能矩阵、ST、Host UT、TTK |
471+| 随机性 | 固定 seed/offset 可重现;改变状态后随机流改变;分布无退化 | checksum、Wilson 区间、双侧二项检验和 Holm 校正 |
472+| 内存 | 与等价 GPU 调用的内存差距小于 5% | 同 shape/dtype/API 层级的隔离进程峰值对比 |
473+| 性能 | 不低于原 A2/A3 实现 | 预热后 device event P50/P90 对比 |
474+| 安全 | 不发生越界、竞争或同步错误 | guard-region 与 mem/race/init/sync Sanitizer |
475+ 
476+随机算子的一般概率输出不要求与 GPU 逐元素相同。固定 seed/offset 用于
477+验证 NPU 新旧实现的可重现性;跨平台正确性通过值域和统计检验判断。
478+ 
479+### 5.2 覆盖矩阵与实测证据
480+ 
481+#### 5.2.1 用例覆盖
482+ 
483+| 验证维度 | 覆盖内容 |
484+| --- | --- |
485+| dtype | 10 种 `self/out` dtype,4 种 `prob` dtype |
486+| shape | 0~8 维、空 Tensor、标量、对齐边界、小 shape 和大 shape |
487+| layout | 连续、转置、切片、非零 offset、oversized storage guard |
488+| 概率 | `0``1`、接近边界及一般概率 |
489+| RNG | 相同/不同 seed,合法/不同/非法 offset |
490+| 接口 | out-of-place、inplace、标量概率及 Tensor-probability 回归 |
491+| 平台 | A2 与 A3 构建和实机验证 |
492+ 
493+#### 5.2.2 A2/A3 核心验收结果
494+ 
495+核心验收项在 A2/A3 上采用对称矩阵和相同判据:
496+ 
497+| 验证项 | A2 | A3 | 结论 |
498+| --- | ---: | ---: | --- |
499+| ACLNN 功能矩阵 | 77/77 | 77/77 | 覆盖 10 dtype、rank 0~8、布局、概率、offset、inplace 和 guard |
500+| ACLNN ST | 97/97 | 97/97 | 覆盖接口校验、重现性、alias/fallback 边界和异常参数 |
501+| 固定状态重现与敏感性 | 通过 | 通过 | 相同 seed/offset checksum 相同,改变 seed 或 offset 后不同 |
502+| 正式分布统计 | 15/15 | 15/15 | 99% Wilson、双侧精确二项检验和 Holm 校正均通过 |
503+| 最大绝对 z 值 / 最小原始 p-value | 1.661061 / 0.097172 | 1.661061 / 0.097172 | 未发现随机分布退化 |
504+| old/new 正式性能 | 200/200 | 200/200 | 5 种 dtype 的 P50/P90 均无回退,输出 hash 一致 |
505+| direct ACLNN 内存 | 160/160 | 160/160 | B011~B014、old/new、每组 20 个隔离进程 |
506+| torch-npu 内存 | 160/160 | 160/160 | 与 GPU 使用等价框架调用协议 |
507+| NPU/GPU total peak | 4/4 `<5%` | 4/4 `<5%` | allocated 和 reserved 均满足阈值 |
508+| Tensor-probability 兼容回归 | 8/8 | 8/8 | 验证系统 Tensor-probability API 未被自定义标量接口回归 |
509+ 
510+#### 5.2.3 工程与安全验证
511+ 
512+TTK 与 Sanitizer 在 A2/A3 上使用相同用例和判定标准。Sanitizer
513+单独使用与 Release 包同源的插桩包,不替代功能、性能和内存测试所用的
514+Release 包。
515+ 
516+| 工具层级 | 结果 | 说明 |
517+| --- | --- | --- |
518+| A2 TTK Kernel | 通用 26/26 + 存储复用 8/8 | 性能、精度和越界检查均为 PASS |
519+| A2 Sanitizer | 115 PASS、13 `LIMITED_MASKED_VSEL`、0 FAIL | 完整执行 128 项 memcheck/racecheck/initcheck/synccheck |
520+| A3 TTK Kernel | 通用 26/26 + 存储复用 8/8 | 性能、精度和越界检查均为 PASS |
521+| A3 Sanitizer | 115 PASS、13 `LIMITED_MASKED_VSEL`、0 FAIL | 完整执行 128 项 memcheck/racecheck/initcheck/synccheck |
522+ 
523+CANN 8.5 对 masked `Select` 的部分 initcheck 用例存在工具识别限制,
524+因此 A2/A3 各有 13 项标记为 `LIMITED_MASKED_VSEL`。这些项目未计入 PASS;
525+其余 115 项通过,未发现代码故障。
526+ 
527+平台专项工程验证中,A2 AscendOpTest 使用默认阈值完成 2 个固定随机状态
528+用例,结果为 2/2 PASS;A3 Host UT 为 36/36 PASS,并完成 Basic Profile
529+采集。A2/A3 的 TTK Kernel 精度检查均为 34/34 PASS。
530+ 
531+### 5.3 兼容性分析
532+ 
533+- 保留 `aclnnBernoulli` / `aclnnInplaceBernoulli` 的公共签名和两阶段执行方式。
534+- `self/out` 的 dtype、shape、format 和非连续 view 语义保持不变。
535+- DSA 的 seed、offset 和 `1-prob` 传参方式与原实现一致。
536+- `prob=0/1` 继续使用原有精确快路径。
537+- FP64 bit-view 仅改变搬运解释,不改变输出数值。
538+- 非目标架构继续沿用原 `StatelessBernoulli` 路径。
539+- 若特定平台或 dtype 的融合路径出现兼容性问题,可在 Host 侧回退到原链路;
540+ 回退后需重新评估内存指标。