1. きっかけ
とある研究室の会話から
ある日、AI研究室でこんな会話がありました。
学生A「先生、Deepseek-r1のQwen3を使った蒸留モデルをSFTで使いたいんですが、うまく動きません!」
教授B「もしかしてテンプレートを手で書き換えたんじゃないか?」
学生A「いえ……普通にQwen3のチャットテンプレートを使いました。」
教授B「それだ! モデル付属のスペシャルトークンを無視すると、学習も推論も破綻するんだよ」
──こうして、スペシャルトークンとテンプレートの重要性を再確認することになりました。
というのはAIが作った架空のストーリーですが、実際にDeepSeek-r1の蒸留モデルでファインチューニングした時に回答が終わらず、無限に繰り返されてしまいました。現象からすると、「あぁ、EOSが出てないんだな」と分かりやすい現象でしたので対処は簡単でした。
そういえば「元々のモデルのスペシャルトークンって調べたことないなぁ」と思ったのでまとめてみました。
2. スペシャルトークンとは?
LLMにおけるスペシャルトークン(special tokens)とは、通常の単語やサブワードではありません。
文の開始・終了や会話の区切り、システム指示、思考領域などを表す特別な記号です。
-
文境界の明示
- BOS(文の開始)
- EOS(文の終了)
- PAD(系列長を揃えるための空白)
-
会話制御
- ユーザー・アシスタント・システムの役割を区切る
-
思考領域やツール呼び出し
- DeepSeek-R1の
<think>や<|tool▁calls▁begin|>など
- DeepSeek-R1の
各モデルの例
| モデル | 主なスペシャルトークン |
|---|---|
| Qwen |
<|im_start|>, <|im_end|>
|
| DeepSeek-R1 |
<|User|>, <|Assistant|>, <think>
|
| Mistral / Swallow |
<s>, </s>, [INST] ... [/INST]
|
| Gemma |
<start_of_turn>, <end_of_turn>
|
| LLaMA 3 |
<|start_header_id|>role<|end_header_id|>, <|eot_id|>
|
ここでもわかる通り、DeepSeekとQwenで使うスペシャルトークンが異なるのです。
3. Qwen3への蒸留モデルを使う時の注意点
きっかけで書いた失敗があったので、以下の点に工夫した方がいいじゃないかなというものを書き出しました。
🌾ChatGPTとチャットをやりまくったものをまとめています。
3-1. 公式テンプレートを使う
-
apply_chat_templateを利用する。自動でタグが付与されます。(下に例があります) - Qwenは当然Qwenのスペシャルトークンを使って会話を構築するが、蒸留モデルはDeepSeekタイプに変更されています。
3-2. EOSとEOTの違いを意識する
- Qwenではターン終端に
<|im_end|>が必要でapply_chat_templateを使えば自動で付与されるが、deepSeekのEOSは付与されない。つまり、僕がやらかしたように、会話が終了しない。 - 停止条件に
<|end▁of▁sentence|>を含めること。
3-3. 思考やツール出力
- 蒸留モデルには
<think>やtool_calls用トークンが組み込まれている場合がある。 - 付属テンプレート前提でデータを整形。
3-4. Tokenizer設定を確認する
- DeepSeek-R1蒸留では、QwenベースでもR1式トークンを採用しているようです。
- Huggingfaceの
File and Virsionsからtokenizer_config.jsonを必ず確認。
3-5. タグについて
- Hugging Face 公式では
<think>タグによる思考フェーズが示されているのみで、公式には<answer>タグはない。 - 一方で、GitHub やコミュニティでのプロンプト例には、… と … を組み合わせた構造が多く見られます。(私も
<answer>タグを使いました。)
じゃ、最適解はなんなんだとChatGPTに聞いてみた。
| ソース | タグ構成 | 特徴・メリット |
|---|---|---|
| Hugging Face(公式) |
<think>(思考)→ 回答はプレーンに記述 |
シンプルかつ推論上の設計に忠実で、導入も容易。過剰な指示がない分、モデルの内発的思考を重視。 |
| GitHub/コミュニティ実装例 |
<think> + <answer>(思考と回答を明示的に区別) |
内部推論と最終応答を明確に分離でき、評価や訓練時に理論過程の解析が容易。 |
よくわかんないけど、`'タグがあるとより明示的に取り出したりすることやりやすいってことかな。 
ChatGPTと会話していくと以下のようにも回答してくれました。
- 精度と自然な推論重視なら:公式フローに準じたシンプル構成(<think> のみ)だけで問題なく動作し、複雑化を避けたい場合に最適です。
- 可追跡性・評価や解析が目的なら:<answer> 評価やユーザーへの説明、訓練データ整備などが目的なら <answer> を使う価値があります。
特に、報酬モデル(RLHF)や Fine-Tuning の際には、<think> と <answer> の分離がフィードバック設計やデータ整形に好ましい場合があります。
3-6. 系列長とpacking
- テンプレ適用後の系列長を確認。
-
max_seq_lenオーバーを防ぐ。
学習データのトークン数をチェックして、設定したmax_seq_lenをオーバーしないようにしましょう。だって、EOSは最後に出てきますからね!
4. 今回の失敗例と修正例
❌ 今回の失敗例
# NG: 異なるスペシャルトークンを使ってしまう
例:Qwenのeosがそのまま使われた
prompt = f"・・・・・・<im_end>"
なんと、Qwenのeosだと会話が止まらないんです。なんで?
っと思ったら、DeepSeekのタグが使われて時後学習させれているっぽい。
✅ 修正例
なので、強引にeosを付け加えることにしました。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("unsloth/DeepSeek-R1-0528-Qwen3-8B", trust_remote_code=True)
question = 'りんごがカゴに2個ずつ入っています。カゴが10個ある時、りんごは何個?'
output = '# <think>これは単純な乗算による算数の問題です。2個のりんごが入ったカゴが10個あるので、\( 2 \\times 10 = 20 \\)\n</think>りんごは全部で20個です。'
messages = [
{"role": "user", "content": question},
{"role" : "assistant", "content" : output + tokenizer.special_tokens_map['eos_token']},
]
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
print(prompt)
# <|im_start|>user
# りんごがカゴに2個ずつ入っています。カゴが10個ある時、りんごは何個?
# <|im_end|>
# <|im_start|>assistant
# <think>
# これは単純な乗算による算数の問題です。
# 2個のりんごが入ったカゴが10個あるので、\( 2 \\times 10 = 20 \\)
# </think>
# りんごは全部で20個です。
# <|end▁of▁sentence|><|im_end|>
少しだけ説明しましょう。
このapply_chat_templateだと<|im_end|>しか付与されず、下の行で強引に<|end▁of▁sentence|>を付け加えました。
{"role" : "assistant", "content" : output + tokenizer.special_tokens_map['eos_token']},
こんな落とし穴もあるようです。w
チェックリスト(多分、足りない。w)
-
モデル付属の
apply_chat_templateを使ってデータを構築したか? - モデルに使われているタグを正確に確認したか?
-
tokenizer_config.jsonを確認し、R1蒸留のような例外に対応したか? - packing後の系列長を検証したか?
5. まとめ
スペシャルトークンは「モデルにとっての暗黙のルールブック」とも言えそうです。
特に蒸留モデルでは、元の系列(Qwen/LLaMAなど)の形式ではなく、蒸留後の専用トークン体系が優先されることがあるようです。その上、今回はapply_chat_templateが十分に対応できていませんでした。ま、学習データを必要に合わせて作る手間より学習の手間のほうが少ないんでしょうね。
学習にはそれなりのコストと時間がかかります。今回は2700データ、約2時間の学習でしたので時間、金銭的コストのロスは小さくてすみましたが、学習時にはテンプレートとトークン設定を確認してください。
それこそが、ファインチューニング成功へのスタートラインかもしれません。