BiLoCo: Binary Low-Rank Corrections for Post-Training Quantization

David Jin, Beshr IslamBouli, Tarushii Goel, Han Guo, Yoon Kim · arXiv 2610.32243 · 发表 2026-10-23
11 分钟阅读时长 4253字 4.10综合分 第 3 名10-26 Infra 日报
目录 · 10 节1它到底在解决什么问题?2核心思路:修正项只用 +1 和 −13秩多一些,比每个秩精确一些更好吗?4二值向量怎么找5算子怎么做到“几乎不加时间”6结果一:精度7结果二:速度8我认为的局限9一个团队可以怎么试10一句话结论
相关 4新颖 4实用 4证据 4时效 5

把大模型训练完直接量化到 FP4,在 Blackwell GPU 上能用原生的 FP4 矩阵乘提速,但精度会掉一截。常见补法 SVDQuant 是另存一个 BF16 的低秩修正项,它既占存储,又要和主计算抢同一批 tensor core。这篇麻省理工的论文提出 BiLoCo,把修正项里的向量全部换成 +1 和 −1,修正只剩加减法,可以放到平时闲着的 CUDA core 上,和 FP4 矩阵乘同时跑。同样的存储能放下 16 倍的秩,在 4B 到 72B 的模型上困惑度持平或更好;batch 为 1 时,整个算子只比纯 FP4 慢 6% 到 11%。

做大模型推理部署、正在评估 FP4 的团队值得读。它的出发点很实在:解码时矩阵乘的瓶颈是从显存读权重,tensor core 在等数据,CUDA core 干脆空着,那就把修正项设计成正好适合 CUDA core 的形式。方法本身不复杂,代码也公开了。但它只在小 batch 的解码场景里划算,batch 一大优势就没了,这一点要先想清楚。

它到底在解决什么问题?

这篇论文做的是训练后直接量化,不再微调。Blackwell 系列 GPU 原生支持 NVFP4 格式的矩阵乘:每 16 个数共用一个 FP8 缩放因子,每个数只有 4 位,平均每个权重约 4.5 位。存储和计算都是低精度,所以很省。

问题是 4 位能表示的值太少,只有 0、±0.5、±1、±1.5、±2、±3、±4、±6 这 15 个。一个块里只要有几个特别大的数,缩放因子就得迁就它们,其余数的误差都会变大。所以直接量化到 NVFP4 之后,模型的困惑度会明显上升。

一个自然的补法是把权重拆成两部分:权重约等于 R 加上一个低秩矩阵,其中 R 用 4 位存,低秩矩阵,用更高精度存,专门吸收 4 位表示不了的那些大方向。SVDQuant 就是这么做的,它用 SVD 取出秩 32 的 BF16 修正项。但这个修正项有两个代价:一是每个秩要存两条 BF16 向量,位数不少;二是它也是一个矩阵乘,要在 tensor core 上和 FP4 主计算抢资源。

核心思路:修正项只用 +1 和 −1

论文图 1:BiLoCo 的推理过程。上面一路在 tensor core 上做 FP4 矩阵乘,输入是量化后的激活和残差权重 R;下面一路在 CUDA core 上做修正:先用 ±1 的右向量 V 对输入做带符号求和,再乘上系数 α,最后用 ±1 的左向量 U 展开。两路结果在矩阵乘的收尾阶段加在一起,输出 BF16。
论文图 1:BiLoCo 的推理过程。上面一路在 tensor core 上做 FP4 矩阵乘,输入是量化后的激活和残差权重 R;下面一路在 CUDA core 上做修正:先用 ±1 的右向量 V 对输入做带符号求和,再乘上系数 α,最后用 ±1 的左向量 U 展开。两路结果在矩阵乘的收尾阶段加在一起,输出 BF16。

BiLoCo 把修正项写成 r 个秩一矩阵之和,每个秩一矩阵是一个系数 α 乘以两条只含 +1 和 −1 的向量。推理时要算的是 α·u·(v·x)。其中 v·x 只需要加减法:v 的某一位是 +1 就加上 x 对应的数,是 −1 就减去。

举个手算的例子。假设 x 是 [0.5, −1.2, 2.0, 0.3],v 是 [+1, −1, +1, −1],那么 v·x = 0.5 + 1.2 + 2.0 − 0.3 = 3.4,全程没有乘法。再乘上系数 α,用 u 的正负号把这个数加到或减到输出的各个位置上即可。

这样做在存储上很划算。不计系数,一对 ±1 向量每个数只占 1 位,而一对 BF16 向量每个数占 16 位,所以同样的存储能放 16 倍的秩。以一个 5120×5120 的矩阵为例,BF16 秩 32 的修正项和二值秩 512 的修正项都约是 5.2 百万位,而 FP4 权重本身约 1.2 亿位,所以修正项只多占约 4% 的存储。

秩多一些,比每个秩精确一些更好吗?

存储一样时,是用少量高精度的秩,还是大量低精度的秩?这取决于权重矩阵的奇异值分布。如果前几个方向特别大,少量高精度秩就够了;如果分布比较平,很多方向都重要,那么秩多更有用。

作者在三个模型上抽了 189 个投影矩阵来测,结果说明前面的奇异值分布很平:排第一的奇异值只比排第三十二的大一倍左右。作者的衡量方式是:把低秩部分的所有元素平方求和再开根号,除以原矩阵同样算出的值,得到一个比例,比例越高说明低秩部分抓住的东西越多。按这个比例,二值秩 512 覆盖了 47% 到 53%,BF16 秩 32 只覆盖 26% 到 29%。

论文图 2:在 Qwen3-4B 的 63 个投影矩阵上,低秩部分占原矩阵大小的比例(纵轴)随存储预算(横轴)的变化。蓝线是 BiLoCo(二值秩等于预算),红线是 SVDQuant(BF16 秩等于预算除以 16),两者存储相同。每个预算下蓝线都更高,预算为 512 时高出 24.2 个百分点。
论文图 2:在 Qwen3-4B 的 63 个投影矩阵上,低秩部分占原矩阵大小的比例(纵轴)随存储预算(横轴)的变化。蓝线是 BiLoCo(二值秩等于预算),红线是 SVDQuant(BF16 秩等于预算除以 16),两者存储相同。每个预算下蓝线都更高,预算为 512 时高出 24.2 个百分点。

上图把这个对比画在了不同的存储预算上,每一档二值修正都更高。这说明在大模型权重上,把有限的存储花在“覆盖更多方向”上,比花在“每个方向更精确”上更值。

二值向量怎么找

最优的二值分解不能直接用 SVD 得到。作者沿用了一个已有的贪心做法,一次找一个秩一分量: Hoot 说

Hoot 说手算一个 3 × 2 矩阵:贪心找两个 ±1 分量,再联合重求系数收起 ✕
交替取正负号能找到方向,但两个分量有重叠时,各自单独求的系数会互相干扰;联合重求一次,误差可以直接降到零。

逐个贪心:W 三行 (1, 1)、(1, 1)、(4, 4)

分量 1 交替
v = (+, +) → Rv = (2, 2, 8) → u = (+, +, +),Rᵀu = (6, 6),v 不变
α₁
12 ÷ 6 = 2
残差
(−1, −1)、(−1, −1)、(2, 2)
分量 2
u = (−, −, +),v = (+, +);α₂ = 8 ÷ 6 ≈ 1.33
剩余误差
平方和 ≈ 1.33

每个 α 只在“当时的残差”上算,没考虑后面的分量。

联合重求系数

重叠在哪
u₁ · u₂ = −1 ≠ 0,v 还相同
第 1、2 行要求
α₁ − α₂ = 1
第 3 行要求
α₁ + α₂ = 4
解出
α₁ = 2.5,α₂ = 1.5,误差 0

两个 ±1 分量没变,只改系数,就从 1.33 降到 0;分量越多、重叠越多,这一步越值。

矩阵 W 是示意的小例子,不是论文的权重;真实做法在最后把剩下的残差量化成 NVFP4。α 取“让误差最小”的值,对 ±1 向量就是 uᵀRv 除以元素个数。 出处: 论文 §4:贪心逐个找秩一分量(u = sign(Rv),v = sign(Rᵀu),交替 5 次),求最小误差系数后从残差减掉;全部找完后联合重求系数,因为分量有重叠;剩余残差量化成 NVFP4

  1. 先令残差 R 等于原权重 W。
  2. 随机初始化一条 ±1 向量 v,然后交替更新:u 取 Rv 的正负号,v 取 Rᵀu 的正负号,重复 5 次就基本稳定了。
  3. 算出让误差最小的系数 α,把 α·u·vᵀ 从残差里减掉,再找下一个分量。
  4. 所有分量找完后,联合重新求一遍所有系数,因为各分量之间有重叠,单独求的系数不是最优。
  5. 剩下的残差量化成 NVFP4。

整个过程很快:秩 512 时,在一张 B300 上处理 Qwen3-4B 约 1 分钟,Qwen3-32B 约 5 分钟。

算子怎么做到“几乎不加时间”

光有加减法还不够,关键是让它和 FP4 矩阵乘真正同时跑。作者在 B300 上写了一个融合算子,B300 有 148 个流式多处理器。

算子启动时,一部分线程块照常做 FP4 矩阵乘,每块负责输出的一个 128×64 的小块。剩下没分到矩阵乘的线程块在 CUDA core 上算修正项的中间结果 z = α·(v·x),写到显存里的一个缓冲区。矩阵乘线程块里负责收尾的那组线程,在主循环期间本来就闲着,它们等 z 写好后,用 u 展开成这个输出块的修正值,在写回结果之前加上去。和“先跑矩阵乘、再单独启动一个修正算子”的做法相比,它只启动一次,也不需要另开一个计算流,修正值直接在写回前加上,不用把整份输出写出去再读回来相加;唯一多出的显存读写是 z 这个很小的缓冲区。

为什么修正项几乎不占时间?因为它读的数据很少。秩 128 时,修正项只需读约 0.16 MiB 的符号位,而 FP4 主计算要读约 12.5 MiB 的权重,相差 80 倍。读数据几乎不花时间,修正的耗时于是落在四个必须依次完成的步骤上:按 v 每一位的正负号,把输入 x 的每个数加上或减去,累加出 z、把 z 写进缓冲区、矩阵乘线程块等到 z 可用、再用 u 展开。只要这四步在矩阵乘主循环结束前做完,修正就完全藏在主计算后面。

结果一:精度

模型BF16 原模型直接 NVFP4SVDQuant 秩 32BiLoCo 秩 512
Qwen3-4B14.2215.1315.0714.94
Llama-3-8B6.277.006.926.84
Qwen3-14B8.899.369.199.17
Qwen3-32B7.858.238.188.16
Qwen2.5-72B3.924.294.234.23

上表是 WikiText-2 上的困惑度,越低越好,BiLoCo 和 SVDQuant 占用相同的存储。BiLoCo 在 4B、8B、14B 上,WikiText-2 和 C4 两个数据集都更好,32B 和 72B 上持平或略好。如果只用四分之一的存储(秩 128),BiLoCo 比 SVDQuant 最多差 0.05。

困惑度的差距看着不大,更能说明问题的是和原模型输出分布的差距。作者在 200 篇 FineWeb 文档上逐个 token 算了量化模型和 BF16 原模型的 KL 散度,既看平均值,也看第 99 百分位。BiLoCo 在全部 5 个模型上第 99 百分位的 KL 都更低,比如 Qwen3-14B 上是 0.130 对 0.167,低了 22%。这说明它不只是平均情况好一点,在最容易出错的那些 token 上改善更明显。

下游任务的提升就小得多了:WinoGrande 和 HellaSwag 的平均准确率,和同样存储的 SVDQuant 相比,在四个 Qwen 模型上高 0.1 到 0.7 个点,在 Llama-3-8B 上低 0.3 个点。

结果二:速度

测试形状是 K = N = 5120,接近 14B 模型注意力投影层的大小,对比基准是同一次运行里最快的纯 FP4 算子。

batch 大小秩 128 的额外耗时秩 512 的额外耗时
18%11%
28%15%
411%44%
815%74%
1655%204%

batch 为 1 时,从秩 16 到秩 512,额外耗时都在 6% 到 11% 之间,也就是 0.34 到 0.64 微秒。但修正项的工作量随 batch 和秩一起增长,而 FP4 矩阵乘在 batch 不超过 128 时耗时几乎不变,因为它的时间花在读权重上。所以 batch 一大,修正就藏不住了。如果把可接受的额外耗时定在 20% 以内,秩 128 最多到 batch 8,秩 512 只到 batch 2。

还有一个容易被忽略的结论:在同一个算子里放一个同样秩的 BF16 稠密修正,耗时和二值修正一样。所以二值格式的好处不是算得更快,而是同样的字节能买到更高的秩。

我认为的局限

第一,适用范围窄。它只在小 batch 解码时划算,线上服务如果 batch 常在 16 以上,额外耗时会翻倍甚至更多,作者自己也建议这时改用 tensor core 上的修正。

第二,算子不支持 MLP 的 gate 和 up 投影。这两层输出太宽,需要的输出块数超过了 B300 的流式多处理器数,而这个算子要求所有矩阵乘线程块同时在跑。这两层恰好是模型里参数最多的部分,所以全模型的端到端提速还需要别的办法补上。

第三,速度只测了单个算子,没有给出整个模型的端到端解码延迟。精度上的提升也偏小,下游任务只差零点几个点,困惑度改善最明显的是小模型。

一个团队可以怎么试

  1. 先确认你的部署场景是不是小 batch 解码,比如端侧、低并发的私有部署或对延迟敏感的单用户服务。
  2. 用公开代码对你的模型跑一遍量化,几分钟就能出结果,先看困惑度和 KL 散度相对直接 NVFP4 的改善。
  3. 如果精度改善明显,再在你常用的 batch 范围内测算子耗时,挑一个在 20% 额外耗时以内的秩。
  4. 对 gate 和 up 投影,可以先保留 SVDQuant 式的修正或直接量化,其余层用 BiLoCo。

一句话结论

解码时 tensor core 在等显存、CUDA core 在闲着,把 FP4 的修正项做成只需加减法的 ±1 向量,就能用同样的存储换来 16 倍的秩,在小 batch 下几乎不加时间。

维度评价
问题重要性中高。FP4 是 Blackwell 上降推理成本的主要手段,精度损失是落地障碍
方法创新性中。分解算法沿用已有工作,新意在于把修正项格式和硬件空闲单元对上
实验证据中。5 个模型的困惑度、KL 和下游任务,加单算子测速;缺端到端延迟
工程成本中。代码公开,量化几分钟;融合算子依赖 B300 且不覆盖所有层
是否值得尝试小 batch 解码、在用 Blackwell 部署 FP4 的团队值得一试

读完这篇,你觉得

点一下即可,无需登录;再点一次撤回。只统计匿名次数,不记录你是谁。

讨论

欢迎补充实验细节、复现经验或不同看法。需 GitHub 登录。

正在加载讨论…