Closed
[Requirement|需求建议]: 【社区任务】Bernoulli算子AscendC A2/A3 内存优化 #2744
m0_69357246Add on Aug 19Closed at Aug 24
Aug 19 Associated pull request
陈思
Aug 19 Comments:
Aug 19 Comments:
/assign @m0_69357246


Aug 19 Assign m0_69357246 as responsible person
Aug 24 Close issue
Aug 24 Added label: resolved
Thanks for sending an requirement! Please fill in the following template to help quickly solve your problem.
一、背景信息 (必填)
需求背景(required)
需求来源
本需求来源于 2026 年 7 月 CANN 算子社区任务。任务要求在不改变
aclnnBernoulli既有功能、精度和随机语义的前提下,以 Atlas A2/A3训练系列产品为目标优化 Device 内存占用,使 NPU 实现与 GPU 实现的峰值内存
差距控制在 5% 以下,同时保证性能不低于优化前的原算子。本设计采用 output
storage alias 和单 Kernel 原地展开方案,已消除导致内存膨胀的全尺寸中间对象,
内存占用满足任务书要求。
任务书指出,原 910B 标量概率路径由多个小算子拼接完成:
其中
Fill和DropoutDoMask都会构造与输出同 shape 的完整临时 Tensor,是内存膨胀的主要来源。任务书给出的参考方向是将
Fill + DropoutDoMask做 inplace 融合。本设计在保持 DSA 随机 mask 生成不变的基础上,进一步将
Fill和DropoutDoMask的功能合并为单个 Ascend C Kernel,直接由 packedmask 生成目标 dtype 的最终 0/1 结果。
设计重点是随机 mask 与最终输出共享 storage、Kernel 原地展开、dtype 原生输出
以及短任务单核/大任务多核倒序 stage 的完整数据流。
背景介绍
aclnnBernoulli算子实现优化
对标 PyTorch
torch.bernoulli和Tensor.bernoulli_的标量概率形式。设输出元素总数为
N,输入概率为p,则每个输出元素满足:self的数据内容不参与计算,只提供输出的 shape、dtype、format 和 view信息。
seed与offset决定随机序列;相同输入条件下必须保持既有随机数生成协议,不得因内存优化更换随机算法或改变 mask 的 bit 顺序。
需要区分两种“inplace”概念:
aclnnInplaceBernoulli将结果写回selfRef对应的用户可见存储本设计实现的是第二种计算融合,并通过公共 API 既有的
ViewCopy机制继续支持第一种用户语义。私有
Bernoulli使用 executor 管理的连续输出 Tensor,随机mask 始终通过该 output storage 的 UINT8 view 由 DSA 写入,随后同一个 Kernel
在原 storage 上展开。mask 不再形成独立 HBM 对象;短任务按 mask 大小选择单核
或多核调度,单核路径在 UB 中完成预取并跳过全核同步。该方案不直接绑定外部
任意 view,因此不会破坏非连续 Tensor 和公共 inplace 接口的适配逻辑。
aclnnBernoulli算子TBE实现现状分析
1. 源码与信息库定位
本任务优化的是 ACLNN 小算子拼接路径,不存在一个可直接替换的单体 TBE
BernoulliKernel。设计分析以公共 ACLNN 实现和各依赖小算子为准。random/dsa_gen_bit_mask/op_host/op_api/aclnn_bernoulli.cpprandom/dsa_gen_bit_mask/op_host/op_api/aclnn_bernoulli.hrandom/dsa_gen_bit_mask/op_host/op_api/dsa_gen_bit_mask.cppexperimental/random/bernoulli/op_api/bernoulli.cppconversion/fill/op_api/fill.cpprandom/drop_out_do_mask/op_api/dropout_do_mask.cppmath/zero_op/op_api/zero_op.cpp、math/ones_like/op_api/ones_like.cppp=0/1路径experimental/random/bernoulli/op_graph/bernoulli_proto.hBernoulliexperimental/random/bernoulli/op_host/bernoulli_def.cppexperimental/random/bernoulli/op_host/bernoulli_tiling.cppexperimental/random/bernoulli/op_host/config/ascend910b/bernoulli_binary.jsonexperimental/random/bernoulli/op_kernel/bernoulli.cpp2. 支持的数据类型、格式和参数
任务交付范围与私有 Kernel 支持范围如下:
self/outdtypeself/outformatself/outrankself/outviewprob0 <= p <= 1seed/offsetint64_t;offset 遵循公共随机接口的 4 倍数约束maskBernoulli公共 ACLNN 对 NCHW、NHWC、HWCN、NDHWC、NCDHW 等既有非私有 storage format
的兼容行为不因本任务改变;私有 Kernel 不解释 format 维度语义,只按连续的
扁平元素序列处理。OpDef 的声明格式为 ND,binary config 使用
FormatAgnostic,L0 输出继承input->GetViewFormat();三者的共同约束是进入Kernel 的物理存储必须连续,而不是把所有公共 view 强制改写为 ND。私有格式
仍由公共参数检查拒绝。
3. 原 910B 标量概率实现描述
原
0 < p < 1路径执行以下步骤:self连续化。1 - p作为 DSA dropout 概率生成 packed bit mask。Fill生成与输出 shape 相同的全 1 Tensor。DropoutDoMask使用 packed mask 过滤全 1 Tensor。ViewCopy写入公共输出或selfRef。原
p=0和p=1路径分别调用ZerosLike和OnesLike,之后仍进入公共Cast/ViewCopy 收尾流程。
4. 原实现流程图
flowchart TD A["aclnnBernoulliGetWorkspaceSize"] --> B["校验 self/prob/out"] B --> C{"self 是否为空?"} C -->|是| Z["workspace=0,返回空 executor"] C -->|否| D["Contiguous self"] D --> E{"当前架构是否 DAV_2201?"} E -->|否| F["保持其他架构 StatelessBernoulli 路径"] E -->|是| G{"prob 模式"} G -->|p=0| H["ZerosLike"] G -->|p=1| I["OnesLike"] G -->|0<p<1| J["DSAGenBitMask"] J --> K["Fill: 生成完整 ones Tensor"] K --> L["DropoutDoMask: 生成完整中间输出"] H --> M["Cast 到目标 dtype"] I --> M L --> M M --> N["ViewCopy 到 out/selfRef"] F --> N N --> O["返回 workspace 与 executor"]5. 原实现内存生命周期
设:
忽略 allocator 对齐和框架固定开销,若将 executor 图中的逻辑结果全部物化且
生命周期重叠,原随机路径的逻辑分配上界可表示为:
其中
Mcast取决于 dtype 和 Cast 是否物化。该公式描述原 executor 图中多个全尺寸逻辑结果的结构性内存开销;allocator 的提前释放或复用不会改变原链路
存在 Fill、DropoutDoMask 和 Cast 中间对象这一根因。
flowchart LR subgraph T0["阶段 0"] A0["self / out"] end subgraph T1["阶段 1"] A1["self / out"] M1["packed mask: O(N/8)"] end subgraph T2["阶段 2"] A2["self / out"] M2["packed mask"] F2["Fill ones: O(N*Sfill)"] end subgraph T3["阶段 3: 可能重叠的逻辑生命周期"] A3["self / out"] M3["packed mask"] F3["Fill ones"] D3["DropoutDoMask out: O(N*Sfill)"] end subgraph T4["阶段 4"] A4["self / out"] C4["Cast/ViewCopy result"] end T0 --> T1 --> T2 --> T3 --> T4该生命周期模型表明,原 executor 图同时包含 Fill、DropoutDoMask 和 Cast
等全尺寸逻辑结果。本设计从算子图中删除这些中间 Tensor,而不是只缩小单个
Kernel 的 UB 使用。
aclnnBernoulli算子功能分析
aclnnBernoulli根据标量概率prob生成与selfshape、dtype 一致的 0/1Tensor,
self的数据内容不参与随机计算。seed和offset决定随机序列;公共 out-of-place 接口写入
out,公共 inplace 接口写回selfRef。优化只替换910B Scalar-prob executor 内部计算链,不改变 Tensor-prob API 和非目标架构路径。
self/selfRefprob0 <= prob <= 1seedoffsetoffset % 4 == 0outself一致self一致需求分析(required)
需求描述
P(1)=pContiguous适配,输出由ViewCopy适配aclnnInplaceBernoulli写回selfRef的公共语义需求拆解
Fill + DropoutDoMask + Cast全尺寸消费链融合为单个 Ascend C Kernel。外部组件依赖
不新增仓外运行时依赖。方案复用 CANN ACLNN/opdev executor、
DSAGenBitMask和 Ascend C AIV 运行时;PyTorch、AscendOpTest 和 profiler只用于对标与验收,不进入算子运行依赖。
内部适配模块
需求模块设计
算子原型
xmasky共享 storageyx一致x一致modeRANDOM_ALIASED/ZERO/ONE公共
aclnnBernoulli和aclnnInplaceBernoulli两段式 ABI 不变。Tensor-prob两组接口保留既有
StatelessBernoulli路径,不进入私有BernoulliKernel。设计硬约束
<5%算子支持型号
Atlas A2/A3 训练系列产品(
ASCEND910B/ASCEND910_93)。详细设计(required)
算子分析
数学公式
设输出元素总数为
N,标量概率为p,则每个输出元素满足:yi∈{0,1},P(yi=1)=p,P(yi=0)=1−p
随机路径由 DSA 根据
seed、offset和dropout=1-p生成 packed bit mask,第
i个输出读取第i个 mask bit 并转换成目标 dtype 的 0 或 1。支持数据类型
self/selfRef/outprobseed/offset支持形状
out的 shape 与 dtype 必须和self一致。Contiguous/ViewCopy适配。算子实现
使能方式
私有算子位于
experimental/random/bernoulli,仅为ascend910b构建,并通过模块依赖复用
dsa_gen_bit_mask和stateless_bernoulli。公共aclnnBernoulli位于random/dsa_gen_bit_mask,DAV_2201 Scalar-prob分支调用私有 L0 helper。
BernoulliKernelBernoulli常量 modeStatelessBernoulliStatelessBernoulli运行时 op type 统一为
Bernoulli。Proto、OpDef、tiling、binary config、AICore 入口和 L0 launcher 使用同一名称。
实现方案
总体设计思想
本方案采用“保留随机源,替换全尺寸消费链”的设计:
设计原则如下:
packed-mask Tensor;DSA 直接写入最终私有输出 storage 的 UINT8 alias。
私有 AIV 常量 mode;其他端点保持 ZerosLike/OnesLike。
追加全尺寸 Cast。
blockDim、stageElements与tileElements。1. 总体执行流程
flowchart TD A["公共 Scalar-prob ACLNN"] --> B["参数校验与空 Tensor 分流"] B --> C["Contiguous self"] C --> D{"NPU 架构"} D -->|DAV_2201 / 910B| E{"prob 模式"} D -->|DAV_3510| F["保持 StatelessBernoulli arch35 路径"] D -->|其他已支持架构| G["保持既有 StatelessBernoulli 路径"] E -->|p=0/1| Q{"该 dtype/value 原端点是否落 AI_CPU?"} Q -->|是| H["Bernoulli ZERO/ONE"] Q -->|否且 p=0| H0["保持原 ZerosLike"] Q -->|否且 p=1| I["保持原 OnesLike"] E -->|0<p<1| J["申请私有输出并创建 UINT8 storage alias"] J --> J0{"maskBytes <= 16 KiB?"} J0 -->|是| J1["blockDim=1,mask 预取到 UB"] J0 -->|否| J2["多核倒序 stage + SyncAll"] J1 --> J3["DSAGenBitMask"] J2 --> J3 J3 --> K["Bernoulli 在 output storage 原地展开"] H --> L["selected branch result"] H0 --> L I --> L K --> L F --> L G --> L L --> M["ViewCopy 到 out/selfRef"] M --> N["返回 workspace 与 executor"]2. 概率路径分流
RANDOM_ALIASED=00 < p < 1私有 L0 提供
BernoulliRandom和BernoulliConstant。端点路由使用显式白名单,其他 dtype/value 组合保留原 ZerosLike/OnesLike 路径。
3. 接口设计
3.1 公共 Scalar-prob 接口
aclnnStatus aclnnBernoulliGetWorkspaceSize( const aclTensor* self, const aclScalar* prob, int64_t seed, int64_t offset, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor); aclnnStatus aclnnBernoulli( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream); aclnnStatus aclnnInplaceBernoulliGetWorkspaceSize( const aclTensor* selfRef, const aclScalar* prob, int64_t seed, int64_t offset, uint64_t* workspaceSize, aclOpExecutor** executor); aclnnStatus aclnnInplaceBernoulli( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream);公共参数约束:
self/selfRefprob0 <= p <= 1seedint64_t,沿用随机子系统语义offsetint64_t,按公共文档要求满足offset % 4 == 0outself一致;支持非连续 TensorworkspaceSize/executor本设计不改变公共错误码。空指针返回
ACLNN_ERR_PARAM_NULLPTR;dtype、shape、format、prob 或 offset 不满足公共约束时返回
ACLNN_ERR_PARAM_INVALID。3.2 Tensor-prob 接口边界
以下接口继续使用既有
StatelessBernoulli设计,不进入本次私有 Kernel:Tensor-prob 支持逐元素概率 Tensor,不进入本设计的 Scalar-prob packed-mask
消费路径。
3.3 私有 L0 接口
const aclTensor* BernoulliRandom( const aclTensor* input, double probability, int64_t seed, int64_t offset, aclOpExecutor* executor); const aclTensor* BernoulliConstant( const aclTensor* input, bool value, aclOpExecutor* executor);两个 helper 最终统一调用:
私有 op 原型:
xmaskmodeyxdtype/shape 一致,L0 继承 view format3.4 两段式接口时序
sequenceDiagram participant U as User participant A as aclnnBernoulliGetWorkspaceSize participant E as aclOpExecutor participant D as DSAGenBitMask participant B as Bernoulli participant R as CommonOpExecutorRun U->>A: self, prob, seed, offset, out A->>A: 参数校验 / 空 Tensor 判断 A->>E: CREATE_EXECUTOR + Contiguous(self) alt 0 < prob < 1 and DAV_2201 A->>E: AllocTensor(y_private) A->>E: CreateView(y_private) + SetDataType(UINT8) A->>E: mode=RANDOM_ALIASED A->>E: maskBytes <= 16 KiB 时 blockDim=1,否则多核倒序 stage A->>D: mask, count, seed, offset, dropout=1-prob D-->>E: packed mask 已生成 A->>B: x, mask, y_private, mode else endpoint 命中 AIV 白名单 A->>B: x, sentinel, mode=ZERO/ONE else prob == 0 A->>E: 原 ZerosLike executor 链 else prob == 1 A->>E: 原 OnesLike executor 链 end E-->>A: selected branch result tensor A->>E: ViewCopy(result, out/selfRef) E-->>U: workspaceSize, executor U->>R: workspace, executor, stream R-->>U: aclnnStatus3.2.1 host侧设计:
1. 参数校验和空 Tensor
第一段接口校验空指针、dtype、shape、format、
prob范围和offset,再构造 executor:
flowchart TD A["入口"] --> B{"self/prob/out/workspace/executor 非空?"} B -->|否| X["返回 PARAM_NULLPTR"] B -->|是| C{"dtype 支持且 self/out dtype 一致?"} C -->|否| Y["返回 PARAM_INVALID"] C -->|是| D{"0 <= prob <= 1?"} D -->|否| Y D -->|是| E{"rank <= 8 且 self/out shape 一致?"} E -->|否| Y E -->|是| F{"format 非私有格式?"} F -->|否| Y F -->|是| G{"offset 满足随机接口约束?"} G -->|否| Y G -->|是| H{"shape 元素数可安全表示?"} H -->|否| Y H -->|是| I{"self 是否为空?"} I -->|是| J["workspace=0,返回"] I -->|否| K["构造 executor 图"]空 Tensor 在 ACLNN 层返回,不进入 DSA、tiling 和 Kernel。这样避免
N=0时创建无意义 mask,也保持原接口的空 Tensor 行为。
私有 Host tiling 按逐维安全乘法计算元素数,并拒绝负维或超出
int64_t表示范围的 shape。2. 连续化与公共 view 适配
私有 Kernel 只处理物理连续的扁平线性空间,不依赖 NCHW、NHWC 或 ND 的逻辑
维度解释。公共接口继续通过两级适配支持非连续 Tensor:
x数据不会被私有 Kernel 读取,但仍执行Contiguous(self),以保持公共executor 的 shape、format、view 语义和其他架构路径一致,并统一处理非连续、
storage offset 和 inplace 场景。
3. 概率模式分流
Host 使用与既有实现一致的 double 比较策略识别端点概率:
端点路径均不启动 DSA。白名单为 DOUBLE ZERO/ONE、INT16 ZERO/ONE 和 INT64 ONE;
其他端点继续复用原 ZerosLike/OnesLike。RANDOM 模式必须满足
0 < p < 1。4. packed mask 长度与概率转换
DSA mask 按 128 bit 对齐:
RANDOM 路径先按输入 shape/dtype 申请最终私有结果
y_private,再在相同storage 上创建 UINT8 mask view:
mask view 与
y_private具有相同 storage 和 view offset,仅改变 shape 与dtype,因此 DSA 在所有随机规模都不申请独立 packed-mask HBM:
DSA 最少写一个 128-bit block,故非空小 Tensor 的 mask 至少为 16 bytes。
CreateView在改成 UINT8 dtype 前按原 output dtype 校验 shape,因此当N < 16时,L0 使用带原始 view shape 的 16 元素 padded output storage。该 padding 上限
为
16 * Sout(DOUBLE 为 128B),属于 output storage 的固定对齐余量,不是独立 mask Tensor,也不随大 Tensor 增长。
原 DSA L0 的参数名为
dropout,bit 为 1 表示保留。Bernoulli 要求 bit 为 1的概率等于
p,因此继续传入:此转换、mask 对齐、seed 和 offset 均沿用原路径。新 Kernel 只改变 mask 的
存放位置和消费方式,不改变 mask 生成协议。
5. 架构兼容分流
StatelessBernoulli,prob 统一转 FP32StatelessBernoulli该分流保证
BernoulliKernel 不会意外覆盖 950 或旧训练产品路径。6. 私有 Op Host tiling 设计
6.1 shape 安全检查
Host 遍历 storage shape 计算:
每个维度必须非负,乘法前检查:
若发现负维或乘积溢出,tiling 返回失败,避免 block 和 GM offset 计算溢出。
该检查保护私有 Kernel 的分核和地址计算,是公共 ACLNN 分配前检查之后的第二道
防线,不负责替代此前已经发生的 output storage 申请和 alias 边界检查。
6.2 分核策略
mask 每字节对应 8 个输出元素。分核和 stage 均以 256 个元素为最小粒度,使
任意 RANDOM stage 起点同时满足字节对齐、32-byte mask DMA 对齐和向量粒度:
blockElements是 Host 对“每核完整数据段”的估计,用于优先规划单 stage 覆盖整段数据。RANDOM Kernel 实际按全局 wave 分配:
stage 从全局高地址向低地址执行。所有起点均为 256 的倍数,各 core 可直接从
maskGm[coreStart / 8]读取,无需跨 core 拼接 bit 或执行位移修正。6.3 UB 分块和 LocalMemory 预算
Host 从平台查询可用 UB,预留 8 KiB 给框架和对齐余量。平台信息不可用时使用
192 KiB 作为保守 fallback,不假设目标设备一定返回固定 UB 大小。设:
RANDOM_ALIASED 保留一个完整 packed-mask stage 和一个计算 tile。mask queue
深度为 1,output queue 深度为 2。统一 UB 模型为:
dtype 专用 scratch:
scratchPerElementRANDOM 计算 tile 上限为 32768 元素;常量 tile 上限同样为 32768。DOUBLE/INT64
常量模式额外限制为 4096 元素,以约束 128-bit vector mask 的 repeatTimes
范围。典型 16M 元素 case 的
blockElements约 420K,mask stage 约 52 KiB,可与 FP16 32K tile、FP32 16K tile 或 DOUBLE 4K tile 同时放入 192 KiB UB,
因此每个全局 stage 只需要一个
SyncAll;当waveElements >= totalElements时整次 Kernel 只有一个 stage。更大的单核数据或更大的全局 Tensor 会退化为
多个全局倒序 stage,但不会重新申请 GM workspace。
最终
stageElements/tileElements都向下对齐到 256;当真实数据不足 256 个元素时仍保留一个对齐单元,GM 读写长度由真实
count裁剪。6.4 tilingData
struct BernoulliTilingData { uint64_t totalElements; uint64_t blockElements; uint64_t stageElements; uint64_t tileElements; uint32_t mode; };totalElementsblockElementsstageElementstileElementsmodeHost tiling 申请固定 512-byte workspace 描述符,用于满足运行时 launcher 约束;
该值不随 shape 或 dtype 增长,不承载 packed mask 或输出数据。
6.5 tilingKey 与二进制规划
当前所有模式和 shape 使用同一
tilingKey=0。mode 是运行时 attr,dtype 由静态二进制配置选择。这样避免按
4 modes x 10 dtypes扩展为 40 份二进制。mode 只影响 Kernel 内的 UB 初始化和计算分支,不改变输出 shape/format,因此
不需要独立 tilingKey。
6.6 Host tiling 流程图
flowchart TD A["Bernoulli tiling"] --> B["读取 input/output shape"] B --> C{"维度非负且元素数不溢出?"} C -->|否| X["GRAPH_FAILED"] C -->|是| D["读取并校验 mode in [0,3]"] D --> E["读取 dtype -> storageBytes/scratchBytes"] E --> F["查询 AIV coreNum 和 UB size"] F --> G["按 256 元素计算 blockNum/blockElements"] G --> H["按 mode + dtype 规划 stageElements/tileElements"] H --> I{"至少可容纳 256 元素?"} I -->|否| X I -->|是| J["写 tilingData"] J --> K["SetBlockDim / tilingKey=0<br/>固定 512-byte workspace"] K --> L["GRAPH_SUCCESS"]3.2.2 kernel侧设计:
1. Kernel 原型和输入使用
extern "C" __global__ __aicore__ void bernoulli( GM_ADDR x, GM_ADDR mask, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling);x只参与 OpDef/L0 的 shape 和 dtype 契约,Kernel 不读取x数据;workspace不使用。Kernel 实际读取mask(仅 RANDOM 模式)并写y;RANDOM_ALIASED 下二者始终指向同一块 GM storage 的不同类型视图。
BOOL 在 GM 上按 1 byte 存储,模板内部使用:
StorageT = conditional_t<is_same_v<T, bool>, uint8_t, T>避免对
GlobalTensor<bool>的存储宽度产生歧义。2. 初始化流程
初始化步骤如下:
totalElements/blockElements/stageElements/tileElements/mode。blockIdx * blockElements计算并裁剪静态核区间。只有 RANDOM_ALIASED 不在初始化阶段绑定固定核区间,而是在每个全局倒序 stage
中按
stageStart + blockIdx * stageElements计算本核区间,保证所有核采用同一同步节拍。
3. RANDOM 模式处理
3.1 RANDOM_ALIASED
RANDOM_ALIASED 的关键不是普通逐 tile 读取,而是“整段预取、全核同步、本地展开”:
若当前 stage 的真实
count <= tileElements,Kernel 走单 tile fast path,直接把整段
maskLocal交给ComputeMask,省去通用内层循环和 LocalTensor 切片;这覆盖中小 shape,同时不影响大 shape 的整段预取设计。
多核路径的
DeQue + SyncAll保证任何 core 开始覆盖 GM 前,当前 stage 的所有mask 都已进入各自 UB。单核路径只有一个生产者/消费者,不需要跨 core 同步。
两条路径都按全局高地址到低地址处理;输出元素
i写入的 byte 地址对应 mask中更高编号的元素,这些 bit 已在当前 stage 预取或在更早的高地址 stage 消费,
因此不会破坏未来低地址 stage。该顺序给出了原地覆盖的完整读写依赖保证。
mask padding 为 0,因此补齐 lane 即使参与向量计算也只生成 0;CopyOut 又只写
真实
count,不会覆盖输出尾部之外的 GM。4. 白名单 ZERO/ONE 常量分支
公共 L0 仅对白名单端点下发 ZERO/ONE mode。常量分支不读取 sentinel,直接在
输出 LocalTensor 中生成目标 dtype 的 0 或 1:
flowchart TD A["ComputeConstant(count)"] --> B["processCount = align_up(count, 256)"] B --> C{"dtype 分类"} C -->|INT8/UINT8/BOOL| D["按 uint16 写 0x0000 或 0x0101"] C -->|BF16| E["写 0x0000 或 0x3f80 位模式"] C -->|INT64| F["清零 32-bit words,仅偶数 word 写 1"] C -->|DOUBLE| G["清零 32-bit words,仅奇数 word 写 0x3ff00000"] C -->|其他 dtype| H["Duplicate(T(0/1))"] D --> I["EnQue output"] E --> I F --> I G --> I H --> I I --> J["CopyOut 真实 count"]该分支不读取 mask,也不申请 mask queue 和 random scratch。单元素 sentinel 仅用于
满足私有算子的固定两输入原型,不随输出 shape 增长。
5. dtype 专用生成策略
Duplicate + SelectDuplicate + Select0x3f80,reinterpret 为 half 做 bit-select所有转换仅作用于 UB tile,不会重新引入全尺寸 Cast Tensor。
6. DOUBLE 位模式和 Gather
DOUBLE 的目标位模式为:
RANDOM 模式准备两个长度为
tileElements的 FP32 区域:0x3ff00000作为 FP32 位模式对应数值1.875f,因此可用 FP32Duplicate + Select生成 high-word 区。Host/Kernel 启动时构造 Gather byteoffset,使输出顺序成为:
该方法只搬运位模式,不执行 FP64 算术,因此 0/1 表示精确。
7. 尾块和越界安全
设计包含三层边界保证:
N个有效 bit。blockStart按 256 元素对齐,保证各 core mask 起点按字节对齐。count。flowchart LR A["真实尾块 count"] --> B["maskBytes=ceil(count/8)"] B --> C["DataCopyPad 到 32B,padding=0"] C --> D["processCount=align_up(count,256)"] D --> E["向量生成对齐结果"] E --> F["DataCopyPad 仅写 count*Sout 字节"]8. stage 预取与输出队列设计
maskQueue 深度为 1;RANDOM_ALIASED 用它保存已经与 GM 解耦的完整 stage。
outputQueue 深度为 2,作为输出 LocalTensor 的资源配置。当前 Kernel 对每个
tile 按
ComputeMask/ComputeConstant -> CopyOut顺序执行,不预取下一个output tile。alias mask 的下一 stage 可能被当前 stage 的输出写回覆盖,
因此保持全局倒序和 stage 同步语义。
RANDOM_ALIASED 的调度模型为:
当 tiling 选择单核短任务时没有全核 Barrier,执行
MTE2(mask stage) -> Vector -> MTE3;多核任务在 MTE2 后执行一次 stage Barrier,两者都复用同一 outputQueue。
常量分支没有 MTE2 mask 阶段,每个 tile 按以下顺序执行:
RANDOM 只在每个 stage 的 mask 整段搬入后执行一次核间同步;队列和
scratch 都是有界 UB 资源,不增加 GM workspace。
9. Kernel 总流程图
flowchart TD A["bernoulli"] --> B["读取 tilingData"] B --> C["Init: GM、stage/tile、queues、scratch"] C --> H{"mode"} H -->|RANDOM_ALIASED| I["按全局高地址到低地址遍历 stage"] I --> I1["每核 CopyIn 整段 mask 到 UB"] I1 --> I2{"blockDim > 1?"} I2 -->|是| I2a["SyncAll"] I2 -->|否| I2b["跳过同步"] I2a --> J I2b --> J I2 --> J["在本地 mask 上按 tile 展开并 CopyOut"] J --> I3{"还有更低地址 stage?"} I3 -->|是| I I3 -->|否| Z["结束"] H -->|ZERO/ONE| K["按静态核区间遍历 tile"] K --> K1["ComputeConstant + CopyOut"] K1 --> Z10. 与原小算子流程差异
Selecty_private3.2.3 关键内存与性能方案
1. 候选方案比较
Fill + DropoutDoMask原地复用y_private(极小 Tensor 只保留 output 对齐余量)方案 B 相比任务书参考方案进一步删除 Fill Tensor,并以单核短任务调度消除
SyncAll,同时保持方案 A 的 DSA 随机协议。2. 新路径内存模型
内存模型分为 HBM/GM 逻辑分配和每核 UB 资源两部分,两者不能相加为同一个
“峰值内存”公式。
2.1 HBM/GM 逻辑对象和生命周期
BernoulliRandom/BernoulliConstant通过executor->AllocTensor申请 shape、dtype与输出一致的私有结果
y_private,随后由ViewCopy写入用户out/selfRef。RANDOM 的 packed mask 是
y_privatestorage 的 UINT8 view;mask 在覆盖前只存在于 DSA 输出区域和每核 UB。因此连续 RANDOM 路径为:
白名单 ZERO/ONE 的 HBM 逻辑对象为:
其他端点保留
M_zeros_like_or_ones_like_result。两类端点都不分配 DSA mask;executor 按各分支结果的最后消费点管理生命周期。
用户原有
self和目标out/selfRef存储不属于融合 Kernel 可消除的中间对象。Madapt对应非连续 view、inplace 和公共输出适配;y_private作为 executor私有结果,计算完成后由 ViewCopy 写入公共
out/selfRef。flowchart LR subgraph G0["公共对象"] X["self"] O["out / selfRef"] end subgraph G1["RANDOM 私有对象"] Y["y_private: N*Sout"] M["UINT8 mask alias: 无独立 storage"] end subgraph G2["适配与回写"] C["可选 Contiguous 存储"] V["ViewCopy 写回公共输出"] end X --> C --> Y M -->|DSA 写入| Y Y -->|Bernoulli 原地展开| Y Y --> V --> O相对于原路径,新设计删除 Fill、DropoutDoMask、全尺寸 Cast 和独立
packed-mask storage,但不把私有输出
y_private或极小 Tensor 的 output 对齐余量从模型中隐去。alias 关系由同一个
aclTensorstorage 上的CreateView + SetDataType(UINT8)明确定义,不依赖 allocator:mask 与y_private共享 storage,公共out/selfRef通过 ViewCopy 接收最终结果。2.2 每核 UB 编译期资源模型
UB 预算由 4.4.3 节的 queue/scratch 公式决定,其性质为:
UB_total_budget只用于证明每核 LocalMemory 不越界和 tile 大小有界,不是 HBM分配,不得计入 GPU/NPU Device HBM 峰值比较。固定 launcher workspace 也不
意味着整个 ACLNN executor 没有 HBM 中间 Tensor。
3. “融合 inplace”语义说明
任务书中的 inplace 融合目标是避免
Fill结果和DropoutDoMask结果同时物化。本设计实现两层不同的 inplace,并为短任务增加调度分支:
y_private共享 storage,DSA写入后由
Bernoulli原地展开;不区分大小申请不同 mask 对象。aclnnInplaceBernoulli最终仍通过ViewCopy写回任意stride/storage offset 的
selfRef。RANDOM 内部 inplace 存在 mask-read/output-write 覆盖依赖:单核短任务由整段
预取到 UB 解决,多核任务由“整段 mask 搬入 UB、全核同步、全局倒序 stage”解决。
私有 Kernel 不读取
x;公共 inplace API 仍通过 executor-owned 结果和ViewCopy写回selfRef。这种分层避免把任意 stride/storage offset 的外部Tensor 直接绑定为私有 AICore 输出。
4. 随机一致性设计
随机一致性由以下不变量保证:
count的 128 bit 对齐规则不变。dropout=1-p的概率转换不变。i个输出读取第i个 mask bit。端点概率继续使用原实现且不消耗随机序列,因此不会改变后续随机调用的序列位置。
上述不变量必须由“构造 mask + 原链路对拍”两层确定性验证支撑,统计均值只能
验证概率分布,不能发现 byte 内 LSB/MSB 顺序反转:
flowchart TD A["位序验证"] --> B["向私有 Kernel 注入已知单字节 mask"] B --> C["0x01: 仅第 0 位为 1"] B --> D["0x80: 仅第 7 位为 1"] B --> E["0x55: 偶数 bit 为 1"] B --> F["0xaa: 奇数 bit 为 1"] C --> G["逐元素核对输出位置"] D --> G E --> G F --> G A --> H["固定 shape/prob/seed/offset"] H --> I["原 DSA + Fill + DropoutDoMask + Cast"] H --> J["新 DSA + Bernoulli"] I --> K["10 种 dtype 逐元素一致"] J --> K已知 mask 用例覆盖 byte 内位序;固定 seed/offset 与原完整链逐元素对拍覆盖 DSA
对齐、跨 byte、跨 tile 和多核起点。两类验证共同保证第
i个输出始终消费第i个 mask bit。5. 性能设计
性能优化点:
Select展开,不使用逐元素GetValue/SetValue。blockDim,小 Tensor 不强制占满所有 core。SyncAll;常见大 shape 每核完整mask 可一次放入 UB,从逐 tile 多次同步降为一次同步。
SyncAll;更大mask 使用多核倒序 stage。
DSA 和 mask 读流量。
支持硬件
ascend910b配置和 AIV Kernelascend910b配置和 AIV Kernel私有
Bernoulli的 OpDef 注册ascend910bAICore 配置;其他架构沿用公共 ACLNN 已有分流。任务要求的 CANN 版本为 8.5.0 及以上。
算子约束限制
aclnnBernoulli/aclnnInplaceBernoulli[0,1]offset % 4 == 0约束可维可测分析
精度标准/性能标准
特性交叉分析
1. dtype 与私有 Kernel mode 交叉
RANDOM 覆盖全部 10 种 dtype;ZERO/ONE 仅对白名单组合进入公共任务链,其他
组合使用原端点算子。
0x3f80位模式 Select2. shape 与资源交叉
maskBytes <= 16 KiBmaskBytes > 16 KiB3. view、inplace 与 mode 交叉
4. 架构与接口交叉
验证方法与判定口径
<5%,当前 alias 融合方案已达标1. 功能与精度验证
0<p<1{0,1}随机路径使用固定 seed/offset 与原 DSA 链逐元素对拍,并使用统计检查验证分布:
2. 内存、性能与任务链验证
内存峰值按相同 shape、dtype、概率和 API 语义对比完整 Device 生命周期。
RANDOM 的 HBM 模型只保留
y_private、公共out/selfRef和必要的 view 适配;packed mask 与
y_private共享 storage,UB 不计入 Device HBM 峰值。性能比较以完整公共
aclnnBernoulli为单位,新旧路径的 Device 任务如下:BernoulliBernoulliBernoulli、DropoutDoMaskBernoulliBernoulli、DropoutDoMask兼容性分析
Bernoulli;不使用带前后缀的名称别名可维护性分析
职责拆分如下:
维护原则:
三者必须同步。
稳定性设计
prob越界或offset非法CheckProb和CheckOffsetint64_t二、价值/作用 (必填)
优化内存
三、设计方案 (必填)
3.1 使能方式(涉及哪些框架:如Aclnn直调、Pytorch训练等)
3.2 总体设计
3.2.1 算子支持的数据类型
3.2.2 host侧设计
3.2.3 kernel侧设计
3.3 支持硬件
3.4 算子约束限制
💡 备注(选填)