HuggingFaceの大規模言語モデル(LLM)のチャットテンプレートの使い方
大規模言語モデル(LLM)を使用してチャットシステムを構築する場合、ユーザーとアシスタントを区別するために、特定の制御トークンを会話の間に挿入する必要があります。制御トークンは、LLMのモデルによってばらつきがあるため、都度調べて適切な制御トークンを挿入する必要があります。HuggingFaceでは、制御トークンの挿入を簡単に行うためのテンプレートが用意されいます。本記事では、HuggingFaceのapply_chat_template()
の使い方について解説します。
チャットモデルのテンプレートとは
大規模言語モデルでチャットを行う場合、単純に文章(文字列)を入力するのではなく、「会話」を入力する必要があります。
会話では「ユーザー」と「アシスタント」のどちらの発言かを区別する必要があります。
これを行うために、文章感に制御トークンの追加が必要になります。しかしながら、残念なことに、現状では制御トークンの標準仕様が存在していません。
したがって、モデルごとに制御トークンを調べ、モデルにあった制御トークンを挿入する必要があります。
この問題を解消するために、HuggingFaceには「チャットモデル向けのテンプレート」を用意したモデルが存在しています。
具体的には、トークナイザーにapply_chat_template
という関数が用意されており、これを利用することでチャットモデルに合わせた制御コードが挿入されたプロンプトを作成することが可能です。
ここでは、apply_chat_template
の使い方をllama2とgemmaを例に解説します。
公式にも解説がありますのでそちらも参照してください。
公式のページ:Templates for Chat Models
Llama2
Meta社のLlamaの場合のテンプレートです
テンプレートを確認
テンプレートのフォーマットは、以下の変数で確認することが可能です。
tokenizer.default_chat_template
{% if messages[0][‘role’] == ‘system’ %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0][‘content’] %}{% elif false == true and not ‘<>’ in messages[0][‘content’] %}{% set loop_messages = messages %}{% set system_message = ‘You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don\’t know the answer to a question, please don\’t share false information.’ %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message[‘role’] == ‘user’) != (loop.index0 % 2 == 0) %}{{ raise_exception(‘Conversation roles must alternate user/assistant/user/assistant/…’) }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = ‘<>\n’ + system_message + ‘\n<>\n\n’ + message[‘content’] %}{% else %}{% set content = message[‘content’] %}{% endif %}{% if message[‘role’] == ‘user’ %}{{ bos_token + ‘[INST] ‘ + content.strip() + ‘ [/INST]’ }}{% elif message[‘role’] == ‘system’ %}{{ ‘<>\n’ + content.strip() + ‘\n<>\n\n’ }}{% elif message[‘role’] == ‘assistant’ %}{{ ‘ ‘ + content.strip() + ‘ ‘ + eos_token }}{% endif %}{% endfor %}
USERの質問のみのパターン
ユーザーの質問のみの場合は、以下のように"role":"uesr"
だけを与えます。content
に続く文字列は、ユーザの入力になります。
tokenizer.apply_chat_template
にchat
を渡すと、プロンプトを作成することができます。
from transformers import AutoTokenizer
model_name = "meta-llama/Llama-2-7b-chat-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
chat = [
{ "role": "user", "content": "What is tallest mountain in the world?" },
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
prompt
llama2の場合は、[INST]...[/INST]
でユーザーを囲むことになっているので、正しく変換されていることがわかります。
<s>[INST] What is tallest mountain in the world? [/INST]
制御トークンが見やすいように色分けしています
USER→ASSISTANT→USERのパターン
チャットを続ける場合は、アシスタントの出力もプロンプトに含める必要があります。アシスタント側は"role":"assistant"
とします。
下記の例は、アシスタントの出力も含めた例です。
from transformers import AutoTokenizer
model_name = "meta-llama/Llama-2-7b-chat-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
chat = [
{ "role": "user", "content": "What is tallest mountain in the world?" },
{ "role": "assistant", "content": "Mount Everest, located in the Himalayas of Asia, is the tallest mountain in the world at 8,848.86 meters (29,032.4 feet) above sea level."},
{ "role": "user", "content": "Second one?"}
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
prompt
apply_chat_template
で変換されたプロンプトは以下のようになります。
<s>[INST] What is tallest mountain in the world? [/INST] Mount Everest, located in the Himalayas of Asia, is the tallest mountain in the world at 8,848.86 meters (29,032.4 feet) above sea level. </s><s>[INST] Second one? [/INST]
SYSTEMのあるパターン
llama2では、system
という項目も含めることができるようです。説明をみると、アシスタントに対する指示を入れるようです。
例えば、以下のような文章をシステムとして入力しておきます。
You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don’t know the answer to a question, please don’t share false information.
(あなたは親切で、礼儀正しく、誠実なアシスタントです。常に安全を保ちながら、できるだけ役立つように答えてください。回答には、有害、非倫理的、人種差別的、性差別的、有毒、危険、または違法なコンテンツを含めてはいけません。回答が社会的に偏見がなく、本質的に前向きであることを確認してください。\n\n質問が意味をなさない場合、または事実に一貫性がない場合は、正しくないことに答えるのではなく、その理由を説明してください。質問の答えがわからない場合は、誤った情報を共有しないでください。)
from transformers import AutoTokenizer
model_name = "meta-llama/Llama-2-7b-chat-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
chat = [
{ "role": "system", "content": "You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information."},
{ "role": "user", "content": "What is tallest mountain in the world?" },
{ "role": "assistant", "content": "Mount Everest, located in the Himalayas of Asia, is the tallest mountain in the world at 8,848.86 meters (29,032.4 feet) above sea level."},
{ "role": "user", "content": "Second one?"}
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
prompt
以下が生成されたプロンプトになります。
<s>[INST] <<SYS>>\nYou are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.\n<</SYS>>\n\nWhat is tallest mountain in the world? [/INST] Mount Everest, located in the Himalayas of Asia, is the tallest mountain in the world at 8,848.86 meters (29,032.4 feet) above sea level. </s><s>[INST] Second one? [/INST]
Gemma
Gemmaは、2024年2月21日に発表されたGoogleが開発した大規模言語モデル(LLM)です。
テンプレートを確認
Gemmaはテンプレートが用意されていないようです。
ただ、google/gemma-7b-it
のモデルのWebページのサンプルプログラムででapply_chat_template
を使っています。
Gemmaは、以下のフォーマットをサポートしているようです。
tokenizer.default_chat_template
No chat template is defined for this tokenizer – using a default chat template that implements the ChatML format (without BOS/EOS tokens!). If the default is not appropriate for your model, please set `tokenizer.chat_template` to an appropriate template. See https://huggingface.co/docs/transformers/main/chat_templating for more information.
{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}
USERの質問のみのパターン
llama2の時と同じで、ユーザーだけのプロンプトです。
from transformers import AutoTokenizer
model_name = "google/gemma-7b-it"
tokenizer = AutoTokenizer.from_pretrained(model_name)
chat = [
{ "role": "user", "content": "What is tallest mountain in the world?" },
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
prompt
テンプレートがllama2と違うので、プロンプトも異なります。
ユーザーはuser
、アシスタントはmodel
に続けて入力するようようです。
<bos><start_of_turn>user\nWhat is tallest mountain in the world?<end_of_turn>\n<start_of_turn>model\n
USER→ASSISTANT→USERのパターン
ユーザーの質問に回答した結果と、次の質問を加えた例です。
from transformers import AutoTokenizer
model_name = "google/gemma-7b-it"
tokenizer = AutoTokenizer.from_pretrained(model_name)
chat = [
{ "role": "user", "content": "What is tallest mountain in the world?" },
{ "role": "assistant", "content": "Mount Everest, located in the Himalayas of Asia, is the tallest mountain in the world at 8,848.86 meters (29,032.4 feet) above sea level."},
{ "role": "user", "content": "Second one?"}
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
prompt
user
→model
→user
と交互になっています。また<start_of_turn>
と<start_of_turn>
にそれぞれ挟まれていることも確認できました。
<bos><start_of_turn>user\nWhat is tallest mountain in the world?<end_of_turn>\n<start_of_turn>model\nMount Everest, located in the Himalayas of Asia, is the tallest mountain in the world at 8,848.86 meters (29,032.4 feet) above sea level.<end_of_turn>\n<start_of_turn>user\nSecond one?<end_of_turn>\n<start_of_turn>model\n
SYSTEMのあるパターン
gemmaはsystem
をサポートしていないようで、エラーになります。
こういう違いがまだあるようなので、利用にはまだまだ注意が必要かもしれません。
from transformers import AutoTokenizer
model_name = "google/gemma-7b-it"
tokenizer = AutoTokenizer.from_pretrained(model_name)
chat = [
{ "role": "system", "content": "You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information."},
{ "role": "user", "content": "What is tallest mountain in the world?" },
{ "role": "assistant", "content": "I'm doing great. How can I help you today?"},
{ "role": "user", "content": "Second one?"}
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
prompt
TemplateError: System role not supported
サポートしていないモデル
現状、チャットテンプレートをサポートしていないモデルもあります。例えば、CALM2はテンプレートをサポートしていません。
CALM2
CyberAgentのCALM2についても調べてみましたが、こちらのモデルはapply_chat_template
をサポートしていないようです。
from transformers import AutoTokenizer
model_name = "cyberagent/calm2-7b-chat"
tokenizer = AutoTokenizer.from_pretrained(model_name)
chat = [
{ "role": "user", "content": "What is tallest mountain in the world?" },
]
prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
prompt
一応、デフォルトのテンプレートで変換は行われます。
No chat template is defined for this tokenizer – using the default template for the GPTNeoXTokenizerFast class. If the default is not appropriate for your model, please set `tokenizer.chat_template` to an appropriate template. See https://huggingface.co/docs/transformers/main/chat_templating for more information.
What is tallest mountain in the world?<|endoftext|>
ただ、calm2は以下のような構造のプロンプトを期待しています。これと変換されたプロンプトの構造は一致していないので、テンプレートを使った変換ではうまくいきません。
USER: {user_message1}
ASSISTANT: {assistant_message1}<|endoftext|>
USER: {user_message2}
ASSISTANT: {assistant_message2}<|endoftext|>
USER: {user_message3}
ASSISTANT: {assistant_message3}<|endoftext|>
まとめ
以上、apply_chat_template
を使った会話プロンプトの作り方について説明しました。
実は、これまで、この機能に気づかずに自力でプロンプト作成していました。毎回、プロンプトで悩んでいましたわけです
本当はモデルのプロンプトが標準化されると良いのですが、関数を使って生成できるだけでもかなりありがたいです。