INT8 W4A8

INT8 W4A8

vLLM은 메모리 절약과 추론 가속을 위해 가중치를 INT4로, 활성화를 INT8로 양자화하는 것을 지원해요. 이 양자화 방법은 좋은 성능을 유지하면서 모델 크기를 줄이는 데 특히 유용합니다.

출처: 문서

본문

사전 준비 (Prerequisites)

vLLM으로 INT8 W4A8 양자화를 사용하려면 llm-compressor 라이브러리를 설치해야 합니다.

(venv-llm-compressor) pip install llmcompressor

또한 평가를 위해 vllmlm-evaluation-harness 를 설치합니다.

(venv-vllm) pip install vllm "lm-eval[api]>=0.4.12"

vLLM과 llm-compressor는 함께 동작하지 못할 수 있으므로 별도의 환경을 사용하세요.

양자화 과정 (Quantization Process)

양자화 과정은 네 가지 주요 단계로 구성됩니다.

  1. 모델 로드
  2. 보정 데이터 준비
  3. 양자화 적용
  4. vLLM에서 정확도 평가

1. 모델 로드 (Loading the Model)

표준 transformers AutoModel 클래스로 모델과 토크나이저를 로드합니다.

from transformers import AutoTokenizer, AutoModelForCausalLM

MODEL_ID = "meta-llama/Meta-Llama-3-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    dtype="auto",
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)

2. 보정 데이터 준비 (Preparing Calibration Data)

활성화를 INT8로, 가중치를 INT4로 양자화할 때 활성화 스케일을 추정할 샘플 데이터가 필요합니다. 배포 데이터와 밀접하게 일치하는 보정 데이터를 사용하는 것이 가장 좋습니다. 범용 instruction-tuned 모델에는 ultrachat 같은 데이터셋을 사용할 수 있어요.

from datasets import load_dataset

NUM_CALIBRATION_SAMPLES = 512
MAX_SEQUENCE_LENGTH = 2048

# Load and preprocess the dataset
ds = load_dataset("HuggingFaceH4/ultrachat_200k", split="train_sft")
ds = ds.shuffle(seed=42).select(range(NUM_CALIBRATION_SAMPLES))

def preprocess(example):
    return {"text": tokenizer.apply_chat_template(example["messages"], tokenize=False)}
ds = ds.map(preprocess)

def tokenize(sample):
    return tokenizer(sample["text"], padding=False, max_length=MAX_SEQUENCE_LENGTH, truncation=True, add_special_tokens=False)
ds = ds.map(tokenize, remove_columns=ds.column_names)

3. 양자화 적용 (Applying Quantization)

이제 양자화 알고리즘을 적용합니다.

다음 레시피는 W4A8 모델(int4 가중치, int8 활성화)을 만듭니다. Arm® CPU에서는 KleidiAI 를 통해 가속됩니다.

최상의 정확도를 위해 groupwise를, 최상의 추론 성능을 위해 channelwise를 사용하세요.

Groupwise:

from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier

# Configure the quantization algorithms
recipe = [
    GPTQModifier(
        targets="Linear",
        scheme="W4A8",
        ignore=["lm_head"],
        dampening_frac=0.01
    ),
]

# Apply quantization
oneshot(
    model=model,
    dataset=ds,
    recipe=recipe,
    max_seq_length=MAX_SEQUENCE_LENGTH,
    num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)

# Save the compressed model: Meta-Llama-3-8B-Instruct-W4A8-G128-Dynamic-Per-Token
SAVE_DIR = MODEL_ID.split("/")[1] + "-W4A8-G128-Dynamic-Per-Token"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)

Channelwise:

from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
from compressed_tensors.quantization import QuantizationStrategy, QuantizationType

scheme = {
    "targets": ["Linear"],
    "weights": {
        "num_bits": 4,
        "type": QuantizationType.INT,
        "strategy": QuantizationStrategy.CHANNEL,
        "symmetric": True,
        "dynamic": False,
        "group_size": None,
    },
    "input_activations": {
        "num_bits": 8,
        "type": QuantizationType.INT,
        "strategy": QuantizationStrategy.TOKEN,
        "dynamic": True,
        "symmetric": False,
        "observer": None,
    },
    "output_activations": None,
}

recipe = [
    GPTQModifier(
        targets="Linear",
        config_groups={"group_0": scheme},
        ignore=["lm_head"],
        dampening_frac=0.01,
    ),
]

oneshot(
    model=model,
    dataset=ds,
    recipe=recipe,
    max_seq_length=MAX_SEQUENCE_LENGTH,
    num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)

# Save the compressed model: Meta-Llama-3-8B-Instruct-W4A8-Channelwise-Dynamic-Per-Token
SAVE_DIR = MODEL_ID.split("/")[1] + "-W4A8-Channelwise-Dynamic-Per-Token"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)

4. 정확도 평가 (Evaluating Accuracy)

양자화 후 vLLM에서 모델을 로드하고 실행할 수 있습니다.

from vllm import LLM

llm = LLM("./Meta-Llama-3-8B-Instruct-W4A8-G128-Dynamic-Per-Token")

lm_eval 로 정확도를 평가할 수 있습니다.

lm_eval --model vllm \
    --model_args pretrained="./Meta-Llama-3-8B-Instruct-W4A8-G128-Dynamic-Per-Token",add_bos_token=true \
    --tasks gsm8k \
    --num_fewshot 5 \
    --limit 250 \
    --batch_size 'auto'

channelwise 모델도 동일하게 평가할 수 있습니다.

llm = LLM("./Meta-Llama-3-8B-Instruct-W4A8-Channelwise-Dynamic-Per-Token")
lm_eval --model vllm \
    --model_args pretrained="./Meta-Llama-3-8B-Instruct-W4A8-Channelwise-Dynamic-Per-Token",add_bos_token=true \
    --tasks gsm8k \
    --num_fewshot 5 \
    --limit 250 \
    --batch_size 'auto'

참고: 양자화된 모델은 bos 토큰의 존재에 민감할 수 있습니다. 평가를 실행할 때 add_bos_token=True 인자를 포함해야 합니다.

모범 사례 (Best Practices)

  • 보정 데이터는 512개 샘플로 시작합니다(정확도가 떨어지면 증가).
  • 시퀀스 길이 2048을 출발점으로 사용합니다.
  • 모델이 훈련된 채팅 템플릿 또는 instruction 템플릿을 사용합니다.
  • 모델을 미세 튜닝했다면 보정을 위해 훈련 데이터의 일부를 사용하는 것을 고려합니다.

트러블슈팅 및 지원 (Troubleshooting and Support)

문제가 발생하거나 기능 요청이 있으면 vllm-project/llm-compressor GitHub 저장소에 이슈를 열어 주세요.

더 알아보기 (Learn more)