LabHub
はじめる
배우기 러닝패스 코스

MiniMind — 小さな言語モデルを最初から最後まで自分で学習する

チャット形式で SFT し、損失マスキングが何を変えるか測る

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

MiniMind 의 채팅 형식(<|im_start|>역할\n…<|im_end|>\n)으로 대화를 자르고, generate_labels 처럼 답(assistant)만 남기는 라벨을 직접 만든다. 기준 사전학습 가중치에서 마스킹한 모델과 뺀 모델을 같은 조건으로 학습해, 떼어 둔 대화에서 답 토큰과 질문 토큰의 손실이 어떻게 갈리는지 숫자로 본다.

왜 중요한가

SFT 는 새 알고리즘이 아니라 같은 다음 토큰 예측이다. 다른 것은 무엇을 정답으로 두느냐 하나다. 대화 전체를 정답으로 두면 모델은 질문을 흉내 내는 데 학습 신호를 나눠 쓰고, 답이 끝난 뒤 스스로 다음 질문을 지어낼 수도 있다. 답만 정답으로 두면 질문은 조건이 되고, 끝 표지까지 정답에 넣어야 언제 멈출지를 배운다. 이 경계는 라벨 배열의 -100 몇 개로 정해지고, 틀려도 오류가 나지 않는다. 그래서 라벨을 직접 찍어 보고, 마스킹이 실제로 무엇을 바꾸는지 두 모델로 재 보는 것이 가장 확실한 확인이다.

단계

  1. /opt/mm/data/sft.jsonl 의 첫 대화를 채팅 형식 문자열로 바꿔 /root/mm/sft/sample0.txt 에 저장하세요.
  2. /root/mm/sft/sftlib.pyencode(대화, max_len=64, mask=True) 를 만들고, 첫 20개 대화의 input_ids·labels/root/mm/sft/labels.json 에 저장하세요.
  3. SFT 자료 전체에서 패딩을 뺀 실제 토큰 수와 라벨이 남은 토큰 수, 그 비율을 /root/mm/sft/ratio.json 에 적으세요.
  4. 기준 사전학습 가중치(/opt/mm/ref/pretrain.pth)에서 마스킹한 라벨로 300걸음 SFT 해 /root/mm/sft/sft_masked.pth 를 저장하세요.
  5. 같은 조건에서 라벨에 입력 전체(패딩만 -100)를 넣어 /root/mm/sft/sft_nomask.pth 를 저장하세요.
  6. 떼어 둔 대화(sft_val.jsonl)에서 두 모델의 답 토큰 손실과 질문 쪽 토큰 손실을 /root/mm/sft/compare.json 에 적으세요.
  7. 마스킹한 모델로 떼어 둔 질문 20개의 답을 욕심쟁이로 뽑아 /root/mm/sft/answers.jsonl 에 저장하세요.
  8. /root/mm/sft/report.md## 채팅 형식 ## 손실 마스킹 ## 한계 세 절을 쓰고, 마스킹한 모델의 질문 손실과 3단계의 비율을 넣으세요.

참고

대화를 한 줄로

/opt/mm/data/sft.jsonl 첫 줄의 conversations 를 MiniMind 채팅 형식(<|im_start|>역할\n내용<|im_end|>\n 을 차례마다)으로 바꿔 /root/mm/sft/sample0.txt 에 저장하세요.

mmkit.chat_text(대화) 가 이 형식을 만듭니다. 직접 만들어도 됩니다 — 역할 뒤에 줄바꿈, 내용 뒤에 <|im_end|> 와 줄바꿈입니다. 생각 모드용 <think> 태그와 system 프롬프트는 넣지 않습니다.

답만 남기는 라벨

/root/mm/sft/sftlib.pyencode(대화, max_len=64, mask=True) 를 만드세요 — 채팅 형식을 /opt/mm/ref/tokenizer.json 으로 잘라 64토큰으로 자르고 패딩(0)으로 채운 input_ids 와, 답의 시작 표지 뒤부터 끝 표지까지만 원래 토큰이고 나머지는 -100 인 labels. 첫 20개 대화의 결과를 /root/mm/sft/labels.json[{"input_ids": […], "labels": […]}, …] 로 저장하세요.

MiniMind generate_labels 처럼 토큰 열을 훑으며 시작 표지 조각과 같은 구간을 찾고, 그 뒤부터 끝 표지 조각이 끝날 때까지 라벨을 채웁니다. 두 차례 대화면 구간이 두 번 생깁니다. 채점기는 같은 규칙으로 만든 라벨과 한 자리씩 견줍니다.

손실에 들어가는 토큰은 몇 %

sft.jsonl 전체를 encode 로 바꿔, 패딩이 아닌 입력 토큰 수(real_tokens)와 -100 이 아닌 라벨 수(label_tokens), 그 비율(ratio)을 /root/mm/sft/ratio.json 에 적으세요.

질문·역할 표지가 조건일 뿐이라면 학습 신호는 나머지에서만 나옵니다. 사전학습과 같은 걸음 수라도 SFT 가 실제로 배우는 토큰은 이 비율만큼입니다.

마스킹한 SFT

스크립트 /root/mm/sft/train_sft.py(--nomask·--out 인자)를 써서 /opt/mm/ref/pretrain.pth 에서 시작해 마스킹한 라벨로 300걸음(배치 16·길이 64·lr 1e-3·MiniMind 코사인·시드 42) SFT 하고 /root/mm/sft/sft_masked.pth 에 저장하세요.

앞 모듈의 루프에서 자료만 바뀝니다 — model(X[ix], labels=Y[ix]). 채점기는 떼어 둔 대화에서 답 손실이 1.2 아래인지, 그리고 질문 쪽 손실이 높게(3 이상) 남았는지를 봅니다. 마스킹했다면 질문은 배우지 않았기 때문입니다.

마스킹을 뺀 SFT

4단계와 모든 조건을 같게 두고 라벨만 input_ids 그대로(패딩 자리만 -100)로 바꿔 학습해 /root/mm/sft/sft_nomask.pth 에 저장하세요.

encode(대화, mask=False) 처럼 한 인자로 두 가지를 만들 수 있게 해 두면 조건이 같다는 것이 코드로 보장됩니다. 채점기는 이 모델의 질문 쪽 손실이 낮은지(1.5 미만) 봅니다 — 질문도 배웠다는 증거입니다.

두 모델을 떼어 둔 대화로 잰다

sft_val.jsonl 의 모든 대화를 두 모델에 넣어, 답 구간 라벨만 남긴 손실(answer_loss)과 답이 아닌 자리(패딩 제외)만 남긴 손실(prompt_loss)을 /root/mm/sft/compare.json{"masked": {…}, "nomask": {…}} 로 적으세요.

같은 입력에 라벨만 두 가지로 가려 cross_entropy(…, ignore_index=-100) 를 두 번 계산하면 됩니다. 로짓은 한 칸 밀어 비교합니다(logits[:, :-1]labels[:, 1:]). 같은 걸음 수에서 두 모델의 답 손실도 견줘 보세요.

떼어 둔 질문에 답하기

마스킹한 모델로 sft_val.jsonl 앞 20개 대화의 첫 질문에 대한 답을 욕심쟁이로 뽑아 /root/mm/sft/answers.jsonl 에 한 줄에 하나씩 {"q": 질문, "a": 답, "ref": 자료의 답} 으로 저장하세요.

mmkit.greedy(model, tok, 질문) 은 채팅 형식에 <|im_start|>assistant\n 을 붙여 생성하고 <|im_end|> 에서 멈춥니다. 떼어 둔 질문은 SFT 에서 본 적 없는 질문 모양이라, 형식은 맞는데 내용이 틀린 답이 나오는 것이 정상입니다 — 마지막 모듈에서 이유를 잽니다.

마스킹의 효과를 남기기

/root/mm/sft/report.md## 채팅 형식 ## 손실 마스킹 ## 한계 세 절을 쓰고, 6단계의 마스킹한 모델 prompt_loss 와 3단계의 ratio 를 숫자로 넣으세요.

질문 손실이 높다는 것이 왜 '좋은' 신호인지 한 줄 적으세요. 한계 절에는 7단계에서 틀린 답의 모양을 적습니다.