9.1 Supervised Fine-Tuning (SFT) Fundamentals
베이스 모델은 토큰 예측을 통해 폭넓은 통계적 구조를 학습합니다. Supervised Fine-Tuning (SFT) 은 엄선한 시범 데이터로 행동을 바꿉니다. 특정 요청에 어떤 형식으로 답할지, 도구 스키마를 어떻게 지킬지, 위험한 요청을 어떻게 거절할지를 가르칩니다. 예시에 지식이 담겨 있으면 과업 지식도 학습할 수 있지만, 자주 바뀌거나 출처가 필요한 사실에는 retrieval을 대신하기 어렵습니다.
목적함수는 단순하지만 데이터 계약은 단순하지 않습니다.
목적함수와 손실 마스크
메시지를 토큰으로 직렬화하고, 학습할 assistant 구간에만 을 둔다고 합시다. Completion-only SFT 손실은 다음과 같습니다.
시스템 프롬프트, 사용자 턴, 패딩, 학습에서 제외할 도구 구간의 label은 -100으로 둡니다. 이 위치는 cross-entropy에 직접 기여하지 않습니다. 그러나 프롬프트는 여전히 forward pass의 문맥이며 assistant 예측이 그 activation에 attention합니다. 따라서 prompt masking만으로 프롬프트 길이에 비례한 activation memory가 절감되지는 않습니다. 실제 메모리는 sequence length, checkpointing, attention 구현, packing 정책이 좌우합니다.
베이스 모델을 구조화된 transcript 분포에 적응시키는 경우에는 full-sequence loss가 의도적일 수 있습니다. 어느 역할에 손실을 주는지 명시해야 하며, 한 정책을 보편적인 정답으로 부르면 안 됩니다.
토크나이저와 템플릿도 모델의 일부다
다른 모델에서 복사한 <|user|> 문자열을 직접 이어 붙이지 마십시오. 체크포인트의 tokenizer-native chat_template을 사용하고 정확한 바이트 또는 해시를 학습 artifact에 고정합니다. 학습·평가·서빙은 같은 템플릿, BOS/EOS 동작, generation configuration을 사용해야 합니다.
실행 전 실제 인코딩 예시를 확인합니다.
- 완성된 학습 대화에는
add_generation_prompt=False를 쓰고, 추론 시 값은 별도로 검증합니다. - BOS가 중복되지 않고 학습할 completion이 의도한 EOS 또는 end-of-turn 토큰으로 끝나는지 봅니다.
- 마지막 턴만이 아니라 모든 assistant 턴에 의도한 loss mask가 적용되는지 확인합니다.
- system, user, padding, 제외한 tool-result 토큰이 모두
-100인지 확인합니다. - truncation이 답변 전체를 잘라 프롬프트만 남기지 않는지 확인합니다.
- 서빙 prefix와 학습 prefix가 토큰 단위로 같은지 확인합니다.
일부 토크나이저는 Jinja 템플릿에 generation block이 있을 때만 assistant mask를 반환합니다. 신뢰할 수 있는 mask를 제공하지 않으면 tokenizer offset이나 template-aware collator로 span을 만들고 테스트해야 합니다. delimiter가 본문에 나타날 수 있으므로 특정 token ID를 검색해 경계를 찾는 방식은 위험합니다.
새 special token 추가는 단순 포맷 변경이 아니라 migration입니다. embedding resize와 초기화, lm_head tie 여부, 새 행의 학습, 체크포인트 호환성을 모두 다뤄야 합니다.
마스크를 검사할 수 있는 Collator
아래 코드는 의도적으로 작게 만든 교육용 예시입니다. chat template이 return_assistant_tokens_mask를 지원하는 최근 Transformers 토크나이저를 가정합니다. API 세부 사항은 토크나이저와 라이브러리 버전에 따라 달라지므로, 실제 실행 전에 고정한 artifact로 검증해야 합니다.
import torch
IGNORE_INDEX = -100
def encode_conversation(tokenizer, messages, max_length=4096):
encoded = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=False,
return_dict=True,
return_assistant_tokens_mask=True,
truncation=True,
max_length=max_length,
)
input_ids = torch.tensor(encoded["input_ids"], dtype=torch.long)
assistant_mask = torch.tensor(encoded["assistant_masks"], dtype=torch.bool)
attention_mask = torch.ones_like(input_ids)
labels = input_ids.clone()
labels[~assistant_mask] = IGNORE_INDEX
if labels.ne(IGNORE_INDEX).sum() == 0:
raise ValueError("truncation이 모든 supervision token을 제거했습니다")
if tokenizer.eos_token_id is not None:
learned = input_ids[assistant_mask]
if learned[-1].item() != tokenizer.eos_token_id:
raise ValueError("assistant span이 예상 EOS로 끝나지 않습니다")
return {"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels}
def pad_batch(tokenizer, examples):
if tokenizer.pad_token_id is None:
raise ValueError("명시적인 padding 정책이 필요합니다")
width = max(x["input_ids"].numel() for x in examples)
batch = {}
for key, pad_value in (
("input_ids", tokenizer.pad_token_id),
("attention_mask", 0),
("labels", IGNORE_INDEX),
):
rows = []
for item in examples:
pad = width - item[key].numel()
rows.append(torch.nn.functional.pad(item[key], (0, pad), value=pad_value))
batch[key] = torch.stack(rows)
return batch
실제 multi-turn, tool-use, 긴 문서, 빈 응답, 다국어 예시에서 convert_ids_to_tokens 결과와 boolean mask를 나란히 시각화하십시오. tensor shape만 확인하는 것보다 훨씬 강한 검증입니다.
Packing과 Truncation
짧은 대화를 한 sequence에 packing하면 활용률이 좋아지지만, 보통의 causal attention에서는 뒤 샘플이 앞 샘플을 볼 수 있습니다. 다음 중 하나를 계약으로 정합니다.
- 명시적 EOS로 문서를 연결하고 cross-sample context를 학습 분포의 일부로 받아들입니다.
- block-diagonal 또는 sequence-ID-aware attention으로 샘플을 격리합니다.
- 어느 동작도 입증할 수 없다면 packing을 끕니다.
EOS는 학습된 토큰일 뿐 attention barrier가 아닙니다. packing 효율, supervision token 비율, prompt/response 길이 꼬리, truncation으로 버린 예시, supervision token이 0개인 샘플을 기록합니다.
실행 계약과 평가
베이스 체크포인트, 토크나이저, chat template, dataset manifest와 split hash, 코드/container, optimizer, precision, sequence length, packing 정책, seed를 고정합니다. weight, gradient, optimizer state, adapter, activation, temporary buffer, checkpoint, 평가 비용을 분리해 계산합니다. 재시작 시 샘플을 건너뛰거나 반복하지 않도록 model/adapter, optimizer, scheduler, scaler, RNG, sampler, data cursor를 저장합니다.
작은 pilot부터 시작하고 token-normalized train/validation loss, gradient norm, nonfinite count, throughput, data wait, prompt/response 길이, domain별 loss를 관측합니다. mask가 비었거나 EOS coverage가 바뀌거나, validation 회귀가 선언한 범위를 넘거나, nonfinite가 반복되거나, safety-critical slice가 실패하면 중단하고 조사합니다.
즉시 수행할 offline evaluation에는 다음이 포함됩니다.
- held-out instruction following과 정확한 format/tool-schema 유효성
- base capability retention과 domain slice
- safety/refusal과 over-refusal 짝 평가
- truncation 정책과 같은 long-context·multi-turn 사례
- 고정 decoding 설정과 frozen baseline을 이용한 paired comparison
게이트를 통과한 immutable artifact bundle만 승격합니다. 이전 bundle은 last-known-good로 유지하고 트래픽 투입 전 rollback을 연습합니다.
Quizzes
Quiz 1: 프롬프트 label을 -100으로 바꿔도 프롬프트 길이에 비례한 activation memory가 절약되지 않는 이유는 무엇인가요?
응답 토큰이 프롬프트를 attention하므로 프롬프트는 causal forward graph에 남습니다. 마스크는 프롬프트 위치의 직접적인 cross-entropy 항만 제거하며, 프롬프트 activation이나 응답 gradient에 대한 기여를 제거하지 않습니다.
Quiz 2: SFT 파이프라인이 체크포인트의 tokenizer-native chat template을 사용해야 하는 이유는 무엇인가요?
역할 표식, BOS/EOS 규칙, generation prefix가 모델별로 다르기 때문입니다. 직접 만든 근사 템플릿은 train/serve 분포 차이와 잘못된 supervision을 만들 수 있지만, 고정한 native template은 토큰 수준 동등성을 검사할 수 있습니다.
Quiz 3: packed sample 사이에 EOS를 넣으면 attention leakage가 막히나요?
아닙니다. EOS는 토큰 분포의 경계를 표시하지만 attention barrier는 아닙니다. 격리하려면 block-diagonal 또는 sequence-aware attention mask가 필요하며, 그렇지 않으면 cross-sample attention을 명시적으로 허용한 것입니다.
Quiz 4: training loss가 내려가도 SFT 실행을 멈춰야 하는 조건은 무엇인가요?
assistant mask가 비거나 이동한 경우, EOS 또는 truncation 정책이 바뀐 경우, nonfinite가 반복되는 경우, held-out 회귀가 선언한 범위를 넘은 경우, 핵심 safety·format·tool-use slice가 실패한 경우가 해당합니다.
References
- Zhou, C., et al. (2023). LIMA: Less Is More for Alignment. arXiv:2305.11206.
- Ouyang, L., et al. (2022). Training language models to follow instructions with human feedback. arXiv:2203.02155.
- Hugging Face. Chat templates. Transformers documentation.