本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2023年12月12日 统计字数: 8528字 阅读时间: 18分钟阅读 本文链接: https://soulteary.com/2023/12/12/cpu-hybrid-inference-unusual-large-language-model-quantization-2-3-5-6-bit-quantization.html ----- # CPU 混合推理,非常见大模型量化方案:“二三五六” 位量化 本篇文章聊聊网上聊的比较少的具体量化操作,非常见整型位数的量化,来自让各种开源模型能够在 CPU 环境、CPU & GPU 环境混合推理的技术方案:llama.cpp 。 ## 写在前面 接下来,有计划分享一些关于各种开源模型的实践内容。为了能让更多的同学能够玩起来,降低入门的硬件门槛还是很有必要的。模型量化技术就是这样一个“神奇、有效”的方案。 看过前两篇文章《[零一万物模型折腾笔记:官方 Yi-34B 模型基础使用](https://soulteary.com/2023/12/10/notes-on-the-01-ai-model-basic-use-of-the-official-yi-34b.html)》、《[本地运行“李开复”的零一万物 34B 大模型](https://soulteary.com/2023/11/26/locally-run-the-yi-34b-large-model-of-kai-fu-lee.html)》的同学知道,类似 34B 的模型,如果想不怎么量化直接跑起来,大概需要 76~85GB 的显存。如果我们进行效果损失比较小的 8 位量化,那么也需要 46GB 左右,如果是 4 位量化模型,那么也需要 21GB 左右的显存。 **虽然模型能跑了,但是有可能模型效果“打了骨折”**。 是的,模型的量化其实就是这样的一个,把类似我们相机拍摄照片(RAW),转换成“PNG”,再转换成“JPG”,最后甚至转换成“GIF”的过程。我们追求的是尽量省钱,在我们的设备上保持最好效果的将模型跑起来。尽量整个接近 “PNG” 的 “JPG”,而不是一定要折腾个 “GIF” 。 尤其是一般情况下,许多同学会使用八位(INT8)或者 四位(INT4)位量化,但是我们很**容易会遇到**两个尴尬的情况: - 有的模型量化成四位(INT4)效果变的不是很好,但是量化成八位(INT8)效果还行。可是八位(INT8)尺寸又太大,运行不太方便,希望模型尺寸能够小一些。 - 有的模型量化成四位(INT4),模型还是太大,硬件跑不起来或者跑起来太费力,希望模型变的更小巧一些。 在今年早些时候的几篇文章和对外分享里,我曾经多次提到了几种不同的模型量化方案,包括 Transformers、GGML 等,感兴趣的同学[可以自行翻阅](https://soulteary.com/tags/python.html),就不多赘述了。 本篇文章,我们主要来聊聊非常见整型位数的模型量化方案。用自己制作的量化程序,将原本在本地用游戏显卡跑不起来的 YI 34B 模型跑起来。 ## 准备材料 关于模型量化需要准备两个素材,一个是模型,另外一个是量化使用的计算设备。 ### 模型程序文件 任意参数量的模型,可以是 7B、13B、14B、20B、33B、34B、68B、70B ...的模型,也可以是更小参数量的小尺寸的模型。 我这里使用的是零一万物开源的 YI-34B 的社区 finetune 微调训练的版本,通常情况下,社区可能有热门模型的量化版本,经常看到一些同学说“等个量化版本”。 **但其实自己动手,丰衣足食。况且,即使是从社区下载量化版本,模型体积也很大,需要来来回回测试模型是否合适,重复下载也非常消耗时间和宽带成本,远不如自己量化来的方便。** 关于模型程序下载,方法很多。如果你想要从 HuggingFace 相对快速下载模型,可以参考这篇文章中的 “[模型程序下载](https://soulteary.com/2023/11/26/locally-run-the-yi-34b-large-model-of-kai-fu-lee.html#%E4%B8%8B%E8%BD%BD%E5%90%88%E9%80%82%E7%9A%84%E6%A8%A1%E5%9E%8B%E6%96%87%E4%BB%B6)” 来解决问题。 ### 量化使用的硬件 而量化模型使用的硬件,需要 CPU 计算能力相对强一些的机器,如果你有 GPU,那么将会极大的提升模型量化速度,如果没有也没有关系。 至于量化后的产物,则是各种设备通用的,你可以在 Windows 量化后给 Linux 或者 macOS 设备使用。你也可以使用有 CPU 和 GPU 的设备,量化后给只有 CPU 的设备使用。 唯一有一些差异的,只有运行这个通用模型格式程序的启动程序,是需要和你当前的运行环境和操作系统有些关联的,比如需要构建,或者安装时需要一些初始化。 相比较模型,程序这个真的就是小意思啦。 ## 模型的 GGUF (GGML Universal File)格式量化准备 GGUF 是 GGML 的全新替代型,被称为 GGML 通用文件格式。 GGUF 支持的模型量化格式非常多,刨除“几种跨开源生态模型转换的场景外”,主要依赖两个程序:`convert.py` 和 `quantize` 程序。前者以 Python 脚本的形态存在于 llama.cpp 项目的目录中,后者需要我们进行项目的构建。 下载 llama.cpp 的代码,然后就能够进行构建操作啦: ```bash # 下载代码 git clone https://github.com/ggerganov/llama.cpp.git # 切换工作目录到项目文件夹内 cd llama.cpp # 构建项目可执行文件(有显卡) make -j LLAMA_CUBLAS=1 # 构建项目执行文件 (没有显卡) make -j ``` 等待程序构建完毕,所有的准备工作就都完成啦。 ## 预转换:Convert.py 转换脚本 这个脚本能够将非 GGML 格式的文件转换为 GGML,以 GGUF 后缀进行保存。程序默认支持转换下面几类格式的模型:`*.pth`、`*.pt`、`*.bin`、`*.safetensors`。 **是我们进行后续非常见整型模型量化的基础操作步骤。** 如果我们只追求使用 8 位量化的,可以使用 CPU 和 GPU 混合推理的模型,那么我们可以参考这篇文章中的“[尝试对模型进行几种不同的量化操作](https://soulteary.com/2023/12/10/notes-on-the-01-ai-model-basic-use-of-the-official-yi-34b.html#%E5%B0%9D%E8%AF%95%E5%AF%B9%E6%A8%A1%E5%9E%8B%E8%BF%9B%E8%A1%8C%E5%87%A0%E7%A7%8D%E4%B8%8D%E5%90%8C%E7%9A%84%E9%87%8F%E5%8C%96%E6%93%8D%E4%BD%9C)”的方法中的命令行参数,将模型转换为 GGML 的 `q8_0` 模型。 但如果,我们希望制作更多其他的不同的类型的模型,比如 2 位量化~ 6 位量化,那么我非常建议大家使用 `convert.py` 脚本制作和转换一个 `f16` 类型的 GGML 模型。 虽然程序的命令行参数看起来很麻烦,但是我们需要使用到的转换命令其实非常简单,使用 Python 调用程序,命令中携带“模型路径”和“输出的模型类型”即可: ```bash python ./convert.py 【模型的路径】 --outtype f16 ``` 我这里以 YI-34B 的社区 finetune 模型 `brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties` 为例子,调整为我自己的模型存放路径: ```bash python ./convert.py ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ --outtype f16 ``` 执行命令后,机器将会火力全开的进行程序编译,输出大量日志: ```bash Loading model file ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/model-00001-of-00008.safetensors Loading model file ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/model-00001-of-00008.safetensors Loading model file ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/model-00002-of-00008.safetensors ... params = Params(n_vocab=64000, n_embd=7168, n_layer=60, n_ctx=200000, n_ff=20480, n_head=56, n_head_kv=8, f_norm_eps=1e-05, rope_scaling_type=None, f_rope_freq_base=5000000.0, f_rope_scale=None, n_orig_ctx=None, rope_finetuned=None, ftype=, path_model=PosixPath('../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties')) Loading vocab file '../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/tokenizer.model', type 'spm' Permuting layer 0 Permuting layer 1 Permuting layer 2 ... model.embed_tokens.weight -> token_embd.weight | BF16 | [64000, 7168] model.layers.0.input_layernorm.weight -> blk.0.attn_norm.weight | BF16 | [7168] ... Writing ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ggml-model-f16.gguf, format 1 gguf: This GGUF file is for Little Endian only gguf: Setting special token type bos to 1 gguf: Setting special token type eos to 2 gguf: Setting special token type unk to 0 gguf: Setting special token type pad to 0 [ 1/543] Writing tensor token_embd.weight | size 64000 x 7168 | type F16 | T+ 1 [ 2/543] Writing tensor blk.0.attn_norm.weight | size 7168 | type F32 | T+ 2 [ 3/543] Writing tensor blk.0.ffn_down.weight | size 7168 x 20480 | type F16 | T+ 2 ... Wrote ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ggml-model-f16.gguf ``` 当一切都结束后,我们能够看到类似上面日志的最后一行提示,告诉我们转换好的模型名字和存放到机器的什么位置了。默认会存放于你指定要转换模型的目录,并以 `.gguf` 后缀保存文件。 ## 最终转换:quantize 量化程序 当我们使用上面的方法,将模型转换为了相对高精度的模型文件后,我们就可以进行下一步量化了。 执行 `./quantize --help`,除了程序的执行命令格式参考外,我们还能够看到量化程序支持的所有类型: ```bash Allowed quantization types: 2 or Q4_0 : 3.56G, +0.2166 ppl @ LLaMA-v1-7B 3 or Q4_1 : 3.90G, +0.1585 ppl @ LLaMA-v1-7B 8 or Q5_0 : 4.33G, +0.0683 ppl @ LLaMA-v1-7B 9 or Q5_1 : 4.70G, +0.0349 ppl @ LLaMA-v1-7B 10 or Q2_K : 2.63G, +0.6717 ppl @ LLaMA-v1-7B 12 or Q3_K : alias for Q3_K_M 11 or Q3_K_S : 2.75G, +0.5551 ppl @ LLaMA-v1-7B 12 or Q3_K_M : 3.07G, +0.2496 ppl @ LLaMA-v1-7B 13 or Q3_K_L : 3.35G, +0.1764 ppl @ LLaMA-v1-7B 15 or Q4_K : alias for Q4_K_M 14 or Q4_K_S : 3.59G, +0.0992 ppl @ LLaMA-v1-7B 15 or Q4_K_M : 3.80G, +0.0532 ppl @ LLaMA-v1-7B 17 or Q5_K : alias for Q5_K_M 16 or Q5_K_S : 4.33G, +0.0400 ppl @ LLaMA-v1-7B 17 or Q5_K_M : 4.45G, +0.0122 ppl @ LLaMA-v1-7B 18 or Q6_K : 5.15G, -0.0008 ppl @ LLaMA-v1-7B 7 or Q8_0 : 6.70G, +0.0004 ppl @ LLaMA-v1-7B 1 or F16 : 13.00G @ 7B 0 or F32 : 26.00G @ 7B COPY : only copy tensors, no quantizing ``` 这里,我建议始终使用 `Q4_K`、`Q5_K` 这类代指名称来进行模型转换,并且在进行转换之前,偶尔执行 `./quantize --help` 看看有没有新的、更适合你的量化方案。名称中的 `Q数字` 中的数字就是对应的量化的位数啦。 一般来说,位数越高,需要的内存和显存就越多,运行起来越慢,但是效果和精度就越接近原始版本。反之,我们虽然得到了省资源的版本,但是效果会有明显的降低。不过,如果你模型跑不起来,效果是零,这种情况下能够量化跑起来的模型,总归是比没有强,某种程度来说,也是不得已而为之。 帮助信息中展示的如何使用命令行的演示信息,同样是内容比较多比较复杂的。但是同样的,我们实际只需要非常简单的使用方法,记住下面的调用方式就足够了: ```bash ./quantize 【模型地址】【模型类型名称】 ``` 如果我们将命令中的“变量”进行替换,改成本文中我使用的模型和我选择的量化方案,命令行如下: ```bash ./quantize ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ggml-model-f16.gguf Q5_K_M ``` 命令执行过程中,我们将看到滚动的日志: ```bash ggml_init_cublas: GGML_CUDA_FORCE_MMQ: no ggml_init_cublas: CUDA_USE_TENSOR_CORES: yes ggml_init_cublas: found 1 CUDA devices: Device 0: NVIDIA GeForce RTX 4090, compute capability 8.9 main: build = 1622 (8a7b2fa) main: built with cc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 for x86_64-linux-gnu main: quantizing '../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ggml-model-f16.gguf' to '../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ggml-model-Q5_K_M.gguf' as Q5_K_M llama_model_loader: loaded meta data with 20 key-value pairs and 543 tensors from ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ggml-model-f16.gguf (version GGUF V3 (latest)) llama_model_loader: - tensor 0: token_embd.weight f16 [ 7168, 64000, 1, 1 ] llama_model_loader: - tensor 1: blk.0.attn_norm.weight f32 [ 7168, 1, 1, 1 ] ... llama_model_loader: - tensor 542: output_norm.weight f32 [ 7168, 1, 1, 1 ] llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output. llama_model_loader: - kv 0: general.architecture str = llama llama_model_loader: - kv 1: general.name str = brucethemoose llama_model_loader: - kv 2: llama.context_length u32 = 200000 llama_model_loader: - kv 3: llama.embedding_length u32 = 7168 ... [ 541/ 543] blk.59.ffn_down.weight - [20480, 7168, 1, 1], type = f16, quantizing to q6_K .. size = 280.00 MiB -> 114.84 MiB | hist: [ 542/ 543] blk.59.ffn_norm.weight - [ 7168, 1, 1, 1], type = f32, size = 0.027 MB [ 543/ 543] output_norm.weight - [ 7168, 1, 1, 1], type = f32, size = 0.027 MB llama_model_quantize_internal: model size = 65593.31 MB llama_model_quantize_internal: quant size = 23193.68 MB main: quantize time = 99803.07 ms main: total time = 99803.07 ms ``` 转换完毕,我们前往模型的目录,查看文件大小,能够看到非常明显的尺寸缩减: ```bash # du -hs * 23G ggml-model-Q5_K_M.gguf 65G ggml-model-f16.gguf ``` 至于模型的使用,就太简单啦。 我们可以使用 llama.cpp 项目中的 `main` 和 `server` 来运行模型,前者会在命令行中启动一个交互式的终端,后者则会启动一个简洁的 Web UI,我们在浏览器中就可以轻松的调节模型的调用参数。 在前两篇相关的文章中有提到过,这里我们再聊一次,顺便聊聊不同的参数的选择和调整策略。 我一般的“起手式”是这样: ```bash ./server --ctx-size 2048 --host 0.0.0.0 --n-gpu-layers 50 --model ../playground/01-ai/Yi-34B/ggml-model-q8_0.gguf ``` 上面的命令中,分别包含了: - “`--ctx-size`” 指定模型会话窗口大小,所能容纳和使用的 Token 数量,这里如果想省点显存和内存,就设置小一些,**能够满足你的任务需要就好**。如果资源比较多,可以开到模型的上限,比如最近的默认模型都是 `4k` 的,就设置 4096,8K 就设置 8192,200K 就设置个 200000,以此类推。 - “`--host`” 默认其实可以不添加,但是如果你在局域网,或者你的服务运行在容器里,那么你需要分享或者在容器外访问,设置 `--host 0.0.0.0` 就很有必要啦。 - “`--n-gpu-layers`” 这个参数需要配合显卡一起使用,如果你有显卡,但是显卡装不下模型,或者装下模型后快满了,可以考虑适当调整下数值,根据自己的需求,将合适的模型层数扔到显卡里,稍微给显卡留点富余量。扔到显卡里的模型层数越多,推理速度越快。 - “`--model`” 这个参数没有什么特别的,指定我们下载或者转换好的 GGML 模型文件就好。 好啦,当这个命令执行后,我们就能够快乐的和模型一起玩耍啦。 ![使用量化后的 Yi-34B 进行角色扮演游戏](https://attachment.soulteary.com/2023/12/12/yi-34b-role-play.jpg) 是不是很有趣,后面有机会我们聊聊上面这个交互体验是如何实现的。 ## 最后 这篇内容就先写到这里,接下来的相关文章,我们来聊聊社区里不同优化方向和模型的模型们。 希望大家玩模型,都能玩的开心。感谢所有为开源模型辛苦工作的模型创作者团队,和所有为开源社区摇旗呐喊,将好的东西分享给更多的人的朋友们。 --EOF