GGML vs. GPTQ vs. NF4

由于大型语言模型(LLMs)的庞大体量,量化已成为一种运行它们的有效技术。通过减少权重的精度,您可以节省内存并加快推理速度,同时保持模型的大部分性能。最近,8位和4位量化解锁了在消费者硬件上运行LLMs的可能性。再加上Llama模型的发布和参数高效的微调技术(LoRA,QLoRA),这就创造了一个与OpenAI的GPT-3.5和GPT-4竞争的本地LLMs的丰富生态系统。
除了本文介绍的简单方法外,还有三种主要的量化技术:NF4,GPTQ和GGML。NF4是QLoRA使用的一种静态方法,用于以4位精度加载模型进行微调。在之前的一篇文章中,我们探索了GPTQ方法,并将自己的模型量化以在消费者GPU上运行。在本文中,我们将介绍GGML技术,了解如何量化Llama模型,并提供实现最佳结果的技巧和诀窍。
您可以在Google Colab和GitHub上找到代码。
GGML是什么?
GGML是一个专注于机器学习的C库。它由Georgi Gerganov创建,这就是“GG”缩写的含义。这个库不仅提供了机器学习的基础元素,如张量,还提供了一种独特的二进制格式来分发LLMs。
这个格式最近更改为GGUF。这种新格式被设计为可扩展的,因此新功能不应破坏与现有模型的兼容性。它还将所有元数据集中在一个文件中,例如特殊标记,RoPE缩放参数等。简而言之,它解决了一些历史上的痛点,并且应该具备未来兼容性。有关更多信息,请阅读此地址的规范。在本文的其余部分,我们将称呼所有使用GGUF或以前格式的模型为“GGML模型”。
GGML旨在与llama.cpp库一起使用,这也是由Georgi Gerganov创建的。这个库使用C/C++编写,用于高效推理Llama模型。它可以加载GGML模型并在CPU上运行。最初,这是与GPTQ模型的主要区别,后者在GPU上加载和运行。但是,现在您可以使用llama.cpp将LLM的某些层次转移到GPU上。举个例子,一个7b参数模型有35个层次。这大大加快了推理速度,并允许您运行不适合VRAM的LLMs。

如果命令行工具是您的选择,那么llama.cpp和GGUF的支持已集成到许多GUI中,如oobabooga的文本生成Web界面,koboldcpp,LM Studio或ctransformers。您可以使用这些工具轻松加载GGML模型,并以ChatGPT的方式与它们交互。幸运的是,许多量化模型直接在Hugging Face Hub上可用。您很快就会注意到,大多数模型都是由LLM社区中广受欢迎的人物TheBloke量化的。
在下一节中,我们将看到如何量化我们自己的模型并在消费者GPU上运行。
如何使用GGML量化LLMs?
让我们来看看TheBloke/Llama-2–13B-chat-GGML仓库中的文件。我们可以看到14种不同的GGML模型,对应不同类型的量化。它们遵循特定的命名约定:用于存储权重(精度)的位数(precision)前面加上“q”,然后是特定的变体。以下是根据TheBloke制作的模型卡片列出的所有可能的量化方法及其相应的用例列表:
q2_k:使用 Q4_K 作为 attention.vw 和 feed_forward.w2 张量的注意机制,使用 Q2_K 作为其他张量的注意机制。q3_k_l:对于 attention.wv、attention.wo 和 feed_forward.w2 张量,使用 Q5_K,否则使用 Q3_K。q3_k_m:对于 attention.wv、attention.wo 和 feed_forward.w2 张量,使用 Q4_K,否则使用 Q3_K。q3_k_s:所有张量都使用 Q3_K。q4_0:原始量化方法,4位。q4_1:比 q4_0 更高的准确性,但不及 q5_0。但是推理速度比 q5 模型快。q4_k_m:对于一半的 attention.wv 和 feed_forward.w2 张量,使用 Q6_K,否则使用 Q4_K。q4_k_s:所有张量都使用 Q4_K。q5_0:更高的准确性,更高的资源使用和较慢的推理速度。q5_1:更高的准确性,更高的资源使用和较慢的推理速度。q5_k_m:对于一半的 attention.wv 和 feed_forward.w2 张量,使用 Q6_K,否则使用 Q5_K。q5_k_s:所有张量都使用 Q5_K。q6_k:所有张量都使用 Q8_K。q8_0:几乎与 float16 无法区分。资源使用高且慢。不建议大多数用户使用。
作为经验法则,我建议使用 Q5_K_M,因为它保留了大部分模型的性能。或者,如果你想节省一些内存,可以使用 Q4_K_M。总的来说,K_M 版本比 K_S 版本更好。我不能推荐 Q2 或 Q3 版本,因为它们会大大降低模型性能。
现在我们对可用的量化类型有了更多了解,让我们看看如何在一个真实的模型上使用它们。你可以在 Google Colab 上的免费 T4 GPU上执行以下代码。第一步是编译 llama.cpp 并在我们的 Python 环境中安装所需的库。
# 安装 llama.cpp!git clone https://github.com/ggerganov/llama.cpp!cd llama.cpp && git pull && make clean && LLAMA_CUBLAS=1 make!pip install -r llama.cpp/requirements.txt
现在我们可以下载我们的模型。我们将使用之前文章中微调的模型,mlabonne/EvolCodeLlama-7b。
MODEL_ID = "mlabonne/EvolCodeLlama-7b"# 下载模型!git lfs install!git clone https://huggingface.co/{MODEL_ID}
这一步可能需要一些时间。完成后,我们需要将权重转换为 GGML FP16 格式。
MODEL_NAME = MODEL_ID.split('/')[-1]GGML_VERSION = "gguf"# 转换为 fp16fp16 = f"{MODEL_NAME}/{MODEL_NAME.lower()}.{GGML_VERSION}.fp16.bin"!python llama.cpp/convert.py {MODEL_NAME} --outtype f16 --outfile {fp16}
最后,我们可以使用一种或多种方法对模型进行量化。在本例中,我们将使用我之前推荐的 Q4_K_M 和 Q5_K_M 方法。这是唯一一个实际需要 GPU 的步骤。
QUANTIZATION_METHODS = ["q4_k_m", "q5_k_m"]for method in QUANTIZATION_METHODS: qtype = f"{MODEL_NAME}/{MODEL_NAME.lower()}.{GGML_VERSION}.{method}.bin" !./llama.cpp/quantize {fp16} {qtype} {method}
我们的两个量化模型现在准备好进行推理了。我们可以检查二进制文件的大小,看看我们压缩了多少。FP16 模型占用 13.5 GB,而 Q4_K_M 模型占用 4.08 GB(缩小了 3.3 倍),Q5_K_M 模型占用 4.78 GB(缩小了 2.8 倍)。
让我们使用llama.cpp来高效地运行它们。由于我们使用了一块具有16 GB VRAM的GPU,我们可以将每一层都卸载到GPU上运行。在这种情况下,它表示35层(7b参数模型),所以我们将使用-ngl 35参数。在下面的代码块中,我们还将输入一个提示和我们想要使用的量化方法。
import osmodel_list = [file for file in os.listdir(MODEL_NAME) if GGML_VERSION in file]prompt = input("输入您的提示:")chosen_method = input("请指定要运行模型的量化方法(选项:" + "、".join(model_list) + "):")# 验证所选方法是否在列表中if chosen_method not in model_list: print("选择的方法无效!")else: qtype = f"{MODEL_NAME}/{MODEL_NAME.lower()}.{GGML_VERSION}.{method}.bin" !./llama.cpp/main -m {qtype} -n 128 --color -ngl 35 -p "{prompt}"
让我们使用Q5_K_M方法向模型提问“编写一个打印第n个斐波那契数的Python函数”。如果我们查看日志,我们可以确认我们成功地将层卸载到了GPU上,得益于“llm_load_tensors: offloaded 35/35 layers to GPU”这一行。这是模型生成的代码:
def fib(n): if n == 0 or n == 1: return n return fib(n - 2) + fib(n - 1)for i in range(1, 10): print(fib(i))
这个提示并不是很复杂,但它成功地在很短的时间内生成了一段可工作的代码。使用这个GGML,您可以使用交互模式(-i标志)在终端上使用您的本地LLM作为助手。请注意,这也适用于配有苹果的Macbook的Metal Performance Shaders(MPS),这是运行LLM的一个很好的选择。
最后,我们可以将我们的量化模型推送到Hugging Face Hub上的一个新存储库,后缀为“-GGUF”。首先,让我们登录并修改以下代码块以匹配您的用户名。
!pip install -q huggingface_hubusername = "mlabonne"from huggingface_hub import notebook_login, create_repo, HfApinotebook_login()
现在我们可以创建存储库并上传我们的模型。我们使用allow_patterns参数来过滤要上传的文件,以便不推送整个目录。
api = HfApi()# 创建存储库create_repo( repo_id=f"{username}/{MODEL_NAME}-GGML", repo_type="model", exist_ok=True)# 上传bin模型api.upload_folder( folder_path=MODEL_NAME, repo_id=f"{username}/{MODEL_NAME}-GGML", allow_patterns=f"*{GGML_VERSION}*",)
我们已成功地对GGML模型进行了量化、运行和推送到Hugging Face Hub!在下一节中,我们将探讨GGML如何实际量化这些模型。
使用GGML进行量化
GGML进行权重量化的方式并不像GPTQ那样复杂。基本上,它将值的块分组并将它们舍入到较低的精度。一些技术,如Q4_K_M和Q5_K_M,实现了关键层的较高精度。在这种情况下,每个权重都以4位精度存储,attention.wv和feed_forward.w2张量的一半例外。实验上,这种混合精度在准确性和资源使用之间取得了良好的平衡。
如果我们查看ggml.c文件,我们可以看到如何定义这些块。例如,block_q4_0结构被定义为:
#define QK4_0 32typedef struct { ggml_fp16_t d; // delta uint8_t qs[QK4_0 / 2]; // nibbles / quants} block_q4_0;
在GGML中,权重按块处理,每个块由32个值组成。对于每个块,从最大权重值中派生出一个比例因子(delta)。然后,对该块中的所有权重进行缩放、量化和高效打包以进行存储(nibbles)。这种方法显著减少了存储要求,同时允许在原始权重和量化权重之间进行相对简单和确定性的转换。
现在我们对量化过程有了更多了解,我们可以将结果与NF4和GPTQ进行比较。
NF4 vs. GGML vs. GPTQ
哪种技术在4位量化方面更好?为了回答这个问题,我们需要介绍运行这些量化LLM的不同后端。对于GGML模型,使用带有Q4_K_M模型的llama.cpp是一个好选择。对于GPTQ模型,我们有两个选项:AutoGPTQ或ExLlama。最后,NF4模型可以直接在transformers中使用--load-in-4bit标志来运行。
Oobabooga在一篇优秀的博客文章中进行了多次实验,比较了不同模型的困惑度(越低越好):

根据这些结果,我们可以说GGML模型在困惑度方面稍微有优势。差异并不特别显著,这就是为什么在生成速度方面以tokens/second为单位更好。最佳技术取决于您的GPU:如果您有足够的VRAM来适应整个量化模型,GPTQ with ExLlama将是最快的。如果情况不是这样,您可以卸载一些层并使用GGML models with llama.cpp来运行您的LLM。
结论
在本文中,我们介绍了GGML库和新的GGUF格式,以高效地存储这些量化模型。我们使用它以不同的格式(Q4_K_M和Q5_K_M)对我们自己的Llama模型进行了量化。然后,我们运行了GGML模型并将我们的bin文件推送到Hugging Face Hub。最后,我们深入研究了GGML的代码,了解它如何实际量化权重,并将其与NF4和GPTQ进行了比较。
量化是降低运行LLM成本的一种强大手段。在未来,混合精度和其他技术将不断改善我们通过量化权重可以实现的性能。在那之前,我希望您喜欢阅读本文并学到了一些新知识。
如果您对LLM周围的更多技术内容感兴趣,请在VoAGI上关注我。
有关量化的文章
第一部分:权重量化简介
使用8位量化减小大型语言模型的大小
towardsdatascience.com
第二部分:使用GPTQ进行4位量化
使用AutoGPTQ对自己的LLM进行量化
towardsdatascience.com
了解更多关于机器学习的内容,并通过一键支持我的工作-在这里成为VoAGI会员:
使用我的推荐链接加入VoAGI- Maxime Labonne
作为VoAGI会员,您的会员费的一部分将用于支付您阅读的作者,并且您将获得对每个故事的完全访问权限…
VoAGI.com