//
밑바닥부터 만들면서 배우는 LLM · 세바스찬 라시카
5장의 사전 훈련을 마친 베이스 모델은 자연스러운 다음 토큰 예측은 잘 한다. 하지만 사용자가 "다음 영어 문장을 한국어로 번역해 줘"라고 입력해도, 그 지시를 수행하기보다 지시문 자체를 이어서 생성하는 식의 답을 내놓는다. 이는 베이스 모델이 학습한 목표가 어디까지나 "다음 토큰 예측"이지, "사용자의 의도를 따르기"가 아니기 때문이다.
이 간극을 메우는 것이 **지시 미세 튜닝(Instruction Fine-tuning)**이다.
ChatGPT, Claude 같은 LLM 어시스턴트가 만들어지는 가장 핵심적인 단계가 바로 이것이다(여기에 RLHF나 DPO 같은 추가 단계가 더해지지만, 이 책은 SFT까지만 다룬다).
지시 미세 튜닝을 위해서는 (지시, 입력, 응답)으로 구성된 데이터가 필요하다. 책에서는 Alpaca 형식을 사용한다. 스탠퍼드의 Alpaca 프로젝트가 정한 텍스트 템플릿이다.
Below is an instruction that describes a task.
Write a response that appropriately completes the request.
### Instruction:
{instruction}
### Input:
{input}
### Response:
{response}
Instruction은 작업을 설명하는 지시문, Input은 작업에 필요한 추가 입력(없을 수도 있음), Response는 모델이 학습해야 할 정답이다. 이 형식을 이용해 (지시, 입력, 응답) 트리플을 하나의 평문 시퀀스로 펼친 다음, 그 시퀀스 전체를 모델에 학습시킨다.
Alpaca 형식이 등장한 2023년 초는 컨텍스트 길이가 4,096 토큰 수준에 머물던 시기다. 다중 턴 대화를 누적하면 토큰을 빠르게 소진하기 때문에, 단일 턴 지시-응답에 최적화된 단순 평문 형식이 효율적이었다. 최근에는 다중 턴을 지원하는 ChatML이나 모델별 채팅 템플릿(Llama 3, Phi-3 등)이 더 많이 쓰인다.
| 형식 | 특징 |
|---|---|
| Alpaca | 단일 턴, 평문 템플릿 |
| Phi-3 | 단일/다중 턴 모두 지원, <|user|>/<|assistant|> 토큰 |
| ChatML / Llama 3 | 다중 턴 대화에 최적화, 시스템/유저/어시스턴트 역할 구분 |
Alpaca 형식의 (지시, 입력, 응답) 데이터를 GPT가 학습할 수 있는 텐서 배치로 변환해야 한다.
배치 내 시퀀스 길이가 들쭉날쭉할 때, 모든 시퀀스를 글로벌 최대 길이(예: 컨텍스트 길이 1,024)로 패딩하면 짧은 시퀀스도 똑같이 큰 텐서로 처리해야 해서 낭비가 크다.
대신 배치 내부의 가장 긴 시퀀스 길이에만 맞춰 동적으로 패딩하는 커스텀 콜레이트 함수를 만든다. 이렇게 하면 짧은 배치는 빠르게, 긴 배치만 느리게 처리되어 평균 학습 속도가 크게 빨라진다.
def custom_collate_fn(batch, pad_token_id=50256, ignore_index=-100):
# 1. 배치 내 최대 길이 계산
batch_max_length = max(len(item) + 1 for item in batch)
inputs_list, targets_list = [], []
for item in batch:
# 2. 입력은 pad_token_id로 패딩
new_item = item.copy()
new_item += [pad_token_id]
padded = new_item + [pad_token_id] * (batch_max_length - len(new_item))
inputs = torch.tensor(padded[:-1])
targets = torch.tensor(padded[1:])
# 3. 패딩 위치는 -100으로 마스킹
mask = targets == pad_token_id
indices = torch.nonzero(mask).squeeze()
if indices.numel() > 1:
targets[indices[1:]] = ignore_index
inputs_list.append(inputs)
targets_list.append(targets)
return torch.stack(inputs_list), torch.stack(targets_list)패딩 토큰은 입력에는 필요하지만(모델이 텐서 형태를 받아야 하므로) 타깃에서는 의미 없는 자리다. 모델이 패딩을 예측하도록 가르치면 학습 신호가 노이즈가 된다.
PyTorch의 cross_entropy는 타깃 값이 -100이면 해당 위치를 손실 계산에서 제외하는 약속이 있다. 그래서 패딩 위치의 타깃을 -100으로 바꾸면, 모델은 패딩에 대한 예측 손실을 받지 않는다.
응답 부분만 학습 신호로 사용하고 싶다면 지시문 부분의 타깃까지 -100으로 만드는 변형도 가능하다. 책의 기본 구현은 패딩만 마스킹한다.
학습 자체는 5장의 사전 훈련 루프와 거의 같다. 차이는 데이터(레이블 없는 코퍼스 → 지시-응답 데이터셋)와 콜레이트 함수뿐이다.
한 가지 주의할 점은 에폭 수다. 지시 데이터셋은 사전 훈련 데이터에 비해 훨씬 작기 때문에 2 에폭 정도가 적정선이다. 그 이상 돌리면 검증 손실이 빠르게 올라가고 모델이 학습 데이터를 외우기 시작한다(과적합).
| epoch 1 | epoch 2 | epoch 3 | epoch 4 |
| ------- | ------- | ------- | ------- |
| train ↓ | train ↓ | train ↓ | train ↓ |
| val ↓ | val ↓ | val ↑ | val ↑↑| ← 과적합
분류와 달리 지시 미세 튜닝의 출력은 자유 형식 텍스트다. 정답이 하나로 떨어지지 않으므로 정확도 같은 지표를 바로 쓸 수 없다.
이 책의 평가 방식이 흥미롭다. 더 큰 모델에게 채점을 맡긴다.
Ollama는 Llama 3나 Mistral 같은 오픈 모델을 로컬에서 띄우고 API로 호출하게 해 주는 툴이다. 책에서는 다음 흐름으로 평가한다.
ollama pull llama3로 채점용 모델을 받는다.이 접근의 장점은 명확하다. 사람이 일일이 채점하지 않아도 되고, OpenAI API 비용도 들지 않는다. 단점은 채점자가 학습된 편향을 그대로 갖고 들어온다는 점이다(예: 더 길고 형식적인 답변에 점수를 더 주는 경향).
이 방식은 LLM-as-a-judge로 불리며, 최근 논문/벤치마크에서 매우 흔하게 쓰인다.
7장은 책 전체를 마무리하는 가장 분량이 큰 장이다. 데이터셋 형식(Alpaca), 동적 패딩 + -100 마스킹, 학습 루프, LLM 채점까지 — 지시 미세 튜닝의 표준 파이프라인 전체를 한 번에 손으로 따라간다.
이 단계까지 마치고 나면, 124M 파라미터의 작은 모델이라도 사용자의 지시를 (어설프게나마) 따르는 어시스턴트로 변신한다. 그 결과물의 품질은 ChatGPT 수준에 한참 못 미치지만, 무엇이 더 필요한가라는 질문은 자연스럽게 다음 학습 주제(스케일, 데이터 품질, RLHF/DPO, 멀티 모달)로 이어진다.
책의 마지막 페이지를 덮을 때, 결국 LLM은 우리가 분해한 부품들의 합이라는 자신감이 남는다. 트랜스포머 블록을 12번 반복하고, 다음 토큰 예측으로 사전 훈련하고, 지시 데이터로 미세 튜닝한 것 — 그게 ChatGPT의 골격이다. 나머지는 규모와 정성의 차이일 뿐이다.
이 글이 도움이 되셨나요?