大模型本地部署神器的瘦身进阶原理,就这两招?

简介: 想本地跑大模型却显存告急?llama.cpp 的量化优化算法藏着一门精妙的平衡艺术:混合精度加双重量化,又瘦又精。凭啥又轻又能打?咱们来一探究竟!#llama.cpp #大模型量化 #Q4_K_M #Ollama #大模型本地部署 #模型压缩 #混合精度 #量化原理 #INT4量化 #本地大模型 #权重优化 #知识分享 #原理 #人工智能 #深度学习

文章标题.png

温馨提示:另有配套视频版,附带图解演示,观感不同,欢迎到各大视频平台搜索同名标题或【蛋先生说识】

开场

丹尼尔:蛋兄,本地跑大模型,有啥好工具推荐不?

蛋先生: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

目录
相关文章
人工智能 缓存 前端开发
11396 55
人工智能 JavaScript 开发工具
4395 13
开发工具 Swift git
1756 4
人工智能 Java BI
1089 1
人工智能 JavaScript 测试技术
1817 2
Web App开发 人工智能 API
893 1
缓存 JavaScript Shell
1967 3
人工智能 JavaScript 测试技术
902 4

热门文章

最新文章