大語言模型量化方法對(duì)比:GPTQ、GGUF、AWQ
在過去的一年里,大型語言模型(llm)有了飛速的發(fā)展,在本文中,我們將探討幾種(量化)的方式,除此以外,還會(huì)介紹分片及不同的保存和壓縮策略。
說明:每次加載LLM示例后,建議清除緩存,以防止出現(xiàn)OutOfMemory錯(cuò)誤。
del model, tokenizer, pipe
import torch
torch.cuda.empty_cache()
如果在jupyter中無法釋放顯存,請(qǐng)重啟這個(gè)jupyter notebook。
模型加載
加載LLM的最直接、最普通的方式是通過??Transformers。HuggingFace已經(jīng)創(chuàng)建了一個(gè)套件,我們能夠直接使用
pip install git+https://github.com/huggingface/transformers.git
pip install accelerate bitsandbytes xformers
安裝完成后,我們可以使用以下管道輕松加載LLM:
from torch import bfloat16
from transformers import pipeline
# Load in your LLM without any compression tricks
pipe = pipeline(
"text-generation",
model="HuggingFaceH4/zephyr-7b-beta",
torch_dtype=bfloat16,
device_map="auto"
)
我們這里使用zephyr-7b-beta作為示例
這種加載LLM的方法通常不會(huì)執(zhí)行任何壓縮技巧。我們來做個(gè)使用的示例
messages = [
{
"role": "system",
"content": "You are a friendly chatbot.",
},
{
"role": "user",
"content": "Tell me a funny joke about Large Language Models."
},
]
prompt = pipe.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
使用內(nèi)部提示模板生成的提示是這樣構(gòu)造的:
然后,我們可將提示傳遞給LLM來生成答案:
outputs = pipe(
prompt,
max_new_tokens=256,
do_sample=True,
temperature=0.1,
top_p=0.95
)
print(outputs[0]["generated_text"])
這是一個(gè)最直接的使用流程,但是對(duì)于純推理,這種方法效率是最低的,因?yàn)樵跊]有任何壓縮或量化策略的情況下加載整個(gè)模型。
分片
在我們進(jìn)入量化策略之前,我們先介紹一個(gè)前置的方法:分片。通過分片可以將模型分割成小塊,每個(gè)分片包含模型的較小部分,通過在不同設(shè)備上分配模型權(quán)重來解決GPU內(nèi)存限制。
雖然它沒有任何的壓縮和量化,但是這種方法算是一個(gè)最簡單的加載大模型的方案。
比如Zephyr-7B-β,實(shí)際上已經(jīng)分片了!如果進(jìn)入模型并點(diǎn)擊“Files and versions”鏈接,可以看到模型被分成了8個(gè)部分。
模型的分片非常簡單,可以直接使用Accelerate 包:
from accelerate import Accelerator
# Shard our model into pieces of 1GB
accelerator = Accelerator()
accelerator.save_model(
model=pipe.model,
save_directory="/content/model",
max_shard_size="4GB"
)
這樣將模型分成4GB的分片
量化
大型語言模型由一堆權(quán)重和激活表示。這些值通常由通常的32位浮點(diǎn)(float32)數(shù)據(jù)類型表示。
比特的數(shù)量告訴你它可以表示多少個(gè)值。Float32可以表示1.18e-38和3.4e38之間的值,相當(dāng)多的值!比特?cái)?shù)越少,它能表示的值就越少。
如果我們選擇較低的位大小,那么模型就會(huì)變得不那么準(zhǔn)確,但它表示更少的值,從而降低其大小和內(nèi)存需求。
量化是指將LLM從其原始Float32表示轉(zhuǎn)換為更小的表示。我們不希望簡單地使用較小的位變體,而是希望在不丟失太多信息的情況下將較大的位表示映射到較小的位。
所以一般情況下,我們經(jīng)常使用一種名為4bit-NormalFloat (NF4)的新格式來實(shí)現(xiàn)這一點(diǎn)。這個(gè)數(shù)據(jù)類型做了一些特殊的技巧,以便有效地表示更大的位數(shù)據(jù)類型。它包括三個(gè)步驟:
歸一化:將模型的權(quán)重歸一化,以便我們期望權(quán)重落在一定范圍內(nèi)。這允許更有效地表示更常見的值。
量化:將權(quán)重量化為4位。在NF4中,量化級(jí)別相對(duì)于歸一化權(quán)重是均勻間隔的,從而有效地表示原始的32位權(quán)重。
去量化:雖然權(quán)重以4位存儲(chǔ),但它們在計(jì)算期間被去量化,從而在推理期間提高性能。
我們可以直接使用Bitsandbytes庫進(jìn)行量化操作:
from transformers import BitsAndBytesConfig
from torch import bfloat16
# Our 4-bit configuration to load the LLM with less GPU memory
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, # 4-bit quantization
bnb_4bit_quant_type='nf4', # Normalized float 4
bnb_4bit_use_double_quant=True, # Second quantization after the first
bnb_4bit_compute_dtype=bfloat16 # Computation type
)
上面的配置指定要使用的量化級(jí)別。比如4位量化表示權(quán)重,但用16位進(jìn)行推理。
然后在管道中加載模型就很簡單了:
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
# Zephyr with BitsAndBytes Configuration
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-alpha")
model = AutoModelForCausalLM.from_pretrained(
"HuggingFaceH4/zephyr-7b-alpha",
quantization_cnotallow=bnb_config,
device_map='auto',
)
# Create a pipeline
pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')
接下來使用與之前相同的提示:
outputs = pipe(
prompt,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.95
)
print(outputs[0]["generated_text"])
量化是一種強(qiáng)大的技術(shù),可以減少模型的內(nèi)存需求,同時(shí)保持性能相似。它允許更快的加載、使用和微調(diào)llm,即使使用較小的gpu。
預(yù)量化(GPTQ、AWQ、GGUF)
我們已經(jīng)探索了分片和量化技術(shù)。但是量化是在每次加載模型時(shí)進(jìn)行的,這是非常耗時(shí)的操作,有沒有辦法直接保存量化后的模型,并且在使用時(shí)直接加載呢?
TheBloke是HuggingFace上的一個(gè)用戶,它為我們執(zhí)行了一系列量化操作,我想用過大模型的人一定對(duì)它非常的熟悉吧
這些量化模型包含了很多格式GPTQ、GGUF和AWQ,我們來進(jìn)行介紹
1、GPTQ: Post-Training Quantization for GPT Models
GPTQ是一種4位量化的訓(xùn)練后量化(PTQ)方法,主要關(guān)注GPU推理和性能。
該方法背后的思想是,嘗試通過最小化該權(quán)重的均方誤差將所有權(quán)重壓縮到4位。在推理過程中,它將動(dòng)態(tài)地將其權(quán)重去量化為float16,以提高性能,同時(shí)保持低內(nèi)存。
我們需要在HuggingFace Transformers中的gptq類模型中加載:
pip install optimum
pip install auto-gptq --extra-index-url https://huggingface.github.io/autogptq-index/whl/cu118/
然后找到需要加載的模型,比如“TheBloke/zephyr-7B-beta-GPTQ”,進(jìn)行加載
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
# Load LLM and Tokenizer
model_id = "TheBloke/zephyr-7B-beta-GPTQ"
tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True)
model = AutoModelForCausalLM.from_pretrained(
model_id,
device_map="auto",
trust_remote_code=False,
revisinotallow="main"
)
# Create a pipeline
pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')
盡管我們安裝了一些額外的依賴項(xiàng),但我們可以使用與之前相同的管道,也就是是不需要修改代碼,這是使用GPTQ的一大好處。
GPTQ是最常用的壓縮方法,因?yàn)樗槍?duì)GPU使用進(jìn)行了優(yōu)化。但是如果你的GPU無法處理如此大的模型,那么從GPTQ開始切換到以cpu為中心的方法(如GGUF)是絕對(duì)值得的。
2、GPT-Generated Unified Format
盡管GPTQ在壓縮方面做得很好,但如果沒有運(yùn)行它的硬件,那么就需要使用其他的方法。
GGUF(以前稱為GGML)是一種量化方法,允許用戶使用CPU來運(yùn)行LLM,但也可以將其某些層加載到GPU以提高速度。
雖然使用CPU進(jìn)行推理通常比使用GPU慢,但對(duì)于那些在CPU或蘋果設(shè)備上運(yùn)行模型的人來說,這是一種非常好的格式。
使用GGUF非常簡單,我們需要先安裝ctransformers包:
pip install ctransformers[cuda]
然后加載模型“TheBloke/zephyr-7B-beta-GGUF”,
from ctransformers import AutoModelForCausalLM
from transformers import AutoTokenizer, pipeline
# Load LLM and Tokenizer
# Use `gpu_layers` to specify how many layers will be offloaded to the GPU.
model = AutoModelForCausalLM.from_pretrained(
"TheBloke/zephyr-7B-beta-GGUF",
model_file="zephyr-7b-beta.Q4_K_M.gguf",
model_type="mistral", gpu_layers=50, hf=True
)
tokenizer = AutoTokenizer.from_pretrained(
"HuggingFaceH4/zephyr-7b-beta", use_fast=True
)
# Create a pipeline
pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')
加載模型后,我們可以運(yùn)行如下提示:
outputs = pipe(prompt, max_new_tokens=256)
print(outputs[0]["generated_text"])
如果你想同時(shí)利用CPU和GPU, GGUF是一個(gè)非常好的格式。
3、AWQ: Activation-aware Weight Quantization
除了上面兩種以外,一種新格式是AWQ(激活感知權(quán)重量化),它是一種類似于GPTQ的量化方法。AWQ和GPTQ作為方法有幾個(gè)不同之處,但最重要的是AWQ假設(shè)并非所有權(quán)重對(duì)LLM的性能都同等重要。
也就是說在量化過程中會(huì)跳過一小部分權(quán)重,這有助于減輕量化損失。所以他們的論文提到了與GPTQ相比的可以由顯著加速,同時(shí)保持了相似的,有時(shí)甚至更好的性能。
該方法還是比較新的,還沒有被采用到GPTQ和GGUF的程度。
對(duì)于AWQ,我們將使用vLLM包:
pip install vllm
使用vLLM可以直接加載模型:
from vllm import LLM, SamplingParams
# Load the LLM
sampling_params = SamplingParams(temperature=0.0, top_p=1.0, max_tokens=256)
llm = LLM(
model="TheBloke/zephyr-7B-beta-AWQ",
quantizatinotallow='awq',
dtype='half',
gpu_memory_utilizatinotallow=.95,
max_model_len=4096
)
然后使用.generate運(yùn)行模型:
output = llm.generate(prompt, sampling_params)
print(output[0].outputs[0].text)
就是這樣。