Hugging Face チャットテンプレート
Hugging Face、サイレントなパフォーマンス低下を防ぐためのチャットテンプレートを導入
Hugging Faceは、トークナイザーにchat_template属性を導入しました。これは、チャットモデルがトレーニング時に使用されたものとは異なる形式でプロンプトされた際に発生する、深刻なモデルの性能低下、いわゆる「サイレントなパフォーマンス・キラー」の問題を解決するためのものです。フォーマットのロジックをJinjaテンプレートとしてトークナイザー自体に保存することで、開発者は会話履歴がモデルの期待する正確な文字列形式に変換されることを保証でき、誤ったフォーマットによる分布のズレ(distribution shifts)を排除できます。
チャットフォーマットにおける分布のズレの問題
標準的な言語モデルでは、同じチェックポイントからトークナイザーとモデルをロードすれば、通常は分布のズレは発生しません。しかし、チャットモデルでは、「user」、「assistant」、「system」といったロールを含む一連のメッセージを、単一のトークン化可能な文字列に変換する必要があります。この変換に関する業界標準が存在しないため、モデルによって以下のように大きく異なるフォーマットが使用されています:
- シンプルなラベル:
User: [text] \n Bot: [text] - 特殊トークン:
[USER] [text] [/USER] \n [ASST] [text] [/ASST] - 境界トークン:
<|im_start|>user \n [text]<|im_end|>
誤ったフォーマットを使用しても、Pythonの例外や明らかなエラーは発生しないため、これは「サイレントなエラー」となります。その結果、モデルのパフォーマンスが著しく低下するだけであり、問題のデバッグを困難にします。
Jinjaテンプレートによる技術的実装
チャットテンプレートは、トークナイザーと共に保存・ロードされるJinjaテンプレート文字列として実装されています。この手法が、より単純なシステム(ロールごとの接頭辞や接尾辞など)よりも選ばれた理由は、テンプレート化によって既知のすべてのメッセージ形式をサポートできる柔軟性が確保され、ロジックやチェックをテンプレート内に直接エンコードできるためです。
テンプレートロジックの例
境界トークンを使用するフォーマットの場合、Jinjaテンプレートは以下のようになります:
{% for message in messages %}
{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}
{% endfor %}
Transformersライブラリとの統合
チャットテンプレートは、前処理情報はモデルのトークナイゼーションロジックに付随すべきであるという原則を維持するため、トークナイザーに直接統合されています。
クラスレベルのフォーマットからの移行
以前は、チャットフォーマットはクラスレベルで処理されていました(例:すべてのLLaMAチェックポイントはtransformersライブラリ内の同じハードコードされたロジックを使用していました)。後方互換性を維持するため、モデルクラスには現在デフォルトのチャットテンプレートが備わっています。しかし、Hugging Faceは、脆弱性を避け、将来のデフォルトテンプレートの変更がモデルのパフォーマンスを損なわないようにするために、すべてのチャットモデルに対して明示的にchat_templateを設定することを強く推奨しています。
使用方法とデプロイ
開発者は、tokenizer.apply_chat_template()メソッドを使用してこれらのテンプレートを適用できます。トークナイザーにchat_template属性がない場合は、クラスのデフォルトが使用されますが、これは前述のサイレントなバグにつながる可能性があります。ユーザーは、モデルカードから正しいフォーマットを確認し、Hugging Face Hub上のチェックポイントにchat_template属性を追加するためのプルリクエストを送信することが推奨されます。
新しいモデルへの推奨事項
Hugging Faceは、単一の標準フォーマットが理想的であることを認めつつも、既存のトレーニング済みモデルの多様性から、ハードコードされた標準を作ることは不可能であるとしています。新しいチャットモデルをトレーニングする方には、OpenAIによって作成された、<|im_start|>と<|im_end|>トークンを使用するChatMLフォーマットを推奨します。このフォーマットはロールに対して柔軟であり、次のような一行のテンプレート割り当てで実装できます:
tokenizer.chat_template = "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}"