我给你一句一句讲透,把你担心的「解码开销、CPU 有没有指令、到底怎么拆」全说清楚,你马上就不糊涂了。
BitNet 快,不是靠省内存 alone,而是它根本不用 “拆成一个个小 bit 再解码”。你脑子里想的那种:
8bit → 拆成 4 个 2bit → 每次 shift+mask → 慢死
那是普通量化(Q4/Q2)的做法,不是 BitNet。
你说的完全对:
- 如果是 2bit 一个权重
- 8bit = 4 个权重
- 就要:
- 4 次 右移 shift
- 4 次 mask 取 2bit
- 再查表映射成 -1/0/+1
这种bit 拆包,CPU 确实没有专门指令帮你干,全靠软件硬算,确实慢。
这也是为什么 llama.cpp 里 Q2 反而比 Q4 慢 的原因:省内存,但解码成本太高,得不偿失。
因为它根本不是按 2bit 一个个存,而是:
一句话:不拆 bit,直接整块喂给 SIMD 指令,一次算 16/32/64 个权重。
CPU 有这种指令:
- x86:AVX2 / AVX-512
- ARM:NEON
这些指令能干一件事:一次对一大块数据做批量运算
BitNet 内核(TL2、i2_s)就是干这个:
- 把一整段三元权重直接读进向量寄存器
- 不拆 bit、不解码单个
- 直接和激活做 -1/0/+1 乘加
- 一次算完几十个权重
解码成本几乎被吞掉,看不到开销。
你问的这个动作,标准名字叫:
更细一点:
- Bit packing(比特打包)
- Bit unpacking(比特解包)
但 BitNet 更进一步,叫:
三元编码 / 三元解码
没有专门 “三元解码指令”。但有替代品,而且足够强:
- SIMD 向量指令(一次算 16/32 个)
- 高速缓存
- 预取
BitNet 就是靠这些把解码成本压到几乎看不见。
你说:
8 个比特,两个比特两个比特装,装 4 个三元,shift 四次,mask 四次
完全正确,就是这样。
- 8bit = 4 × 2bit
- 每个 2bit 表示一个三元(浪费一个编码不用)
- 取的时候:
- shift 0 → mask 0b11 → 第 1 个
- shift 2 → mask 0b11 → 第 2 个
- shift 4 → mask 0b11 → 第 3 个
- shift 6 → mask 0b11 → 第 4 个
这就是普通 Q2 量化的做法,确实慢。
BitNet 快,是因为它把 “解码 + 计算” 融合在一起,不解完再算。
- 你想的 拆 2bit → shift+mask 确实存在,也确实慢
- 但 BitNet 不用这种笨办法
- 它靠 SIMD 向量指令整块算,解码成本几乎没有
- CPU 没有专门三元解码指令,但 SIMD 足够顶
- 所以它才能:
- 省内存(≈1.58bit)
- 速度还比 llama.cpp Q4 快几倍
如果你愿意,我可以用一行 C 语言伪代码,给你看 BitNet 内核真正怎么一次算 32 个三元权重,你一眼就懂它为什么快,要不要?