12

CPU 混合推理,非常见大模型量化方案:“二三五六” 位量化

 9 months ago
source link: https://soulteary.com/2023/12/12/cpu-hybrid-inference-unusual-large-language-model-quantization-2-3-5-6-bit-quantization.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

CPU 混合推理,非常见大模型量化方案:“二三五六” 位量化

2023年12月12日阅读Markdown格式8528字18分钟阅读

本篇文章聊聊网上聊的比较少的具体量化操作,非常见整型位数的量化,来自让各种开源模型能够在 CPU 环境、CPU & GPU 环境混合推理的技术方案:llama.cpp 。

接下来,有计划分享一些关于各种开源模型的实践内容。为了能让更多的同学能够玩起来,降低入门的硬件门槛还是很有必要的。模型量化技术就是这样一个“神奇、有效”的方案。

看过前两篇文章《零一万物模型折腾笔记:官方 Yi-34B 模型基础使用》、《本地运行“李开复”的零一万物 34B 大模型》的同学知道,类似 34B 的模型,如果想不怎么量化直接跑起来,大概需要 76~85GB 的显存。如果我们进行效果损失比较小的 8 位量化,那么也需要 46GB 左右,如果是 4 位量化模型,那么也需要 21GB 左右的显存。

虽然模型能跑了,但是有可能模型效果“打了骨折”

是的,模型的量化其实就是这样的一个,把类似我们相机拍摄照片(RAW),转换成“PNG”,再转换成“JPG”,最后甚至转换成“GIF”的过程。我们追求的是尽量省钱,在我们的设备上保持最好效果的将模型跑起来。尽量整个接近 “PNG” 的 “JPG”,而不是一定要折腾个 “GIF” 。

尤其是一般情况下,许多同学会使用八位(INT8)或者 四位(INT4)位量化,但是我们很容易会遇到两个尴尬的情况:

  • 有的模型量化成四位(INT4)效果变的不是很好,但是量化成八位(INT8)效果还行。可是八位(INT8)尺寸又太大,运行不太方便,希望模型尺寸能够小一些。
  • 有的模型量化成四位(INT4),模型还是太大,硬件跑不起来或者跑起来太费力,希望模型变的更小巧一些。

在今年早些时候的几篇文章和对外分享里,我曾经多次提到了几种不同的模型量化方案,包括 Transformers、GGML 等,感兴趣的同学可以自行翻阅,就不多赘述了。

本篇文章,我们主要来聊聊非常见整型位数的模型量化方案。用自己制作的量化程序,将原本在本地用游戏显卡跑不起来的 YI 34B 模型跑起来。

关于模型量化需要准备两个素材,一个是模型,另外一个是量化使用的计算设备。

模型程序文件

任意参数量的模型,可以是 7B、13B、14B、20B、33B、34B、68B、70B …的模型,也可以是更小参数量的小尺寸的模型。

我这里使用的是零一万物开源的 YI-34B 的社区 finetune 微调训练的版本,通常情况下,社区可能有热门模型的量化版本,经常看到一些同学说“等个量化版本”。

但其实自己动手,丰衣足食。况且,即使是从社区下载量化版本,模型体积也很大,需要来来回回测试模型是否合适,重复下载也非常消耗时间和宽带成本,远不如自己量化来的方便。

关于模型程序下载,方法很多。如果你想要从 HuggingFace 相对快速下载模型,可以参考这篇文章中的 “模型程序下载” 来解决问题。

量化使用的硬件

而量化模型使用的硬件,需要 CPU 计算能力相对强一些的机器,如果你有 GPU,那么将会极大的提升模型量化速度,如果没有也没有关系。

至于量化后的产物,则是各种设备通用的,你可以在 Windows 量化后给 Linux 或者 macOS 设备使用。你也可以使用有 CPU 和 GPU 的设备,量化后给只有 CPU 的设备使用。

唯一有一些差异的,只有运行这个通用模型格式程序的启动程序,是需要和你当前的运行环境和操作系统有些关联的,比如需要构建,或者安装时需要一些初始化。

相比较模型,程序这个真的就是小意思啦。

模型的 GGUF (GGML Universal File)格式量化准备

GGUF 是 GGML 的全新替代型,被称为 GGML 通用文件格式。

GGUF 支持的模型量化格式非常多,刨除“几种跨开源生态模型转换的场景外”,主要依赖两个程序:convert.pyquantize 程序。前者以 Python 脚本的形态存在于 llama.cpp 项目的目录中,后者需要我们进行项目的构建。

下载 llama.cpp 的代码,然后就能够进行构建操作啦:

# 下载代码
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 混合推理的模型,那么我们可以参考这篇文章中的“尝试对模型进行几种不同的量化操作”的方法中的命令行参数,将模型转换为 GGML 的 q8_0 模型。

但如果,我们希望制作更多其他的不同的类型的模型,比如 2 位量化~ 6 位量化,那么我非常建议大家使用 convert.py 脚本制作和转换一个 f16 类型的 GGML 模型。

虽然程序的命令行参数看起来很麻烦,但是我们需要使用到的转换命令其实非常简单,使用 Python 调用程序,命令中携带“模型路径”和“输出的模型类型”即可:

python ./convert.py 【模型的路径】 --outtype f16

我这里以 YI-34B 的社区 finetune 模型 brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties 为例子,调整为我自己的模型存放路径:

python ./convert.py ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ --outtype f16

执行命令后,机器将会火力全开的进行程序编译,输出大量日志:

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=<GGMLFileType.MostlyF16: 1>, 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,除了程序的执行命令格式参考外,我们还能够看到量化程序支持的所有类型:

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_KQ5_K 这类代指名称来进行模型转换,并且在进行转换之前,偶尔执行 ./quantize --help 看看有没有新的、更适合你的量化方案。名称中的 Q数字 中的数字就是对应的量化的位数啦。

一般来说,位数越高,需要的内存和显存就越多,运行起来越慢,但是效果和精度就越接近原始版本。反之,我们虽然得到了省资源的版本,但是效果会有明显的降低。不过,如果你模型跑不起来,效果是零,这种情况下能够量化跑起来的模型,总归是比没有强,某种程度来说,也是不得已而为之。

帮助信息中展示的如何使用命令行的演示信息,同样是内容比较多比较复杂的。但是同样的,我们实际只需要非常简单的使用方法,记住下面的调用方式就足够了:

./quantize 【模型地址】【模型类型名称】

如果我们将命令中的“变量”进行替换,改成本文中我使用的模型和我选择的量化方案,命令行如下:

./quantize ../brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties/ggml-model-f16.gguf Q5_K_M

命令执行过程中,我们将看到滚动的日志:

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

转换完毕,我们前往模型的目录,查看文件大小,能够看到非常明显的尺寸缩减:

# du -hs *
23G	ggml-model-Q5_K_M.gguf
65G	ggml-model-f16.gguf

至于模型的使用,就太简单啦。

我们可以使用 llama.cpp 项目中的 mainserver 来运行模型,前者会在命令行中启动一个交互式的终端,后者则会启动一个简洁的 Web UI,我们在浏览器中就可以轻松的调节模型的调用参数。

在前两篇相关的文章中有提到过,这里我们再聊一次,顺便聊聊不同的参数的选择和调整策略。

我一般的“起手式”是这样:

./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 进行角色扮演游戏
使用量化后的 Yi-34B 进行角色扮演游戏

是不是很有趣,后面有机会我们聊聊上面这个交互体验是如何实现的。

这篇内容就先写到这里,接下来的相关文章,我们来聊聊社区里不同优化方向和模型的模型们。

希望大家玩模型,都能玩的开心。感谢所有为开源模型辛苦工作的模型创作者团队,和所有为开源社区摇旗呐喊,将好的东西分享给更多的人的朋友们。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK