BitNet.cpp 介绍

保存时间:2026/4/2 09:33:48


BitNet.cpp 介绍
内容由豆包 AI 生成
我听说微软有一个 BitNet 点 cpp 的开源项目,这个据说可以用 1.5 比特的量化来压缩大模型。你知道这个事情吗?你介绍一下,它是 C 加加编写的吗是开源的吗?有什么特点?为什么这么大的压缩量化没有降低模型的参数的精度呢?
微软的 BitNet.cpp 是一个用 C++ 编写的开源项目。它是 1 - bit 大语言模型的官方推理框架,能够在普通 CPU 上高效运行,无需依赖 GPU。以下是其特点:
  • 低硬件要求:支持在 arm 和 x86 架构的 CPU 上运行,未来还计划扩展至 NPU、GPU 和移动设备,让用户可在本地设备上运行大规模模型。
  • 高效压缩:通过量化技术将模型参数压缩至 1.58 位,大幅减少模型的存储空间和计算资源需求,能将模型压缩到原来的 1/20 大小。
  • 速度提升与能耗降低:在 arm CPU 上可实现 1.37 倍至 5.07 倍的加速,x86 CPU 上加速效果介于 2.37 倍至 6.17 倍之间,同时能耗显著降低,减少 55.4% 至 82.2%。
  • 支持多种模型:支持包括 BitNet、Llama3 和 Falcon3 等系列的模型,最高可处理 1000 亿参数的模型,推理速度可达每秒 5 - 7 个 token,接近人类阅读速度。
BitNet.cpp 采用 1.58 位量化之所以没有降低模型参数的精度,主要有以下原因:
  • 采用三进制表示:将模型参数表示为三进制的 {-1, 0, 1},每个参数用 1.58 位信息来表示,相比传统的 16 位浮点数存储方式,虽然精度降低,但这种表示方式在矩阵计算中仅涉及加法,能在一定程度上保持模型的性能。
  • 量化函数约束:采用 abs - mean 量化函数来约束权重,将权重矩阵通过其平均绝对值进行缩放,然后四舍五入到最接近的整数值(-1、0 或 1),在激活量化中,激活值被缩放到 (−qb, qb) 的范围以消除零点量化,这种量化方式有助于在压缩模型的同时保持精度。
  • 架构设计合理:BitNet.cpp 在架构设计上借鉴了 Llama,使用了 rmsnorm、swiglu、旋转位置编码等组件,并移除了所有偏置项,这种设计使其能够轻松集成到主流的开源框架中,也有助于保持模型的精度和性能。
相关视频
参考 24 篇资料
你能不能解释一下采用三进制表示?如果采用三进制表示的话,它的这个数要怎么表达啊?就是说这个比特是三个比特就等于进到下一个,下一级嘛。为什么说每个参数用 1.58 位来表示为什么说会是有一个小数的 1.58 呢? BF16 是 16 个比特,你现在用三比特三进制来表达十一个 16 比特的浮点数,难道你的比特数难道还有小数?那就是变长来表达了请解释一下。
在 BitNet.cpp 中,采用三进制表示是将模型参数表示为 {-1, 0, 1} 这三种状态。它并非传统意义上像十进制或二进制那样,每三个比特就进位到下一级。这里的三进制主要是指参数取值只有三种可能,不是基于位运算的常规三进制计数方式。
每个参数用 1.58 位来表示,是因为在信息论中,当对参数进行量化为 {-1, 0, 1} 三值时,根据香农熵的计算,平均每个参数所需要的信息量约为 1.58 位。简单来说,就是通过这种量化方式,平均每个参数能够用 1.58 位的信息来进行有效表示,从而实现模型的压缩和高效计算,这是一种对存储和计算资源进行优化的方式,并非是真正存在小数位的比特,也不是变长表示。
BF16 是 16 位浮点数格式,它用 16 个比特来表示一个浮点数,能够表示的数值范围和精度是确定的。而 BitNet.cpp 的 1.58 位量化是一种特殊的量化策略,通过将参数限制在 {-1, 0, 1},并利用特定的量化函数和架构设计,在大大减少存储和计算需求的同时,尽可能保持模型的精度和性能,与 BF16 这种常规的浮点数表示方式有很大的不同。
我很好奇,怎样能够将一个 BF16 这种取值范围非常大的一个权重参数,最后量化到-101 这么简单的一个取值范围,而不失掉很多精度就比如说,我有原来有三个 BF16 的浮点数,那么它可能是随便举例子是 2001.56,1800.12 或者是 1.099,那你现在这样怎样去把它们量化呢?一个 2000 多,只是 2000 多,一个是只有一点多,你现在把它量化,难道就把它们变成负一、零、正一,这样三种情况吗这差别是不是太大了?
将 BF16 这样取值范围大的权重参数量化到 {-1, 0, 1},主要通过以下方式来尽量减少精度损失:
  • 采用特定量化函数:使用 abs - mean 量化函数,先计算权重矩阵的平均绝对值,然后将权重通过该平均绝对值进行缩放。例如,对于一组权重参数,先计算它们的平均绝对值假设为,那么原来的权重就被缩放为。经过缩放后,大部分权重值会被映射到一个相对较小的范围,再将其四舍五入到最接近的 {-1, 0, 1} 值。对于像 2001.56、1800.12 这样较大的值,如果它们所在的权重矩阵平均绝对值较大,经过缩放后可能会被舍入为 1;而对于 1.099 这样较小的值,可能会根据缩放后的结果舍入为 0 或 - 1(如果是负数)。
  • 模型架构配合:BitNet.cpp 的模型架构采用了 rmsnorm、swiglu、旋转位置编码等组件,并移除了所有偏置项。这种架构设计可以在一定程度上弥补量化带来的精度损失,使得模型对量化后的参数有更好的适应性,能够在保持性能的同时,将参数有效地量化为 {-1, 0, 1}。
  • 训练优化:在模型训练过程中,会采用一些优化策略来适应量化后的参数表示。例如,调整学习率、采用合适的优化算法等,使得模型能够在量化后的参数空间中找到较好的解,从而减少因量化导致的精度下降。
通过这些综合的方法,虽然将参数量化到了 {-1, 0, 1} 这样简单的取值范围,但在实际应用中能够在一定程度上保持模型的精度和性能,使得模型在压缩后仍能有效工作。
我还是对此有所怀疑。这种取平均数,然后再来进行量化,肯定是一个前提,就是说你的这些数据集,它们大概是一个,就比如说是一个正态分布,而且是一个比较高耸的,就是比较集中的正态分布。他们的大部分的数值分布在 mean,就是所谓的平均值这个地方,而他的扩散或者说离散现象比较没有那么明显,就是在,比如说就是大半 80% 以上的数据量都是在它的这个这个靠近 mean 的这个这个范围内所以你去做量化的话,是比较效果比较好的。但是你现在就是对于任何一个这个权重就只有三种情况,正一,零,负一。那对于同样都在正一这个范围的,这有大量的数,他们之间的比较就没有意义了,因为他们都是一。对于这个零,这个同样,负一那一侧,所有的数的比较也都是没有意义,因为都是他,他们都是负一。这个就好像是一个人,这个叫做 simple minded 的,就是说对这个世界只有非黑即白的这种二极管的思维,对任何事物只有一个简单的对与错的理解,而没有一个细分的这么一个具体问题具体分析的这样一个模式你觉得是不是这样?
这种看法有一定道理,但 BitNet.cpp 的量化方式并非如此简单绝对。
确实,量化过程中会面临信息丢失的问题,将大量不同的权重值映射到 {-1, 0, 1} 会导致同一取值的不同原始值之间的差异被忽略,就像你说的在正一或负一范围内的不同数值比较变得无意义。
然而,BitNet.cpp 的量化策略并非单纯基于简单的平均值和假设数据是集中的正态分布。它通过模型架构的设计,如使用 rmsnorm、swiglu、旋转位置编码等组件,以及在训练过程中的优化调整,使得模型能够适应这种量化带来的变化。这些措施可以在一定程度上弥补量化导致的精度损失,让模型依然能够捕捉到数据中的关键特征和关系,并非是完全的 “非黑即白” 思维。虽然量化会丢失一些细节信息,但在实际应用中,该方法在模型压缩和性能之间取得了一定的平衡,使得模型在存储和计算效率大幅提升的情况下,仍能保持较好的性能表现。
那你的意思就是说,这个 BitNet 点 cpp,它实际上是要不仅仅是做现有模型的微调,实际上它是要把现有模型要重新做强化训练,就等于是重新重新去训练这个模型,用它的这种量化策略来做训练,然后才能这个 loss function 就是这个损失函数才能够符合它的这种量化策略。因为在我看来这样像做 fine tune 或者是微调,不改变原来模型的这种架构的话,是没办法做到这一点的,是不是这个意思?
是的,你理解得大致正确。在 BitNet.cpp 中,通常需要对模型进行重新训练,而不仅仅是微调。
这些三值语言模型是从头开始预训练以找到最佳权重的,采用了专门为低精度参数设计的训练过程。之所以要重新训练,是因为 BitNet.cpp 的量化策略将模型参数表示为 {-1, 0, 1},这与传统模型使用的 32 位或 16 位浮点数表示权重有很大不同。如果只是对现有模型进行微调,很难让模型适应这种极端的量化方式,也难以使损失函数符合量化策略的要求。通过重新训练,可以在训练过程中让模型学习如何在 {-1, 0, 1} 的参数空间中进行有效的表示和计算,从而在保持模型性能的同时,实现高效的压缩和量化。
如果这个方向是可行的,那其实原本这种模型训练公司早就应该采用这种方式去做,而不应该使用这个占位很多的这种浮点数,甚至于 32 位的浮点数来做训练。一一方面这个浮点数的这种计算,这个耗费是太高了,比整数高太多了。另一方面这个它的存储量也比这个相对来说比很多一一个 byte,两个 byte 的整数来说要多很多吧?当然了 4 个 byte 你说是整数也行,这个浮点数也行,都是存储是一样多的,但是它们计算效率是差很多。但之所以我们使用浮点数,是因为浮点数它能表达的范围比整数 Integer 要大得多啊,是不是?尤其是在那个小数部分,这个是能够表达非常细微的这种差异。而整数实际上是这个 16 个比特的整数已经是六五五三五,就 64K 的这种比,那个这个点位,这个位点呢,就这个这个差差别啊,就只有 64 K 个。而浮点数是这个因为有小数的参与,它能够表达的不同是远远大于 64 K 的多的多了。那这个我的意思是说如如果能够把这个世界当初就这样简化的话,用这种方式去训练模型,而能够同样达到同样的复杂度,同样的这种,这个人工智能的这种 Aha moment 能够就出现的话,大家应该早就去去这样做训练了,而不至于说到今天是遇到了说这个应用推理遇到瓶颈了,普通人本地化部署有困难了,才想到说我要去走一个量化或者说是压缩的这个被迫的采取这种方式。
虽然 BitNet.cpp 的量化策略有其优势,但模型训练公司之前没有广泛采用这种方式,主要有以下原因:
  • 技术成熟度和认知:传统的浮点数表示和基于浮点数的训练方法在深度学习领域已经发展成熟,有完善的理论基础、工具和框架支持。研究人员和工程师对浮点数计算的特性、数值稳定性等方面有深入的理解和丰富的经验。而像 BitNet.cpp 这种极端量化的方法是相对较新的技术,需要时间来研究、开发和验证。
  • 模型性能风险:虽然 BitNet.cpp 声称在量化后能保持一定的模型精度,但在早期,人们对于将参数量化到 {-1, 0, 1} 这样的极端情况能否在各种任务和模型架构中普遍保持高性能存在疑虑。传统的浮点数表示能够精确地表示模型参数,对于一些需要高精度计算的任务,如语音识别、图像精细处理等,使用浮点数可以更好地捕捉数据中的细微特征,避免因量化导致的精度损失对模型性能产生较大影响。
  • 硬件支持:硬件设计和优化通常是围绕传统的浮点数计算进行的。例如,GPU 等加速设备在处理浮点数运算方面有专门的硬件单元和优化措施,能够高效地执行浮点数乘法、加法等操作。而对于像 BitNet.cpp 这种特殊的量化表示,硬件支持相对较少,需要开发新的硬件架构或者在现有硬件上进行复杂的软件适配,这增加了技术实现的难度和成本。
随着技术的发展,如今模型部署面临着存储和计算资源的瓶颈,尤其是对于大规模语言模型,量化和压缩技术的需求日益迫切,像 BitNet.cpp 这样的创新方法才得到了更多的关注和研究。
参考 4 篇资料
模型训练公司没有广泛采用BitNet.cpp量化策略的原因是什么?
BitNet.cpp量化策略有哪些优势和劣势?