前段时间我在论坛分享了如何为 QEMU softfloat 添加适用于神经网络的浮点精度(fp8/tf32),正好关注到 RISC-V 上游已经有相关的指令集扩展提案 Zvfba 和 Zvfofp8min。这篇文章我们简单讨论一下。
主要增加了两个扩展:
-
Zvfbfa :增加了更完整的 BF16 向量计算支持;
-
Zvfofp8min :为开放计算项目 OFP8 规范中定义的两种 8 位浮点格式(OFP8 E4M3 和 OFP8 E5M2)提供了基本支持。
下文将依次介绍相关内容,内容基于官方文档的机器翻译,并经过人工润色优化。
Zvfbfa:更完整的 BF16 向量计算支持
Zvfbfa 扩展需要依赖 Zve32f 和 Zfbfmin 扩展(与 Zvfbfwma 和 Zvfbfmin 兼容,但并不依赖它们)。
Zvfbfa 在 vtype 控制状态寄存器的第 8 位添加了一个 1 位字段 altfmt 。尝试设置 altfmt = 1 且 SEW ≥ 32 的情况被保留。
注意:
推荐的汇编语法设置
altfmt= 1 的方式是在 SEW 设置后附加alt标记,例如vsetvli a0, a1, e16alt, m1, ta, ma。当选择了
altfmt和 SEW 的保留组合时,实现应在vtype中设置vill
当 altfmt=0 时,硬件的行为如同未实现 Zvfbfa 扩展。
当 altfmt=1 且 SEW=8 时,所有向量浮点指令均变为保留指令,但以下指令除外——这些指令被重新定义为对原本使用 FP16 格式的任何操作数改用 BF16 格式:
vfwcvt.f.x[u].v
vfncvt.x[u].f.w
vfncvt.rtz.x[u].f.w
当 altfmt = 1 且 SEW = 16 时,除以下指令外,所有向量浮点指令均被保留,以下指令被重新定义为对原本使用 FP16 格式的任何操作数改用 BF16 格式:
vfadd.v[vf]
vfsub.v[vf]
vfmin.v[vf]
vfmax.v[vf]
vfsgnj.v[vf] ††
vfsgnjn.v[vf] ††
vfsgnjx.v[vf] ††
vfslide1up.vf ††
vfslide1down.vf ††
vfmv.v.f ††
vfmerge.vfm ††
vmfeq.v[vf]
vmfle.v[vf]
vmflt.v[vf]
vmfne.v[vf]
vmfgt.vf
vmfge.vf
vfmul.v[vf]
vfrsub.vf
vfmadd.v[vf]
vfnmadd.v[vf]
vfmsub.v[vf]
vfnmsub.v[vf]
vfmacc.v[vf]
vfnmacc.v[vf]
vfmsac.v[vf]
vfnmsac.v[vf]
vfwadd.v[vf]
vfwsub.v[vf]
vfwadd.w[vf]
vfwsub.w[vf]
vfwmul.v[vf]
vfwmacc.v[vf](语义与 vfwmaccbf16.v[vf] 相同)
vfwnmacc.v[vf]
vfwmsac.v[vf]
vfwnmsac.v[vf]
vfmv.s.f ††
vfmv.f.s †
vfwcvt.f.f.v(语义与 vfwcvtbf16.f.f.v 相同)
vfncvt.f.f.w(语义与 vfncvtbf16.f.f.w 相同)
vfncvt.rod.f.f.w
vfrsqrt7.v
vfrec7.v
vfclass.v
vfwmaccbf16.v[vf] †
vfwcvtbf16.f.f.v †
vfncvtbf16.f.f.w †
标记有 † 的指令无论 altfmt 如何都具有相同的语义。 标记有††的指令仅在以下方面有所不同:对于未正确 NaN 装箱的 f 寄存器操作数,必须替换为 BF16 标准 NaN 而非 FP16 标准 NaN。
注意:被排除的操作包括除法、平方根、归约运算以及与超过 8 位整数的相互转换。这些操作可通过与 FP32 格式的相互转换来实现。
对于 vfrec7.v 指令,部分大于 2126 的输入会产生次正规结果,这些结果无法用 BF16 有限的精度精确表示。此类结果会向零方向舍入。
Zvfofp8min: 对 FP8 的基本支持
Zvfofp8min 扩展需要依赖 Zve32f 扩展。
在某些应用中,OFP8 格式被直接用于表示数值。 而在其他应用中,它们被用作块浮点格式的组成部分,详见OCP 微缩放规范 。
本扩展定义的转换指令支持这两种使用场景。软件可将 OFP8 值转换为 BF16 或 FP32,然后在更高精度格式中应用缩放因子,例如使用
vfmul.vf指令。若未来能定量证明需求,向量或矩阵扩展可能会直接提供对微缩放的支持。
目前仅提议对 OFP8 格式提供向量支持,因为这些格式几乎完全用于高度数据并行的计算场景。
E4M3 和 E5M2 两种格式的规范 NaN 均为 0x7f。
OFP8 to BF16 conversion instructions
现有指令 vfwcvtbf16.f.f.v 用于将 OFP8 格式转换为 BF16。当 SEW=8 且 altfmt=0 时,该指令将 vs2 中的 OFP8 E4M3 值向量转换为 BF16 值向量,结果写入 vd。不进行舍入操作,也不设置浮点异常标志。当 SEW=8 且 altfmt=1 时,该指令将 vs2 视为 OFP8 E5M2 值向量,其他行为完全相同。
注意:
通过先转换为 BF16 格式,再利用 Zvfbfmin、Zvfbfa、Zvfhmin 和 Zve32f 扩展中的现有指令,可实现向 FP32、FP16 及整数格式的转换。从 OFP8 直接转换为 FP32 并不常见,因为 OFP8 值通常用作乘数。乘法运算本身可在需要时扩展结果精度。
两种 OFP8 格式间的转换虽不常见,但可通过先转换为 BF16 格式,再使用下节定义的指令之一来实现。
BF16 to OFP8 conversion instructions
现有 vfncvtbf16.f.f.w 指令用于从 BF16 转换为 OFP8 格式。当 SEW=8 且 altfmt=0 时,该指令将向量中的 BF16 值 将 vs2 转换为 OFP8 E4M3 格式的向量,结果写入 vd。由于 E4M3 无法表示无穷大,无限结果会被转换为标准 NaN。
当 SEW = 8 且 altfmt = 1 时,指令会转换为 OFP8 E5M2 格式。不过在这种情况下,无限结果是可表示的。两种情况下,结果都使用 frm 寄存器中的动态舍入模式进行舍入,浮点异常会在 fflags 中报告。 与其他浮点转换操作相同的方式处理寄存器。
OFP8 标准还定义了饱和转换操作,其中无限结果会被转换为相同符号下最大幅度的有限值。新增指令 vfncvtbf16.sat.f.f.w 实现了这一功能。
该指令在 SEW = 8 且 altfmt = 0 时,以及 SEW = 8 且 altfmt = 1 时均适用,其功能与 vfncvtbf16.f.f.w 相同,但具备饱和特性。其编码方式与 vfncvtbf16.f.f.w 一致,只是将 vs1 设置为 11111。
注意:从 8 位整数到 OFP8 的转换首先通过 Zvfbfa 扩展中的指令转换为 BF16,然后使用本节定义的指令完成。
FP32 to OFP8 conversion instructions
新增了一条从 FP32 转换为 OFP8 格式的指令 vfncvt.f.f.q。当 SEW=8 且 altfmt=0 时,该指令将 vs2(EMUL = 4×LMUL)中的 FP32 值向量转换为 OFP8 E4M3 值向量,结果写入 vd(EMUL=LMUL)。
由于 E4M3 无法表示无穷大,无限结果将转换为规范 NaN。当 SEW = 8 且 altfmt = 1 时,指令转换为 OFP8 E5M2 格式。此时无限结果是可表示的。两种情况下,结果都使用 frm 寄存器中的动态舍入模式进行舍入,浮点异常在 fflags 中报告。 与其他浮点转换操作相同的寄存器。 vfncvt.f.f.q 的编码方式与 vfncvt.f.f.w 相同,但 vs1 设置为 11001。
另一条新指令 vfncvt.sat.f.f.q 被定义为用于 SEW = 8 且 altfmt = 0 的情况,以及 SEW = 8 且 altfmt = 1 的情况,执行与 vfncvt.f.f.q 指令,但带有饱和处理(即无限结果会被 转换为相同符号的最大有限值。 vfncvt.sat.f.f.q 的编码方式与 vfncvt.f.f.w 相同,但 vs1 字段设为 11011。
注意:
另一种设计方案是先将 FP32 转换为 BF16,采用奇进偶舍的舍入方式,然后再使用前文定义的指令转换为 OFP8。但考虑到 FP32 到 OFP8 的转换场景足够普遍,因此直接转换的设计更为合理。
从 FP16 和 16 位整数格式的转换首先通过使用 Zvfhmin 和 Zve32f 扩展中的指令转换为 FP32,然后使用本节定义的指令完成。
