Gemini

Gemma 4 12B 완전분석 3편 : Unsloth로 내 데이터에 파인튜닝하기

cell-devlog 2026. 6. 5. 10:01
반응형

소비자 GPU 한 장으로, 내 도메인에 맞는 12B 모델을 직접 학습시킵니다.


✅ 핵심 요약

→ Unsloth는 FA2 대비 ~1.5배 빠르고 ~60% 적은 VRAM으로 Gemma 4를 학습시킵니다 → 12B QLoRA는 RTX 4090(24GB) 권장 — RTX 3060(12GB)에서도 가능하나 배치 크기 줄여야 함 → Encoder-Free 아키텍처 덕분에 멀티모달(이미지·오디오·텍스트) 어댑터가 단일 pass로 학습됩니다 → 기존 인코더 기반 모델처럼 인코더를 freeze할 필요가 없습니다 → use_gradient_checkpointing="unsloth" 필수 — 없으면 VRAM 30% 더 잡아먹습니다 → 26B-A4B MoE는 QLoRA 대신 16-bit LoRA 사용 권장 (MoE routing + 4-bit 충돌) → Thinking mode 보존하려면 학습 데이터에 reasoning 예시 최소 75% 유지 필요 → 학습 완료 후 GGUF로 변환하면 vLLM / SGLang / Ollama에 그대로 배포 가능


파인튜닝이 필요한 시점

모델이 좋아도 "내 도메인"에서는 삐걱거리는 순간이 있습니다. 아래 기준으로 판단하세요.

상황 접근법

특정 스타일·톤으로 항상 답하게 하고 싶다 파인튜닝
내부 전문 용어·도메인 지식이 필요하다 파인튜닝
특정 출력 포맷(JSON, 특수 구조)을 강제하고 싶다 파인튜닝
최신 외부 데이터를 참조해야 한다 RAG가 더 적합
프롬프트 엔지니어링으로 해결 가능한 수준 파인튜닝 불필요

VRAM 요구량 — 모델 크기별

모델 방법 VRAM

E2B LoRA 8~10GB
E4B LoRA ~17GB
12B QLoRA (4-bit) ~16~20GB
26B-A4B 16-bit LoRA (QLoRA 비권장) 40GB+
31B QLoRA (4-bit) ~22GB

💡 12B QLoRA 기준으로 RTX 4090(24GB)이 편하게 돌아갑니다. RTX 3060(12GB)에서도 per_device_train_batch_size=1, gradient_accumulation_steps=8로 줄이면 가능합니다. 훈련 시간은 10K 샘플 기준 RTX 3060에서 약 3시간, RTX 4090에서 30~45분입니다.


Step 1. 설치

pip install unsloth
pip install --upgrade trl transformers datasets

Step 2. 모델 로드 (QLoRA)

from unsloth import FastLanguageModel
import torch

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="google/gemma-4-12b-it",
    max_seq_length=4096,       # 학습에 쓸 최대 시퀀스 길이
    load_in_4bit=True,         # QLoRA — 4-bit NF4 양자화
    dtype=None,                # 자동 감지 (BF16 권장)
)

⚠️ 26B-A4B는 예외 — MoE routing과 4-bit 양자화가 충돌합니다. 26B는 load_in_4bit=False, load_in_16bit=True로 16-bit LoRA를 써야 합니다.


Step 3. LoRA 어댑터 붙이기

model = FastLanguageModel.get_peft_model(
    model,
    r=16,                      # LoRA rank — 복잡한 태스크면 32, 간단하면 8
    lora_alpha=16,             # 보통 r과 동일하게 설정
    lora_dropout=0,            # Unsloth 최적화 — 0으로 유지
    bias="none",
    target_modules=[           # 어텐션 + FFN 레이어 모두 타겟
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    use_gradient_checkpointing="unsloth",  # VRAM 30% 절약 — 필수
    random_state=42,
    max_seq_length=4096,
)

💡 LoRA rank 선택 가이드

  • r=8: 스타일·톤 조정처럼 단순한 태스크
  • r=16: 범용 기본값, 대부분 여기서 시작
  • r=32: 도메인 전문 지식, 복잡한 출력 포맷
  • r=64: VRAM 여유 있을 때만, 과적합 주의

Step 4. 학습 데이터 준비

Gemma 4는 standard chat format을 씁니다. conversations 키에 role/content 리스트를 넣습니다.

데이터 포맷 (JSONL)

{"conversations": [{"role": "user", "content": "FastAPI 헬스체크 엔드포인트 짜줘"}, {"role": "assistant", "content": "from fastapi import FastAPI\n\napp = FastAPI()\n\n@app.get('/health')\ndef health_check():\n    return {'status': 'ok'}"}]}
{"conversations": [{"role": "user", "content": "Redis pub/sub 파이썬 예제"}, {"role": "assistant", "content": "..."}]}

Thinking mode 보존할 때

추론 능력을 유지하려면 학습 데이터의 최소 75%에 reasoning 예시를 포함해야 합니다.

{
  "conversations": [
    {"role": "user", "content": "이 SQL 쿼리 최적화해줘: SELECT * FROM orders WHERE ..."},
    {"role": "assistant", "content": "<|channel>thought\n인덱스가 없는 컬럼에 WHERE 절이 걸려있다. EXPLAIN을 먼저 보면...\n<channel|>\n\n인덱스 추가와 SELECT * 대신 필요한 컬럼만 지정하면 됩니다:\n\nCREATE INDEX idx_orders_status ON orders(status);\nSELECT id, user_id, total FROM orders WHERE status = 'pending';"}
  ]
}

데이터셋 로드

from datasets import load_dataset

dataset = load_dataset(
    "json",
    data_files={"train": "my_data.jsonl"},
    split="train"
)

💡 데이터 양 가이드

  • 스타일·톤 조정: 500~1,000개
  • 태스크 특화 적응: 1,000~5,000개
  • 도메인 지식 주입: 10,000~50,000개

양보다 질이 중요합니다. 노이즈 많은 10,000개보다 깨끗한 1,000개가 낫습니다.


Step 5. 학습 실행

from trl import SFTTrainer, SFTConfig

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    args=SFTConfig(
        output_dir="./gemma4-12b-finetuned",
        per_device_train_batch_size=2,     # VRAM 빡빡하면 1로
        gradient_accumulation_steps=4,     # effective batch = 2 * 4 = 8
        num_train_epochs=3,
        learning_rate=2e-4,
        warmup_ratio=0.1,
        lr_scheduler_type="cosine",
        bf16=True,                         # BF16 필수
        logging_steps=10,
        save_strategy="epoch",
        max_seq_length=4096,
        dataset_text_field="conversations",
        # Thinking mode 쓸 경우 chat template 설정
        # tokenizer.chat_template = "gemma-4-thinking"
    ),
)

trainer.train()

학습 중 loss 이상 여부

정상 범위:
- 12B: 초기 loss 2.0~3.5, 수렴 시 0.8~1.5
- E2B / E4B: 초기 loss 13~15 → 정상 (멀티모달 모델 특성)
  ← 이 숫자에 놀라지 마세요, 수렴은 정상적으로 됩니다

Step 6. 어댑터 저장 및 병합

어댑터만 저장 (빠른 실험용)

# 어댑터 파일만 저장 (~50MB)
model.save_pretrained("./gemma4-12b-adapter")
tokenizer.save_pretrained("./gemma4-12b-adapter")

베이스 모델과 병합 (배포용)

# LoRA 병합 후 저장
merged_model = model.merge_and_unload()
merged_model.save_pretrained("./gemma4-12b-merged")
tokenizer.save_pretrained("./gemma4-12b-merged")

Step 7. GGUF 변환 → 배포

병합된 모델을 GGUF로 변환하면 vLLM / SGLang / Ollama에 바로 올릴 수 있습니다.

# llama.cpp 변환 스크립트
python llama.cpp/convert_hf_to_gguf.py ./gemma4-12b-merged \
  --outfile gemma4-12b-finetuned.gguf \
  --outtype q4_k_m

Ollama에 올리기

# Modelfile
FROM ./gemma4-12b-finetuned.gguf

PARAMETER num_ctx 32768
PARAMETER temperature 1.0
PARAMETER top_p 0.95
PARAMETER top_k 64
ollama create my-gemma4 -f Modelfile
ollama run my-gemma4

자주 만나는 에러와 해결법

에러 원인 해결

CUDA OOM 배치 크기 과다 per_device_train_batch_size=1, gradient_accumulation_steps 늘리기
loss가 수렴 안 함 lr 너무 낮음 learning_rate=5e-4로 올려보기
E2B/E4B loss 13~15 멀티모달 정상 동작 정상 — 무시하고 계속
fp16 overflow on T4 attention logits -1e9 overflow Unsloth 최신 버전으로 업데이트 (버그 픽스 완료)
추론 능력 저하 thinking 예시 부족 학습 데이터 75% 이상 reasoning 포함

✅ 전체 파이프라인 요약

1. pip install unsloth
2. FastLanguageModel.from_pretrained()  ← load_in_4bit=True
3. FastLanguageModel.get_peft_model()   ← r=16, gradient_checkpointing="unsloth"
4. JSONL 데이터셋 준비                  ← conversations 포맷
5. SFTTrainer.train()
6. merge_and_unload() → save_pretrained()
7. convert_hf_to_gguf.py → Q4_K_M
8. vLLM / SGLang / Ollama 배포

관련 글


 

반응형