본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 5장: QLoRA로 소비자 GPU에서 파인튜닝하기
2026년 1월 22일·AI / ML·

5장: QLoRA로 소비자 GPU에서 파인튜닝하기

4비트 양자화와 LoRA를 결합한 QLoRA의 원리를 이해하고, 단일 소비자 GPU에서 대규모 모델을 파인튜닝하는 실전 방법을 다룹니다.

14분741자8개 섹션
llmtrainingmlopsdata-engineering
공유
fine-tuning5 / 10
12345678910
이전4장: LoRA의 원리와 실전 적용다음6장: 학습 파이프라인 구축과 하이퍼파라미터 최적화

QLoRA가 열어준 가능성

2023년 워싱턴 대학교 연구팀이 발표한 QLoRA(Quantized Low-Rank Adaptation)는 파인튜닝의 접근성을 혁신적으로 높였습니다. 70억 파라미터 모델의 파인튜닝에 필요한 GPU 메모리를 56GB에서 12GB 수준으로 줄여, 단일 소비자용 GPU에서도 대규모 모델의 파인튜닝을 가능하게 했습니다.

QLoRA의 핵심은 세 가지 기술의 결합에 있습니다. 4비트 NormalFloat(NF4) 양자화, 이중 양자화(Double Quantization), 그리고 페이지드 옵티마이저(Paged Optimizers)입니다. 이들을 LoRA와 결합하여 메모리 효율과 성능을 동시에 달성합니다.

text
메모리 사용량 비교 (7B 모델 기준):
 
  방법                GPU 메모리     학습 가능
  Full Fine-Tuning    ~56 GB        A100 80GB 필요
  LoRA (FP16)         ~18 GB        A100 40GB / A6000
  QLoRA (NF4)         ~12 GB        RTX 4070 Ti
  QLoRA + GC           ~8 GB        RTX 4060 Ti 16GB
 
  GC = Gradient Checkpointing

양자화의 기초

양자화(Quantization)는 모델 가중치의 수치 정밀도를 낮추어 메모리 사용량을 줄이는 기법입니다. 원래 32비트(FP32)나 16비트(FP16/BF16) 부동소수점으로 저장되는 가중치를 8비트, 4비트, 심지어 2비트로 변환합니다.

비트 수에 따른 정밀도

text
데이터 타입별 메모리 사용량 (파라미터당):
 
  FP32:    4 바이트   (32비트, 전체 정밀도)
  FP16:    2 바이트   (16비트, 반정밀도)
  BF16:    2 바이트   (16비트, Brain Float)
  INT8:    1 바이트   (8비트 정수)
  NF4:     0.5 바이트 (4비트, NormalFloat)
 
7B 모델의 가중치 크기:
  FP32:   28 GB
  FP16:   14 GB
  INT8:    7 GB
  NF4:   3.5 GB

NF4 (4-bit NormalFloat)

QLoRA에서 사용하는 NF4는 일반적인 4비트 정수 양자화와 다릅니다. 사전 학습된 모델의 가중치가 정규 분포(Normal Distribution)를 따른다는 관찰에 기반하여, 정규 분포에 최적화된 양자화 방식을 설계했습니다.

text
일반 INT4 양자화:
  16개의 양자화 레벨을 균등하게 분포
  [-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7]
  
  문제: 대부분의 가중치는 0 근처에 집중되어 있으므로
        극단값 영역의 레벨이 낭비됨
 
NF4 양자화:
  16개의 양자화 레벨을 정규 분포에 맞게 불균등 분포
  0 근처에 더 많은 레벨을 배치하여 정밀도 극대화
  
  결과: 동일한 4비트로도 더 높은 정밀도 달성

이중 양자화 (Double Quantization)

양자화에는 양자화 상수(Quantization Constants)가 필요합니다. 이 상수 자체도 메모리를 차지하는데, 이중 양자화는 이 상수를 다시 한번 양자화하여 메모리를 추가로 절약합니다.

text
단일 양자화:
  가중치 블록 (64개) + 양자화 상수 (FP32, 4바이트)
  상수 오버헤드: 4바이트 / 64 = 파라미터당 0.0625바이트
 
이중 양자화:
  양자화 상수를 다시 8비트로 양자화
  상수 오버헤드: 1바이트 / 64 + 4바이트 / (64*256)
               = 파라미터당 약 0.016바이트
 
  절약량: 7B 모델 기준 약 0.37 GB 추가 절약

QLoRA 실전 구현

4비트 모델 로드

python
import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig
)
 
# 4비트 양자화 설정
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,               # 4비트 양자화 활성화
    bnb_4bit_quant_type="nf4",       # NF4 양자화 사용
    bnb_4bit_compute_dtype=torch.bfloat16,  # 연산 시 BF16 사용
    bnb_4bit_use_double_quant=True,  # 이중 양자화 활성화
)
 
# 모델 로드 (4비트 양자화 적용)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    quantization_config=bnb_config,
    device_map="auto",
    attn_implementation="flash_attention_2",  # Flash Attention 사용
)
 
tokenizer = AutoTokenizer.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct"
)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token
Info

bnb_4bit_compute_dtype은 실제 연산에 사용되는 데이터 타입입니다. 가중치는 4비트로 저장되지만, 순전파와 역전파 시에는 이 타입으로 역양자화(Dequantize)되어 연산됩니다. BF16이 FP16보다 수치 안정성이 높아 권장됩니다.

LoRA 설정 및 적용

python
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
 
# 양자화된 모델에 대한 학습 준비
model = prepare_model_for_kbit_training(
    model,
    use_gradient_checkpointing=True  # 메모리 절약
)
 
# LoRA 설정
lora_config = LoraConfig(
    r=32,
    lora_alpha=64,
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj"
    ],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)
 
# LoRA 적용
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 167,772,160 || all params: 8,281,985,024
# || trainable%: 2.0258

학습 설정과 실행

python
from transformers import TrainingArguments
from trl import SFTTrainer
from datasets import load_dataset
 
# 데이터셋 로드
dataset = load_dataset("json", data_files={
    "train": "data/train.jsonl",
    "validation": "data/val.jsonl"
})
 
 
def formatting_func(example):
    """대화 형식 데이터를 텍스트로 변환"""
    return tokenizer.apply_chat_template(
        example["messages"],
        tokenize=False,
        add_generation_prompt=False
    )
 
 
# 학습 설정
training_args = TrainingArguments(
    output_dir="./qlora-output",
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,    # 실효 배치 크기 = 16
    learning_rate=2e-4,
    lr_scheduler_type="cosine",
    warmup_ratio=0.1,
    weight_decay=0.01,
    logging_steps=10,
    save_strategy="steps",
    save_steps=200,
    eval_strategy="steps",
    eval_steps=200,
    save_total_limit=3,
    bf16=True,                        # BF16 혼합 정밀도
    gradient_checkpointing=True,      # 메모리 절약
    gradient_checkpointing_kwargs={
        "use_reentrant": False
    },
    optim="paged_adamw_8bit",         # 페이지드 옵티마이저
    max_grad_norm=0.3,
    report_to="wandb",
    run_name="qlora-llama3-8b",
)
 
# 트레이너 설정
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset["train"],
    eval_dataset=dataset["validation"],
    tokenizer=tokenizer,
    formatting_func=formatting_func,
    max_seq_length=2048,
    packing=True,                     # 시퀀스 패킹 활성화
)
 
# 학습 시작
trainer.train()
 
# 어댑터 저장
trainer.save_model("./qlora-adapter")

메모리 최적화 기법

QLoRA만으로도 메모리가 부족한 경우 추가 최적화 기법을 적용할 수 있습니다.

Gradient Checkpointing

순전파 시 중간 활성화 값을 저장하지 않고, 역전파 시 필요할 때 재계산하는 방법입니다. 메모리를 절약하는 대신 약 30%의 학습 시간이 추가됩니다.

text
일반 학습:
  순전파: 모든 레이어의 활성화 값 저장 (메모리 O(n))
  역전파: 저장된 값 활용 (빠름)
 
Gradient Checkpointing:
  순전파: 체크포인트 레이어만 저장 (메모리 O(sqrt(n)))
  역전파: 필요한 활성화 값 재계산 (느림, 약 30% 추가)

페이지드 옵티마이저

NVIDIA GPU의 통합 메모리(Unified Memory) 기능을 활용하여, GPU 메모리가 부족할 때 옵티마이저 상태를 CPU 메모리로 자동 이전합니다.

python
# 8비트 Adam + 페이지드 메모리
training_args = TrainingArguments(
    optim="paged_adamw_8bit",
    # 또는 "paged_adamw_32bit" (메모리는 더 쓰지만 안정적)
)

Flash Attention 2

어텐션 연산의 메모리 사용량을 O(n^2)에서 O(n)으로 줄이는 기법입니다. 특히 긴 시퀀스를 처리할 때 효과적입니다.

python
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto",
    attn_implementation="flash_attention_2",
)
Warning

Flash Attention 2는 Ampere 이상의 GPU(RTX 30xx, A100 등)에서만 지원됩니다. 구형 GPU에서는 sdpa(Scaled Dot-Product Attention)를 대안으로 사용할 수 있습니다.

GPU별 최적 설정 가이드

사용 가능한 GPU에 따라 최적의 설정이 달라집니다. 주요 GPU별 권장 설정을 정리합니다.

text
GPU별 QLoRA 설정 가이드 (7B 모델 기준):
 
  RTX 4060 Ti 16GB:
    batch_size: 1
    gradient_accumulation: 16
    max_seq_length: 1024
    gradient_checkpointing: True
    packing: True
    optim: paged_adamw_8bit
 
  RTX 4070 Ti / 4080 (16GB):
    batch_size: 2
    gradient_accumulation: 8
    max_seq_length: 2048
    gradient_checkpointing: True
    packing: True
    optim: paged_adamw_8bit
 
  RTX 4090 (24GB):
    batch_size: 4
    gradient_accumulation: 4
    max_seq_length: 2048
    gradient_checkpointing: 선택적
    packing: True
    optim: paged_adamw_8bit
 
  A100 40GB:
    batch_size: 8
    gradient_accumulation: 2
    max_seq_length: 4096
    gradient_checkpointing: 불필요
    packing: True
    optim: adamw_torch

OOM 오류 대처법

GPU 메모리 부족(Out of Memory, OOM) 오류는 파인튜닝에서 가장 흔한 문제입니다. 단계별 대처 방법을 정리합니다.

text
OOM 발생 시 순차적 대처법:
 
  1단계: batch_size를 1로 줄이고 gradient_accumulation을 늘림
  2단계: gradient_checkpointing 활성화
  3단계: max_seq_length 줄이기 (4096 -> 2048 -> 1024)
  4단계: paged_adamw_8bit 옵티마이저 사용
  5단계: LoRA rank 줄이기 (32 -> 16 -> 8)
  6단계: target_modules 줄이기 (전체 -> Attention만)
  7단계: 더 작은 베이스 모델 사용 고려
python
# GPU 메모리 모니터링 함수
import torch
 
 
def print_gpu_memory():
    """현재 GPU 메모리 사용량 출력"""
    if torch.cuda.is_available():
        allocated = torch.cuda.memory_allocated() / 1024**3
        reserved = torch.cuda.memory_reserved() / 1024**3
        total = torch.cuda.get_device_properties(0).total_mem / 1024**3
        print("GPU 메모리:")
        print("  할당됨: " + str(round(allocated, 2)) + " GB")
        print("  예약됨: " + str(round(reserved, 2)) + " GB")
        print("  전체:   " + str(round(total, 2)) + " GB")
        print("  여유:   " + str(round(total - reserved, 2)) + " GB")

QLoRA 모델의 추론과 배포

학습이 완료된 QLoRA 어댑터를 로드하여 추론하는 방법입니다.

양자화 상태로 추론

python
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import PeftModel
import torch
 
# 베이스 모델을 4비트로 로드
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)
 
base_model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    quantization_config=bnb_config,
    device_map="auto",
)
 
# LoRA 어댑터 로드
model = PeftModel.from_pretrained(base_model, "./qlora-adapter")
 
# 추론 모드로 전환
model.eval()

FP16으로 병합하여 배포

프로덕션 환경에서는 양자화 어댑터를 FP16 모델로 병합하여 배포하는 것이 일반적입니다.

python
from transformers import AutoModelForCausalLM
from peft import PeftModel, AutoPeftModelForCausalLM
import torch
 
# FP16으로 베이스 모델 로드
base_model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    torch_dtype=torch.float16,
    device_map="auto",
)
 
# 어댑터 로드 및 병합
model = PeftModel.from_pretrained(base_model, "./qlora-adapter")
merged_model = model.merge_and_unload()
 
# FP16 모델로 저장
merged_model.save_pretrained("./merged-model-fp16")
Tip

병합된 FP16 모델은 vLLM, TGI 등의 서빙 프레임워크에서 바로 사용할 수 있습니다. 서빙 시에는 서빙 프레임워크 자체의 양자화 기능(AWQ, GPTQ 등)을 적용하여 추론 성능을 최적화하는 것이 일반적입니다.

정리

이번 장에서는 QLoRA를 사용한 메모리 효율적 파인튜닝을 실습했습니다.

  • QLoRA는 NF4 양자화, 이중 양자화, 페이지드 옵티마이저를 결합하여 메모리 사용량을 극적으로 줄입니다.
  • 단일 소비자 GPU(16GB)에서도 7B 모델의 파인튜닝이 가능합니다.
  • Gradient Checkpointing, Flash Attention 등 추가 최적화 기법으로 메모리를 더 절약할 수 있습니다.
  • GPU별 최적 설정과 OOM 오류 대처법을 정리했습니다.

다음 장에서는 학습 파이프라인의 전체적인 구축과 하이퍼파라미터 최적화 전략을 다룹니다. 학습률 스케줄링, 배치 크기 전략, 조기 종료 등 학습 효율을 극대화하는 기법을 체계적으로 안내합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#llm#training#mlops#data-engineering

관련 글

AI / ML

6장: 학습 파이프라인 구축과 하이퍼파라미터 최적화

파인튜닝 학습 파이프라인의 전체 구조를 설계하고, 학습률, 배치 크기, 스케줄링 등 핵심 하이퍼파라미터를 최적화하는 전략을 다룹니다.

2026년 1월 24일·18분
AI / ML

4장: LoRA의 원리와 실전 적용

LoRA(Low-Rank Adaptation)의 수학적 원리를 이해하고, 타겟 레이어 선택부터 하이퍼파라미터 튜닝까지 실전 적용법을 다룹니다.

2026년 1월 20일·15분
AI / ML

7장: 파인튜닝 모델 평가와 벤치마킹

파인튜닝된 모델의 성능을 자동 메트릭, LLM 평가, 인간 평가를 통해 다각적으로 측정하고 벤치마킹하는 체계적인 방법을 다룹니다.

2026년 1월 26일·16분
이전 글4장: LoRA의 원리와 실전 적용
다음 글6장: 학습 파이프라인 구축과 하이퍼파라미터 최적화

댓글

목차

약 14분 남음
  • QLoRA가 열어준 가능성
  • 양자화의 기초
    • 비트 수에 따른 정밀도
    • NF4 (4-bit NormalFloat)
    • 이중 양자화 (Double Quantization)
  • QLoRA 실전 구현
    • 4비트 모델 로드
    • LoRA 설정 및 적용
    • 학습 설정과 실행
  • 메모리 최적화 기법
    • Gradient Checkpointing
    • 페이지드 옵티마이저
    • Flash Attention 2
  • GPU별 최적 설정 가이드
  • OOM 오류 대처법
  • QLoRA 모델의 추론과 배포
    • 양자화 상태로 추론
    • FP16으로 병합하여 배포
  • 정리