GPTQ 양자화

GPTQ 양자화

GPTQ는 GPT 계열 LLM을 위한 양자화 방법으로, 근사 2차 정보(approximate second-order information)에 기반한 원샷(one-shot) 가중치 양자화를 사용해요. 이 문서에서는 양자화 모델을 Hugging Face transformers로 사용하는 방법과 AutoGPTQ로 나만의 모델을 양자화하는 방법을 보여드려요.

출처: 문서

본문

⚠️ 주의: 이 페이지는 Qwen3 기준으로 업데이트 예정이에요.

GPTQ는 GPT 계열 LLM을 위한 양자화 방법으로, 근사 2차 정보에 기반한 원샷 가중치 양자화를 사용해요. 이 문서에서는 양자화 모델을 Hugging Face transformers로 사용하는 방법과 AutoGPTQ로 나만의 모델을 양자화하는 방법을 보여드려요.

Hugging Face transformers로 GPTQ 모델 사용하기

📝 참고: 공식 Qwen2.5 GPTQ 모델을 transformers로 사용하려면 optimum>=1.20.0과 호환되는 버전의 transformers, auto_gptq가 설치되어 있어야 해요.

다음 명령으로 설치할 수 있어요:

pip install -U "optimum>=1.20.0"

이제 transformers가 AutoGPTQ를 공식 지원하므로, 양자화 모델을 transformers로 직접 사용할 수 있어요. Qwen2.5의 각 크기마다 Int4와 Int8 GPTQ 양자화 모델을 모두 제공해요. 다음은 Qwen2.5-7B-Instruct-GPTQ-Int4를 실행하는 아주 간단한 코드예요:

from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4"

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

prompt = "Give me a short introduction to large language models."
messages = [
    {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
    {"role": "user", "content": prompt},
]
text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)

generated_ids = model.generate(
    **model_inputs,
    max_new_tokens=512,
)
generated_ids = [
    output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]

response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]

vLLM으로 GPTQ 모델 사용하기

vLLM이 GPTQ를 지원하므로, 우리가 제공하는 GPTQ 모델이나 AutoGPTQ로 훈련한 모델을 vLLM과 직접 사용할 수 있어요. 가능하면 더 효율적인 GPTQ Marlin 커널을 자동으로 사용해요.

실제로 사용법은 vLLM의 기본 사용법과 동일해요. vLLM과 Qwen2.5-7B-Instruct-GPTQ-Int4로 OpenAI-API 호환 API를 실행하는 간단한 예시를 제공할게요:

셸에서 다음을 실행해 OpenAI 호환 API 서비스를 시작하세요:

vllm serve Qwen2.5-7B-Instruct-GPTQ-Int4

그 다음 아래처럼 API를 호출할 수 있어요:

curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d '{
  "model": "Qwen2.5-7B-Instruct-GPTQ-Int4",
  "messages": [
    {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
    {"role": "user", "content": "Tell me something about large language models."}
  ],
  "temperature": 0.7,
  "top_p": 0.8,
  "repetition_penalty": 1.05,
  "max_tokens": 512
}'

또는 아래처럼 openai Python 패키지로 API 클라이언트를 사용할 수 있어요:

from openai import OpenAI

openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8000/v1"

client = OpenAI(
    api_key=openai_api_key,
    base_url=openai_api_base,
)

chat_response = client.chat.completions.create(
    model="Qwen2.5-7B-Instruct-GPTQ-Int4",
    messages=[
        {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
        {"role": "user", "content": "Tell me something about large language models."},
    ],
    temperature=0.7,
    top_p=0.8,
    max_tokens=512,
    extra_body={
        "repetition_penalty": 1.05,
    },
)
print("Chat response:", chat_response)

AutoGPTQ로 나만의 모델 양자화하기

나만의 모델을 GPTQ 양자화 모델로 만들고 싶다면 AutoGPTQ를 사용하는 것을 권장해요. 패키지의 최신 버전을 소스 코드에서 설치하는 것을 권장해요:

git clone https://github.com/AutoGPTQ/AutoGPTQ
cd AutoGPTQ
pip install -e .

Qwen2.5-7B를 기반으로 파인튜닝한 Qwen2.5-7B-finetuned라는 모델이 있고, 이를 Alpaca 같은 여러분만의 데이터셋으로 만들었다고 가정해 볼게요. 나만의 GPTQ 양자화 모델을 만들려면 캘리브레이션에 훈련 데이터를 사용해야 해요. 아래에 실행할 간단한 데모를 제공할게요:

from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer

# Specify paths and hyperparameters for quantization
model_path = "your_model_path"
quant_path = "your_quantized_model_path"
quantize_config = BaseQuantizeConfig(
    bits=8, # 4 or 8
    group_size=128,
    damp_percent=0.01,
    desc_act=False,  # set to False can significantly speed up inference but the perplexity may slightly bad
    static_groups=False,
    sym=True,
    true_sequential=True,
    model_name_or_path=None,
    model_file_base_name="model"
)
max_len = 8192

# Load your tokenizer and model with AutoGPTQ
# To learn about loading model to multiple GPUs,
# visit https://github.com/AutoGPTQ/AutoGPTQ/blob/main/docs/tutorial/02-Advanced-Model-Loading-and-Best-Practice.md
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoGPTQForCausalLM.from_pretrained(model_path, quantize_config)

하지만 모델을 여러 GPU에 로드하고 싶다면 device_map 대신 max_memory를 사용해야 해요. 예시는 다음과 같아요:

model = AutoGPTQForCausalLM.from_pretrained(
    model_path,
    quantize_config,
    max_memory={i: "20GB" for i in range(4)}
)

그 다음 캘리브레이션 데이터를 준비해야 해요. 샘플을 리스트에 넣기만 하면 되고, 각 샘플은 텍스트예요. 파인튜닝 데이터를 캘리브레이션에 직접 사용하므로 먼저 ChatML 템플릿으로 포맷해요. 예를 들어:

import torch

data = []
for msg in dataset:
    text = tokenizer.apply_chat_template(msg, tokenize=False, add_generation_prompt=False)
    model_inputs = tokenizer([text])
    input_ids = torch.tensor(model_inputs.input_ids[:max_len], dtype=torch.int)
    data.append(dict(input_ids=input_ids, attention_mask=input_ids.ne(tokenizer.pad_token_id)))

여기서 각 msg는 아래와 같은 전형적인 채팅 메시지예요:

[
    {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
    {"role": "user", "content": "Tell me who you are."},
    {"role": "assistant", "content": "I am a large language model named Qwen..."}
]

그 다음 한 줄의 코드로 캘리브레이션 과정을 실행하세요:

import logging

logging.basicConfig(
    format="%(asctime)s %(levelname)s [%(name)s] %(message)s", level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S"
)
model.quantize(data, cache_examples_on_gpu=False)

마지막으로 양자화 모델을 저장하세요:

model.save_quantized(quant_path, use_safetensors=True)
tokenizer.save_pretrained(quant_path)

아쉽게도 save_quantized 메서드는 sharding을 지원하지 않아요. sharding이 필요하다면 모델을 로드한 뒤 transformers의 save_pretrained를 사용해 모델을 저장·샤딩해야 해요. 이 점만 제외하면 모든 것이 아주 간단해요. 즐기세요!

알려진 문제

Qwen2.5-72B-Instruct-GPTQ-Int4가 제대로 생성을 멈추지 못함

  • 모델: Qwen2.5-72B-Instruct-GPTQ-Int4
  • 프레임워크: vLLM, AutoGPTQ (Hugging Face transformers 포함)
  • 설명: 생성이 제대로 멈추지 못해요. 멈춰야 할 지점 이후에도 계속 생성되며, 단일 문자, 구문 또는 단락 등 반복된 텍스트가 생성돼요.
  • 해결 방법: 다음을 고려해 볼 수 있어요.
    • 16비트 부동소수점 원본 모델 사용
    • 비정상 생성 확률을 줄이기 위해 AWQ 변형 또는 llama.cpp 기반 모델 사용

Qwen2.5-32B-Instruct-GPTQ-Int4가 vLLM으로 멀티 GPU에서 깨짐

  • 모델: Qwen2.5-32B-Instruct-GPTQ-Int4
  • 프레임워크: vLLM
  • 설명: 멀티 GPU 배포 시 !!!!!!!!!!!!!!!!!! 같은 깨진 텍스트만 생성돼요.
  • 해결 방법: 다음 각각을 고려할 수 있어요.
    • AWQ 또는 GPTQ-Int8 변형 사용
    • 단일 GPU 사용
    • 지연 시간과 처리량이 크게 중요하지 않다면 Hugging Face transformers 사용

문제 해결

transformers와 auto_gptq를 쓰는데 로그가 CUDA extension not installed.라고 하고 추론이 느려요. — auto_gptq가 여러분 환경과 호환되는 fused CUDA 커널을 찾지 못해 일반 구현으로 대체한 거예요. 설치 가이드를 따라 미리 빌드된 wheel을 설치하거나 소스에서 auto_gptq를 설치해 보세요.

vLLM으로 자체 양자화한 Qwen2.5-72B-Instruct-GPTQ에서 ValueError: ... must be divisible by ...가 발생해요. — 자체 양자화 모델의 intermediate size가 공식 Qwen2.5-72B-Instruct-GPTQ 모델과 달라서예요.

양자화 후 양자화 가중치의 크기는 그룹 크기(보통 128)로 나눠져요. Qwen2.5-72B의 FFN 블록 intermediate size는 29568이에요. 안타깝게도 29568 ÷ 128 = 231이에요. 어텐션 헤드 수와 가중치 차원이 텐서 병렬 크기로 나누어져야 하므로, tensor_parallel_size=1(즉 GPU 카드 1개)에서만 양자화 모델을 실행할 수 있음을 뜻해요.

해결 방법은 intermediate size를 128 × 8 = 1024로 나누어 떨어지게 만드는 거예요. 이를 위해 가중치를 0으로 패딩해야 해요. 가중치를 0으로 패딩하기 전과 후는 수학적으로 동등하지만, 실제 결과는 약간 다를 수 있어요.

다음을 시도해 보세요:

import torch
from torch.nn import functional as F

from transformers import AutoModelForCausalLM

# must use AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-72B-Instruct", torch_dtype="auto")

# this size is Qwen2.5-72B only
pad_size = 128

sd = model.state_dict()

for i, k in enumerate(sd):
    v = sd[k]
    print(k, i)
    # interleaving the padded zeros
    if ('mlp.up_proj.weight' in k) or ('mlp.gate_proj.weight' in k):
        prev_v = F.pad(v.unsqueeze(1), (0, 0, 0, 1, 0, 0)).reshape(29568*2, -1)[:pad_size*2]
        new_v = torch.cat([prev_v, v[pad_size:]], dim=0)
        sd[k] = new_v
    elif 'mlp.down_proj.weight' in k:
        prev_v= F.pad(v.unsqueeze(2), (0, 1)).reshape(8192, 29568*2)[:, :pad_size*2]
        new_v = torch.cat([prev_v, v[:, pad_size:]], dim=1)
        sd[k] = new_v

# this is a very large file; make sure your RAM is enough to load the model
torch.save(sd, '/path/to/padded_model/pytorch_model.bin')

이렇게 하면 패딩된 체크포인트가 지정된 디렉터리에 저장돼요. 그 다음 원본 체크포인트의 다른 파일들을 새 디렉터리에 복사하고 config.json의 intermediate_size를 29696으로 수정하세요. 마지막으로 저장된 모델 체크포인트를 양자화할 수 있어요.

더 알아보기 (Learn more)