본문 바로가기

LLM

Gemma 4 파인튜닝 Unsloth로 30분에 끝내기 — API 비용 0원, 도메인 특화 모델

반응형

GPT-4급 성능을 API 비용 없이. 내 도메인 데이터로 특화된 모델을.

Gemma 4는 2026년 4월 2일 Google DeepMind가 출시한 오픈소스 모델이에요. Apache 2.0 라이선스 — 상업적 사용, 재배포, 수정 모두 자유예요.

파인튜닝이 필요한 이유:
프롬프트 엔지니어링: "항상 JSON으로 응답해줘" → 30% 실패율
RAG: 지식 주입엔 좋지만 스타일/형식 제어 어려움
파인튜닝: 99% 이상 일관된 출력, 도메인 특화 지식

언제 파인튜닝이 답인가:
→ 출력 형식이 항상 일정해야 할 때
→ 특정 도메인 용어/스타일이 필요할 때
→ 프롬프트가 너무 길어서 비용 문제가 될 때
→ API 없이 로컬/온프레미스 배포가 필요할 때

Unsloth가 뭔가

HuggingFace 기본 방식 대비:

속도:   2배 빠른 학습
메모리: 최대 70% 절약

기본 HuggingFace로 E4B 파인튜닝:
→ VRAM 15GB 필요

Unsloth QLoRA로:
→ VRAM 8GB면 충분 (RTX 3070, 3060 ti 가능)

0단계 — 모델 선택

E2B (2B): 스마트폰/라즈베리파이 수준
          → 매우 가벼운 분류/추출 작업

E4B (4B): ← 이 글의 대상
          무료 Google Colab T4 GPU에서 가능
          RTX 3060 이상이면 로컬에서도 가능
          텍스트 + 이미지 + 오디오 지원

26B A4B:  A100 Colab 또는 RTX 4090 필요
          MoE 구조 → QLoRA 비권장, 16bit LoRA 권장

31B:      RTX 4090 24GB + QLoRA
          최고 품질

1단계 — 환경 설치

# 가상환경 생성
python -m venv gemma4-finetune
source gemma4-finetune/bin/activate  # Windows: .\gemma4-finetune\Scripts\activate

# Unsloth 설치 (의존성 전부 포함)
pip install unsloth

# GPU 없이 CPU만 있는 경우
pip install unsloth[cpu]

# 추가 라이브러리
pip install datasets trl transformers

Google Colab에서 할 경우 (무료 T4 GPU):

# Colab 첫 셀에 실행
!pip install unsloth
!pip install datasets trl

2단계 — 학습 데이터 만들기

파인튜닝의 핵심은 데이터예요. 코드보다 훨씬 중요해요.

데이터 형식

{"messages": [
  {"role": "user", "content": "FastAPI에서 JWT 인증 미들웨어 만들어줘"},
  {"role": "assistant", "content": "```python\nfrom fastapi import Request, HTTPException\nfrom jose import jwt\n...\n```\n\n위 코드는 Bearer 토큰을 검증합니다..."}
]}
{"messages": [
  {"role": "user", "content": "PostgreSQL 연결 풀 설정하는 법"},
  {"role": "assistant", "content": "```python\nfrom sqlalchemy.ext.asyncio import create_async_engine\n...\n```"}
]}

데이터 수집 방법

# 방법 1: 직접 작성 (품질 최고)
training_data = [
    {
        "messages": [
            {"role": "user", "content": "질문"},
            {"role": "assistant", "content": "답변"}
        ]
    },
    # ...
]

# JSONL로 저장
import json
with open("training_data.jsonl", "w", encoding="utf-8") as f:
    for item in training_data:
        f.write(json.dumps(item, ensure_ascii=False) + "\n")
# 방법 2: 기존 문서/로그에서 자동 생성
import anthropic

client = anthropic.Anthropic()

def generate_qa_pairs(document: str, n: int = 10) -> list:
    """문서에서 Q&A 쌍 자동 생성"""

    response = client.messages.create(
        model="claude-haiku-4-5",  # 생성은 Haiku로 절약
        max_tokens=2000,
        messages=[{
            "role": "user",
            "content": f"""
다음 문서를 기반으로 Q&A 쌍 {n}개를 만들어줘.
실제 사용자가 물어볼 법한 질문과 정확한 답변으로.
JSON 배열로만 응답해줘:
[{{"user": "질문", "assistant": "답변"}}, ...]

문서:
{document}
"""
        }]
    )

    pairs = json.loads(response.content[0].text)
    return [
        {"messages": [
            {"role": "user", "content": p["user"]},
            {"role": "assistant", "content": p["assistant"]}
        ]}
        for p in pairs
    ]

# 내 회사 문서에서 생성
docs = ["API 문서 내용", "코딩 가이드라인", "도메인 지식 문서"]
all_pairs = []
for doc in docs:
    pairs = generate_qa_pairs(doc, n=20)
    all_pairs.extend(pairs)

print(f"총 {len(all_pairs)}개 학습 데이터 생성")

최소 데이터 기준

50개:  기본적인 스타일/형식 학습 가능
100개: 도메인 용어 습득
200개: 대부분의 도메인 특화 작업에 충분
500개+: 전문가 수준 도메인 모델

3단계 — 파인튜닝 실행

from unsloth import FastLanguageModel
import torch
from datasets import load_dataset
from trl import SFTTrainer, SFTConfig

# ── 1. 모델 로드 ──────────────────────────────────
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/gemma-4-E4B-it",  # E4B 권장
    max_seq_length=2048,   # 보수적으로 시작
    load_in_4bit=True,     # QLoRA — VRAM 절반으로
    dtype=None,            # 자동 감지
)

# ── 2. LoRA 어댑터 추가 ───────────────────────────
model = FastLanguageModel.get_peft_model(
    model,
    r=16,                    # LoRA 랭크 (8~32 권장)
    lora_alpha=16,           # 스케일링 파라미터
    lora_dropout=0,          # Unsloth 최적화 — 0 유지
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    use_gradient_checkpointing="unsloth",  # VRAM 30% 추가 절약
    bias="none",
)

# ── 3. 데이터셋 로드 ──────────────────────────────
dataset = load_dataset(
    "json",
    data_files="training_data.jsonl",
    split="train"
)

# 채팅 템플릿 적용
def apply_chat_template(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}

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

# ── 4. 학습 설정 ──────────────────────────────────
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    args=SFTConfig(
        output_dir="./gemma4-finetuned",

        # 핵심 설정
        num_train_epochs=3,              # 데이터 200개 이하면 3, 이상이면 1~2
        per_device_train_batch_size=2,   # VRAM 부족하면 1로
        gradient_accumulation_steps=4,   # 사실상 배치 8

        # 학습률
        learning_rate=2e-4,
        warmup_ratio=0.1,
        lr_scheduler_type="cosine",

        # 정밀도
        bf16=True,                       # RTX 30xx 이상 지원
        fp16=False,

        # 로깅
        logging_steps=10,
        save_strategy="epoch",

        # 최적화
        dataset_text_field="text",
        max_seq_length=2048,
        packing=True,                    # 짧은 샘플 묶어서 효율 향상
    ),
)

# ── 5. 학습 시작 ──────────────────────────────────
print("학습 시작...")
trainer.train()
print("완료!")

4단계 — 결과 확인

# 학습 전후 비교
from unsloth import FastLanguageModel

# 추론 모드로 전환 (필수!)
FastLanguageModel.for_inference(model)

def ask(prompt: str) -> str:
    messages = [{"role": "user", "content": prompt}]

    inputs = tokenizer.apply_chat_template(
        messages,
        tokenize=True,
        add_generation_prompt=True,
        return_tensors="pt"
    ).to("cuda")

    outputs = model.generate(
        input_ids=inputs,
        max_new_tokens=512,
        temperature=0.7,
        do_sample=True,
    )

    # 입력 부분 제거하고 응답만 추출
    response = tokenizer.decode(
        outputs[0][inputs.shape[-1]:],
        skip_special_tokens=True
    )
    return response

# 테스트
print(ask("FastAPI에서 JWT 인증 어떻게 구현해?"))

5단계 — GGUF로 내보내서 Ollama에 배포

# GGUF 변환 + 저장
model.save_pretrained_gguf(
    "gemma4-custom",
    tokenizer,
    quantization_method="q4_k_m"  # 권장: q4_k_m (품질/크기 균형)
    # 옵션: q8_0 (고품질), q4_0 (소형), q2_k (초소형)
)

Ollama에 등록:

# Modelfile 생성
cat > Modelfile << 'EOF'
FROM ./gemma4-custom-Q4_K_M.gguf

SYSTEM """
당신은 우리 회사 FastAPI/Python 전문 코딩 어시스턴트입니다.
항상 실행 가능한 코드를 제공하고, 우리 프로젝트 구조를 따릅니다.
"""
EOF

# Ollama에 등록
ollama create my-gemma4 -f Modelfile

# 실행
ollama run my-gemma4

GPU별 예상 시간 (데이터 200개 기준)

무료 Colab T4 (16GB):   약 25~35분
RTX 3060 (12GB):        약 40~50분
RTX 3090 (24GB):        약 15~20분
RTX 4090 (24GB):        약 10~15분
A100 (40GB):            약 5~10분

흔한 실수와 해결법

OOM (Out of Memory):
→ per_device_train_batch_size=1 로 줄이기
→ max_seq_length=1024 로 줄이기
→ gradient_accumulation_steps=8 으로 늘리기

Loss가 안 떨어짐:
→ learning_rate=5e-4 로 높이기
→ 데이터 품질 확인 (형식 일관성)
→ num_train_epochs=5 로 늘리기

파인튜닝 후 성능 저하:
→ r=8 로 낮추기 (과적합 방지)
→ 데이터에 일반 지식 샘플 20~30% 섞기
→ epoch 3에서 멈추기 (더 이상 하면 오히려 나빠짐)

추론 모드 안 함:
→ FastLanguageModel.for_inference(model) 반드시 호출
→ 안 하면 학습 모드로 동작 → 느리고 이상한 출력

전체 파이프라인 요약

1. 데이터 준비 (15분)
   → JSONL 형식 200개 이상
   → 기존 문서/로그에서 AI로 자동 생성 가능

2. 학습 실행 (10~35분, GPU에 따라)
   → pip install unsloth 한 줄
   → 코드 복붙 후 dataset 경로만 수정

3. 결과 확인 (5분)
   → 학습 전/후 같은 질문으로 비교

4. GGUF 내보내기 (5분)
   → Ollama에 등록하면 어디서든 사용

 

반응형