MiniMind — 小さな言語モデルを最初から最後まで自分で学習する
チャット形式で SFT し、損失マスキングが何を変えるか測る
目標
MiniMindのチャット形式(<|im_start|>역할\n…<|im_end|>\n)で対話を切り(プレースホルダーは役割です)、generate_labelsのように、答え(assistant)だけを残すラベルを自分で作ります。基準の事前学習の重みから、マスキングしたモデルと外したモデルを同じ条件で学習して、取り分けておいた対話で、答えトークンと質問トークンの損失がどう分かれるかを数字で見ます。
なぜ重要なのか
SFTは新しいアルゴリズムではなく、同じ次トークン予測です。違うのは、何を正解にするかの1点だけです。対話全体を正解にすると、モデルは質問を真似ることに学習信号を分けて使い、答えが終わったあと、自分で次の質問を作り出すこともあります。答えだけを正解にすると、質問は条件になり、終わりの目印まで正解に入れてはじめて、いつ止まるかを学びます。 この境界は、ラベル配列の-100の数個で決まり、間違えてもエラーが出ません。そのため、ラベルを自分で出力して見て、マスキングが実際に何を変えるのかを、2つのモデルで測ってみるのが、最も確実な確認です。
ステップ
/opt/mm/data/sft.jsonlの最初の対話をチャット形式の文字列に変えて、保存してください(出力先: /root/mm/sft/sample0.txt)。- スクリプト(/root/mm/sft/sftlib.py)に
encode(대화, max_len=64, mask=True)(プレースホルダーは対話です)を作り、最初の20個の対話のinput_idsとlabelsを保存してください(出力先: /root/mm/sft/labels.json)。 - SFTデータ全体で、パディングを除いた実際のトークン数と、ラベルが残ったトークン数、その割合を書いてください(出力先: /root/mm/sft/ratio.json)。
- 基準の事前学習の重み(
/opt/mm/ref/pretrain.pth)から、マスキングしたラベルで300学習ステップSFTして、保存してください(出力先: /root/mm/sft/sft_masked.pth)。 - 同じ条件で、ラベルに入力全体(パディングだけ-100)を入れて学習し、保存してください(出力先: /root/mm/sft/sft_nomask.pth)。
- 取り分けておいた対話(
sft_val.jsonl)で、2つのモデルの答えトークンの損失と、質問側のトークンの損失を書いてください(出力先: /root/mm/sft/compare.json)。 - マスキングしたモデルで、取り分けておいた質問20個の答えを、グリーディ生成で取り出して保存してください(出力先: /root/mm/sft/answers.jsonl)。
## 채팅 형식## 손실 마스킹## 한계の3つのセクションを書き、マスキングしたモデルの質問の損失とステップ3の割合を入れてください(出力先: /root/mm/sft/report.md)。韓国語の見出しは、順に「チャット形式」「損失のマスキング」「限界」という意味です。
参考
- チャット形式は、
mmkit.chat_text(대화)が作ってくれます(プレースホルダーは対話です)。答えの開始の目印は<|im_start|>assistant\n、終わりの目印は<|im_end|>\nを、トークナイザーで切ったトークンの断片です。 - SFTの300学習ステップ(バッチ16・長さ64・lr 1e-3)は、ノードで20秒前後です。
- よくある間違い: 終わりの目印(
<|im_end|>\n)をラベルから外して、モデルが止まれなくなること、役割の目印assistant\nまで正解に入れること、マスキングを外したモデルで、パディングまで正解にすること。 - 原典: lm_dataset.py — SFTDataset・generate_labels · train_full_sft.py · PyTorch cross_entropy ignore_index
対話を1行にする
/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.py)に、encode(대화, max_len=64, mask=True)(プレースホルダーは対話です)を作ってください。チャット形式を/opt/mm/ref/tokenizer.jsonで切り、64トークンに切ってパディング(0)で埋めたinput_idsと、答えの開始の目印の後ろから終わりの目印までだけが元のトークンで、残りは-100のlabelsです。最初の20個の対話の結果を、[{"input_ids": […], "labels": […]}, …]の形で保存してください(出力先: /root/mm/sft/labels.json)。
MiniMindのgenerate_labelsのように、トークン列を走査して、開始の目印の断片と同じ区間を探し、その後ろから、終わりの目印の断片が終わるまでラベルを埋めます。2往復の対話なら、区間が2回できます。採点ツールは、同じ規則で作ったラベルと、1つずつ比べます。
損失に入るトークンが何%かを数える
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つの引数で2通りを作れるようにしておくと、条件が同じであることがコードで保証されます。採点ツールは、このモデルの質問側の損失が低いか(1.5未満)を見ます。質問も学んだという証拠です。
2つのモデルを取り分けておいた対話で測る
sft_val.jsonlのすべての対話を2つのモデルに入れて、答えの区間のラベルだけを残した損失(answer_loss)と、答えでない位置(パディングを除く)だけを残した損失(prompt_loss)を、{"masked": {…}, "nomask": {…}}の形で書いてください(出力先: /root/mm/sft/compare.json)。
同じ入力に、ラベルだけを2通りに隠して、cross_entropy(…, ignore_index=-100)を2回計算すればよいです。ロジットは1つずらして比べます(logits[:, :-1]とlabels[:, 1:])。同じ学習ステップ数での、2つのモデルの答えの損失も比べてみてください。
取り分けておいた質問に答える
マスキングしたモデルで、sft_val.jsonlの先頭20個の対話の最初の質問に対する答えを、グリーディ生成で取り出し、1行に1つずつ{"q": 질문, "a": 답, "ref": 자료의 답}の形で保存してください(プレースホルダーは、順に質問・答え・データの答えです。出力先: /root/mm/sft/answers.jsonl)。
mmkit.greedy(model, tok, 질문)は、チャット形式に<|im_start|>assistant\nを付けて生成し、<|im_end|>で止まります(プレースホルダーは質問です)。取り分けておいた質問は、SFTで見たことのない質問の形なので、形式は合っているのに内容が間違った答えが出るのが正常です。最後のモジュールで、その理由を測ります。
マスキングの効果を残す
## 채팅 형식 ## 손실 마스킹 ## 한계の3つのセクションを書き、ステップ6のマスキングしたモデルのprompt_lossと、ステップ3のratioを数字で入れてください(出力先: /root/mm/sft/report.md)。韓国語の見出しは、順に「チャット形式」「損失のマスキング」「限界」という意味です。
質問の損失が高いことが、なぜ「良い」サインなのかを、1行書いてください。限界のセクションには、ステップ7で間違った答えの形を書きます。