如何在8位字节中打包三进制数
摘要
一篇博客文章,描述了一种高效的方法,使用SIMD友好的解包将三进制数打包到8位字节中,实现了每trit 1.6比特,并应用于LLM权重量化如BitNet b1.58。
暂无内容
查看缓存全文
缓存时间: 2026/07/21 09:36
# 如何将三进制数打包进8位字节
来源:https://compilade.net/blog/ternary-packing
并可进行高效的SIMD解包
---
发布日期:2024-06-26
三进制数的每一位有3种可能的值。这3种可能的值实际上可以是任何东西。
最近我因为一个技术彩蛋[¹](https://compilade.net/blog/ternary-packing#fn:1)而试图将 BitNet b1.58[²](https://arxiv.org/abs/2402.17764) 的三进制权重打包到接近理论极限 `log(3) / log(2)` [³](https://compilade.net/blog/ternary-packing#fn:2) 位每三进制位的程度。
我将“三进制位”称为“trit”,就像“二进制位”称为“bit”一样。
## 块大小
由于目标是实现快速的***并行***解包,三进制位的块不能无限大。需要找到一个较小的“块”大小,既能在信息密度上高效,又能在当前硬件上方便使用。
要找到合适的块大小,我们需要找到一个3的幂,使其下一个2的幂非常接近。
很幸运,5个三进制位可以非常紧凑地放入8位中,达到每三进制位`1.6位`。与完美打包相比,效率为`99.06%`。
## 每三进制位1.6位
这种打包方案的基本思路很简单:用三进制位组成一个数字。
将三进制位打包进字节的方式应该类似。
### 快速乘法解包
虽然可以通过反复取余和除法来提取数字的各位,但在 SIMD 编程中,除法和取模通常不受支持。
解决这个问题的方法*显然*是以不同的方式看待数字。
如果我们能通过乘法提取最高有效位,而不是通过取模提取最低有效位,那该多好啊?
定点数来拯救!
瞧!
现在,当用这个8位字节乘以3时,可以从得到的10位数字的高两位轻松提取出三进制位。
与使用取模相比,这在 SIMD 解包时方便得多。
在这个方案中,只有将三进制位打包进字节时才会用到除法。这假设打包操作比解包操作少,这在 LLM 权重的上下文中确实如此。
相关的关键代码如下:
它实现了上图所示的操作,但由于是整数运算,所以先进行乘法。这里使用向上取整除法是为了消除后续提取数字时截断导致的偏移误差。
要*不*使用取模运算符进行解包:
为了让自己相信这是可行的,我写了一个 C 程序来验证它确实是无损的:
用以下命令编译并运行:
对于所有能放入8位的243个三进制数,我都得到了`PASS`结果。
这就是在 `llama.cpp` 中用于 TriLM 和 BitNet b1.58 的三进制类型所使用的技术,相应的拉取请求在 https://github.com/ggml-org/llama.cpp/pull/8151,并且包含了针对 AVX2 和 ARM NEON 的 SIMD 实现。
相似文章
ExTernD: 扩展秩三值分解——精度逼近任意量化水平的LLM训练后量化
ExTernD引入了一种扩展秩三值分解用于LLM训练后量化,通过使用具有自由内秩的因子化表示,使精度接近bf16。在Gemma-4和Qwen3.5等模型上,它以每个权重5.2-5.5有效比特达到Q4_K的精度。
Bitnet.cpp:面向三值大语言模型的高效边缘推理
Bitnet.cpp 提出了一个混合精度矩阵乘法库,用于高效边缘推理三值大语言模型(如 BitNet b1.58),相比全精度基线实现了高达 6.25 倍的加速。该系统已在 GitHub 上开源。
Ternary Bonsai:1.58 比特下的顶级智能
一种使用三值权重(-1、0、1)的高效 AI 模型架构,仅需 1.58 比特/参数即可实现具有竞争力的性能,可部署在极度受限的设备上。
我尝试了三元分解而不是量化。它的效果与q4km一样好,但占用稍多的VRAM。同时完全是三元,且完全是PTQ(无QAT)
作者声称,三元分解的性能与Q4_K_M量化一样好,同时使用稍多的VRAM,并且完全是三元和PTQ,无需QAT。
CAT-Q: 用于LLM的高效且准确的三值量化
CAT-Q 提出了一种面向LLM的训练后三值量化方法,该方法使用可学习调制和软化三值化技术,仅需512个校准样本即可实现优于BitNet 1.58-bit的性能,并可扩展到235B参数规模。