ONNX 4 位整数类型(UINT4 / INT4)完全指南:低位量化、打包格式与算子支持
2026/9/20 9:14:59 网站建设 项目流程

ONNX 4 位整数类型(UINT4 / INT4)完全指南:低位量化、打包格式与算子支持

【免费下载链接】onnxOpen standard for machine learning interoperability项目地址: https://gitcode.com/gh_mirrors/onn/onnx

导读

本文围绕 ONNX 标准中自onnx==1.17.0引入的两种 4 位整数数据类型——UINT4(无符号,取值范围 [0, 15])与INT4(有符号补码表示,取值范围 [-8, 7])展开,系统讲解它们在 LLM 权重量化(weight-only quantization)与激活量化场景中的设计动机、与 Cast 算子的转换规则、字节内打包/解包布局以及底层实现细节。读完本文,你将掌握 4 位类型的取值范围与位布局、pack/unpack数学公式、ceil(N/2)存储大小的由来,并能结合源码理解 ONNX 在 TensorProto、Checker、Python helper 与参考实现中的完整支持链路,从而在自己的量化模型流程中正确构造与校验 4 位张量。

为什么需要 4 位整数:背景与动机

低位整数在 LLM 压缩中的价值

4 位整数的引入直接源于 2023 年大规模语言模型(LLM)的量化研究浪潮。虽然 4 位表示的数值范围非常有限,但只要精心选择缩放参数(scaling parameters),就能在权重量化场景下获得很好的精度,部分场景下甚至可以对激活值进行量化。ONNX 官方文档列举了三篇关键论文作为设计依据:

  • AWQ(Activation-aware Weight Quantization):该工作观察到并非所有权重同等重要,通过感知激活值来保护关键(salient)权重,而非依赖反向传播或重建技术;通过搜索最优的 per-channel 缩放来保留重要权重,从而最小化量化误差。
  • GPTQ(Accurate Post-Training Quantization for Generative Pre-trained Transformers):提出基于近似二阶信息的一次性权重量化方法,可将每个权重的位宽压缩到 3~4 位,且相比未压缩基线精度损失可忽略不计。
  • Understanding INT4 Quantization for Transformer Models:系统研究了将权重与激活同时量化为 4 位(即 W4A4)的影响。结论是:对 encoder-only 与 encoder-decoder 模型,W4A4 几乎不带来精度损失;但对 decoder-only 模型会造成显著精度下降。该研究还构建了高度优化的端到端 W4A4 encoder 推理流水线,支持多种量化策略。

正是基于这些背景,ONNX 在onnx==1.17.0中引入了两种 4 位整数类型,用于支撑 4 位数据类型的模型压缩,并配套了有限集合的算子支持。

两种 4 位类型的精确定义

类型全称表示方式取值范围
UINT44 位无符号整数普通二进制[0, 15]
INT44 位有符号整数二进制补码(two's complement)[-8, 7]

在 protobuf 定义(onnx/onnx.proto)中,两者的枚举值分别为UINT4 = 21INT4 = 22,注释明确写明了取值范围与补码表示约定:

UINT4 = 21; // Unsigned integer in range [0, 15] INT4 = 22; // Signed integer in range [-8, 7], using two's-complement representation

与 Cast 算子的转换规则

文档明确了 4 位类型与其他精度类型之间的转换语义:

  • 从 4 位转换到任意更高精度类型是精确的(exact):例如UINT4 → UINT8/INT32/FLOATINT4 → INT8/FLOAT都不会引入误差,因为高精度类型可以无损表示 4 位范围内的所有值。
  • 向 4 位类型的转换是取整 + 截断:先按"最近整数、平局取偶数"(round-to-nearest-integer, ties to even)的规则舍入到最接近的整数,再截断到 4 位范围。这也意味着超出 [0, 15] 或 [-8, 7] 范围的值会发生溢出环绕。

源码佐证:Cast 算子的测试覆盖

ONNX 仓库的 Cast 算子测试(onnx/backend/test/case/node/cast.py)直接印证了这一规则,测试中定义了FOUR_BIT_TYPES = frozenset({"UINT4", "INT4", "FLOAT4E2M1"}),并显式列出了与 4 位类型相关的合法转换组合,包括:

  • FLOAT → UINT4FLOAT16 → UINT4FLOAT → INT4FLOAT16 → INT4(高精度向 4 位转换,需要舍入与截断);
  • UINT4 → FLOATUINT4 → FLOAT16UINT4 → UINT8(4 位向高精度转换,精确无损失);
  • INT4 → FLOATINT4 → FLOAT16INT4 → INT8

同时,cast.py中还特别处理了from_type in ("UINT4", "INT4") or to_type in ("UINT4", "INT4")的情况(cast.py),用于构造合法的 4 位转换测试数据。castlike.py测试同样覆盖了这些类型组合。

打包与解包(Packing and Unpacking):核心字节布局

基本规则

所有 4 位类型都以2×4bit 打包进 1 个字节的方式存储:

  • 第一个元素存放在字节的低 4 位(4 LSB)
  • 第二个元素存放在字节的高 4 位(4 MSB)

假设数组中连续的两个元素为xy,则:

pack(x, y): y << 4 | x & 0x0F unpack(z): x = z & 0x0F, y = z >> 4

这里x & 0x0F的含义是把x截断/掩码到低 4 位(等价于x % 16),避免越界位污染相邻元素;y << 4y移入高 4 位。

奇数元素与填充

当 4 位张量的总元素个数N为奇数时,最后一个字节的高 4 位会追加 4 位填充(padding)。因此:

  • 存储大小为ceil(N/2)字节;
  • 例如N=5时占用ceil(5/2)=3字节:第 1、2 字节完整存储 4 个元素,第 3 字节低 4 位存第 5 个元素,高 4 位为填充位。

源码佐证:Python 侧打包实现

onnx/numpy_helper.py中的_pack_4bitx2(onnx/numpy_helper.py)是文档公式的精确 Python 实现:

def _pack_4bitx2(array: np.ndarray) -> npt.NDArray[np.uint8]: """Convert a numpy array to flatten, packed int4/uint4. Elements must be in the correct range.""" # Create a 1D copy array_flat = array.ravel().view(np.uint8).copy() size = array.size odd_sized = size % 2 == 1 if odd_sized: array_flat.resize([size + 1], refcheck=False) array_flat &= 0x0F array_flat[1::2] <<= 4 return array_flat[0::2] | array_flat[1::2]

逐行解读:

  1. 将输入展平为 1D 数组并视为uint8视图;
  2. 若元素个数为奇数(odd_sized),扩展一个元素位以容纳填充;
  3. array_flat &= 0x0F对每个元素掩码到低 4 位(对应公式中的x & 0x0F);
  4. array_flat[1::2] <<= 4把偶数索引(第 2、4、6…个)元素左移 4 位(对应y << 4);
  5. array_flat[0::2] | array_flat[1::2]将相邻奇偶元素按位或合并,得到 2 元素/字节的打包结果。

与此对称,helper.pymake_tensor(onnx/helper.py)在构造 4 位原始张量时,会先计算expected_size_bytes = ceil(prod(dims) * 4 / 8),并调用numpy_helper._pack_4bitx2(vals)完成打包后再写入raw_data;非 raw 模式下(onnx/helper.py)同样先_pack_4bitx2再填充int32_data等字段。

源码佐证:Checker 的存储大小校验

onnx/checker.cc在模型校验阶段对 4 位类型的raw_data长度做了严格检查(onnx/checker.cc):

case TensorProto::UINT4: case TensorProto::INT4: case TensorProto::FLOAT4E2M1: expected_bytes = (nelem + 1) / 2; // 2 elements per byte, ceiling division break;

这里(nelem + 1) / 2正是ceil(N/2)的整数实现:当N为偶数时等于N/2,当N为奇数时(N+1)/2向上取整。若raw_data的字节数与形状推算出的期望字节数不符,校验会直接失败,从而在模型加载早期拦截错误的 4 位张量。

与 2 位类型的对比(延伸参考)

仓库内同系列的 2 位整数文档(docs/docsgen/source/technical/int2.md)展示了更极致的低位压缩:2 位类型以 4×2bit 打包进 1 个字节,元素按 LSB→MSB 依次存放,存储大小为ceil(N/4)字节。4 位与 2 位打包的差异(LSB/MSB 拆分 vs. 顺序铺满)说明不同低位类型在 ONNX 中的位布局约定并不完全一致,使用时务必以各类型对应的官方定义为准。

Python 侧的类型映射

在 Python API 中,4 位类型通过ml_dtypes库提供对应的 NumPy dtype。onnx/_mapping.py(onnx/_mapping.py)中的映射定义如下:

int(TensorProto.UINT4): TensorDtypeMap( np.dtype(ml_dtypes.uint4), int(TensorProto.INT32), "TensorProto.UINT4" ), int(TensorProto.INT4): TensorDtypeMap( np.dtype(ml_dtypes.int4), int(TensorProto.INT32), "TensorProto.INT4" ),

可以看到:

  • TensorProto.UINT4映射为ml_dtypes.uint4TensorProto.INT4映射为ml_dtypes.int4
  • 两者都以INT32作为"落盘字段"(即序列化时值存放在int32_data中);
  • 由于 NumPy 原生没有 4 位 dtype,打包表示由numpy_helper._pack_4bitx2uint8缓冲完成,这一点在 onnx/helper.py 的注释中有明确说明:"NumPy doesn't have INT2/INT4/FP4/FP6. It is packed in couples to UINT8 buffers."

支持的算子与使用场景

有限算子集合

如文档所述,4 位类型最初"支持有限集合的算子"以启用压缩。从仓库证据看,UINT4/INT4出现的算子相关文件包括:

  • Cast / CastLike:类型转换(onnx/backend/test/case/node/cast.py、onnx/backend/test/case/node/castlike.py);
  • QuantizeLinear / DequantizeLinear:线性量化与反量化(onnx/backend/test/case/node/quantizelinear.py、onnx/backend/test/case/node/dequantizelinear.py),参考实现见 onnx/reference/ops/op_quantize_linear.py;
  • 底层 schema 注册与类型约束位于 onnx/defs/tensor/defs.cc,类型工具函数位于 onnx/defs/data_type_utils.cc。

这符合低位量化模型的典型工作流:权重以 4 位打包存储 → DequantizeLinear 反量化为高精度 → 参与常规计算,或在推理时由支持 4 位的后端直接消费。

典型落地流程(结合代码推断)

结合helper.make_tensor与 checker 校验逻辑,一个 4 位权重张量的构造与加载链路可以概括为:

  1. 使用ml_dtypes.uint4/int4生成 NumPy 数组(或直接提供合法范围内的 Python 值列表);
  2. 调用onnx.helper.make_tensor(name, TensorProto.INT4/UINT4, dims, vals),内部自动完成_pack_4bitx2打包;
  3. 模型保存后,任何读取方(如 ONNX Runtime)在校验阶段由checker.cc依据ceil(N/2)校验raw_data长度,确保字节布局正确;
  4. 运行时通过 Cast / DequantizeLinear 还原为高精度数值参与计算。

需要说明的是,这一步流程属于从代码结构推导的通用用法,具体后端(如 ONNX Runtime)对 4 位算子的支持程度与执行路径不在本仓库范围内。

小结

主题关键结论
引入版本onnx==1.17.0
类型与范围UINT4:[0, 15];INT4:补码 [-8, 7]
转换规则4 位 → 高精度精确;高精度 → 4 位为"最近偶数舍入 + 截断"
打包布局低 4 位存第 1 个元素,高 4 位存第 2 个元素;pack: y<<4 \| x&0x0F
存储大小ceil(N/2)字节;奇数元素补 4 位填充
主要算子Cast / CastLike、QuantizeLinear / DequantizeLinear

4 位整数类型是 ONNX 支撑 LLM 低位量化生态的基础设施。理解其补码取值约定、字节打包布局与存储大小公式,是正确构造、校验和跨框架交换量化模型的前提。如需进一步了解同系列更低位宽的表示,可继续阅读仓库内的 2 位整数类型文档(docs/docsgen/source/technical/int2.md)与 float4 文档(docs/docsgen/source/technical/float4.md)。

【免费下载链接】onnxOpen standard for machine learning interoperability项目地址: https://gitcode.com/gh_mirrors/onn/onnx

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询