4장|SLM 파인튜닝 실습

1. 환경 준비(필수 패키지)

pip install -U transformers datasets peft accelerate bitsandbytes
# GPU가 없다면 bitsandbytes는 생략 가능(QLoRA 미사용)

Python 3.10+ 권장. GPU가 없으면 QLoRA 대신 LoRA만 시도하세요(속도 이슈 고려).


2. 데이터 불러오기(3장의 train.jsonl 사용)

  • 3장에서 만든 train.jsonl(한 줄에 {"text": "..."} 형태)을 그대로 사용한다.
from datasets import load_dataset

dataset = load_dataset("json", data_files="train.jsonl")
# 작은 샘플로 먼저 확인하고 싶다면:
# dataset["train"] = dataset["train"].select(range(200))

3. 토크나이저/모델 준비 + “labels” 생성

  • 핵심: labels = input_ids + 패딩 토큰 위치는 -100으로 마스킹
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForLanguageModeling

# SLM 계열: 크기가 너무 큰 모델은 내 머신에서 과부하가 날 수 있습니다.
# 예시 1) OPT 1.3B
model_name = "facebook/opt-1.3b"

tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)
# pad 토큰 지정(없으면 eos를 pad로 사용)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

def tokenize_and_label(batch):
    enc = tokenizer(
        batch["text"],
        truncation=True,
        padding="max_length",
        max_length=512,
    )
    # labels = input_ids (언어모델용 next-token 예측)
    labels = []
    for ids in enc["input_ids"]:
        # pad 토큰 위치는 -100으로 마스킹 → loss에서 제외
        labels.append([tok if tok != tokenizer.pad_token_id else -100 for tok in ids])
    enc["labels"] = labels
    return enc

tokenized = dataset.map(tokenize_and_label, batched=True, remove_columns=["text"])

# causal LM용 collator (MLM=False 중요)
collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

# 모델 로드(LoRA 적용 전 base 모델)
base_model = AutoModelForCausalLM.from_pretrained(model_name)
# 토큰 개수 변경 시(위에서 pad_token 추가) 안전하게 resize
base_model.resize_token_embeddings(len(tokenizer))

4. LoRA 적용(메모리 절약형 미세조정)

from peft import LoraConfig, get_peft_model

lora_cfg = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],  # OPT/LLaMA 계열에서 널리 쓰는 패턴
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

lora_model = get_peft_model(base_model, lora_cfg)

모델 유형에 따라 target_modules가 다를 수 있다. 만약 architecture가 달라 에러가 나면
['q_proj','k_proj','v_proj','o_proj'] 중 존재하는 레이어로 조정하라.


5. 학습 루프(Trainer)

from transformers import Trainer, TrainingArguments
import torch

# bfloat16/float16 자동 선택
use_bf16 = torch.cuda.is_available() and torch.cuda.get_device_capability(0)[0] >= 8
use_fp16 = torch.cuda.is_available() and not use_bf16

args = TrainingArguments(
    output_dir="./lora-out",
    num_train_epochs=2,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    logging_steps=20,
    save_strategy="epoch",
    save_total_limit=2,
    report_to="none",
    bf16=use_bf16,
    fp16=use_fp16,
    optim="paged_adamw_8bit" if torch.cuda.is_available() else "adamw_torch",
)

trainer = Trainer(
    model=lora_model,
    args=args,
    train_dataset=tokenized["train"],
    data_collator=collator,
)
trainer.train()
  • 이 시점에서 loss가 정상적으로 출력되어야 한다.
  • CPU만 있는 환경에서는 시간이 오래 걸린다. 처음엔 select(range(200))로 200개 정도만 학습해 파이프라인 정상 작동 먼저 확인하는 것을 권장.

6. QLoRA(선택) — GPU 메모리가 아주 적을 때

import torch
from peft import prepare_model_for_kbit_training
from transformers import BitsAndBytesConfig

bnb_cfg = BitsAndBytesConfig(
    load_in_4bit=True,                      # 4비트 양자화
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.float16,
)

qlora_base = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_cfg,
    device_map="auto",
)
qlora_base.resize_token_embeddings(len(tokenizer))
qlora_base = prepare_model_for_kbit_training(qlora_base)
qlora_model = get_peft_model(qlora_base, lora_cfg)

trainer = Trainer(
    model=qlora_model,
    args=args,
    train_dataset=tokenized["train"],
    data_collator=collator,
)
trainer.train()

GPU가 없다면 QLoRA는 불가(또는 무의미). 이 경우 LoRA만 사용.


7. 저장(어댑터/토크나이저)

lora_model.save_pretrained("./lora-out/adapter")  # 또는 qlora_model
tokenizer.save_pretrained("./lora-out/tokenizer")
  • 후속 장에서 이 LoRA 어댑터를 base 모델에 merge → GGUF 변환 → Ollama용 Modelfile 생성까지 진행한다.
  • 주의(중요): Ollama는 Hugging Face LoRA 어댑터를 곧바로 읽지 않는다. 다음 장에서 LoRA merge → GGUF 변환 절차를 반드시 수행한다.

8. 간단 검증(학습 전후 비교)

학습 전/후 비교는 간단한 프롬프트로도 감이 온다. (정교 평가는 다음 장)

from transformers import pipeline

gen = pipeline("text-generation", model=lora_model, tokenizer=tokenizer, device_map="auto")
print(gen("### Instruction:\nExplain the difference between regression and classification.\n### Response:\n", 
          max_new_tokens=80)[0]["generated_text"])

9. 정리 — 이번 수정에서 달라진 점

  • 오류 해결: labels가 없어서 loss가 없던 문제를 해결 → 학습(loss) 정상 출력
  • 안전 장치: pad_token 지정 및 -100 마스킹, collator 설정(mlm=False)
  • 실행 신뢰성: GPU/CPU 환경 모두 고려한 설정(bf16/fp16 자동)

다음 5장에서는 성능 평가와 실험 추적(Perplexity/ROUGE 등) 을 다루고,
6장에서는 LoRA 병합 → GGUF 변환 → Ollama용 모형 생성(Modelfile) → 로컬 실행을 마무리한다.


참고(문서/가이드)

  • Hugging Face Transformers — Causal LM 학습에서 labels 필요성, DataCollatorForLanguageModeling(mlm=False) 사용
  • PEFT(LoRA/QLoRA) 기본 개념 및 사용법
  • bitsandbytes — 4bit 양자화 설정(BitsAndBytesConfig)

위 참고 문서는 변동될 수 있으니, 최신 버전 기준의 Transformers / PEFT 공식 문서를 확인해 주세요.

0개의 댓글