大規模言語モデル(LLM)を実用環境に導入する際、モデルの推論結果が想定と異なる挙動を示すケースの多くは、チャットテンプレート(chat template)の不一致に起因します。これは、モデルの学習時・微調整時に用いられたトークン化形式と、推論時に実際に与えられるプロンプト構造が食い違っているために発生します。
典型的な障害事例と対応
- コード補完ツールでの出力崩れ
Continue.dev のタブ補完機能で、CPT(Code-Pretrained Tuning)済みモデルを指定したところ、補完候補が不完全なコード断片や構文エラーを含む出力を返すようになった。これは、Continue がデフォルトで適用するテンプレートが、モデルの訓練時に使われた FIM(Fill-in-the-Middle)形式と合致しなかったためです。
修正方法:設定ファイルのtabAutocompleteOptions.templateに、モデルに適合する FIM テンプレートを明示的に指定します。
{
"tabAutocompleteOptions": {
"template": "<|fim_prefix|>{{{prefix}}}<|fim_suffix|>{{{suffix}}}<|fim_middle|>"
}
}
- Ollama 環境での意図しない応答
vLLM 上では正常に動作していた Qwen2.5-code 基盤の CPT モデルを、GGUF 形式に変換して Ollama で実行すると、ユーザーの質問に対して無関係な内容や雑談調の返答が返される現象が観測されました。原因は、Ollama のデフォルトテンプレートが ChatML 形式に対応しておらず、<|im_start|>や<|im_end|>といった特殊トークンが正しく解釈されなかったことでした。
修正方法:Modelfile でTEMPLATEディレクティブを用いて、モデル固有のチャット構造を明記します。
FROM ./qwen25-code-cpt.gguf
TEMPLATE """<|im_start|>system
{{ .System }}<|im_end|>
<|im_start|>user
{{ .Prompt }}<|im_end|>
<|im_start|>assistant
"""
テンプレート抽象化の実践パターン
多くの LLM インターフェース(Hugging Face Transformers、LangChain、Llama.cpp など)は、メッセージリストを内部でテンプレートに展開します。例えば、以下のような構造:
messages = [
{"role": "system", "content": "You are a code-aware assistant."},
{"role": "user", "content": "Refactor this function to use list comprehension."},
{"role": "assistant", "content": "Here's the refactored version:"}
]
LangChain では、SystemMessage/HumanMessage/AIMessage クラスを通じてロールを型安全に表現し、内部で自動的に対応するテンプレートへ変換します:
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
messages = [
SystemMessage("You are a code-aware assistant."),
HumanMessage("Refactor this function to use list comprehension."),
AIMessage("Here's the refactored version:")
]
なぜテンプレートの差異が問題になるのか?
事前学習済みの LLM は、単一のテキスト連続体として設計されています。対話能力(multi-turn chat)や役割分離(system/user/assistant)は、Instruction-tuning 段階で特定のフォーマット(例:ChatML、Mistral Instruct、Llama3 Chat)で学習された結果に依存しています。Hugging Face の NLP Course でも、Qwen2 と Mistral のテンプレート差異が明示されています:
- Qwen2 / SmolLM2(ChatML)
<|im_start|>system\nYou are helpful.<|im_end|><|im_start|>user\nHi<|im_end|><|im_start|>assistant\nHello!<|im_end|> - Mistral(Instruct)
<s>[INST] You are helpful. [/INST] Hello!</s>[INST] Hi? [/INST]
テンプレートの拡張的活用
単なる対話構造の枠組みを超えて、チャットテンプレートは以下のような高度な機能を実現する基盤となります:
- Tool calling のための JSON スキーマ埋め込み(例:
{"name": "get_weather", "arguments": "{...}"}を role="tool" として注入) - 多モーダル入力(画像説明、音声要約)のための `
` タグや `
- コンテキスト長制御のための動的トリミング指示(例:
{{ .TruncatedHistory }})
したがって、複数の LLM ランタイム(vLLM / Ollama / llama.cpp / Text Generation Inference)間でモデルを移行する際には、単に重みファイルを転送するだけでなく、テンプレートの再定義と検証が必須ステップとなります。