Q-LoRA

Contents

  1. Quantization이란?
  2. Q-LoRA의 필요성
  3. Q-LoRA 개요
  4. Q-LoRA Details
  5. 코드 예시


1. Quantization (양자화)란?

모델이 사용하는 수치 표현을 줄여서 연산을 가볍게 하고, 메모리 사용을 줄이는 기법

Example

  1. FP32 (32-bit 부동소수점)
    • 3.141592653589793 (매우 정밀한 숫자 표현)
    • 메모리 사용량: 4바이트(32비트)
  2. FP16 (16-bit 부동소수점)
    • 3.1416 (소수점 이하 일부 손실)
    • 메모리 사용량: 2바이트(16비트)
  3. INT8 (8-bit 정수형)
    • 3 또는 3.14 (더 적은 정밀도)
    • 메모리 사용량: 1바이트(8비트)


2. Q-LoRA의 필요성

LLM은 많은 GPU 메모리를 필요로 함

  • ex) LLaMA-7B: FP16 사용 시 약 28GB GPU 메모리 필요
    • FP32로 학습하면 최소 100GB 이상 필요 → 일반 GPU로 불가능
  • 비록 LoRA가 일부 작은 어댑터만 학습한다고 하더라도, 원본 모델은 여전히 큰 메모리를 차지


3. Q-LoRA 개요

Q-LoRA (Quantized Low-Rank Adaptation)

  • 기존 LoRAQuantization을 추가
  • 더 적은 메모리로 학습을 가능하게 만든 방법!


LoRA vs. Q-LoRA

  LoRA Q-LoRA
모델 크기 원본 모델 크기 유지 Quantization으로 모델 크기 감소
파인튜닝 방식 LoRA 어댑터만 학습 LoRA 어댑터만 학습 (동일)
메모리 사용 기존보다 적지만 여전히 큼 더 적은 메모리 사용 가능


4. Q-LoRA Details

Q-LoRA (Quantized Low-Rank Adaptation)

  • 메모리 효율적인 방법으로 대형 언어 모델(LLM)을 파인튜닝하는 기술


How?

  • 기존 LoRA(Low-Rank Adaptation)에서 Quantization(양자화)를 추가
  • 훨씬 적은 GPU 메모리로 파인튜닝 가능
    • 모델 전체를 수정하지 않고, 일부 작은 어댑터(LoRA)만 학습
  • 4-bit 양자화를 활용해 기존보다 훨씬 적은 메모리로 저장 및 연산 가능


핵심 아이디어

아래의 2가지를 결합

  1. 4-bit Quantization
    • 모델의 파라미터를 FP16 (16-bit) → INT4 (4-bit)로 변환
    • 메모리를 최대 4배 절약 가능
    • 원본 모델을 변경하지 않고 (즉, weight를 직접 업데이트하지 않고) 양자화된 상태에서 작동
  2. LoRA (Low-Rank Adaptation)
    • 원본 모델의 가중치를 직접 수정하지 않고, 작은 어댑터 행렬을 추가로 학습
    • 메모리 효율이 뛰어나고, 적은 데이터로도 파인튜닝 가능


Full Fine-Tuning vs. LoRA vs. Q-LoRA

방법 모델 양자화 메모리 사용량 파인튜닝 방식
Full Fine-Tuning X (FP16, FP32) 매우 큼 모든 가중치 업데이트
LoRA X (FP16, FP32) 중간 작은 LoRA 어댑터 추가
Q-LoRA 4-bit Quantization 최소 작은 LoRA 어댑터 추가


5. 코드 예시

아래 코드는 bitsandbytes 라이브러리를 사용해 LLaMA 모델을 4-bit로 양자화하고, Q-LoRA를 적용하는 예제이다.

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import bitsandbytes as bnb

# (1) LLaMA 모델을 4-bit로 Quantization
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
    model_name, 
    load_in_4bit=True,  # 4-bit 양자화 적용
    quantization_config=bnb.nn.Linear4bitQuantizationConfig(
        compute_dtype=torch.float16,  # 연산은 FP16으로 유지
        use_double_quant=True,  # 추가적인 양자화 최적화
    )
)

# (2) Tokenizer 불러오기
tokenizer = AutoTokenizer.from_pretrained(model_name)

# (3) LoRA 적용을 위한 PEFT (Parameter-Efficient Fine-Tuning) 설정
from peft import get_peft_model, LoraConfig, TaskType

config = LoraConfig(
    task_type=TaskType.CAUSAL_LM, # 언어 모델용 LoRA
    r=16, # Low-Rank 차원 (낮을수록 적은 메모리 사용)
    lora_alpha=32, 
    lora_dropout=0.1
)

# (4) 모델에 LoRA 적용
model = get_peft_model(model, config)

# (5) 모델 파라미터 확인 (양자화 + LoRA 적용됨)
print(model)

# (6) 텍스트 생성 테스트
text = "Q-LoRA is a technique that"
inputs = tokenizer(text, return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_length=50)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
  • 참고) Q-LoRA는 기존 모델을 양자화(quantize)하지만, LoRA 어댑터는 양자화하지 않는다!


왜 LoRA 어댑터는 quantize하지 않을까?

  1. 학습 가능한 가중치는 높은 정밀도가 필요함

    • INT4는 연산이 빠르고 메모리를 절약하지만, 정보 손실이 큼.

    • LoRA 어댑터는 학습을 해야 하므로 FP16을 유지해서 미세한 변화도 반영할 수 있도록 함.

  2. 모델 본체는 양자화 가능하지만, 업데이트해야 할 부분은 정밀도가 필요함

    • 원래 모델의 가중치는 업데이트하지 않고, 4-bit (INT4)로 변환하여 메모리를 절약.

    • 대신, 학습해야 하는 LoRA 어댑터는 FP16으로 유지해서 학습 가능하게 만듦.


요약

  • Pretrained 모델 → INT4 (NF4로 양자화됨)
  • LoRA 어댑터 → FP16 (학습을 위해 높은 정밀도 유지)