如果一个模型里的参数权重,只允许取-1、0、1三个整数,模型还能正常工作吗?这听起来像是一个数字魔术,但它确实是近两年模型量化领域最让人眼前一亮的方向之一,外界通常把它叫做1.58位大模型。
在AI Infra视角下,1.58位量化的价值被严重低估了。它不是“把模型压得更小”这么简单,而是直接改变了推理阶段的算术结构:矩阵乘法里的乘法消失了,只剩下加减法。这篇文章不讲宏大叙事,只从工程角度拆三件事:
- 1.58位到底是怎么算出来的;
- 为什么它能把乘法变成加减法;
- 如果你要自己验证、实现、落地,应该怎么做,以及有哪些坑。
读完这篇文章,你至少能回答一个问题:同样都在做模型压缩,INT4和1.58位的本质区别在哪里?为什么后者不是“更强”的INT4,而是另一条技术路线。
1. 1.58位模型到底解决了什么问题
先看大模型推理的成本结构。
一次模型推理,要读大量权重参数,然后反复做矩阵乘法。在GPU或专用加速器上,矩阵乘法的“乘累加”操作占比极高,而权重从内存搬到计算单元的过程更消耗时间和功耗。带宽和算力,是推理系统绕不开的两个瓶颈。
传统量化的思路是“把权重从FP16压缩到更小的离散值”,比如INT8、INT4,权重每个元素占用8比特或4比特,内存占用下降了,但矩阵乘法的基本单元还是“乘法加加法”。乘法器依然存在,功耗和硬件面积也不会凭空消失。
1.58位量化的思路完全不同。
它的目标不是“继续减小数值的比特数”,而是把权重约束到三个离散值-1、0、1。这意味着矩阵乘法的每一项都可以提前判断结果:1乘以x就是x,-1乘以x就是负x,0乘以x直接跳过。乘法操作会被编译器或内核掉,推理矩阵乘法的时间复杂度不变,但算术复杂度从“乘加”变成了“只有加减”。
这也是文章标题里那个看起来反直觉的问题的答案:模型还能不能跑?能跑,但它跑得快、省资源的前提,不是“无损”,而是经过专门的训练方法与量化策略配合。
所以,1.58位模型真正解决的是:如何在推理资源极其受限的设备上,用最小的算术代价换取可接受的模型能力。它更像是一种“算法结构换硬件效率”的权衡,而不是简单的精度换体积。
什么类型的读者适合关注这个方向?
如果你在做大模型端侧部署、CPU推理、边缘设备推理,或者你在为某个专有推理引擎设计算子,1.58位非常值得研究。如果你只想在数据中心GPU上跑高精度对话模型,那这个方向暂时不是你的核心优先级,因为你更看重精度与通用算力的匹配。
2. 为什么是1.58位?从比特位宽到三值权重
“1.58位”是怎么来的?
一个权重如果只能取三种状态,也就是-1、0、1,那么每个权重需要的信息量就是log2(3)比特,约等于1.585比特。这是信息论的下界,不表示CPU或GPU真的用1.585比特去存储,而是说,从信息熵的角度看,这个离散取值集合的理论成本就是1.58比特。
所以严格讲,1.58位量化不是一种新的低比特数据格式,而是一种“权重离散取值集合”的设计。它和二值化、INT4、FP8有本质区别。
二值化权重只能取-1和1,每个权重只要1比特,但它强迫每个参数都必须有非零响应。三值权重多了0这个状态,这让模型可以显式表达“这个连接不重要”。在大模型中,很多权重对最终输出贡献很小,用0去掩盖它们,既压缩了信息,又让推理计算量进一步下降。
三值权重还有一个工程优势:稀疏性和零跳过结合很自然。权重为0的位置,计算时可以直接跳过。如果分布得好,实际计算量可能比理论上的三值权重更低。
那为什么不用“纯1比特”呢?
因为二值化丢掉了“0”这个中性状态,表达力太弱。三值化在几乎不增加存储成本的前提下,让权重分布有了“正、负、忽略”三种语义,信息表达能力提升了一个台阶。更关键的是,在三值化基础上,配合一个整体缩放因子,可以恢复权重的尺度信息,从而让前向输出和原始连续权重尽量接近。
这里要额外强调一点:1.58位说的是“权重”,不是“激活”。激活值通常仍然保留8比特或更高精度。这是很关键的设计选择,因为激活是依赖输入动态变化的,过低的精度容易在深层网络中放大误差。
用一个信息论的类比,权重承担的是模型长期“记忆”,可以被压缩成极稀疏的三值符号;激活承担的是当前输入带来的临时“状态”,需要保留更多细节才能维持表达能力。两者不能混为一谈。
一句话总结:1.58位模型,本质是“三值权重 + 缩放因子 + 较高比特激活”的混合量化方案。
3. 1.58位量化与传统量化的本质区别
很多读者会把1.58位量化理解成“INT4的下一代”,这个理解不够准确。它和传统低比特量化走的是完全不同的分支。
| 对比维度 | INT8/INT4量化 | 1.58位三值量化 |
|---|---|---|
| 权重取值 | 连续映射到256或16个离散值 | 只有-1、0、1 |
| 理论存储 | 8比特或4比特/权重 | log2(3)≈1.58比特/权重 |
| 矩阵乘基元 | 乘累加 | 只有加减 |
| 核心难点 | 校准、scale、舍入误差 | 训练稳定性、分布适配、算子重写 |
| 硬件友好度 | 需要低比特乘法和累加器 | 只需要加法器 |
| 精度恢复方式 | 通常靠量化校准与微调 | 通常靠量化感知训练 |
传统INT8路线,量化后仍然是“数”之间的运算。INT8乘INT8的硬件指令很成熟,但因为乘法器本身更耗电、面积更大,它在功耗上的收益会逐渐碰到天花板。INT4量化则更依赖硬件对INT4的专门支持,否则会有反量化的额外开销。
1.58位路线没有走“继续压低比特位宽”这条路,而是改变了数值语义。推理时权重只有三个符号,真正的矩阵运算变成了“选择加法、减法或什么都不做”。在硬件上,这意味着可以移除乘法器,只保留加法树和累加器。对CPU推理、FPGA推断、自研AI专用芯片而言,这是非常诱人的特性。
但传统量化技术有一个优势,1.58位目前比不了:生态成熟。
INT8量化几乎在所有主流推理框架中都能直接用,算子支持丰富,性能调优经验也多。1.58位三值量化,从底层的runtime到上层模型训练方法都还在快速演进中,不能指望“训练完一个模型,随便塞进现有推理引擎就自动加速”。
所以,更稳妥的判断是:1.58位不是替代INT8/INT4的方案,而是一条面向特定硬件的激进路线。它最大的价值在推理架构的“算术成本坍塌”,而不是单纯的模型体积下降。
4. 从训练到推理,1.58位模型如何落地
如果要落地一个真正的1.58位大模型,只靠“训练完再量化”是不够的。
先从训练阶段说起。
如果直接把一个已经训练好的FP16模型四舍五入到三值,你大概率得到的是一个能力崩溃的模型。原因很简单:模型原始权重连续分布,三值化会丢失大量信息;预训练模型从头到尾都没有适应“只有三个值”的约束,权重分布和激活分布自然不匹配。
所以,1.58位模型的前向路径必须在训练阶段就参与进来。常见做法是量化感知训练:
- 模型权重仍然以高精度连续值保存;
- 每次前向计算时,把当前权重量化为三值;
- 用三值权重完成前向传播;
- 反向传播时,用直通估计器绕过取整操作,让梯度更新仍然作用于高精度权重。
这样训练出来的模型,从出生到成熟都在适应用“三值权重”去表达,精度损失会小得多。
训练过程中,通常还会引入一个缩放因子alpha,由当前权重矩阵的平均绝对值得到。权重除以alpha之后,数值分布在-1附近、0附近、1附近,再离散映射到三值。前向输出最后乘回alpha,补偿权重的整体量纲。
推理阶段的落地又是一层不看文章根本意识不到的工作。
权重虽然存储为三值,但runtime要做几件事:把三个符号尽可能紧凑地编码到内存中;把0值权重对应的激活跳过;把加法和减法组织成向量化指令;尽可能让权重加载与激活计算流水线重叠。如果只是把三值权重扩展成INT8再计算,那内存带宽的收益会大打折扣,加减法的算术优势也发挥不出来。
这里还要考虑一个现实问题:在GPU上,INT8张量核心已经非常成熟,1比特等效算术未必能赢;但在CPU、嵌入式设备、自研低功耗芯片上,去掉乘法器带来的收益非常直接。
所以,1.58位模型能不能发挥威力,不取决于模型结构本身,而取决于训练方法和推理内核是否配套。它对“软硬协同”的要求,比传统量化高得多。
5. 环境准备与最小复现流程
如果你只是想理解1.58位的核心逻辑,不需要先搭建一个大模型训练平台。可以先在本地跑一个最小示例,验证三值量化和推理过程中的算术变化。
推荐环境:
- Python 3.9以上;
- NumPy,用于第一个验证脚本;
- PyTorch,用于后续量化感知训练的演示;
- 一个文本编辑器即可,暂时不需要GPU。
安装依赖:
pip install numpy torch不需要指定最新版本,以你自己环境中稳定可用的版本为准。本文重点演示通用思路,不绑定特定版本。
建议按下面三步走:
- 用NumPy完成“权重三值化 + 推理矩阵乘法”的玩具实现;
- 用PyTorch写一个带直通估计器的三值线性层,跑几次训练迭代;
- 再用一个C语言风格的伪代码,理解真实推理内核可以如何“只做加减法”。
这三步不是平行关系,而是层层递进:先看数学,再看训练,最后看工程优化。
6. 权重量化与推理的完整代码示例
6.1 三值量化与推理的NumPy实现
下面的脚本演示最核心的absmean量化:先求权重平均绝对值,再映射到三值,最后用缩放因子恢复尺度。
# 文件路径:ternary_demo.py import numpy as np def absmean_quantize(w): # 1. 计算整个权重矩阵的平均绝对值,作为缩放因子 alpha = np.abs(w).mean() # 2. 权重除以缩放因子,得到相对比值 ratio = w / alpha # 3. 按阈值映射到 {-1, 0, 1} w_q = np.where(ratio > 0.5, 1.0, np.where(ratio < -0.5, -1.0, 0.0)) return w_q, alpha def ternary_inference(x, w_q, alpha, bias=None): # 三值权重推理:先做矩阵乘法,最后统一乘缩放因子 out = w_q @ x out = out * alpha if bias is not None: out = out + bias return out if __name__ == "__main__": np.random.seed(0) x = np.array([0.5, -2.0], dtype=np.float32) w = np.array([[0.8, -0.4], [0.2, 1.1]], dtype=np.float32) w_q, alpha = absmean_quantize(w) print("alpha:", alpha) print("w_q:\n", w_q) fp_out = w @ x tri_out = ternary_inference(x, w_q, alpha) print("fp_out:", fp_out) print("tri_out:", tri_out) print("diff:", fp_out - tri_out)关键逻辑说明:
absmean_quantize计算的是权重矩阵的全局缩放因子,不是每个元素单独缩放;- 阈值取0.5,意味着相对值超出一半才映射为1或-1,否则置0;
- 矩阵乘法仍然用的是
@,但实际权重量化后只会从-1、0、1中取值,这为后续去掉乘法提供了前提; - 最后乘回
alpha,是为了让输出量级和原模型保持一致,不至于整体偏移。
6.2 量化感知训练中的直通估计器
真正训练三值权重模型时,反向传播会遇到一个经典问题:round到三值的过程几乎处处导数为0,梯度会直接消失。直通估计器的做法是:前向照常量化为三值,反向传播时把梯度当成从恒等函数传来的,原样回传。
下面的代码演示了一个最小的三值量化线性层和几步训练迭代:
# 文件路径:ternary_ste_demo.py import torch import torch.nn.functional as F class TernaryQuant(torch.autograd.Function): @staticmethod def forward(ctx, w): # 1. 计算缩放因子 alpha = w.abs().mean() # 2. 映射到三值 ratio = w / alpha w_q = torch.where(ratio > 0.5, torch.ones_like(w), torch.where(ratio < -0.5, -torch.ones_like(w), torch.zeros_like(w))) return w_q @staticmethod def backward(ctx, grad_output): # 直通估计器:直接把梯度回传,绕过取整对梯度的阻断 return grad_output def ternary_linear(x, w): # 量化权重 + 前向线性层 w_q = TernaryQuant.apply(w) out = F.linear(x, w_q) alpha = w.abs().mean() return out * alpha torch.manual_seed(0) x = torch.randn(2, 4) w = torch.nn.Parameter(torch.randn(4, 3)) y = torch.randn(2, 3) optimizer = torch.optim.SGD([w], lr=0.1) for step in range(3): pred = ternary_linear(x, w) loss = F.mse_loss(pred, y) optimizer.zero_grad() loss.backward() optimizer.step() print(f"step {step}: loss={loss.item():.4f}, grad_norm={w.grad.norm().item():.4f}")这个例子虽然玩具,但它揭示了1.58位模型训练的核心机制:
w始终以高精度保存,优化器更新它;TernaryQuant前向输出三值权重,使网络确实在用三值计算;backward直接返回grad_output,让梯度能够穿过量化层;- 每次迭代都会重新计算缩放因子,体现的是动态缩放思想。
你可能会问:为什么训练时不直接把三值权重保存下来?因为训练过程需要权重连续变化,如果权重被锁死在三值,梯度更新后所有参数仍然保持原值,模型就不可能收敛。连续权重是“影子变量”,三值权重是“实际参与计算的版本”,二者各司其职。
6.3 推理内核核心循环:只有加减法
真正部署1.58位模型时,推理内核不会笨拙地把三值权重展开成浮点数再算。它会按下面的逻辑,直接跳过乘法。
// 伪代码:三值权重矩阵乘向量的核心循环 // wq 是 {-1, 0, 1} 的三值权重,x 是激活向量(通常为 8bit 定点) for (int i = 0; i < M; i++) { float acc = 0.0f; for (int k = 0; k < K; k++) { int8_t wval = wq[i * K + k]; if (wval == 1) { acc += x[k]; } else if (wval == -1) { acc -= x[k]; } // wval == 0 时直接跳过 } out[i] = acc * alpha; }这段伪代码的核心价值在于:循环体内没有乘法运算。1*x等价于x,-1*x等价于-x,0*x等价于什么都不做。真实的优化内核还会在此基础上做分块、向量化、跳过连续0等优化,但基本思想是一致的。
这也回应了文章标题那个问题:模型能跑,而且跑的是更廉价的算术。
7. 运行结果与效果验证
跑第一个NumPy脚本,你会看到类似下面的输出:
alpha: 0.625 w_q: [[ 1. -1.] [ 0. 1.]] fp_out: [ 1.2 -2.1] tri_out: [1.5625 -1.25] diff: [-0.3625 -0.85]如何判断这个结果“成功”?
alpha计算正确,说明缩放因子与权重分布匹配;w_q每一列都在-1、0、1范围内,说明量化映射生效;tri_out和fp_out方向一致,虽然数值有偏差,但整体量级没有完全脱离原始结果;diff不为0,说明三值量化是有损的,这是正常的。
需要额外理解的是:单看diff很小不是目标。在真实1.58位模型中,三值权重本来就不是为了让某个前向输出和FP16输出尽量一致,而是让模型经过训练后自动形成“适合三值表达”的语义。所以如果你上手去测,不要只盯着“逐层输出误差”,更要看“整体任务精度变化”。
如果要继续验证,建议把上面的NumPy脚本升级为对某个小模型逐层做三值化模拟,观察模型最终在验证集上的表现。这一步能让“量化误差”从抽象数字变成可感知的质量损失。
跑第二个PyTorch脚本时,你会看到loss在几次迭代中明显下降,w.grad也不为0。这说明直通估计器让梯度成功穿过取整层,训练循环是有效的。
如果你在真实设备上测性能,至少需要看三个指标:
- 模型体积或内存占用是否下降;
- 端到端延迟或吞吐是否提升;
- 精度变化是否在业务可接受范围内。
其中“内存占用下降”不等于“延迟下降”,这与算子实现、内存带宽、计算密度都有关系。不要在没测延迟前就断言“1.58位一定比INT8快”。
8. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 三值量化后模型精度大幅下降 | 直接对训练好的FP16权重做量化,没有量化感知训练 | 检查权重分布与激活分布是否匹配 | 改成QAT,让前向传播在训练阶段就使用三值权重 |
| 训练不收敛,loss震荡明显 | 直通估计器后的梯度尺度不合适 | 观察梯度范数、量化层输出的数值范围 | 调整学习率,必要时对量化梯度做裁剪或缩放 |
| 推理速度没有明显提升 | 推理内核没有真正跳过乘法,仍然展开成浮点 | 检查编译产物或内核实现,确认循环体内只有加减 | 重写算子,结合0值跳过和SIMD向量化 |
| 内存下降幅度远低于预期 | 权重没有紧凑编码,仍然按INT8或更高精度存储 | 检查序列化格式 | 对三值做打包编码,例如每4个权重压缩成若干字节 |
| 量化后的权重大量为0 | 缩放因子计算方式与权重分布不匹配 | 绘制权重相对值直方图 | 调整缩放策略或改用更细粒度的分组缩放 |
| GPU上收益不明显 | GPU张量核心对INT8低比特乘加优化充分,算术优势被稀释 | 测量GPU kernel吞吐和功耗 | 优先在CPU、边缘设备或自研芯片上评估 |
| 激活值过于激进地量化后崩溃 | 激活被压到过低比特,信息不足 | 检查每层激活误差 | 保持激活为8bit或更高精度,不要与权重同时极端量化 |
这张表里最容易被忽视的是第一行。很多人第一次听说三值量化,会以为这就是“把模型权重改成三值再存起来”,结果压完直接精度崩盘。实际上,1.58位模型的技术含量集中在“训练方式”和“推理内核”,而不是“压缩编码”。
9. 最佳实践:什么场景该用1.58位模型
1.58位模型不是什么场景都该上。它的性价比有明显边界。
适合使用的场景:
- CPU推理,尤其是没有强大低比特张量核心的普通服务器CPU;
- 嵌入式设备和端侧模型,内存带宽和功耗极其受限;
- 自研AI芯片或FPGA,可以在硬件设计阶段就砍掉乘法器阵列;
- 对精度容忍度较高、对成本和能耗敏感的任务,比如部分分类、摘要、简单生成场景;
- 与稀疏化、剪枝技术叠加的探索型项目。
不适合使用的场景:
- 数据中心GPU推理,尤其当现有INT8/F8路线已经优化得很好时;
- 对时延抖动和输出精度要求极高的生产系统;
- 需要频繁处理长尾知识、精细数值计算的模型;
- 推理框架尚未支持三值权重快捷部署的团队项目。
工程建议上,优先考虑下面几条。
第一,激活精度与权重精度要分开看待。权重可以极致三值化,激活通常需要保留8bit以上。把二者混为一谈,是很多实验失败的直接原因。
第二,缩放因子的设计直接影响量化效果。按整个权重矩阵求平均绝对值是最简方案;更进一步可以考虑逐通道、逐token或分组计算缩放因子。分组越细,表达能力越强,但存储和计算开销也会增加。
第三,训练策略要配套。建议先用较高精度权重训练,再逐步切换到三值权重,并加入学习率衰减、梯度裁剪、少量蒸馏数据,帮助网络平稳过渡。
第四,推理阶段一定要重新审视内核。官方现成框架大概率没有针对三值权重的优化算子,需要评估是否有足够精力维护一套自定义runtime。否则,再好的三值权重也会被通用框架“展成浮点”吃掉所有收益。
第五,谨慎对待“理论压缩比例”。从FP16到三值,理论位宽从16比特降到约1.58比特,接近10倍。但实际收益要扣除缩放因子开销、激活存储、Runtime中间结果和算子开销。对外汇报时,至少要区分“权重内存节约”和“端到端内存节约”,避免夸大。
10. 总结与后续学习方向
1.58位模型给AI Infra带来的真正启示,不是“模型可以被压缩到多小”,而是“当权重只剩三个符号时,整个算术体系都可以被重塑”。
它证明了,在某些场景下,与其在现有乘法器体系里不断压低比特数,不如直接改变权重语义,让推理过程绕过乘法。这是一个底层计算范式的变化,而非单纯的量化精度变化。
如果接下来想继续深入,建议按这个顺序推进:
- 先读懂三值化和直通估计器的数学原理;
- 自己从零实现一个三值线性层,在小数据集上做超参实验;
- 观察不同缩放策略、不同激活精度对loss曲线的影响;
- 再尝试在真实推理框架中接入三值权重,看内存和延迟的实际变化;
- 最后,如果条件允许,在CPU或边缘设备上写一个只有加减法的内核,对比通用框架的差距。
对大多数团队而言,现阶段最稳妥的切入点是先完成前两步。因为1.58位模型在工程链路上还不像INT8那样“开箱即用”,真正决定它是否有价值的是你是否愿意投入一套配套的训练流程和推理内核。这个投入,比选一个论文里好看的数字要重要得多。