전문가반의 진짜 마지막 봉우리입니다. 22회차까지는 '완성된 남의 모델(Claude·GPT·오픈모델)을 코드로 잘 쓰는' 엔지니어였어요. 여기서부터는 그 모델 자체를 '내 데이터로 만드는' 쪽입니다 — 비싼 상용 모델을 우리말·우리 도메인에 특화된 자체 모델로 대체하는 일이죠. 이 회차의 목표는 두 가지: (1) AI 뉴스·기술 글에 자주 나오는 SFT·DPO·LoRA·양자화·분산학습이 각각 뭔지 '지도'를 그리고, (2) 작은 모델 하나를 실제로 내 데이터로 튜닝해 점수를 숫자로 확인하는 것.
🧭 언제 '모델을 직접 만드나' — 대부분은 안 만들어도 된다
모델 학습은 비싸고 오래 걸립니다. 그래서 순서가 중요해요 — 11회차의 'RAG vs 파인튜닝'을 다시 떠올리면, 항상 프롬프트 → RAG → 파인튜닝 순으로 시도하고, 앞 둘로 안 될 때만 모델을 건드립니다.
- 프롬프트로 충분 — 지시·예시 몇 개로 되면 그냥 프롬프트(가장 싸다).
- RAG로 충분 — '최신 지식·내 문서'가 문제면 파인튜닝 말고 RAG(20회차).
- 그래도 만들 이유 — 비용(상용 API가 너무 비쌈)·프라이버시(데이터 외부 반출 불가)·한국어/도메인 특화·지연(더 빠르게)·오프라인. 이 중 하나가 진짜 문제일 때 모델 학습.
🗺️ 큰 그림 — 이미 똑똑한 모델에 '세 번 더' 가르친다 (post-training)
바닥부터 학습(pretraining, 인터넷을 통째로 읽히기)은 수조 토큰·수백~수천 장 GPU라 스타트업이 처음부터 하지 않습니다. 우리가 하는 일의 대부분은 '이미 똑똑한 오픈 모델(Llama·Qwen·Exaone 등)에 우리 것만 추가로 가르치는' post-training이에요. 2025~2026 실무는 이걸 세 개의 모듈로 쌓습니다.
- ① SFT(지도학습) — '이렇게 답해' 모범답을 따라하게 (기본기·말투·형식).
- ② 선호 정렬(DPO) — '이 답이 저 답보다 낫다'는 취향을 맞추기.
- ③ RL(GRPO/RLVR) — '정답을 기계가 채점'할 수 있는 문제에서 추론력을 끌어올리기.
🎓 ① SFT — 지도학습 파인튜닝 (가장 기본)
'입력 → 모범답' 쌍을 잔뜩 보여주며 따라하게 하는 것. 우리 도메인의 말투·형식·지식을 '추가 과외'시키는 단계입니다. 자체 모델을 만들 때 '데이터 구축 → 모델 학습'의 첫 칸이에요. 데이터 질이 결과의 8할 — 예시 500개가 잘 정제됐으면 5만 개 잡음보다 낫습니다.
말보다 예시가 빠릅니다. SFT 학습 데이터는 결국 아래처럼 '사용자 질문 → 우리가 원하는 모범답' 쌍의 모음이에요(JSONL — 한 줄이 예시 하나). 이런 걸 수백 개 모아 보여주면, 모델이 '아, 이 서비스는 이런 말투·형식으로 답하는구나'를 흉내 내게 됩니다.
# sft_data.jsonl — '입력→모범답' 쌍 모음 (한 줄 = 예시 하나)
{"messages": [
{"role": "user", "content": "AI Radar에 새 툴 'Sora'를 한 줄로 소개해줘."},
{"role": "assistant", "content": "Sora — OpenAI의 텍스트→영상 생성 AI. 프롬프트만으로 최대 1분 영상 제작. [카테고리: 영상 생성]"}
]}
{"messages": [
{"role": "user", "content": "'fal.ai'는 어떤 도구야?"},
{"role": "assistant", "content": "fal.ai — 이미지·영상 생성 모델을 API로 빠르게 돌리는 서버리스 추론 플랫폼. [카테고리: 인프라·애그리게이터]"}
]}
# 핵심은 '형식의 일관성' — 모든 모범답을 똑같은 틀(한 줄 요약 + [카테고리])로 맞추면
# 모델이 그 틀을 그대로 학습한다. (실제 학습 코드는 아래 '🔧 만들 것'의 SFTTrainer 참고)
그럼 이 데이터로 '학습한다'는 게 정확히 무슨 일일까요? LLM은 본질이 '다음 단어(토큰) 맞추기' 기계라는 걸 떠올리면(1회차), SFT는 그 성질을 그대로 이용하는 겁니다.
- ① 토큰화 — 각 대화(질문+모범답)를 토큰 열로 변환한다.
- ② 다음 토큰 예측 — 문장을 앞에서부터 보여주며, 모범답(assistant) 부분을 한 토큰씩 예측하게 한다.
- ③ 손실 계산 — 예측이 모범답과 다를수록 오차가 크다. 질문(user) 부분은 채점에서 제외 — '답하는 법'만 배우게.
- ④ 가중치 업데이트 — 오차가 줄어드는 방향으로 파라미터를 아주 조금씩 조정한다(LoRA면 어댑터만).
- ⑤ 반복(epoch) — 수백 개 예시를 몇 바퀴 돌면 '우리 형식으로 답하는 습관'이 가중치에 새겨진다.
# 위 5단계를 '개념 코드'로 — SFTTrainer.train() 안에서 벌어지는 일 (형태만 감 잡기용)
for epoch in range(3): # ⑤ 반복 — 데이터셋을 3바퀴
for example in dataset: # 예시(대화) 하나씩
tokens = tokenizer(example) # ① 토큰화
pred = model(tokens[:-1]) # ② 다음 토큰 예측
loss = cross_entropy(pred, tokens[1:], # ③ 오차 계산 —
mask=assistant_only) # 질문(user) 부분은 채점 제외
loss.backward() # ④ 오차를 거꾸로 전파해
optimizer.step() # 가중치를 조금씩 조정
# 실전에선 이 루프 전체가 SFTTrainer.train() 한 줄. 그래도 '안에서 뭐가 도는지'는 이제 안다.
🧭 ② 선호 정렬 — DPO(그리고 RLHF)
SFT는 '맞는 답'은 가르쳐도 '더 나은 답 vs 못한 답'의 취향은 못 가르칩니다. RLHF는 사람 피드백으로 '보상 모델'을 만들어 강화학습으로 정렬하는데 — 강력하지만 무겁습니다(모델을 네 개나 동시에 메모리에 올림). DPO는 보상 모델 없이 '좋은 답/나쁜 답 쌍'만으로 비슷한 효과의 대부분을 절반 비용에 냅니다. 그래서 2025 실무의 사실상 기본값이에요.
예시로 보면 확실합니다. SFT 데이터가 '정답 하나'였다면, DPO 데이터는 같은 질문에 대한 '더 나은 답(chosen) / 못한 답(rejected)' 한 쌍이에요. 사람은 둘 중 나은 쪽만 골라주면 됩니다.
# dpo_pairs.jsonl — 같은 질문에 'chosen(더 나은 답)'과 'rejected(못한 답)'를 쌍으로
{"prompt": "AI Radar에 'Claude'를 한 줄로 소개해줘.",
"chosen": "Claude — Anthropic의 대화형 AI. 긴 문서 분석·코딩에 강함. [카테고리: 대화형 AI]",
"rejected": "Claude는 그냥 챗봇이에요. 좋습니다."}
# chosen은 우리 형식([카테고리] 포함)을 지키고, rejected는 성의 없는 답.
# 이런 쌍 수백 개로 '무엇이 더 나은 답인지'를 각인 — 보상 모델 없이 바로 학습(DPO).
🏆 ③ RL — 검증가능 보상 (GRPO/RLVR)
수학·코드처럼 '정답을 기계가 채점할 수 있는' 문제는, 사람 취향 대신 '맞았나?'를 보상으로 씁니다(RLVR=검증가능 보상). GRPO가 추론에서 특히 강해요(DeepSeek-R1이 이 방식으로 유명). 2025의 가장 큰 변화가 바로 이것 — 값비싼 '사람 라벨'에서 자동 채점되는 '검증가능 보상'으로의 이동입니다.
여기선 '데이터'가 아니라 '채점 규칙(보상 함수)'이 핵심입니다. 정답을 기계로 매길 수 있으면, 사람 라벨 없이 모델이 스스로 점수를 높이는 방향으로 학습해요.
# GRPO/RLVR의 핵심은 '보상 함수' — 사람 취향 대신 기계가 점수를 매긴다
def reward(answer: str, expected_category: str) -> float:
score = 0.0
if expected_category in answer: # ① 카테고리를 맞혔나
score += 0.7
if answer.count('[') == 1 and answer.endswith(']'): # ② 우리 형식([카테고리])을 지켰나
score += 0.3
return score # 0.0~1.0 — 모델은 이 점수가 커지는 방향으로 스스로 학습
# 채점이 자동이라(형식 준수·수학 정답·코드 실행 결과 등) 사람 라벨 없이 대량 반복 가능.
💾 현실적으로 — LoRA·QLoRA·양자화 (작은 GPU로)
큰 모델 '전체'를 다시 학습하면 GPU가 수십 장 필요합니다. LoRA는 큰 가중치는 얼려두고 '작은 어댑터 두 장'만 학습해요 — 저장·학습 비용이 수백 배 쌉니다. QLoRA는 여기에 4비트 양자화를 더해, 65B급 모델을 48GB GPU 한 장에서도 튜닝할 수 있게 합니다. 양자화(GPTQ·AWQ)는 '16비트를 4비트로 줄여' 서빙 메모리·속도를 확보하는 기술이고요(품질은 거의 유지).
# 왜 싼가 — 모델 '전체'가 아니라 '작은 어댑터'만 학습하기 때문
# full fine-tuning : 학습 대상 ≈ 70억 개 파라미터 전부 (GPU 수십 장)
# LoRA (r=16) : 학습 대상 ≈ 수백만 개, 전체의 0.1~0.5% (GPU 1장)
from transformers import BitsAndBytesConfig # QLoRA = 4비트 로드 + LoRA
bnb = BitsAndBytesConfig(load_in_4bit=True) # 원본을 4비트로 압축해 GPU에 올림
# → 결과물(어댑터)은 수 MB라 갈아끼우기 쉽고, 48GB GPU 한 장에 65B급도 튜닝 가능.
- LoRA — 원본은 동결, 작은 어댑터만 학습. 결과물이 수 MB라 갈아끼우기 쉽다.
- QLoRA — 4비트로 압축한 모델 위에 LoRA. '개인 GPU 1장으로 큰 모델 튜닝'의 문을 연 기법.
- 양자화(PTQ: GPTQ·AWQ) — 학습이 아니라 서빙용 경량화. AWQ는 '중요한 가중치'를 골라 보존해 품질 손실을 줄인다.
🧱 데이터가 8할 — 구축·합성·품질검증
모델 종류보다 데이터 품질이 결과를 가릅니다(자체 모델을 만드는 일에서 '데이터 구축'이 가장 앞에 오는 이유예요). 서비스 로그를 정제해 학습셋으로 만들고, 부족하면 강한 teacher 모델로 '합성 데이터'를 찍어 채웁니다. 단, 합성 데이터는 품질 검증이 생명 — 나쁜 합성은 모델을 조용히 망칩니다.
- 실데이터 정제 — 중복 제거·품질 필터·개인정보(PII) 마스킹.
- 합성 데이터 — 강한 모델(teacher)로 예시를 생성해 부족한 부분을 메운다.
- 품질 영향 A/B — 22회차 골든셋으로 '이 데이터가 점수를 올렸나'를 숫자로 검증(넣었다고 다 좋아지지 않는다).
📊 평가·벤치마크 — 22회차 eval의 '모델판'
튜닝이 진짜 나아졌는지 숫자로 봐야 합니다. 22회차에서 만든 eval 하니스를 그대로 확장하면 돼요 — 도메인 지표(우리 문제) + 일반 능력(한국어는 KMMLU·KMMLU-Hard 등)을 같이 재서, '우리 문제는 올랐는데 일반 능력은 까먹지 않았나'까지 봅니다. 자체 모델을 만드는 일의 세 번째 축이 바로 이 '평가 벤치마킹'이에요.
🌐 규모를 키울 때 — 분산학습 '맛보기'(선택)
GPU 한 장을 넘어서면 여러 장·여러 머신에 나눠 학습합니다. 지금은 세 도구의 '역할'만 알면 충분해요.
- FSDP2 — 파이토치 기본. 7B~30B 튜닝의 사실상 기본값(가장 단순).
- DeepSpeed ZeRO-3 — 메모리가 빠듯할 때 CPU·NVMe로 밀어내기. 30B~70B에 강점.
- Megatron-LM — 텐서·파이프라인·데이터 3D 병렬. 100B+ 프런티어급 사전학습이 쓰는 스택(Llama 3 405B 등).
- HF Accelerate — 위 셋을 '코드는 그대로, 설정만 바꿔' 전환하는 래퍼.
🔧 만들 것 — 작은 모델을 내 데이터로 LoRA 튜닝 (캡스톤)
이 트랙 전체의 마지막 실습입니다. 작은 오픈 모델(1~3B급) + 우리 도메인 예시 수백 개 + LoRA로 튜닝하고, 22회차 eval로 before/after 점수를 냅니다. 로컬이 버거우면 클라우드 GPU 1장(Colab 등)이나 관리형 파인튜닝 API로 대체해도 '데이터→학습→평가' 경험은 똑같이 남습니다.
- ① 데이터 — '입력→모범답' 50~500쌍을 JSONL로(20~22회차에서 모은 도메인 예시 재활용).
- ② 학습 — HF Transformers + PEFT(LoRA)로 SFT 1 epoch. 작게 시작해 과적합 감시.
- ③ 로드·추론 — 어댑터를 저장하고 원본 위에 얹어 몇 문장 돌려본다.
- ④ 평가 — 22회차 골든셋으로 튜닝 전/후 점수표(before X% → after Y%)를 만든다.
- ⑤ (선택) DPO 한 스텝 — '좋은 답/나쁜 답' 쌍 몇 개로 선호 정렬을 맛본다.
# 개념 스켈레톤 — TRL의 SFTTrainer + LoRA (전체가 아니라 '형태'만 감 잡기용)
from datasets import load_dataset
from peft import LoraConfig
from trl import SFTTrainer, SFTConfig
# 1) 데이터: {"prompt": "...", "completion": "..."} 형태의 JSONL
data = load_dataset('json', data_files='train.jsonl', split='train')
# 2) LoRA: 원본은 얼리고 '작은 어댑터'만 학습
lora = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05, task_type='CAUSAL_LM')
# 3) 학습: 작은 오픈 모델 위에 SFT 1 epoch
trainer = SFTTrainer(
model='Qwen/Qwen2.5-1.5B-Instruct', # 작게 시작 (1~3B급)
train_dataset=data,
peft_config=lora,
args=SFTConfig(num_train_epochs=1, per_device_train_batch_size=2,
learning_rate=2e-4, output_dir='out'),
)
trainer.train() # → out/ 에 LoRA 어댑터 저장
# 이후: 원본+어댑터 로드해 추론 → 22회차 eval로 before/after 채점
✅ 완료 기준 (숫자로 닫기)
- 작은 모델을 내 데이터로 LoRA 튜닝해, 실제로 우리 말투·형식으로 답하게 만들었다.
- 22회차 eval로 '튜닝 전 X% → 후 Y%'를 숫자로 답할 수 있다(그리고 일반 능력이 안 떨어졌는지도 확인).
- SFT·DPO·RL·LoRA·양자화·분산학습을 각각 남에게 한 문장으로 설명할 수 있다.
🧭 이 회차 ↔ 실제 현업 — 자체 모델을 만드는 일
- 데이터 구축 = 서비스 로그 정제 + 합성 데이터 파이프라인 + 품질 영향 분석.
- 모델 학습 = SFT·DPO·RL·LoRA·양자화로, 성능·지연·비용의 균형을 잡는 것.
- 평가 벤치마킹 = 도메인 + 일반 지표(KMMLU 등)를 자동화해 '나아졌나'를 숫자로.
- 한 걸음 더 = 분산학습(FSDP/DeepSpeed/Megatron)·오픈소스 LLM 기여 — 이 회차의 '맛보기'가 그 입구.
📚 자료
- Andrej Karpathy — 'Let's build GPT' / 'Neural Networks: Zero to Hero' (밑바닥 직관)
- Hugging Face — TRL(SFT·DPO·GRPO) · PEFT(LoRA) 공식 문서 (실습 표준)
- LoRA·QLoRA·DPO·GRPO 원논문 — 초록·그림 위주로 '왜 싼지'만 잡기
- 한국어 평가 — KMMLU / KMMLU-Hard, LG Exaone 등 국내 오픈모델 리포트
📌 23회차 핵심
- 순서는 프롬프트 → RAG → 파인튜닝. 앞 둘로 안 될 때만 모델을 건드린다.
- post-training 3단 — SFT(기본기) → DPO(취향) → RL/GRPO(검증가능 보상 추론). 많은 실무는 SFT(+DPO)까지.
- 현실은 LoRA·QLoRA·양자화 — 작은 GPU로도 큰 모델을 튜닝·서빙한다.
- 데이터 품질이 8할, 그리고 '나아졌음'은 항상 평가로 숫자로 닫는다.
🗺️ 전문가반 한 바퀴 — 코드로 쓰고, 이제 모델까지
- 18 코드 토대 → 19 LLM 호출 → 20 RAG(임베딩·검색→챗봇+평가) → 21 에이전트 → 22 평가 심화+서빙·플랫폼화 → 23 자체 모델 학습(파인튜닝).
- 채운 축 — RAG · 에이전트 · 평가(Eval) · LLM 서빙(LLMOps) · 멀티 프로바이더 · 플랫폼/SW · 그리고 모델 학습(post-training).
- 관통하는 한 가지 — AI는 적극 쓰되, 이해·판단·검증의 운전대는 내가 잡는다.