Skip to content

필사 모드: Unsloth로 LLM 파인튜닝 완전 가이드 2025: QLoRA, 4bit 양자화, 2배 빠른 학습

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

서론: 왜 Unsloth인가?

LLM 파인튜닝의 가장 큰 진입 장벽은 **GPU 메모리(VRAM)**입니다. Llama 3.1 8B를 Full Fine-tuning하려면 약 60GB VRAM이 필요하고, 이는 A100 80GB 하나로도 빠듯합니다. QLoRA가 이 문제를 해결했지만, 학습 속도는 여전히 느렸습니다.

**Unsloth**는 이 두 가지 문제를 동시에 해결합니다:

| 비교 항목 | HuggingFace PEFT | Axolotl | Unsloth |

|-----------|-----------------|---------|---------|

| 학습 속도 | 1x (기준) | 1.1x | **2x** |

| 메모리 사용 | 100% | 95% | **40%** |

| 설정 난이도 | 중간 | 높음 | **낮음** |

| 지원 모델 | 전체 | 전체 | 주요 모델 |

| Flash Attention | 별도 설치 | 내장 | **내장** |

| 커스텀 커널 | 없음 | 없음 | **Triton 커널** |

Unsloth의 핵심 비밀은 **커스텀 Triton 커널**입니다. Attention, MLP, Cross-Entropy Loss 등의 핵심 연산을 GPU에 최적화된 커스텀 커널로 대체하여 2배 빠른 학습과 60% 메모리 절약을 달성합니다.

**지원 모델 (2025년 기준):**

- Llama 3 / 3.1 / 3.2 (8B, 70B)

- Mistral / Mixtral

- Phi-3 / Phi-3.5

- Qwen 2 / 2.5

- Gemma 2

- Yi

- DeepSeek V2

1. LoRA/QLoRA 이론

1.1 Full Fine-tuning vs LoRA vs QLoRA

Full Fine-tuning (모든 파라미터 업데이트)

┌──────────────────────┐

│ W (d x d) │ <- 전체 가중치 업데이트

│ 예: 4096 x 4096 │ = 16M 파라미터

│ = 64MB (FP16) │

└──────────────────────┘

LoRA (Low-Rank Adaptation)

┌──────────────────────┐

│ W0 (고정) + B * A │

│ W0: 4096 x 4096 │ <- 고정 (업데이트 안 함)

│ B: 4096 x 16 │ <- 학습 (65K 파라미터)

│ A: 16 x 4096 │ <- 학습 (65K 파라미터)

│ = 0.25MB (FP16) │ 총 130K 파라미터

└──────────────────────┘

QLoRA (Quantized LoRA)

┌──────────────────────┐

│ W0 (4bit) + B * A │

│ W0: 4096 x 4096 │ <- 4bit 양자화 (8MB)

│ B: 4096 x 16 │ <- FP16 학습

│ A: 16 x 4096 │ <- FP16 학습

│ = 8.25MB total │

└──────────────────────┘

1.2 Low-Rank Decomposition 원리

LoRA의 핵심 아이디어는 **가중치 업데이트 행렬이 실제로 저차원(low-rank)**이라는 관찰에 기반합니다.

원래의 가중치 업데이트:

W_new = W_old + delta_W

LoRA는 delta_W를 두 개의 작은 행렬의 곱으로 분해합니다:

delta_W = B * A

여기서:

B는 d x r 행렬 (d=모델 차원, r=LoRA 랭크)

A는 r x d 행렬

r << d (예: r=16, d=4096)

**파라미터 절약 효과:**

Full Fine-tuning 파라미터 수

d = 4096

full_params = d * d # = 16,777,216 (16.7M)

LoRA 파라미터 수

r = 16

lora_params = d * r + r * d # = 131,072 (131K)

절약률

savings = 1 - (lora_params / full_params)

print(f"파라미터 절약: {savings:.2%}") # 99.22%

1.3 4-bit NormalFloat 양자화 (NF4)

QLoRA에서 사용하는 NF4 양자화는 일반 4-bit와 다릅니다:

**일반 4-bit INT 양자화:**

- 균일하게 16개 구간으로 나눔

- 값 분포를 고려하지 않음

**NF4 (NormalFloat4):**

- 가중치가 정규분포를 따른다는 사실을 활용

- 정규분포의 분위수(quantile)에 맞춰 16개 값 설정

- 정보 이론적으로 최적에 가까운 양자화

NF4 양자화 값 예시 (정규분포 분위수 기반)

nf4_values = [

-1.0, -0.6962, -0.5251, -0.3949,

-0.2844, -0.1848, -0.0911, 0.0,

0.0796, 0.1609, 0.2461, 0.3379,

0.4407, 0.5626, 0.7230, 1.0,

]

1.4 Double Quantization

QLoRA의 또 다른 혁신은 **이중 양자화(Double Quantization)**입니다:

1. 가중치를 4-bit로 양자화 (NF4)

2. 양자화 상수(scaling factor)를 다시 8-bit로 양자화

3. 추가 메모리 절약: 블록당 32bit에서 8bit로

1.5 메모리 비교표

| 모델 | Full FT (FP16) | LoRA (FP16) | QLoRA (4bit) |

|------|---------------|-------------|-------------|

| Llama 3 8B | ~60GB | ~18GB | **~6GB** |

| Llama 3 70B | ~500GB | ~160GB | **~40GB** |

| Mistral 7B | ~52GB | ~16GB | **~5GB** |

| Phi-3 3.8B | ~28GB | ~9GB | **~3GB** |

| Qwen 2 7B | ~52GB | ~16GB | **~5GB** |

2. 환경 설정

2.1 GPU 요구사항

| GPU | VRAM | 학습 가능 모델 (QLoRA) |

|-----|------|----------------------|

| T4 (Colab Free) | 16GB | 7B~8B (seq_len 1024) |

| A10G | 24GB | 7B~13B |

| RTX 4090 | 24GB | 7B~13B |

| A100 40GB | 40GB | 7B~70B |

| A100 80GB | 80GB | 70B+ |

| Apple M2 Ultra | 192GB | CPU 학습 (느림) |

2.2 Google Colab 설정

Colab에서 Unsloth 설치 (T4 GPU 기준)

런타임 -> 런타임 유형 변경 -> T4 GPU 선택

1. Unsloth 설치

!pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"

!pip install --no-deps "xformers<0.0.27" "trl<0.9.0" peft accelerate bitsandbytes

2. GPU 확인

print(f"GPU: {torch.cuda.get_device_name(0)}")

print(f"VRAM: {torch.cuda.get_device_properties(0).total_mem / 1024**3:.1f} GB")

2.3 로컬 환경 설정

Conda 환경 생성

conda create -n unsloth python=3.11

conda activate unsloth

PyTorch 설치 (CUDA 12.1)

conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia

Unsloth 설치

pip install "unsloth[cu121] @ git+https://github.com/unslothai/unsloth.git"

pip install --no-deps trl peft accelerate bitsandbytes

설치 확인

python -c "from unsloth import FastLanguageModel; print('Unsloth OK')"

2.4 Docker 환경

FROM nvidia/cuda:12.1.0-devel-ubuntu22.04

RUN apt-get update && apt-get install -y python3.11 python3-pip git

RUN pip install torch --index-url https://download.pytorch.org/whl/cu121

RUN pip install "unsloth[cu121] @ git+https://github.com/unslothai/unsloth.git"

RUN pip install --no-deps trl peft accelerate bitsandbytes

WORKDIR /workspace

CMD ["python3"]

3. Unsloth 파인튜닝 단계별 가이드

3.1 모델 로딩

from unsloth import FastLanguageModel

모델과 토크나이저 로딩

model, tokenizer = FastLanguageModel.from_pretrained(

model_name="unsloth/Meta-Llama-3.1-8B-bnb-4bit", # 4bit 사전 양자화 모델

max_seq_length=2048, # 최대 시퀀스 길이

dtype=None, # 자동 감지 (A100: bfloat16, 기타: float16)

load_in_4bit=True, # 4bit 양자화 로드

)

GPU 메모리 확인

gpu_stats = torch.cuda.get_device_properties(0)

start_gpu_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)

max_memory = round(gpu_stats.total_mem / 1024 / 1024 / 1024, 3)

print(f"GPU = {gpu_stats.name}. Max memory = {max_memory} GB.")

print(f"{start_gpu_memory} GB of memory reserved.")

**사전 양자화 모델 추천:**

| 용도 | 모델 | 크기 |

|------|------|------|

| 일반 한국어 | `unsloth/Meta-Llama-3.1-8B-bnb-4bit` | ~5GB |

| 한국어 특화 | `beomi/Llama-3-Open-Ko-8B-bnb-4bit` | ~5GB |

| 코딩 | `unsloth/Mistral-7B-v0.3-bnb-4bit` | ~4.5GB |

| 경량 | `unsloth/Phi-3.5-mini-instruct-bnb-4bit` | ~2.5GB |

| 다국어 | `unsloth/Qwen2.5-7B-bnb-4bit` | ~4.5GB |

3.2 LoRA 어댑터 설정

LoRA 어댑터 추가

model = FastLanguageModel.get_peft_model(

model,

r=16, # LoRA 랭크 (8, 16, 32, 64)

target_modules=[ # LoRA를 적용할 모듈

"q_proj", "k_proj", "v_proj", "o_proj", # Attention

"gate_proj", "up_proj", "down_proj", # MLP

],

lora_alpha=16, # LoRA alpha (보통 r과 같게)

lora_dropout=0, # Unsloth에서는 0이 최적

bias="none", # bias 학습 안 함

use_gradient_checkpointing="unsloth", # Unsloth 최적화 체크포인팅

random_state=3407,

use_rslora=False, # Rank-Stabilized LoRA (실험적)

loftq_config=None, # LoftQ 설정

)

학습 가능한 파라미터 확인

def print_trainable_parameters(model):

trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)

total = sum(p.numel() for p in model.parameters())

print(f"학습 가능: {trainable:,} / 전체: {total:,} = {trainable/total:.2%}")

print_trainable_parameters(model)

학습 가능: 41,943,040 / 전체: 8,030,261,248 = 0.52%

**LoRA 랭크 선택 가이드:**

| LoRA r | 파라미터 수 | VRAM 추가 | 권장 사용 |

|--------|-----------|----------|----------|

| 8 | ~21M | ~80MB | 간단한 태스크, VRAM 제한 |

| 16 | ~42M | ~160MB | 일반적 권장값 |

| 32 | ~84M | ~320MB | 복잡한 태스크 |

| 64 | ~168M | ~640MB | 대규모 데이터, 높은 표현력 필요 |

| 128 | ~336M | ~1.3GB | 실험적, Full FT에 근접 |

4. 데이터 준비

4.1 Chat Template 포매팅

Alpaca 프롬프트 템플릿

alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

Instruction:

{}

Input:

{}

Response:

{}"""

데이터셋 포매팅 함수

EOS_TOKEN = tokenizer.eos_token

def formatting_prompts_func(examples):

instructions = examples["instruction"]

inputs = examples["input"]

outputs = examples["output"]

texts = []

for instruction, input_text, output in zip(instructions, inputs, outputs):

text = alpaca_prompt.format(instruction, input_text, output) + EOS_TOKEN

texts.append(text)

return {"text": texts}

4.2 데이터셋 로딩 및 변환

from datasets import load_dataset

KoAlpaca 데이터셋 로딩

dataset = load_dataset("beomi/KoAlpaca-v1.1a", split="train")

포맷 변환

def format_koalpaca(examples):

texts = []

for instruction, output in zip(examples["instruction"], examples["output"]):

text = alpaca_prompt.format(instruction, "", output) + EOS_TOKEN

texts.append(text)

return {"text": texts}

dataset = dataset.map(format_koalpaca, batched=True)

ShareGPT 형식 데이터 로딩 (다중 턴)

sharegpt_dataset = load_dataset("philschmid/sharegpt-raw", split="train")

def format_sharegpt(examples):

texts = []

for conversations in examples["conversations"]:

text = ""

for turn in conversations:

if turn["from"] == "human":

text += f"### Human:\n{turn['value']}\n\n"

elif turn["from"] == "gpt":

text += f"### Assistant:\n{turn['value']}\n\n"

text += EOS_TOKEN

texts.append(text)

return {"text": texts}

OpenAI Messages 형식 (Llama 3 chat template 사용)

def format_openai_messages(examples):

texts = []

for messages in examples["messages"]:

text = tokenizer.apply_chat_template(

messages,

tokenize=False,

add_generation_prompt=False,

)

texts.append(text)

return {"text": texts}

4.3 Max Sequence Length 고려사항

시퀀스 길이 분포 분석

def analyze_sequence_lengths(dataset, tokenizer):

lengths = []

for item in dataset:

tokens = tokenizer.encode(item["text"])

lengths.append(len(tokens))

print(f"평균 길이: {np.mean(lengths):.0f}")

print(f"중앙값: {np.median(lengths):.0f}")

print(f"95 퍼센타일: {np.percentile(lengths, 95):.0f}")

print(f"99 퍼센타일: {np.percentile(lengths, 99):.0f}")

print(f"최대 길이: {max(lengths)}")

권장 max_seq_length = 95 퍼센타일

recommended = int(np.percentile(lengths, 95))

print(f"\n권장 max_seq_length: {recommended}")

return lengths

analyze_sequence_lengths(dataset, tokenizer)

5. 학습 설정

5.1 SFTTrainer 설정

from trl import SFTTrainer

from transformers import TrainingArguments

from unsloth import is_bfloat16_supported

trainer = SFTTrainer(

model=model,

tokenizer=tokenizer,

train_dataset=dataset,

dataset_text_field="text",

max_seq_length=2048,

dataset_num_proc=2, # 데이터 전처리 병렬 수

packing=False, # 짧은 시퀀스 패킹 (True: 메모리 효율)

args=TrainingArguments(

=== 기본 설정 ===

output_dir="./outputs",

num_train_epochs=3,

=== 배치 & 메모리 ===

per_device_train_batch_size=2,

gradient_accumulation_steps=4, # 유효 배치 = 2 * 4 = 8

=== 학습률 ===

learning_rate=2e-4, # QLoRA 권장 학습률

lr_scheduler_type="cosine", # 코사인 스케줄러

warmup_steps=5, # 워밍업 스텝

=== 정밀도 ===

fp16=not is_bfloat16_supported(),

bf16=is_bfloat16_supported(),

=== 로깅 ===

logging_steps=1,

logging_dir="./logs",

report_to="wandb", # Weights & Biases 연동

=== 저장 ===

save_strategy="steps",

save_steps=100,

save_total_limit=3,

=== 최적화 ===

optim="adamw_8bit", # 8bit AdamW (메모리 절약)

weight_decay=0.01,

max_grad_norm=0.3,

seed=3407,

),

)

5.2 학습 파라미터 상세 설명

**학습률 (Learning Rate) 가이드:**

| 시나리오 | 권장 학습률 | 이유 |

|---------|-----------|------|

| QLoRA 기본 | 2e-4 | QLoRA 논문 권장값 |

| 큰 데이터셋 (100K+) | 1e-4 | 과적합 방지 |

| 작은 데이터셋 (1K 이하) | 5e-5 ~ 1e-4 | 세밀한 학습 |

| 도메인 적응 | 2e-5 ~ 5e-5 | 기존 지식 보존 |

| Continued Pre-training | 1e-5 ~ 5e-5 | 안정적 학습 |

**배치 크기 vs Gradient Accumulation:**

동일한 유효 배치 크기 8 달성하는 두 가지 방법

방법 1: 큰 배치 (VRAM 많이 필요)

per_device_train_batch_size = 8

gradient_accumulation_steps = 1

유효 배치 = 8 * 1 = 8, VRAM: ~12GB

방법 2: 작은 배치 + Gradient Accumulation (VRAM 적게 필요)

per_device_train_batch_size = 2

gradient_accumulation_steps = 4

유효 배치 = 2 * 4 = 8, VRAM: ~6GB

주의: 학습 속도는 조금 느려짐

5.3 Wandb 연동

Wandb 로그인 및 프로젝트 설정

wandb.login(key="your-wandb-api-key")

wandb.init(

project="korean-llm-finetuning",

name="llama3-8b-koalpaca-qlora",

config={

"model": "Meta-Llama-3.1-8B",

"dataset": "KoAlpaca-v1.1a",

"lora_r": 16,

"learning_rate": 2e-4,

"epochs": 3,

},

)

5.4 학습 실행

학습 시작

trainer_stats = trainer.train()

학습 결과 출력

print(f"학습 시간: {trainer_stats.metrics['train_runtime']:.2f}초")

print(f"최종 Loss: {trainer_stats.metrics['train_loss']:.4f}")

GPU 메모리 사용량 확인

used_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)

print(f"최대 VRAM 사용: {used_memory} GB")

6. VRAM 최적화 기법

6.1 Gradient Checkpointing

Unsloth 최적화 Gradient Checkpointing

model = FastLanguageModel.get_peft_model(

model,

r=16,

target_modules=["q_proj", "k_proj", "v_proj", "o_proj",

"gate_proj", "up_proj", "down_proj"],

use_gradient_checkpointing="unsloth", # 핵심! 30% VRAM 절약

)

일반 gradient checkpointing vs Unsloth

"unsloth": Unsloth 최적화 버전 (더 빠르고 메모리 효율적)

True: 표준 PyTorch gradient checkpointing

False: 사용 안 함 (가장 빠르지만 메모리 많이 사용)

6.2 Flash Attention 2

Unsloth는 Flash Attention 2를 자동으로 사용

별도 설정 불필요!

수동으로 확인하려면:

print(f"Flash Attention 사용: {hasattr(model.config, '_attn_implementation')}")

6.3 시퀀스 패킹

짧은 시퀀스를 하나로 묶어 GPU 활용률 향상

trainer = SFTTrainer(

model=model,

train_dataset=dataset,

packing=True, # 시퀀스 패킹 활성화

max_seq_length=2048, # 패킹된 전체 길이

)

패킹 효과:

패킹 OFF: [토큰토큰PAD PAD PAD PAD] [토큰PAD PAD PAD PAD PAD]

패킹 ON: [토큰토큰토큰SEP토큰토큰토큰] -> GPU 활용률 증가

6.4 VRAM 사용량 표 (Unsloth QLoRA 기준)

| 모델 | Batch=1 | Batch=2 | Batch=4 | Batch=8 |

|------|---------|---------|---------|---------|

| Llama 3 8B | 4.2GB | 5.8GB | 8.5GB | 14.2GB |

| Mistral 7B | 3.8GB | 5.2GB | 7.8GB | 13.0GB |

| Phi-3 3.8B | 2.4GB | 3.2GB | 4.8GB | 7.6GB |

| Qwen 2 7B | 3.8GB | 5.2GB | 7.8GB | 13.0GB |

| Llama 3 70B | 36GB | 42GB | 56GB | OOM |

`*` max_seq_length=2048, gradient_checkpointing="unsloth" 기준

7. 모델 내보내기 및 변환

7.1 LoRA 어댑터 저장

LoRA 어댑터만 저장 (작은 크기)

model.save_pretrained("lora_adapter")

tokenizer.save_pretrained("lora_adapter")

저장된 파일 확인

for f in os.listdir("lora_adapter"):

size = os.path.getsize(f"lora_adapter/{f}") / 1024 / 1024

print(f" {f}: {size:.1f} MB")

adapter_config.json: 0.0 MB

adapter_model.safetensors: 160.0 MB <- LoRA 가중치

tokenizer.json: 17.1 MB

7.2 어댑터 병합 (Merge)

LoRA 어댑터를 베이스 모델과 병합

merged_model = model.merge_and_unload()

병합된 모델 저장

merged_model.save_pretrained("merged_model")

tokenizer.save_pretrained("merged_model")

7.3 GGUF 변환 (llama.cpp용)

Unsloth의 내장 GGUF 변환 기능

다양한 양자화 레벨 지원

Q4_K_M: 가장 일반적 (품질/크기 균형)

model.save_pretrained_gguf(

"model_gguf",

tokenizer,

quantization_method="q4_k_m",

)

Q5_K_M: 더 높은 품질

model.save_pretrained_gguf(

"model_q5",

tokenizer,

quantization_method="q5_k_m",

)

Q8_0: 최고 품질 (크기 큼)

model.save_pretrained_gguf(

"model_q8",

tokenizer,

quantization_method="q8_0",

)

F16: 양자화 없음 (가장 큼)

model.save_pretrained_gguf(

"model_f16",

tokenizer,

quantization_method="f16",

)

**GGUF 양자화 비교:**

| 양자화 | 파일 크기 (8B) | 품질 | 추론 속도 | 권장 |

|--------|--------------|------|----------|------|

| Q4_K_M | ~4.5GB | 좋음 | 빠름 | 일반 사용 |

| Q5_K_M | ~5.5GB | 매우 좋음 | 보통 | 품질 중시 |

| Q8_0 | ~8.0GB | 우수 | 느림 | 최고 품질 |

| F16 | ~16GB | 원본 | 가장 느림 | 참고용 |

7.4 GPTQ 변환

GPTQ 양자화 (GPU 추론용)

from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig

quantize_config = BaseQuantizeConfig(

bits=4,

group_size=128,

desc_act=False,

)

캘리브레이션 데이터 준비

calibration_data = [

tokenizer(text, return_tensors="pt")

for text in calibration_texts[:128]

]

GPTQ 양자화 실행

gptq_model = AutoGPTQForCausalLM.from_pretrained(

"merged_model",

quantize_config=quantize_config,

)

gptq_model.quantize(calibration_data)

gptq_model.save_quantized("model_gptq")

7.5 Hugging Face Hub 업로드

모델을 Hugging Face Hub에 업로드

LoRA 어댑터만 업로드

model.push_to_hub(

"my-org/llama3-8b-korean-lora",

token="hf_xxxxx",

private=True,

)

tokenizer.push_to_hub(

"my-org/llama3-8b-korean-lora",

token="hf_xxxxx",

private=True,

)

GGUF 파일 업로드

model.push_to_hub_gguf(

"my-org/llama3-8b-korean-gguf",

tokenizer,

quantization_method="q4_k_m",

token="hf_xxxxx",

)

8. 평가 및 테스트

8.1 파인튜닝된 모델로 추론

추론 모드로 전환

FastLanguageModel.for_inference(model)

단일 프롬프트 추론

def generate_response(instruction, input_text=""):

prompt = alpaca_prompt.format(instruction, input_text, "")

inputs = tokenizer([prompt], return_tensors="pt").to("cuda")

outputs = model.generate(

**inputs,

max_new_tokens=512,

temperature=0.7,

top_p=0.9,

repetition_penalty=1.15,

do_sample=True,

)

response = tokenizer.batch_decode(outputs)[0]

Response 부분만 추출

response = response.split("### Response:\n")[-1]

response = response.replace(tokenizer.eos_token, "").strip()

return response

테스트

test_questions = [

"한국의 전통 명절에 대해 설명해주세요.",

"파이썬에서 데코레이터의 동작 원리를 설명해주세요.",

"건강한 식습관을 위한 팁을 알려주세요.",

]

for q in test_questions:

print(f"Q: {q}")

print(f"A: {generate_response(q)}")

print("-" * 80)

8.2 생성 파라미터 튜닝

생성 파라미터별 효과

generation_configs = {

"정확한 답변 (factual)": {

"temperature": 0.1,

"top_p": 0.9,

"repetition_penalty": 1.0,

},

"창의적 답변 (creative)": {

"temperature": 0.8,

"top_p": 0.95,

"repetition_penalty": 1.15,

},

"균형잡힌 답변 (balanced)": {

"temperature": 0.5,

"top_p": 0.9,

"repetition_penalty": 1.1,

},

}

8.3 lm-eval-harness 벤치마크

lm-eval-harness로 벤치마크 평가

pip install lm-eval

한국어 벤치마크 평가

lm_eval --model hf \

--model_args pretrained=./merged_model \

--tasks kobest_boolq,kobest_copa,kobest_hellaswag,kobest_sentineg,kobest_wic \

--batch_size 4 \

--output_path ./eval_results

Python에서 실행

from lm_eval import evaluator

results = evaluator.simple_evaluate(

model="hf",

model_args="pretrained=./merged_model",

tasks=["kobest_boolq", "kobest_copa", "kobest_hellaswag"],

batch_size=4,

)

for task, metrics in results["results"].items():

print(f"{task}: acc={metrics.get('acc', 'N/A')}")

9. 고급 기법

9.1 Multi-GPU 학습 (DeepSpeed ZeRO)

deepspeed_config.json

"""

{

"zero_optimization": {

"stage": 2,

"offload_optimizer": {

"device": "cpu",

"pin_memory": true

},

"allgather_partitions": true,

"reduce_scatter": true

},

"bf16": {

"enabled": true

},

"train_batch_size": "auto",

"train_micro_batch_size_per_gpu": "auto"

}

"""

실행

deepspeed --num_gpus 4 train.py --deepspeed deepspeed_config.json

9.2 DPO 학습

from trl import DPOTrainer, DPOConfig

from unsloth import FastLanguageModel, PatchDPOTrainer

DPO 패치 적용

PatchDPOTrainer()

DPO 데이터셋 준비

dpo_dataset = load_dataset("argilla/ultrafeedback-binarized-preferences", split="train")

DPO Trainer 설정

dpo_trainer = DPOTrainer(

model=model,

ref_model=None, # Unsloth에서는 None (자동 처리)

args=DPOConfig(

output_dir="./dpo_output",

per_device_train_batch_size=2,

gradient_accumulation_steps=4,

learning_rate=5e-7, # DPO는 낮은 학습률

num_train_epochs=1,

beta=0.1, # DPO beta (KL divergence 가중치)

fp16=not is_bfloat16_supported(),

bf16=is_bfloat16_supported(),

logging_steps=1,

),

train_dataset=dpo_dataset,

tokenizer=tokenizer,

)

dpo_trainer.train()

9.3 Continued Pre-training (도메인 적응)

도메인 특화 텍스트로 Continued Pre-training

from trl import SFTTrainer

도메인 텍스트 데이터 (의료, 법률, 금융 등)

domain_dataset = load_dataset("my-org/medical-korean-corpus", split="train")

Continued Pre-training은 낮은 학습률 사용

trainer = SFTTrainer(

model=model,

tokenizer=tokenizer,

train_dataset=domain_dataset,

dataset_text_field="text",

max_seq_length=4096, # 긴 문서

packing=True, # 효율성 위해 패킹 사용

args=TrainingArguments(

output_dir="./cpt_output",

learning_rate=2e-5, # 매우 낮은 학습률

num_train_epochs=1, # 1 epoch면 충분

per_device_train_batch_size=1,

gradient_accumulation_steps=8,

optim="adamw_8bit",

warmup_ratio=0.1,

),

)

trainer.train()

10. 일반적인 문제와 해결법

10.1 OOM (Out of Memory) 오류

증상: CUDA out of memory

RuntimeError: CUDA out of memory.

Tried to allocate 256.00 MiB

해결법 순서:

1. batch_size 줄이기

per_device_train_batch_size = 1 # 최소값

2. gradient_accumulation_steps 늘리기

gradient_accumulation_steps = 8

3. max_seq_length 줄이기

max_seq_length = 1024 # 2048 -> 1024

4. LoRA rank 줄이기

r = 8 # 16 -> 8

5. gradient checkpointing 확인

use_gradient_checkpointing = "unsloth"

6. 캐시 비우기

torch.cuda.empty_cache()

gc.collect()

10.2 NaN Loss

증상: loss가 NaN으로 발산

원인: 학습률이 너무 높거나 데이터 문제

해결법:

1. 학습률 낮추기

learning_rate = 1e-5 # 2e-4 -> 1e-5

2. max_grad_norm 설정

max_grad_norm = 0.3 # gradient clipping

3. 데이터 검증

def check_data_issues(dataset, tokenizer):

"""데이터 문제 검사"""

issues = []

for i, item in enumerate(dataset):

text = item["text"]

빈 텍스트 확인

if not text.strip():

issues.append(f"[{i}] 빈 텍스트")

너무 긴 텍스트

tokens = tokenizer.encode(text)

if len(tokens) > 4096:

issues.append(f"[{i}] 너무 긴 텍스트: {len(tokens)} tokens")

특수 문자만 있는 경우

if not any(c.isalnum() for c in text):

issues.append(f"[{i}] 유효한 텍스트 없음")

return issues

10.3 Catastrophic Forgetting (파국적 망각)

증상: 파인튜닝 후 기존 지식이 사라짐

해결법:

1. 낮은 학습률 사용

learning_rate = 5e-5

2. 적은 epoch (1-3)

num_train_epochs = 1

3. 데이터에 일반 지식 혼합

원래 데이터 80% + 일반 지식 데이터 20%

4. LoRA rank 낮추기 (변경 폭 제한)

r = 8

5. 정규화 강화

weight_decay = 0.1

10.4 과적합 탐지

과적합 지표

1. Train Loss는 줄어드는데 Eval Loss가 증가

2. 학습 데이터를 거의 외우는 수준의 출력

3. 새로운 프롬프트에 대한 성능 저하

해결법:

1. 데이터 양 늘리기

2. 정규화 (dropout, weight_decay)

3. Early stopping

from transformers import EarlyStoppingCallback

trainer = SFTTrainer(

callbacks=[EarlyStoppingCallback(early_stopping_patience=3)],

args=TrainingArguments(

evaluation_strategy="steps",

eval_steps=50,

load_best_model_at_end=True,

),

)

11. 퀴즈

**정답: 약 0.5% (99.5% 절약)**

d=4096인 경우:

- Full: 4096 x 4096 = 16,777,216

- LoRA r=16: (4096 x 16) + (16 x 4096) = 131,072

- 비율: 131,072 / 16,777,216 = 0.78%

실제로 여러 모듈(q, k, v, o, gate, up, down)에 적용하므로 총 파라미터 대비 약 0.5% 수준입니다.

**정답: 가중치의 정규분포 특성을 활용한 최적 양자화**

NF4는 신경망 가중치가 대체로 정규분포를 따른다는 점을 이용합니다. 정규분포의 분위수에 맞춰 16개 양자화 값을 배치하므로, 균일 분할인 INT4보다 정보 손실이 적습니다. 이론적으로 정규분포 데이터에 대해 최적에 가까운 양자화를 달성합니다.

**정답: 커스텀 Triton 커널**

Unsloth는 Attention, MLP, Cross-Entropy Loss 등의 핵심 연산을 Triton으로 작성한 커스텀 GPU 커널로 대체합니다. 이 커널들은 메모리 접근 패턴을 최적화하고, 불필요한 중간 텐서 생성을 줄여 2배 빠른 학습과 60% 메모리 절약을 달성합니다.

**정답:**

**원리:** Forward pass에서 중간 활성화값(activation)을 메모리에 저장하지 않고, Backward pass에서 필요할 때 다시 계산합니다.

**트레이드오프:**

- 장점: VRAM 사용량 약 30~50% 감소

- 단점: 재계산으로 인해 학습 시간 약 20~30% 증가

Unsloth의 커스텀 gradient checkpointing은 일반 PyTorch 구현보다 더 효율적이어서 시간 증가가 적습니다.

**정답:**

**Q4_K_M (4-bit Mixed):**

- 파일 크기: 원본의 약 28% (8B 모델 기준 약 4.5GB)

- 품질: 원본 대비 약간의 성능 저하

- 속도: 빠름

- 권장: 일상 사용, 모바일/엣지 배포, VRAM/RAM 제한 환경

**Q8_0 (8-bit):**

- 파일 크기: 원본의 약 50% (8B 모델 기준 약 8GB)

- 품질: 원본에 매우 가까움

- 속도: Q4 대비 느림

- 권장: 품질 최우선, 충분한 메모리가 있는 환경, 정확한 추론이 필요한 서비스

12. 참고 자료

1. **LoRA: Low-Rank Adaptation of Large Language Models** - Hu et al., 2021

2. **QLoRA: Efficient Finetuning of Quantized LLMs** - Dettmers et al., 2023

3. **Unsloth Documentation** - github.com/unslothai/unsloth

4. **PEFT: Parameter-Efficient Fine-Tuning** - HuggingFace

5. **TRL: Transformer Reinforcement Learning** - HuggingFace

6. **Flash Attention 2** - Dao et al., 2023

7. **LLM.int8(): 8-bit Matrix Multiplication** - Dettmers et al., 2022

8. **llama.cpp** - github.com/ggerganov/llama.cpp

9. **GPTQ: Accurate Post-Training Quantization** - Frantar et al., 2022

10. **DeepSpeed ZeRO** - Rajbhandari et al., 2020

11. **Direct Preference Optimization** - Rafailov et al., 2023

12. **Scaling Data-Constrained Language Models** - Muennighoff et al., 2023

13. **Training Compute-Optimal Large Language Models (Chinchilla)** - Hoffmann et al., 2022

14. **The Llama 3 Herd of Models** - Meta AI, 2024

현재 단락 (1/560)

LLM 파인튜닝의 가장 큰 진입 장벽은 **GPU 메모리(VRAM)**입니다. Llama 3.1 8B를 Full Fine-tuning하려면 약 60GB VRAM이 필요하고, 이는 ...

작성 글자: 0원문 글자: 19,024작성 단락: 0/560