尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ONNX 6 位浮点格式详解:FLOAT6E2M3 与 FLOAT6E3M2 的位布局、Cast 语义与打包实现

ONNX 6 位浮点格式详解:FLOAT6E2M3 与 FLOAT6E3M2 的位布局、Cast 语义与打包实现 ONNX 6 位浮点格式详解FLOAT6E2M3 与 FLOAT6E3M2 的位布局、Cast 语义与打包实现【免费下载链接】onnxOpen standard for machine learning interoperability项目地址: https://gitcode.com/gh_mirrors/onn/onnx本指南围绕 ONNX 在onnx1.23.0引入的两种 6 位浮点数据类型FLOAT6E2M3与FLOAT6E3M2系统讲解其位布局与数值范围、向上/向下转换Cast的舍入与饱和语义以及 LSB-first 的 6 位打包/解包存储格式。读者读完本文将掌握这两种类型的可表示数值表、转换行为边界并能结合源码理解它们在 TensorProto 序列化、numpy_helper 解包与 ReferenceEvaluator 求值中的完整实现链路。背景面向推理与训练的低精度浮点6 位浮点格式源自 Open Compute Project (OCP) Microscaling Formats (MX) v1.0 规范 同源的低精度浮点体系其引入目的是在模型推理与训练中进一步降低精度与存储开销。与更常见的 8 位、4 位浮点类似6 位格式在保持足够动态范围的前提下用更少的比特位表示数值从而减少显存占用与带宽压力。基于该规范onnx1.23.0在 TensorProto 数据类型枚举 中新增了两个类型用于支持一组受限的算子FLOAT6E2M31 位符号sign、2 位指数exp、3 位尾数mant枚举值为 27FLOAT6E3M21 位符号、3 位指数、2 位尾数枚举值为 28。对应的 proto 注释明确写出两种类型的位划分方式同时 data_type_utils.cc 为它们注册了字符串别名float6e2m3与float6e3m2供类型字符串解析使用。E2M3 与 E3M2位布局与数值范围两种类型的核心差异在于指数位与尾数位的权衡E2M3 牺牲指数范围换取更高精度E3M2 则相反。下表汇总了原文档给出的完整规格属性FLOAT6E2M3FLOAT6E3M2位划分sign:1 exp:2 mant:3sign:1 exp:3 mant:2指数偏置Exponent bias13无穷Infinities无无NaN无无零/-00x00 / 0x20/-00x00 / 0x20最大规范化值1.111 × 2² 7.51.11 × 2⁴ 28最小规范化值1.000 × 2⁰ 11.00 × 2⁻² 0.25最小非规范化值denorm0.001 × 2⁰ 0.1250.01 × 2⁻² 0.0625可以读出几个关键特性对称的零表示两种格式的 0 与 -0 分别编码为0x00与0x20符号位为最高位即0x00 | (1 5)。无 Inf / NaN与 FLOAT4E2M1 相同6 位格式不保留特殊值编码所有 6 位组合都用于表示有限数值这也直接影响了向下转换时的处理策略见下一节。动态范围差异E3M2 可表示的最大值达到 28、最小值denorm低至 0.0625动态范围更大E2M3 最大仅 7.5、最小 denorm 为 0.125但尾数多 1 位相对精度更高。Python 侧类型映射表_mapping.py将这两个枚举值分别映射到ml_dtypes提供的float6_e2m3fn与float6_e3m2fnNumPy dtypefn后缀表示 finite-only即不含 Inf/NaN与上表一致这也是 ONNX Python 绑定中数值运算与转换的实际载体。Cast 语义向上精确、向下 RNE 饱和原文档对 6 位浮点与其他类型之间的转换给出了明确的语义约定Upcasting升位转换精确从 FLOAT6 转为 float32、float16、bfloat16、float8 等更高精度类型时由于目标类型总能精确表示源类型的所有数值转换是精确无损的。Downcasting降位转换采用 RNE 饱和从 float32 等类型转回 FLOAT6 时按舍入到最近偶数Round to Nearest Even, RNE取整并对超出表示范围的数值执行饱和saturate即截断到最大可表示值而不是产生 Inf。原文档给出了如下具体示例以 sattrue 的饱和路径为例float32FLOAT6E2M3sattrueFLOAT6E3M2sattrue25.07.5饱和24.0舍入-0.0-0.0-0.0inf7.5饱和28.0饱和nan-0.0未指定与 FLOAT4E2M1 的 cast 行为一致不饱和-0.0未指定与 FLOAT4E2M1 的 cast 行为一致不饱和示例中几个值得注意的细节25.0 对 E3M2 转成 24.0 而非 28.0是因为 RNE 舍入在 24 与 28 之间选择了更近的偶数倍数对应可表示值网格上最接近的数值。inf一律饱和到各自的最大值7.5 / 28.0。nan的结果在原文档中明确标注为未指定unspecified且刻意与 FLOAT4E2M1 的 cast 行为保持一致——即转换为 -0.0 而不是饱和到最大值。这一行为差异在实现与测试中均被保留属于格式规范中未强制约束的部分用户不应依赖其作为稳定契约。测试方面tests/python/fp6_test.py中的test_cast_fp6_e2m3/test_cast_fp6_e3m2用ReferenceEvaluator构造Cast(toFLOAT6E2M3/FLOAT6E3M2)模型将 float32 输入与ml_dtypes直接转换结果逐位比对并额外用np.signbit校验 -0.0 的符号位不被规范化丢失防止回归到手工位运算的错误实现。Packing 与 UnpackingLSB-first 的 6 位连续位流存储层面6 位值并不按每值一字节存放而是把连续的 6 位编码紧凑地打包进连续的字节流打包顺序为LSB-first最低位优先即第一个元素的 6 位码占据第一个字节的低 6 位后续元素依次向高位和后续字节延伸N个元素所需的载荷字节数为ceil(6N/8)最后一个字节中未使用的高位以零填充zero-padded。这一约定与 4 位ceil(N/2)字节和 2 位ceil(N/4)字节格式的紧凑位流思路一脉相承但 6 不是 8 的因数无法像 4/2 位那样整字节对齐因此采用 4 个 6 位值恰好拼成 3 个字节4×6 24 bit 3 byte的4 转 3打包策略。源码中的打包实现onnx/numpy_helper.py中的_pack_6bit展示了这一策略def _pack_6bit(values): flat values.astype(np.uint8, copyFalse).ravel() 0x3F # 只保留低 6 位 n flat.size packed_size math.ceil(n * 6 / 8) # 载荷字节数 pad -n % 4 # 不足 4 的倍数则补零 ... # 每 4 个 6 位值 v0..v3 打包进 3 字节 packed[:, 0] v0 | ((v1 0x03) 6) packed[:, 1] (v1 2) | ((v2 0x0F) 4) packed[:, 2] (v2 4) | (v3 2)其位划分如下字节 0v0全部 6 位低 6 位v1的低 2 位第 6-7 位字节 1v1的高 4 位低 4 位v2的低 4 位第 4-7 位字节 2v2的高 2 位低 2 位v3全部 6 位第 2-7 位。对应地_unpack_6bit按同样的分组从 3 字节组中还原出 4 个 6 位码并在输入字节数小于声明形状所需的最少字节数ceil(6N/8)时抛出ValueError防止截断的 raw_data 被静默解出错误形状。序列化路径的选择在 TensorProto 的raw_data与int32_data双路径 设计中FLOAT6 的两种表示规则如下使用raw_data原始字节流时内容始终是上述紧凑打包的 6 位流使用int32_data每元素一个 int32时每个条目只存放一个 6 位编码仅低 6 位有意义numpy_helper.to_array在解包时会对条目执行 0x3F掩码后再按位重新解释与打包路径的掩码行为保持一致。由于ceil(6N/8)的字节数对某些小张量例如 3 个元素恰好也是 3 字节与每值一字节的字节数相同to_array特意不在两种表示之间做字节数嗅探而是固定把 FLOAT6 的raw_data视为打包位流处理——这一点在源码注释中有明确说明也提示开发者序列化时须遵守raw 即打包的单一约定。构建与验证从创建张量到端到端求值结合测试文件tests/python/fp6_test.py可以把整个使用链路串起来。创建 6 位常量张量import math import numpy as np import ml_dtypes import onnx from onnx import helper from onnx.numpy_helper import to_array vals np.array([0.0, 0.5, 1.0, 2.0], dtypenp.float32) tp helper.make_tensor( t, onnx.TensorProto.FLOAT6E2M3, # 或 onnx.TensorProto.FLOAT6E3M2 [4], vals.astype(ml_dtypes.float6_e2m3fn), rawTrue, # 以打包位流写入 raw_data ) assert len(tp.raw_data) 3 # ceil(4*6/8) 3 字节 back to_array(tp) np.testing.assert_array_equal(back.astype(np.float32), vals)test_pack_unpack_roundtrip_even/test_pack_unpack_roundtrip_odd分别验证了元素数为 4恰好 3 字节与 3需要补齐高位零仍为 3 字节两种情形下的往返一致性test_to_array_raises_on_truncated_raw_data则确认了截断载荷会被to_array拒绝。通过 Cast 与量化算子使用FLOAT6 支持一组受限的算子核心入口是Cast以及QuantizeLinear/DequantizeLinear量化通路。测试test_qdq_fp6_paths构造了一个QuantizeLinear(saturate1, output_dtypeFLOAT6E2M3/FLOAT6E3M2) → DequantizeLinear的模型验证量化-反量化结果与直接ml_dtypes转换完全一致且 -0.0 符号位得以保留。m helper.make_model(helper.make_graph( [helper.make_node(QuantizeLinear, [X, S], [Q], saturate1, output_dtypeonnx.TensorProto.FLOAT6E2M3), helper.make_node(DequantizeLinear, [Q, S], [Y], output_dtypeonnx.TensorProto.FLOAT)], g, [helper.make_tensor_value_info(X, onnx.TensorProto.FLOAT, [6]), helper.make_tensor_value_info(S, onnx.TensorProto.FLOAT, [])], [helper.make_tensor_value_info(Y, onnx.TensorProto.FLOAT, [6])], )) ref onnx.reference.ReferenceEvaluator(m) (y,) ref.run(None, {X: np.array([0.0, -0.0, 0.125, 1.0, 8.0, 1000.0], dtypenp.float32), S: np.array(1.0, dtypenp.float32)})注意QuantizeLinear的saturate1与上文Downcasting RNE with saturation的语义直接对应——饱和行为正是通过量化算子的 saturate 属性暴露给用户的。适用边界与注意事项算子覆盖有限FLOAT6 仅支持一组受限算子Cast、量化通路等并非所有算子都接受这两种类型使用前应通过onnx.checker校验模型tests/python/fp6_test.py中每个模型均先经过onnx.checker.check_model再求值可作为正确用法参考。依赖 ml_dtypesPython 侧的类型映射依赖ml_dtypes提供的float6_e2m3fn/float6_e3m2fndtype见 onnx/_mapping.py使用make_tensor等 API 前需确保环境中已安装该依赖。NaN 行为未指定如原文档所述float32 的nan向下转换为 -0.0 属于未指定行为与 FLOAT4E2M1 一致、不做饱和不应作为稳定契约依赖。raw_data 唯一打包约定FLOAT6 的raw_data恒为 LSB-first 的 6 位紧凑位流to_array不做打包/逐值嗅探手工构造TensorProto时必须保证载荷长度为ceil(6N/8)。延伸阅读ONNX 技术细节索引 收录了同系列的 8 位、4 位、2 位浮点与整数格式文档float8、float4、int4、int2FLOAT6 的转换与打包语义与它们同源可对照阅读以理解 ONNX 低精度数据类型的完整演进脉络。【免费下载链接】onnxOpen standard for machine learning interoperability项目地址: https://gitcode.com/gh_mirrors/onn/onnx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表