SFT 只教回答 — 损失掩码划出这条界线
한국어 원문으로 표시합니다.
한 줄 요약
사전학습 모델은 말뭉치를 이어 쓸 뿐 질문에 답하지 않는다. SFT(지도 미세조정)는 질문–답 대화를 채팅 형식의 한 줄로 만들어 같은 다음 토큰 예측으로 학습하되, 답(assistant) 토큰에서만 손실을 계산한다. MiniMind 의 generate_labels 가 그 경계를 긋는다. 이 모듈에서는 마스킹한 모델과 뺀 모델을 같은 걸음 수로 학습해, 질문 토큰의 손실과 답 토큰의 손실이 어떻게 갈리는지 숫자로 본다.
왜 이게 필요했나
모델에게 '질문을 받으면 답한다' 는 것을 가르치려면 두 가지가 필요하다. 하나는 어디까지가 질문이고 어디부터가 답인지 모델이 알아볼 표지이고, 다른 하나는 무엇을 배울지의 선택이다. 대화 전체로 학습하면 모델은 질문을 쓰는 법까지 배운다 — 사용자가 할 법한 말을 흉내 내는 데 학습 신호의 상당 부분을 쓰고, 답이 끝난 뒤 스스로 다음 질문을 지어내기도 한다. 우리가 원하는 것은 질문을 조건으로 보고 답을 내는 모델이다.
어떻게 동작하나
채팅 형식. MiniMind 는 Qwen 계열과 같은 ChatML 모양을 쓴다.
<|im_start|>user
가람 마을의 특산물은 뭐야?<|im_end|>
<|im_start|>assistant
가람 마을의 특산물은 인삼입니다.<|im_end|>
MiniMind 의 실제 템플릿은 assistant 차례마다 빈 <think>\n\n</think>\n\n 을 넣고, 학습 때 80% 확률로 지운다(생각 모드와 맞추기 위한 장치). 20% 확률로 system 프롬프트를 앞에 붙이기도 한다. 이 코스는 생각 모드를 다루지 않으므로 둘 다 빼고 위의 모양만 쓴다(mmkit.chat_text).
라벨 마스킹. generate_labels 는 토큰 열에서 <|im_start|>assistant\n 의 토큰 조각을 찾고, 그 뒤부터 <|im_end|>\n 조각이 끝날 때까지만 라벨에 원래 토큰을 넣는다. 나머지는 전부 -100 이다. 모델의 cross_entropy(..., ignore_index=-100) 가 그 자리를 건너뛴다.
labels = [-100] * len(input_ids)
# <|im_start|>assistant\n 을 찾으면 그 뒤부터 <|im_end|>\n 까지 labels[j] = input_ids[j]
끝 표지 <|im_end|> 까지 정답에 넣는 것이 중요하다. 그래야 모델이 답을 언제 멈출지를 배운다. 여러 차례 주고받는 대화에서는 assistant 차례마다 이 구간이 생긴다. 우리 SFT 자료로 세어 보면 실제 토큰 가운데 손실에 들어가는 것은 22% 남짓이다 — 나머지 78%는 조건일 뿐이다.
하이퍼파라미터. MiniMind 의 train_full_sft.py 는 사전학습 가중치에서 시작해 lr 1e-5(사전학습 5e-4 의 50분의 1)로 두 에폭 돈다. 이미 배운 언어 능력을 크게 흔들지 않으려는 선택이다. 이 코스의 작은 모델은 1e-3 으로 훨씬 크게 잡는데, 그 대가로 사전학습 말뭉치를 이어 쓰는 능력을 상당히 잊는다 — 마지막 모듈에서 퍼플렉서티로 잰다.
현장에서 만나는 모습
사내 상담 기록으로 SFT 를 했더니 모델이 답 뒤에 "고객: …" 으로 시작하는 가짜 질문을 이어 쓴다면, 마스킹이 빠졌거나 끝 표지를 정답에서 뺀 것이다. 반대로 끝 표지를 넣지 않으면 모델이 멈추지 않고 최대 길이까지 쓴다. 두 증상 모두 라벨 한 줄을 찍어 보면 바로 보인다 — MiniMind 의 SFTDataset.__getitem__ 에 주석으로 남아 있는 디버깅 출력(입력 토큰과 다음 토큰, 라벨을 나란히 찍는 것)이 정확히 그 용도다.
또 하나 흔한 착각은 'SFT 로 지식을 넣는다' 는 기대다. SFT 가 가르치는 것은 주로 형식과 태도 — 질문을 받으면 어떤 모양으로, 어디서 멈추며 답하는가 — 이고, 사실은 대부분 사전학습에서 온다. 자료에 없는 사실을 SFT 몇 천 건으로 넣으려 하면 모델은 그 문장을 외울 뿐, 다른 질문 모양으로 물으면 꺼내지 못한다. 이 코스의 마지막 모듈에서 그 현상을 숫자로 본다.
MiniMind 원본과 이 코스가 다른 점
MiniMind 의 SFT 자료에는 도구 호출 대화(system 에 도구 목록, assistant 가 <tool_call> 로 부르고 tool 역할이 결과를 돌려주는 모양)가 섞여 있고, pre_processing_chat 은 도구가 있는 대화는 손대지 않는다. 템플릿이 도구 결과를 user 차례로 감싸므로 그 자리도 라벨에서 빠진다 — 모델은 도구 결과를 흉내 내지 않고, 그것을 보고 이어 답하는 법만 배운다. 이 코스는 도구 호출을 다루지 않고, 한두 차례짜리 질문–답과 인사말만 쓴다. 최대 길이도 MiniMind 권장값(768)이 아니라 64 다 — 우리 대화는 가장 긴 것도 40여 토큰이라 64 면 잘리는 것이 없고, 패딩도 적다. 반대로 실제 자료로 할 때는 대화 길이 분포를 먼저 재고 최대 길이를 정해야 한다. 너무 짧으면 답의 끝과 끝 표지가 잘려 멈추는 법을 배우지 못한다.
다음 실습에서 할 것
SFT 자료의 첫 대화를 채팅 형식으로 바꾸고, 답만 남기는 라벨을 직접 만든다. 손실에 들어가는 토큰의 비율을 세고, 기준 사전학습 가중치에서 마스킹한 모델과 뺀 모델을 같은 조건으로 학습한다. 떼어 둔 대화에서 두 모델의 답 손실과 질문 손실을 견주고, 떼어 둔 질문에 대한 답을 뽑아 본다.