
温馨提示:另有配套视频版,附带图解演示,观感不同,欢迎到各大视频平台搜索同名标题或【蛋先生说识】
开场
丹尼尔:蛋兄,本地跑大模型,有啥好工具推荐不?
蛋先生:Ollama 啊,装上就能用
丹尼尔:跟你聊多了,忍不住想问——它的实现原理是什么?
蛋先生:哈哈!底层靠的是 llama.cpp,Ollama 只是站在它肩膀上的众多应用之一
丹尼尔:llama.cpp?上次听你讲量化原理(可在各平台搜索《你瘦不下来但大模型可以:量化原理了解一下》)的时候,好像听过这个词
蛋先生:没错,本地算力有限,不量化根本跑不动。llama.cpp 在量化算法优化这块上,还是下了不少功夫的
丹尼尔:量化原理我是懂了个大概,那今天就跟我讲讲 llama.cpp 在量化上的优化呗
性价比之王:Q4_K_M
蛋先生:llama.cpp 的量化类型有不少,有 I-Quants,K-Quants 等等。而 K-Quants 又可分为 Q4_K_M,Q5_K_M 等等
丹尼尔:等等等等。。。你就挑一个最有代表性的讲就好了,我听不了这么多的名词
蛋先生:作为技术男,性价比肯定是首位。那就 Q4_K_M 吧,用得最多,官方也默认推荐
丹尼尔:Q4_K_M 各表示啥?
蛋先生:Q4 表示使用 4 位来量化权重,K 表示使用 K-Quant 优化算法类型,M 表示中等"平衡"
核心优化一:混合精度
丹尼尔:哦,那具体都优化了啥?
蛋先生:核心就两点。第一点,混合精度(Mixed Precision)。大模型是一层一层摞起来的,深度学习里"深度"俩字就是这么来的。如果每一层都用 INT4,那就是吃大锅饭——一刀切。不管哪层更重要,统统一个待遇
丹尼尔:咦,就有点像埋没人才的意思?
蛋先生:对喽,大锅饭养不出尖子生,好钢得用在刀刃上
丹尼尔:那怎么区别对待?
蛋先生:给重要层的一些关键张量多分点精度,比如 5 位或 6 位
丹尼尔:张量是啥?
蛋先生:我就简单科普一下,说多就扯远了。张量(Tensor),是数值和数组的通用叫法。数值就是标量(比如 1.1),1维数组就是向量(比如 [1.1, 2.2, 3.3])、2维数组就是矩阵(比如 [[$1.1, 2.2], [3.3, 4.4]])等
丹尼尔:大概明白了。那大模型哪些层是更重要的?然后这些层的哪些张量是更关键的?
蛋先生:┐(´-`)┌ 这是另一个深度话题了,就不展开了。顺嘴提一句 - 现在大模型基本都是 Transformer 架构,注意力层(Attention Layer)肯定算重要层之一
丹尼尔:行!所以结论是:大部分层权重 4 位,像注意力层这种重要层的一些关键张量就给更多位精度,比如 5 位或 6 位,这就是混合精度
蛋先生:总结到位!
核心优化二:两级分组+双重量化
丹尼尔:那第二点呢?
蛋先生:第二点更硬核——两级分组加双重量化
丹尼尔:等等,我觉得你有必要说下分组是怎么肥事先
蛋先生:上次聊量化(再次提示:可在各平台搜索《你瘦不下来但大模型可以:量化原理了解一下》)的时候,结尾有提到。简单理解就是把所有权重分成多个分组进行量化
丹尼尔:哦,结尾啊,好像有点印象了。算了,我等会再去翻翻。那两级?怎么讲?
蛋先生:把分好的分组内部再进行一次分组,不就两级了嘛 (˘ᵕ˘)
第一次分组的组叫超级块(Super-block),每个超级块含 256 个权重参数
[ w0 w1 w2 ... w255 ] [ w256 w257 ... w511 ] [ ... ]
└──── 超级块 1 ─────┘ └──── 超级块 2 ─────┘
256 个权重参数 256 个权重参数
然后再在每个超级块内部再进行一次分组分成 8 个子块,每个子块包含 32 个权重参数
└─────────────────── 超级块(256 个权重参数)────────────────────┘
└──── 子块 1 ─────┘ └──── ... ─────┘ └──── 子块 8 ─────┘
32 个权重参数 32 个权重参数 32 个权重参数
丹尼尔:哦,听是听懂了,但为什么要这么干呢?直接按 32 个权重参数一组一次分了不就完事了吗?
[ w0 w1 ... w31 ] [ w32 ... w63 ] [ ... ] [ w480 ... w511 ]
└──── 分组 1 ────┘ └──── 分组 2 ────┘ └──── 分组 16 ────┘
32 个权重参数 32 个权重参数 32 个权重参数
蛋先生:这就又得说回量化原理了。不太熟悉量化原理的同学,你暂且可以用对称加密来简单理解(提示:不一样的东西哈,只是借用了把 A 转成 B,把 B 转回 A 这一相同特性来说明而已)。对称加密,是不是得有一个密钥来解密?
丹尼尔:当然!
蛋先生:在量化中,密钥就是缩放因子(scale)和偏移量(min - 仅非对称量化需要)。“密钥”是需要存储的,我们先来算一笔帐:
假设有 512 个权重参数,按每 32 个权重参数一组分,一共有 16 个组
每个组要记两个“密钥”信息,假设都用 FP16,那么所有密钥得用 FP16 * 2 * 16 = 32 * 2 * 64 = 512 位
而所有的权重参数如果按 INT 量化,也就 4 * 512 位
丹尼尔:啊,模型不就又又又胖回去了?
蛋先生:对,所以得怎么处理呢?
丹尼尔:难道?是把“密钥”也压(量)缩(化)了?
蛋先生:猜对了!这就是双重量化。它不直接用 FP16 存子块的“密钥”,而是把它们再量化成 6-bit 整数。但我们知道,对“权重参数”量化需要“权重参数密钥”,那对“权重参数密钥”量化就需要“权重参数密钥的密钥”
丹尼尔:好绕口
蛋先生:哈哈!那把它放在哪好呢?肯定是往上一级放是吧。那没有上一级怎么办?那就创造上一级,也就两级分组咯。结果就是:
每个超级块额外存两个高精度 FP16 的"权重参数密钥的密钥",用来把那些 6-bit 的 “权重参数密钥” 还原回来。既保精度,又压体积
丹尼尔:这样能省多少?
蛋先生:用刚刚的示例再来算一笔帐
512 个权重参数,按 256 个权重参数进行一级分组,一共有 2 个超级块
它们存放的所有“权重参数密钥的密钥”大小为 FP16 * 2 * 2 = 64 位
每个超级块按 32 个权重参数一组一组分,同样一共有 256 / 32 * 2 = 16 个子块
所有子块的“权重参数密钥”大小为 6 * 2 * 16 = 192 位
“密钥”总共消耗 64 + 192 = 256 位,对比只进行一级分组 512 位的消耗,足足节省了一半
丹尼尔:我捋一下……超级块存 2 个 FP16,子块的只存 2 个 6-bit,靠超级块这两把"钥匙"还原。是这个意思吧?
蛋先生:正是。总结一下:子块分组小(32 个),精度保住了;双重量化又压住了元数据的体积膨胀。这就是平衡的艺术——在精度和体积之间找到那个性价比天花板
丹尼尔:多么精巧的设计啊,服了!
写在最后
亲们,都到这了,要不,点赞 / 收藏 / 关注支持下呗 o( ̄▽ ̄)d