- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
在昇腾 AI 处理器算子开发中,AscendC(asc-devkit)的 Reg 矢量计算(寄存器计算)接口以RegTensor为操作对象,通过一条指令完成一整组数据的搬运、计算与转换。要写出高效且行为可预期的算子,仅理解主接口(如Cast、LoadAlign、Duplicate)远远不够,还必须掌握一组贯穿所有 Reg 矢量计算接口的辅助数据类型(aux data types):PostLiteral、RegLayout、MaskMergeMode、SatMode与RoundMode。它们分别控制搬入/搬出时 Unified Buffer(UB)地址是否自动更新、位宽不匹配时窄元素在寄存器中的排布位置、mask 未选中元素如何写入、类型转换时的饱和行为以及舍入策略。本文以 aux_data_types 文档 为主体,结合仓库源码中CastTrait的构造与默认取值,逐项讲解这 5 个枚举的完整取值、语义、适用场景与组合用法,帮助你在编写 Reg 矢量计算算子时准确选参、避免踩坑。
一、辅助数据类型的定位:Reg 矢量计算接口的"控制旋钮"
Reg 矢量计算(寄存器计算)与传统向量计算的最大区别在于:数据以RegTensor形式整体驻留在向量寄存器中,通过MaskReg指定每次操作参与计算的元素(即 mask 有效位),一条指令即可完成批量运算。而PostLiteral、RegLayout、MaskMergeMode、SatMode、RoundMode这 5 个枚举,正是定义在这类接口背后的行为控制参数。
从源码可以清晰地看到它们的归属与命名空间。在 kernel_reg_compute_utils.h 中,RegLayout、SatMode、MaskMergeMode、PostLiteral均定义在AscendC::Reg命名空间下:
namespace Reg { enum class RegLayout { UNKNOWN = -1, ZERO, ONE, TWO, THREE }; enum class SatMode { UNKNOWN = -1, NO_SAT, SAT }; enum class MaskMergeMode { UNKNOWN, MERGING, ZEROING }; enum class PostLiteral { POST_MODE_NORMAL, POST_MODE_UPDATE }; }而RoundMode定义在 kernel_utils_mode.h 中,位于AscendC命名空间下,取值按架构有所扩展(后文详述)。这些枚举主要出现在两类场景中:
- 作为模板参数:如
PostLiteral直接作为LoadAlign、StoreAlign、DataCopy等搬数接口的模板参数,控制地址更新行为; - 作为
CastTrait结构体的成员:RegLayout、SatMode、MaskMergeMode、RoundMode四者组合成CastTrait,作为Cast等类型转换接口的模板参数整体传入,见 Cast 接口文档。
struct CastTrait { RegLayout layoutMode = RegLayout::UNKNOWN; SatMode satMode = SatMode::UNKNOWN; MaskMergeMode mrgMode = MaskMergeMode::UNKNOWN; RoundMode roundMode = RoundMode::UNKNOWN; };理解这 5 个枚举的语义,是读懂 Reg 矢量计算 API 调用示例(如 Cast 文档中的__simd_vf__内核示例)的前提。
二、PostLiteral:控制 UB 地址自动更新(post update)
PostLiteral用于控制是否开启 Unified Buffer(UB)地址自动更新功能,完整定义如下:
enum class PostLiteral { POST_MODE_NORMAL, POST_MODE_UPDATE };各取值语义如下表所示:
| 枚举值 | 描述 |
|---|---|
POST_MODE_NORMAL | 常规场景使用,UB 操作数地址不更新。连续非对齐搬入(LoadUnAlign)不支持POST_MODE_NORMAL模式。 |
POST_MODE_UPDATE | UB 地址同时作为输入和输出,每次调用会自动更新,适合在循环体内逐段搬数/存数。 |
从源码实现看,PostLiteral大量作为搬数接口的模板参数使用,且默认值即为POST_MODE_UPDATE。例如在 kernel_operator_common_impl.h 中,多个LoadAlign/StoreAlign重载均以PostLiteral postMode = PostLiteral::POST_MODE_UPDATE作为默认模板参数,并在函数体内通过if constexpr (postMode == PostLiteral::POST_MODE_UPDATE)或if (post == PostLiteral::POST_MODE_UPDATE)判断是否更新地址(见同文件第 725、744 行)。这解释了为什么在 Cast.md 的循环示例中,LoadAlign、StoreAlign都显式传入PostLiteral::POST_MODE_UPDATE——只有地址自动更新,才能在repeatTimes次循环中逐段推进读写指针。
实践要点:
- 在
for循环中连续搬数/存数,优先使用POST_MODE_UPDATE,避免手动维护地址偏移; - 若一次调用后地址需保持原样(例如同一寄存器反复作为输入参与多条指令),使用
POST_MODE_NORMAL; - 连续非对齐搬入(LoadUnAlign)仅支持
POST_MODE_UPDATE,使用POST_MODE_NORMAL会触发不支持错误。
三、RegLayout:位宽不匹配时的元素排布控制
3.1 为什么需要 RegLayout
Reg 矢量计算中,单条指令的计算量以位宽更大的数据类型为准。当源操作数与目的操作数位宽不同时(例如half转int32_t),窄位宽元素在寄存器中的排布方式会直接影响计算结果的位置。RegLayout正是用于控制"位宽小的元素在寄存器中的排布方式",完整定义如下:
enum class RegLayout { UNKNOWN = -1, ZERO, // 大转小(dst位宽小):结果位于dst组内第0个位宽小位置;小转大(src位宽小):选取src组内第0个位宽小元素 ONE, // 大转小(dst位宽小):结果位于dst组内第1个位宽小位置;小转大(src位宽小):选取src组内第1个位宽小元素 TWO, // 大转小(dst位宽小):结果位于dst组内第2个位宽小位置;小转大(src位宽小):选取src组内第2个位宽小元素 THREE // 大转小(dst位宽小):结果位于dst组内第3个位宽小位置;小转大(src位宽小):选取src组内第3个位宽小元素 };UNKNOWN(取值为 -1)表示该场景下RegLayout不生效(此时源、目的位宽一致,无排布歧义);ZERO/ONE/TWO/THREE则对应窄元素在"组"内的第 0、1、2、3 个位置。
3.2 四种位宽比场景
源操作数与目的操作数类型位宽不同时,可细分为四种情况。用户按实际场景选择对应的RegLayout,其中mask 的有效情况均以输入数据位宽为准。
场景一:源操作数与目的操作数位宽比为 1:2(小转大)
例如ExpSub接口输入为half(b16)、输出为float(b32)时,mask 每 2 位有效,RegLayout确定源操作数中每 2 个half数据中参与计算的数据的位置:
场景二:源操作数与目的操作数位宽比为 2:1(大转小)
例如Cast接口输入为float(b32)、输出为half(b16)时,mask 每 4 位有效,RegLayout确定目的操作数中每 2 个half数据中有效数据的位置:
场景三:源操作数与目的操作数位宽比为 1:4(小转大)
例如Cast接口输入为int8_t(b8)、输出为int32_t(b32)时,mask 每 1 位有效,RegLayout确定源操作数中每 4 个int8_t数据中参与计算的数据的位置:
场景四:源操作数与目的操作数位宽比为 4:1(大转小)
例如Cast接口输入为int32_t(b32)、输出为uint8_t(b8)时,mask 每 4 位有效,RegLayout确定目的操作数中每 4 个uint8_t数据中有效数据的位置:
3.3 RegLayout 排布伪代码(数值验证)
为精确描述Cast在不同RegLayout取值下的数据排布,原文档给出了可运行的 Python 伪代码。其中layout参数与枚举值的映射关系为:0 对应RegLayout::ZERO,1 对应RegLayout::ONE,2 对应RegLayout::TWO,3 对应RegLayout::THREE:
import numpy as np # MaskReg行为:MaskReg长度为256bit,每4bit最低位为1时选取对应位置的b32元素进行转换。 def cast_b32_to_b16(src, dst, layout): dst[:] = 0 dst[layout:128:2] = src[0:64].astype(np.int16) # MaskReg行为:MaskReg长度为256bit,每2bit最低位为1时选取对应位置的b16元素进行转换。 def cast_b16_to_b32(src, dst, layout): dst[0:64] = src[layout:128:2].astype(np.int32) # MaskReg行为:MaskReg长度为256bit,每4bit最低位为1时选取对应位置的b32元素进行转换。 def cast_b32_to_b8(src, dst, layout): dst[:] = 0 dst[layout:256:4] = src[0:64].astype(np.uint8) # MaskReg行为:MaskReg长度为256bit,每1bit为1时选取对应位置的b8元素进行转换。 def cast_b8_to_b32(src, dst, layout): dst[0:64] = src[layout:256:4].astype(np.int32) # MaskReg行为:MaskReg长度为256bit,每4bit最低位为1时选取对应位置连续的两个b16元素进行转换。 def cast_b16_to_b4(src, dst, layout): dst[:] = 0 dst[2*layout : 512 : 8] = src[0:128:2].astype(np.uint8) dst[2*layout + 1: 512 : 8] = src[1:128:2].astype(np.uint8) # MaskReg行为:MaskReg长度为256bit,每1bit为1时选取对应位置连续的两个b4元素进行转换。 def cast_b4_to_b16(src, dst, layout): dst[0:128:2] = src[2*layout : 512 : 8].astype(np.int16) dst[1:128:2] = src[2*layout + 1: 512 : 8].astype(np.int16)解读这份伪代码,可以提炼出三条规律:
- 大转小(如 b32→b16、b32→b8)时,窄元素只落在"组"内
layout指定的位置,其余位置补 0,即dst[layout::组宽] = src 的前半段; - 小转大(如 b16→b32、b8→b32)时,从源寄存器中按
layout指定的位置抽取窄元素再扩展位宽,即dst[0:64] = src[layout::组宽]; - 极窄类型(b4)与 b16 互转时,指令以每 2 个元素为一对进行读写,
layout同时决定一对元素的首位置(2*layout与2*layout+1)。
实践要点:RegLayout并非"随便选一个都行",每种src dtype → dst dtype组合支持的layoutMode取值集合是受限的。以 Cast 为例,float→int64_t仅支持ZERO/ONE,而float→hifloat8_t、int8_t→int32_t、int32_t→uint8_t等支持全部ZERO/ONE/TWO/THREE,位宽相同时(如half→bfloat16_t、int16_t→half)则必须使用RegLayout::UNKNOWN。选错取值会导致编译报错或结果位置错误,具体支持范围请查阅 Cast.md 的表 6~表 9。
四、MaskMergeMode:mask 未选中元素的写入模式
MaskMergeMode用于指定写入寄存器数据模式,即当MaskReg的某些位未选中时,目的寄存器对应位置的元素如何处理,完整定义如下:
enum class MaskMergeMode { UNKNOWN, MERGING, ZEROING };各取值语义:
| 枚举值 | 描述 |
|---|---|
UNKNOWN | 当转换类型不支持MaskMergeMode时,选择该模式。 |
ZEROING | mask 未选择的元素在 dst 中置零。 |
MERGING | mask 未选择的元素对应 dst 元素中保留 dst 原值。 |
以Duplicate接口为例,ZEROING与MERGING两种模式的差异如下图所示:
实践要点:
- 需要干净输出(dst 中未被 mask 覆盖的位置不残留旧数据)时,使用
ZEROING; - 需要累积/合并写入(未被 mask 覆盖的位置保持 dst 原有值,便于多段合并)时,使用
MERGING; - 从源码的默认
CastTrait看,绝大多数类型转换的默认mrgMode为MaskMergeMode::ZEROING(见 kernel_operator_vec_vconv_impl.h),这保证转换输出是确定性的;MERGING需要用户显式指定。
五、SatMode:Cast 类型转换的饱和与不饱和模式
SatMode用于设置 Cast 类型转换的饱和与不饱和模式,完整定义如下:
enum class SatMode { UNKNOWN = -1, NO_SAT, SAT };各取值语义:
| 枚举值 | 描述 |
|---|---|
UNKNOWN | 当转换类型不支持SatMode时,选择该模式。 |
NO_SAT | 不饱和模式。 |
SAT | 饱和模式。 |
饱和模式在不同转换场景下的行为差异很大,结合 Cast.md 的表 4 可以归纳:
| 场景 | 不饱和模式(NO_SAT) | 饱和模式(SAT) |
|---|---|---|
| 浮点转整数 | 输入超过输出类型最值时,结果被截断为目标格式数据宽度(保留最低有效位),例如输入 half 值 257 输出 uint8_t 值 1;输入 ±inf 返回输出类型对应最值;输入 nan 返回 0。 | 输入超过输出类型最值时,返回输出类型对应最值,例如输入 half 值 257 输出 uint8_t 值 255;输入 -inf 输出 uint8_t 值 0;输入 nan 返回 0。 |
| 浮点转浮点 | 输入 nan 输出 nan;输入 ±inf 输出 ±inf。 | 输入 nan 输出 0;输入超过输出类型最值时返回对应最值。 |
| 整数转浮点 | 不支持不饱和模式。 | 输入超过输出类型最值时返回对应最值;该场景默认饱和模式,无需配置。 |
| 整数转整数 | 输入会截断为目标数据宽度,例如输入 int32_t 值 256 输出 uint8_t 值 0。 | 输入超出目标数据范围,饱和为目标数据最值。 |
配置前提(重要):从 Cast.md 可知,使能SatMode生效需与SetCtrlSpr配合使用,且SetCtrlSpr需在SIMD_VF外调用。饱和模式支持"全局使能"与"单指令生效"两种配置路径:
| 全局使能位 | 控制位 | 功能描述 |
|---|---|---|
CTRL[60] = 1'b0 | Reg 矢量计算 Cast API 的 trait 模板参数中satMode设为SatMode::NO_SAT | 单指令非饱和模式 |
CTRL[60] = 1'b0 | Reg 矢量计算 Cast API 的 trait 模板参数中satMode设为SatMode::SAT | 单指令饱和模式 |
CTRL[60] = 1'b1 | CTRL[48] = 1'b1 | 全局非饱和模式(浮点数计算和浮点数精度转换) |
CTRL[60] = 1'b1 | CTRL[48] = 1'b0 | 全局饱和模式(浮点数计算和浮点数精度转换) |
CTRL[60] = 1'b1 | CTRL[59] = 1'b1 | 全局非饱和模式(浮点数转整数或整数转整数时的精度转换) |
CTRL[60] = 1'b1 | CTRL[59] = 1'b0 | 全局饱和模式(浮点数转整数或整数转整数时的精度转换) |
实践要点:
- 需要避免数值回绕(如数据归一化后转定点)时使用
SAT; - 需要保留低位截断行为(模拟硬件截断语义)时使用
NO_SAT; - 某些转换组合不支持
SatMode(此时取值须为SatMode::UNKNOWN),例如浮点转浮点中的hifloat8_t→half、fp8_e4m3fn_t→float、half→float,以及整数转浮点的大部分组合(见 Cast.md 表 6~表 9)。
六、RoundMode:舍入模式详解
RoundMode用于设置舍入模式,是 5 个辅助类型中取值最多、规则最细的一个,完整定义如下:
enum class RoundMode { CAST_NONE = 0, CAST_RINT, CAST_FLOOR, CAST_CEIL, CAST_ROUND, CAST_TRUNC, CAST_ODD, CAST_HYBRID };各取值语义("待舍入部分"指被丢弃的低位部分):
| 枚举值 | 语义与判定规则 |
|---|---|
CAST_NONE | 当转换过程存在精度损失时,按CAST_RINT模式处理;当不存在精度损失时,不进行舍入。 |
CAST_RINT | 向最近的偶数舍入(round to nearest even)。待舍入部分首位为 0 则不进位;首位为 1 且后续位不全为 0 则进位;首位为 1 且后续位全为 0 时,看非舍入部分末位:末位为 0 不进位,末位为 1 进位。 |
CAST_FLOOR | 向负无穷大方向舍入。符号位 S 为 0(正数)不进位;S 为 1(负数)时,待舍入部分全为 0 不进位,否则进位。 |
CAST_CEIL | 向正无穷大方向舍入。符号位 S 为 1(负数)不进位;S 为 0(正数)时,待舍入部分全为 0 不进位,否则进位。 |
CAST_ROUND | 四舍五入(round half away from zero)。待舍入部分首位为 0 不进位,否则进位。 |
CAST_TRUNC | 直接丢弃待舍入部分(向零截断)。 |
CAST_ODD | 向最近的奇数舍入(Von Neumann 舍入)。待舍入部分全为 0 不进位;不全为 0 时,非舍入部分末位为 1 不进位,末位为 0 则进位。 |
CAST_HYBRID | 随机舍入,目前特指输出结果是hifloat8_t数据类型时的随机舍入。 |
源码补充:在 kernel_utils_mode.h 中,RoundMode的实际定义比文档更完整——当目标架构为__NPU_ARCH__3510/5102/5162/3003/3113/5101/5161/5165/5163 或__ASC_NPU_HOST__时,还会追加CAST_HYBRID、CAST_EVEN、CAST_ZERO三个枚举(CAST_HYBRID即文档中的随机舍入),并额外定义UNKNOWN = 0xFF作为无效占位值。因此不同架构下RoundMode的可用取值集合不同,跨架构移植算子时需注意这一点。
实践要点:
- 精度关键场景默认首选
CAST_RINT(银行家舍入,无系统性偏差),这也是仓库中大量CastTrait默认值的选择(见 kernel_operator_vec_binary_continuous_impl.h 等文件中的RoundMode::CAST_RINT默认组合); - 语义明确的数据处理(如图像像素截断)用
CAST_TRUNC,需要向 ±∞ 收敛用CAST_FLOOR/CAST_CEIL; CAST_HYBRID仅特定数据类型(hifloat8_t输出)与特定架构支持;- 同
SatMode一样,每种src→dst组合支持的roundMode取值受限,且舍入模式仅在可能导致精度损失且支持该舍入模式的转换中生效,具体请对照 Cast.md 表 6~表 9。
七、组合使用:CastTrait 与真实调用示例
7.1 源码中的默认 CastTrait
在 kernel_operator_vec_vconv_impl.h 中可以看到大量针对不同src→dst组合预置的CastTrait默认值,例如:
// 大转小且需饱和的典型组合 {Reg::RegLayout::ZERO, Reg::SatMode::SAT, Reg::MaskMergeMode::ZEROING, RoundMode::CAST_RINT} // 位宽一致、无需饱和的组合 {Reg::RegLayout::UNKNOWN, Reg::SatMode::UNKNOWN, Reg::MaskMergeMode::ZEROING, RoundMode::CAST_RINT}这印证了 5 个辅助类型的"组合拳"用法:RegLayout负责"位置",SatMode负责"范围",MaskMergeMode负责"未选中元素",RoundMode负责"精度取舍"。
7.2 完整调用示例(大转小)
以下示例取自 Cast.md 的场景 2(float→int16_t),综合展示了PostLiteral、RegLayout、SatMode、MaskMergeMode、RoundMode的完整配合:
__simd_vf__ inline void CastVFF322S16(__ubuf__ float* xAddr, __ubuf__ int16_t* yAddr, uint32_t repeatTimes, uint16_t oneRepeatSize) { AscendC::Reg::MaskReg mask = AscendC::Reg::CreateMask<float, AscendC::Reg::MaskPattern::ALL>(); AscendC::Reg::RegTensor<float> xReg; AscendC::Reg::RegTensor<int16_t> yReg; static constexpr AscendC::Reg::CastTrait castTrait = { AscendC::Reg::RegLayout::ZERO, // float(b32)→int16_t(b16),窄元素落在组内第0个位置 AscendC::Reg::SatMode::SAT, // 饱和模式,超出 int16_t 范围时取最值 AscendC::Reg::MaskMergeMode::ZEROING,// mask 未选中元素在 dst 中置零 AscendC::RoundMode::CAST_ROUND // 四舍五入 }; for (uint16_t i = 0; i < repeatTimes; ++i) { AscendC::Reg::LoadAlign<float, AscendC::Reg::PostLiteral::POST_MODE_UPDATE>(xReg, xAddr, oneRepeatSize); AscendC::Reg::Cast<int16_t, float, castTrait>(yReg, xReg, mask); AscendC::Reg::StoreAlign<int16_t, AscendC::Reg::PostLiteral::POST_MODE_UPDATE, AscendC::Reg::StoreDist::DIST_PACK_B32>(yAddr, yReg, oneRepeatSize, mask); } }7.3 参数选择检查清单
编写 Reg 矢量计算内核时,可按下表逐项自查:
| 枚举 | 选参前必须确认的问题 |
|---|---|
PostLiteral | 搬数是否在循环内连续推进?非对齐搬入是否误用了POST_MODE_NORMAL? |
RegLayout | 源/目的位宽比是 1:2、2:1、1:4 还是 4:1?该src→dst组合支持哪些layoutMode? |
MaskMergeMode | 未选中元素需要置零还是保留原值?该组合是否支持该模式? |
SatMode | 是否需要防溢出饱和?是否需要配合SetCtrlSpr(须在SIMD_VF外调用)?该组合是否支持SatMode? |
RoundMode | 舍入语义是否符合业务(偶数舍入/四舍五入/截断/±∞/奇数/随机)?目标架构是否支持该取值? |
八、总结
PostLiteral、RegLayout、MaskMergeMode、SatMode、RoundMode是 asc-devkit Reg 矢量计算体系中五个不可或缺的辅助数据类型:
PostLiteral决定搬数接口的 UB 地址是否随调用自动更新,是循环流水搬数的关键;RegLayout决定位宽不匹配时窄元素在寄存器"组"内的位置,其取值受src→dst组合严格约束;MaskMergeMode决定 mask 未选中元素的写入方式(置零或保留原值);SatMode决定类型转换的饱和行为,且需与SetCtrlSpr配合才能生效;RoundMode提供 8 种(部分架构更多)舍入模式,精度关键场景推荐CAST_RINT。
这些枚举的具体支持范围均以每种src→dst数据类型组合为准,实际开发中请对照 Cast.md 中的 CastTrait 取值表(表 6~表 9),并结合 kernel_reg_compute_utils.h 与 kernel_utils_mode.h 中的源码定义确认当前目标架构的可用取值,方能写出既正确又高效的 Reg 矢量计算算子。
- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
相关推荐
asc-devkit TensorTrait 实战指南:从矢量计算到矩阵计算的完整样例解析
asc devkit TensorTrait 实战指南:从矢量计算到矩阵计算的完整样例解析 本文以 asc devkit 官方文档《更多样例》( more_ex
人工智能深度学习算子库CANNAscendRope 免费人脸交换工具 3 步从安装到导出第一条视频:完整指南
Rope 免费人脸交换工具 3 步从安装到导出第一条视频:完整指南 Rope 是一个免费的开源 GUI 换脸工具,内建 insightface 的 inswap
asc-devkit 算子开发实战:GlobalTensor 全局张量 API 全解析
asc devkit 算子开发实战:GlobalTensor 全局张量 API 全解析 导读 GlobalTensor 是 CANN asc devkit(昇腾
人工智能深度学习算子库CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考