如果你听说一个大模型的全部权重只能用 -1、0、1 三个数来表示同时它的任务效果还能对齐全精度的 LLaMA 基线你大概率会先怀疑这是标题党还是压缩算法的魔法这不是魔法。2024 年 10 月微软研究院公开的 BitNet b1.58 论文标题就叫《The Era of 1-bit LLMs: All Large Language Models are in 1.58 Bits》。它把 Transformer 里所有线性层的权重矩阵全部量化到三值空间也就是每个权重只取 {-1, 0, 1} 三种状态信息论意义上的位宽只有 log2(3) ≈ 1.58 位。我的判断是1.58 位并不是把量化精度又往下“削了一刀”而是把神经网络里最核心的矩阵乘法从“乘加运算”变成了“加减和跳过”。从 AI Infra 的视角看这是计算范式层面的变化不只是内存变小的附带好处。这篇文章会把下面几个问题讲透1.58 位、三值权重这些概念到底是什么意思BitNet b1.58 的量化算法是怎么设计的为什么模型没有崩省下的账到底算在内存、带宽还是算力上给你一份能跑的最小 PyTorch 实现以及训练和部署时必须避开的坑。如果你正在做大模型推理优化、量化部署、AI 芯片或 AI Infra 相关工作这篇值得收藏后读完。1. 这篇文章真正要解决的问题先回到一个非常现实的问题大模型推理为什么贵当你部署一个 70B 模型时显存里装的主要是权重。FP16 权重每个占 2 字节70B 权重就是 140GB单卡 A100 的 80GB 显存根本装不下。就算装下了每次推理都要把权重从 HBM 搬到计算单元内存带宽决定吞吐上限。很多场景里模型不是“算不动”而是“搬不动”。于是量化成了必选项。INT8、FP8、INT4 都是把权重位宽往下压但压到一定程度精度损失就开始放大。各家推理框架都在 4-bit 附近打转很少继续往下走因为大家都默认位宽越低、模型越笨。BitNet b1.58 打破了这条默认路径。它不是说“我再把 4-bit 压到 2-bit”而是直接用三值权重训练模型让模型从第一天起就适应“权重只能是 -1、0、1”这个约束。结果是在 3B 规模下它的困惑度和下游任务表现对齐了 FP16 的 LLaMA 对照模型同时吞吐提升 2.71 倍显存占用降为原来的约 1/3.55。所以这篇文章真正要解决的问题是为什么三个数字就能撑起一个大模型以及这种模型在工程上到底怎么落地、适合什么场景、不适合什么场景。读完你应该能形成三个清晰的判断1.58 位的核心收益不在“省显存”本身而在矩阵乘法从乘加变成加减运算这改变了硬件设计的成本模型它不能通过后量化把现成的 FP16 模型转成三值权重必须从零开始做量化感知训练在没有定制 kernel 的通用 GPU 上你不一定能拿到论文里的提升落地前必须做实测。2. 基础概念1.58 位、三值权重与常见量化对比2.1 什么是量化量化就是把连续取值映射到有限离散值的过程。模型的权重和激活原本是 FP16 或 BF16 的高精度数值量化后变成 INT8、INT4 这种更窄的表示目的是减少存储和计算开销。传统量化关心的是“如何逼近原始数值”误差越小越好。比如 INT8 有 256 个取值等级INT4 有 16 个等级。量化后一般还需要做反量化把结果缩放回原始范围精度损失主要发生在这一步。2.2 三值权重{-1, 0, 1}BitNet b1.58 的权重不是 256 个等级也不是 16 个等级而是只有三个-1, 0, 1每个权重要么是正方向上的激活要么是负方向上的抑制要么是零。零这个状态非常关键它让模型可以“关掉”一部分连接这相当于在三值空间里内嵌了一定程度的稀疏性。传统二值量化只有 {-1, 1}没有“无所谓”这个选项表达能力弱得多。2.3 为什么叫 1.58 位三个取值的信息量就是 log2(3) ≈ 1.58496 位约等于 1.58 位。这是信息论的下限不是说有 1.58 个比特的物理存储。实际落地时为了寻址方便主流做法是用 2 个比特表示一个三值权重00、01、10一个字节能放 4 个权重。更紧凑的打包可以做到 5 个权重塞进一个字节因为 3 的 5 次方等于 243刚好小于 256代价是解包时要做查表或位运算。2.4 量化和位宽方案对比方案权重取值范围理论位宽特点FP16浮点数16 bit精度高存储和带宽开销大INT8-128 ~ 1278 bit当前主流后量化方案生态成熟FP8有限浮点8 bit新一代 GPU 支持混合精度训练常用INT4-8 ~ 74 bit部署常见精度损失需校准1-bit 二值{-1, 1}1 bit无零状态表达能力受限1.58-bit 三值{-1, 0, 1}log2(3) ≈ 1.58 bit有零状态乘法变加减2.5 从 BitNet 到 BitNet b1.58微软最早提出的 BitNet 是 1-bit 模型权重只取 {-1, 1}。b1.58 是在这个基础上的扩展增加了零值同时把激活量化成 8 位整数。零值让模型不是简单的“正反二值”而是可以表达“这条连接不重要”这对维持模型表达能力非常关键。一个辅助理解的角度三值权重不是“压缩后的 FP16”而是一种从头训练出来的、和约束共存的参数表示。它更像“原生低比特模型”而不是“事后瘦身”。3. 核心原理BitNet b1.58 的量化方案拆解3.1 权重三值化absmean 量化BitNet b1.58 的权重量化分为三步。第一步计算权重矩阵的平均绝对值 gammagamma mean(|W|)第二步把每个权重除以 gamma做归一化W_norm W / gamma第三步用阈值 0.5 做四舍五入并截断到 [-1, 1]W_q clamp(round(W_norm), -1, 1)用阈值 0.5 的含义是归一化后绝对值小于 0.5 的权重被清零大于 0.5 的保留正负号。这个过程看起来很粗暴但 gamma 是动态计算的每个权重矩阵会自适应地落到 {-1, 0, 1} 这个集合里。用一个具体例子说明。假设一行权重是W [0.20, -0.90, 1.30, -0.60, 0.05]平均绝对值是 0.61归一化后变成W_norm [0.33, -1.48, 2.13, -0.98, 0.08]按 0.5 阈值做三值化W_q [0, -1, 1, -1, 0]原先是正的小权重 0.2 变成了零负的大权重 -0.9 变成了 -1正的大权重 1.3 变成了 1。信息确实有损失但 gamma 缩放会在后续矩阵乘里补偿数值范围。3.2 激活量化per-token 的 8 位量化权重可以三值化激活不能用三值化。激活的取值随输入变化动态范围大如果也压成三个值信息会严重丢失。BitNet b1.58 的做法是对激活做 8 位整数量化而且按 per-token 方式计算缩放因子。每个 token 对应的那一整行激活用各自的最大绝对值做缩放scale max(|x|) # 按 token 维度计算 x_q clamp(round(x / scale * 127), -127, 127)为什么按 token 而不是按整个矩阵因为不同位置的 token 激活分布差异可能很大全局一个缩放因子会放大量化误差per-token 可以保证每个 token 行都能充分利用 8 位的动态范围。在通用 GPU 上量化后的 INT8 激活通常会再转回 BF16 做矩阵乘因为现有矩阵乘单元不一定支持直接拿 INT8 激活和低比特权重做运算。在专用硬件上这一步可以全程保留整数计算。3.3 非线性部分保持高精度不是所有的东西都是 1.58 位。LayerNorm、Softmax、GELU 这些非线性算子以及注意力得分计算仍然保留在 BF16 或 FP32。原因很直接这些算子的输入输出分布极不稳定强行低比特化会造成数值崩溃收益也不大。矩阵乘的累加器通常用 FP32 或 INT32 来减少累加误差。这是量化模型里非常容易被忽略的部分累加精度不够低位宽权重的误差会被放大。3.4 这里真正容易踩坑的地方最容易踩的坑是把 BitNet b1.58 当成一个“压缩工具”以为可以直接加载一个 FP16 模型然后用三值量化做推理。这个思路对 INT8、INT4 可能有效对三值权重几乎必崩。原因在于三值化是一个极端非线性的映射FP16 模型学到的权重分布完全不是围绕 -1、0、1 分布的硬切过去等于把大部分权重信息直接丢弃而且无法靠一个缩放因子找回。BitNet b1.58 的做法是量化感知训练模型从随机初始化开始训练时就使用三值权重参与前向传播让整个网络在“权重只能是三个值”的约束下调整参数和激活分布。这个区别是整篇文章最值得记住的一点。4. 从 AI Infra 角度看收益内存、能耗、吞吐4.1 内存和带宽账怎么算以 70B 参数模型为例FP16 权重70B × 2 字节 140GB三值权重按 2 比特存储70B × 0.25 字节 17.5GB如果做 5 权重 1 字节的紧凑打包约 14GB。从 140GB 到 17.5GB权重占用的显存缩小到原来的八分之一。对推理服务来说这直接决定了能不能单卡部署也决定了权重复用时的带宽压力。但这里要泼一盆冷水模型权重只是显存的一部分。推理时的激活、KV Cache、临时中间结果仍然占用大量显存。三值化解决的是“权重读带宽”这半边问题KV Cache 和激活的优化是另外半边需要通过 8 位 KV Cache 等方案继续处理。4.2 计算模型的变化乘法变加减这是 1.58 位最核心的硬件价值。一个常规线性层的输出是y_i sum_j (w_ij * x_j)当权重只有 -1、0、1 时乘法变为w 1 时累加 x_j w -1 时累减 x_j w 0 时跳过也就是说矩阵乘的乘加单元MACmultiply-accumulate可以直接替换为加减单元。微电子领域里加法器在面积、功耗、延迟上比乘法器低一个量级这是三值模型对芯片设计最具有吸引力的地方。对于现有 GPU情况略微复杂。你依然可以用 INT8 或 BF16 的矩阵乘去模拟三值权重但这样做拿不到“乘法消失”的红利主要收益来自权重体积变小带来的带宽下降。要拿到论文里的 2.71 倍吞吐提升必须依赖为三值权重定制实现的算子或专用硬件。4.3 论文报告的收益数据论文里最关键的一组数据是 3B 规模下的对比效果上BitNet b1.58 3B 在困惑度和下游任务上和 FP16 的 LLaMA 对照基本持平吞吐上三值版本比 FP16 版本快约 2.71 倍显存上三值版本占用约为 FP16 版本的 1/3.55。论文还强调三值模型同样遵循参数规模缩放规律70B 级别的三值模型可以在有限 GPU 资源下高效运行显存成本相比 FP16 大幅下降。也就是说位宽虽低但不是只能在小模型上玩的花活。4.4 对部署形态的影响三值模型最大的意义可能不在数据中心而在端侧和边缘设备。权重小、带宽需求低、加减运算的门槛低这让 CPU 推理、FPGA 实现和专用 NPU 都有了新的设计空间。微软后续也开源了面向三值模型的 CPU 推理方向的工作这条链路正在从论文走向工程。5. 最小代码实现三值量化线性层下面用 PyTorch 写一个可运行的最小实现。它不是论文的完整复现但完整覆盖了权重三值化、激活 8 位量化和直通估计器STE这几个关键机制。5.1 环境准备Python 3.10 及以上PyTorch 2.x不需要额外依赖CPU 上即可验证。如果你要在 GPU 上训练建议使用 BF16 混合精度但代码本身不依赖 GPU。5.2 量化函数实现先写两个量化函数一个是权重三值化一个是激活 8 位量化。import torch import torch.nn as nn import torch.nn.functional as F def ternary_weight_quantize(W: torch.Tensor): 权重三值化将权重映射到 {-1, 0, 1}。 使用 absmean 量化 1. 计算权重平均绝对值 gamma 2. 权重除以 gamma 归一化 3. 用 0.5 阈值四舍五入并截断。 gamma W.abs().mean().clamp(min1e-6) W_norm W / gamma W_ternary torch.clamp(torch.round(W_norm), -1, 1) return W_ternary, gamma def activation_quantize_int8(x: torch.Tensor): 激活 8 位量化per-token 方式。 每个 token 对应的一行用该行的最大绝对值作为缩放因子。 量化到 [-127, 127] 后再反量化回原始数值范围。 scale x.abs().amax(dim-1, keepdimTrue).clamp(min1e-6) x_q torch.clamp(torch.round(x / scale * 127), -127, 127) x_deq x_q / 127.0 * scale return x_deq, scale可以先拿一个小矩阵验证W torch.tensor([[0.20, -0.90, 1.30, -0.60, 0.05]]) W_q, gamma ternary_weight_quantize(W) print(W_q) # 预期输出类似 [0, -1, 1, -1, 0] assert set(W_q.unique().tolist()).issubset({-1.0, 0.0, 1.0})5.3 BitLinear 层与直通估计器直接使用三值权重做前向会带来一个问题round和clamp不可导训练时梯度会全部消失。解决方法是直通估计器Straight-Through Estimator前向使用量化后的权重反向把量化函数近似为恒等映射。class BitLinear(nn.Module): 三值权重线性层。 前向过程 1. 权重三值化 2. 激活 8 位量化 3. 用三值权重做矩阵乘 4. 乘回缩放因子 gamma。 训练时通过 STE 让梯度穿过量化函数。 def __init__(self, in_features: int, out_features: int): super().__init__() self.weight nn.Parameter(torch.randn(out_features, in_features) * 0.02) def forward(self, x: torch.Tensor) - torch.Tensor: W_ternary, gamma ternary_weight_quantize(self.weight) # STE 技巧前向使用 W_ternary反向梯度等价于恒等映射 W_effective W_ternary self.weight - self.weight.detach() x_q, _ activation_quantize_int8(x) out F.linear(x_q, W_effective) return out * gamma解释一下关键行W_effective W_ternary self.weight - self.weight.detach()前向传播时self.weight - self.weight.detach()等于 0所以实际参与计算的是W_ternary。反向传播时W_ternary对self.weight的导数是 0但self.weight - self.weight.detach()对self.weight的导数是 1合成之后梯度可以直接回传到原始权重上。这是 QAT 里最常见的 STE 写法。5.4 替换注意力投影把标准多头自注意力里的 QKV 和输出投影替换成 BitLinear 即可其他结构不变。class TinyBitAttention(nn.Module): def __init__(self, d_model: int, n_heads: int): super().__init__() self.n_heads n_heads self.d_head d_model // n_heads self.q_proj BitLinear(d_model, d_model) self.k_proj BitLinear(d_model, d_model) self.v_proj BitLinear(d_model, d_model) self.o_proj BitLinear(d_model, d_model) def forward(self, x: torch.Tensor) - torch.Tensor: b, t, c x.shape q self.q_proj(x).view(b, t, self.n_heads, self.d_head).transpose(1, 2) k self.k_proj(x).view(b, t, self.n_heads, self.d_head).transpose(1, 2) v self.v_proj(x).view(b, t, self.n_heads, self.d_head).transpose(1, 2) attn F.softmax(q k.transpose(-1, -2) / (self.d_head ** 0.5), dim-1) out (attn v).transpose(1, 2).reshape(b, t, c) return self.o_proj(out)需要注意上面代码省略了因果掩码、LayerNorm 以及位置编码只用于演示投影层替换方式。实际训练时每个 Block 入口应该先做 LayerNorm再做投影这一点对应论文中对输入分布稳定性的设计。5.5 训练配置示例下面是一份训练配置示例可以根据自己项目调整model: name: bitnet_tiny hidden_size: 256 num_layers: 4 num_heads: 4 vocab_size: 10000 max_seq_len: 128 quantization: weight: ternary # 权重只用 {-1, 0, 1} weight_storage_bit: 2 # 物理存储按 2 位 activation: int8 # 激活 8 位 per-token 量化 nonlinear_part: bf16 # LayerNorm / GELU / Softmax 保持高精度 training: lr: 2.0e-4 warmup_steps: 2000 total_steps: 100000 batch_size: 64 grad_clip: 1.0训练循环核心部分optimizer torch.optim.AdamW(model.parameters(), lr2e-4, weight_decay0.1) for step in range(10000): x_batch, y_batch next_batch() # 自行准备数据 logits model(x_batch) loss F.cross_entropy(logits.view(-1, logits.size(-1)), y_batch.view(-1)) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() optimizer.zero_grad()5.6 如何运行和验证结果用一个几千万参数的玩具模型在小型语料上跑通整个训练流程后可以这样验证检查权重确实三值化训练结束后打印model.q_proj.weight应该观察到大量 0 和正负 1 附近的分布说明量化约束生效对比同一规模的 FP16 Transformer观察困惑度是否接近推理时把BitLinear.forward里的 STE 分支去掉或者直接用torch.no_grad()之后的三值权重参与计算确认输出稳定。如果训练 loss 在初期就不降优先检查是否用了 STE梯度是否为零学习率是否过大是否在 LayerNorm 之后再做量化。6. 训练与推理的工程要点6.1 从零训练是唯一的可靠路径必须再次强调BitNet b1.58 的性能来自量化感知训练不是后量化。你没办法加载一个 LLaMA 的 FP16 权重然后直接转成三值权重用来部署精度会崩得很彻底。如果确实要在一个已有模型上做低比特化可以尝试长时间 QAT 恢复训练也就是用原有权重做初始化但强制前向使用三值权重继续训练几十万步。这个方案成本不低效果也未必能对齐从零训练实践中要提前做好评估。6.2 训练稳定性控制三值化引入了强非线性训练时数值稳定性需要重点管理使用 AdamW 时学习率不要太大2e-4 到 5e-4 是现金流范围打开梯度裁剪范围 0.5 到 1.0 比较安全监控 gamma 的数值gamma 是权重平均绝对值如果持续变大或突变说明权重分布出了问题监控激活量化后的位分布如果大量集中在 ±127说明 per-token 缩放不稳定。6.3 推理 kernel 与内存布局论文的吞吐收益依赖专门的推理算子。在通用框架里直接把三值权重存成 INT8 走通用 GEMM虽然能跑但无法发挥“乘法变加减”的优势。实用的内存布局有两种2 位存储一个字节存 4 个三值权重解包简单适合带宽敏感但计算单元通用的场景5 权重打包5 个权重用 3^5243 种状态编码进 1 个字节接近 1.58 位的理论下限但解包需要查表或位运算要评估解包开销换来带宽收益是否划算。6.4 KV Cache 和通信不能忽视三值化把权重内存降了很多但大模型推理的显存大头不只有权重。长上下文场景下KV Cache 会和序列长度线性增长甚至成为主要瓶颈。论文里的激活量化是 8 位KV Cache 也有继续量化的空间二者是独立优化维度可以叠加使用。在多卡推理时权重变小也意味着通信量变小这对张量并行是利好但通信的固定开销不会因为权重三值化消失。7. 常见问题与误区排查问题现象可能原因排查方式解决方案训练 loss 不降或急剧升高梯度被量化函数截断学习率过大没有使用 STE检查权重梯度范数对比是否禁用 QAT引入 STE调低学习率先在小模型上复现直接加载 FP16 模型三值化后效果崩坏使用了后量化路线查看单 layer 输出差异改为从零训练或长周期 QAT 恢复激活量化后训练不稳定per-token 缩放异常出现 NaN打印激活 scale 统计检查归一化前置层加 epsilon必要时降激活量化位宽推理速度没有提升没有定制 kernel用 profiler 查看算子和内存耗时更换专用三值 kernel或改用更紧凑的权重打包显存降了但延迟没降模型变成带宽不敏感算子成了瓶颈分别测计算量和带宽占比优化算子展开考虑批量推理提升带宽利用率7.1 常见的三个概念误区误区一1.58 位就是 2 位量化。2 位量化有 4 个状态三值权重只有 3 个状态。1.58 是信息论位宽物理上更常用 2 位容器但语义和表达力完全不同。误区二三值模型一定比 INT8 快。在没有定制算子的通用 GPU 上三值权重通常还是要走模拟计算提升主要来自带宽下降。要拿到质的提升必须用支持三值累加的精简内核或专用硬件。误区三这个技术已经能直接替换现有部署。现阶段 BitNet 类模型需要从零训练这意味着模型权重、训练流程、推理栈全部要配套支持。对于已经投入成本训练好的 FP16 模型转成三值的迁移成本远高于 INT8 或 INT4 方案。8. 最佳实践与工程建议8.1 什么场景适合引入 1.58 位适合显存和带宽是主要约束的端侧推理场景高通量、长上下文的批量推理服务权重搬移占比高自研 FPGA、ASIC 或 NPU希望在硬件层去掉乘法器从零训练新模型并且评估质量容错度。不适合已经有稳定 FP16 或 INT8 模型且没有重新训练预算团队没有底层算子开发能力只靠通用框架搬运任务对极小精度损失极其敏感且无法通过校准兜底。8.2 落地时的操作清单推荐按下面的顺序推进先用 100M 到 1B 的模型做 QAT 训练实验验证质量对比完成权重三值化验证确认训练完成后权重分布符合预期用 profiling 工具对比三值实现与 INT8/FP16 基线在目标硬件上的差异评估打包方案优先选 2 位存储再做 5 权重打包的收益测试部署阶段把 KV Cache 量化、连续批处理、投机采样等优化叠加进来看整体吞吐是否还有增量。8.3 质量验证不能只看 loss训练 loss 下降不代表下游任务没问题。建议准备一个固定的评测集对比三值模型和 FP16 基线在不同任务上的差距。特别要关注长文本、代码生成、多轮对话这类对细节敏感的指标因为这些任务最容易暴露量化误差积累。8.4 关注开源生态与工具链三值大模型正在从论文走向开源实现。微软官方开源了面向 BitNet 类模型的推理工具链社区里也出现了大量基于 PyTorch 的三值量化训练复现。建议在做技术选型前先跑通一个公开的模型权重和推理 demo再评估是否要把自己的训练链路改造成 QAT 模式。工具链成熟度是当前最大的风险点。你别只盯着理论位宽要确认推理框架、算子库、模型转换工具、微调流程是否都支持三值权重这一条链路缺一环项目推进都会很吃力。9. 总结与后续学习方向这篇文章真正讲清楚了几件事。第一1.58 位不是简单的“更激进的量化”而是把模型权重放进三值空间核心价值在于矩阵乘法从乘加运算变成加减运算这改变了 LLM 推理的成本结构。第二BitNet b1.58 的核心机制是 absmean 权重量化加 per-token 8 位激活量化非线性部分保留高精度模型必须从零训练通过 STE 让梯度穿过不可导的量化函数。第三工程落地的收益需要分清楚内存和带宽的收益是确定的计算加速则依赖专用 kernel 或硬件。论文里快速、省显存的数据是定制实现下的结果通用 GPU 上需要重新实测。如果你想继续深入建议按这个顺序往下学KV Cache 量化解决长上下文场景的显存瓶颈激活值的进一步低比特化判断 8 位激活是不是下一块硬骨头三值模型与 MoE、稀疏化的结合看看能不能把稀疏性和低比特叠加CPU 和 FPGA 上的三值矩阵乘算子实现理解带宽受限场景下真正有效的循环展开和打包方式。动手时有一条实用的建议先用一个亿级以内的小模型把 QAT 全流程跑通不要直接冲向 70B。验证完质量、稳定性和性能三项指标后再决定是否把三值化引入你真正负责的业务项目。技术选型的判断永远要建立在你能复现的数据上。
阅读完成 · 觉得有帮助?