Transformers documentation

Шаблоны чата

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Шаблоны чата

Руководство по основам чата описывает, как хранить историю чата и генерировать текст с помощью чат-моделей, используя TextGenerationPipeline.

Это руководство предназначено для более опытных пользователей и описывает базовые классы и методы, а также ключевые концепции, необходимые для понимания того, что происходит при взаимодействии с моделью.

Ключевой момент, необходимый для понимания чат-моделей, заключается в следующем: все каузальные языковые модели, независимо от того, обучены ли они для чата, продолжают последовательность токенов. При обучении каузальных языковых моделей процесс обычно начинается с «предварительного обучения» на огромном корпусе текстов, что приводит к созданию «базовой» модели. Затем эти базовые модели часто «дообучают» для чата, что означает их обучение на данных, оформленных в виде последовательности сообщений. При этом чат по-прежнему представляет собой просто последовательность токенов! Список словарей role и content, который вы передаете чат-модели, преобразуется в последовательность токенов, часто с использованием управляющих токенов, таких как <|user|> или <|assistant|> или <|end_of_message|>, которые позволяют модели видеть структуру чата. Существует множество возможных форматов чата, и разные модели могут использовать различные форматы или управляющие токены, даже если они были дообучены из одной и той же базовой модели!

Не паникуйте: чтобы работать с чат-моделями, не нужно запоминать все возможные форматы чатов. Чат-модели поставляются с шаблонами чата, которые указывают, в каком формате они ожидают получать чаты. К ним можно обратиться с помощью метода apply_chat_template. Рассмотрим два примера. Обе эти модели дообучены на основе одной и той же базовой модели Mistral-7B:

Mistral
Zephyr
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1")
chat = [
  {"role": "user", "content": "Hello, how are you?"},
  {"role": "assistant", "content": "I'm doing great. How can I help you today?"},
  {"role": "user", "content": "I'd like to show off how chat templating works!"},
]

tokenizer.apply_chat_template(chat, tokenize=False)
<s>[INST] Hello, how are you? [/INST]I'm doing great. How can I help you today?</s> [INST] I'd like to show off how chat templating works! [/INST]

Mistral-7B-Instruct использует токены [INST] и [/INST] для обозначения начала и конца сообщений пользователя, тогда как Zephyr-7B использует токены <|user|> и <|assistant|> для указания ролей участников диалога. Именно поэтому шаблоны чата так важны: при использовании неверных управляющих токенов производительность этих моделей резко ухудшится.

Использование apply_chat_template

Входные данные для apply_chat_template должны быть структурированы в виде списка словарей с ключами role и content. Ключ role указывает на говорящего, а ключ content содержит сообщение. Общие роли:

  • user для сообщений от пользователя
  • assistant для сообщений от модели
  • system для указания модели, как она должна вести себя (обычно размещается в начале чата)

apply_chat_template принимает этот список и возвращает отформатированную последовательность. Установите tokenize=True, если вы хотите токенизировать последовательность.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")
model = AutoModelForCausalLM.from_pretrained("HuggingFaceH4/zephyr-7b-beta", device_map="auto", dtype=torch.bfloat16)

messages = [
    {"role": "system", "content": "You are a friendly chatbot who always responds in the style of a pirate",},
    {"role": "user", "content": "How many helicopters can a human eat in one sitting?"},
 ]
tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to(model.device)
print(tokenizer.decode(tokenized_chat["input_ids"][0]))
<|system|>
You are a friendly chatbot who always responds in the style of a pirate</s>
<|user|>
How many helicopters can a human eat in one sitting?</s>
<|assistant|>

Передайте токенизированный чат в generate(), чтобы сгенерировать ответ.

outputs = model.generate(**tokenized_chat, max_new_tokens=128)
print(tokenizer.decode(outputs[0]))
<|system|>
You are a friendly chatbot who always responds in the style of a pirate</s>
<|user|>
How many helicopters can a human eat in one sitting?</s>
<|assistant|>
Matey, I'm afraid I must inform ye that humans cannot eat helicopters. Helicopters are not food, they are flying machines. Food is meant to be eaten, like a hearty plate o' grog, a savory bowl o' stew, or a delicious loaf o' bread. But helicopters, they be for transportin' and movin' around, not for eatin'. So, I'd say none, me hearties. None at all.

Некоторые токенизаторы добавляют специальные токены <bos> и <eos>. Шаблоны чата должны уже включать все необходимые специальные токены, а добавление дополнительных специальных токенов часто является ошибочным или приводит к дублированию, что ухудшает производительность модели. При форматировании текста с помощью apply_chat_template(tokenize=False) убедитесь, что вы установили add_special_tokens=False, если планируете токенизировать текст позже, чтобы избежать дублирования этих токенов. Эта проблема не возникает при использовании apply_chat_template(tokenize=True), поэтому это обычно более безопасный вариант!

add_generation_prompt

Вы могли заметить аргумент add_generation_prompt в приведенных выше примерах. Этот аргумент добавляет в конец чата токены, указывающие на начало ответа assistant. Помните: за всеми абстракциями чата чат-модели по сути остаются языковыми моделями, которые продолжают последовательность токенов! Если вы включите токены, сообщающие модели, что теперь идет ответ assistant, она корректно сгенерирует ответ. Но если вы не включите эти токены, модель может запутаться и сделать что-то странное, например, продолжить сообщение пользователя вместо того, чтобы ответить на него!

Рассмотрим пример, чтобы понять, что именно делает add_generation_prompt. Сначала сформатируем чат без add_generation_prompt:

tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
tokenized_chat
<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>

Теперь отформатируем тот же чат с add_generation_prompt=True:

tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
tokenized_chat
<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>
<|im_start|>assistant

Если add_generation_prompt=True, в конце добавляется <|im_start|>assistant, чтобы обозначить начало сообщения assistant. Это позволяет модели понять, что далее ожидается ответ assistant.

Не все модели требуют промптов для генерации, а некоторые модели, такие как Llama, не имеют специальных токенов перед ответом assistant. В таких случаях add_generation_prompt не оказывает никакого эффекта.

continue_final_message

Параметр continue_final_message управляет тем, следует ли продолжать последнее сообщение в чате, а не начинать новое. Он удаляет токены конца последовательности, чтобы модель продолжала генерацию с последнего сообщения.

Это полезно для «предзаполнения» ответа модели. В примере ниже модель генерирует текст, продолжающий строку JSON, а не начинающий новое сообщение. Это может быть очень полезно для повышения точности следования инструкциям, если вы знаете, как должны начинаться её ответы.

chat = [
    {"role": "user", "content": "Can you format the answer in JSON?"},
    {"role": "assistant", "content": '{"name": "'},
]

formatted_chat = tokenizer.apply_chat_template(chat, tokenize=True, return_dict=True, continue_final_message=True)
model.generate(**formatted_chat)

Не следует использовать add_generation_prompt и continue_final_message одновременно. Первый добавляет токены, начинающие новое сообщение, а второй удаляет токены конца последовательности. Использование их вместе приводит к ошибке.

Передайте имя поля в виде строки, чтобы предварительно заполнить именно это поле, а не content. Модели рассуждений читают свои рассуждения из отдельного поля reasoning_content. Предварительное заполнение content закрывает блок рассуждений до начала генерации, поэтому модель не может продолжить внутри него. Предварительное заполнение reasoning_content напрямую оставляет блок открытым.

chat = [
    {"role": "user", "content": "Explain 1+1"},
    {"role": "assistant", "reasoning_content": "The user wants a simple addition. ", "content": ""},
]

formatted_chat = tokenizer.apply_chat_template(chat, tokenize=False, continue_final_message="reasoning_content")

Именованное поле должно существовать в конечном сообщении и должно быть указано в шаблоне чата. Ошибка возникает, если не выполняется ни одна из этих проверок.

TextGenerationPipeline по умолчанию устанавливает add_generation_prompt в True, чтобы начать новое сообщение. Однако, если последнее сообщение в чате имеет роль assistant, считается, что это префилл, и включается режим continue_final_message=True. Это связано с тем, что большинство моделей не поддерживают несколько последовательных сообщений от ассистента. Чтобы переопределить это поведение, явно передайте аргумент continue_final_message в пайплайн.

Обучение модели

Обучение модели с использованием шаблона чата — это хороший способ убедиться, что шаблон соответствует токенам, на которых обучалась модель. Применяйте шаблон чата как этап предобработки к вашему набору данных. Установите add_generation_prompt=False, поскольку дополнительные токены для запуска ответа ассистента не полезны во время обучения.

Ниже приведен пример предобработки набора данных с использованием шаблона чата.

from transformers import AutoTokenizer
from datasets import Dataset

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")

chat1 = [
    {"role": "user", "content": "Which is bigger, the moon or the sun?"},
    {"role": "assistant", "content": "The sun."}
]
chat2 = [
    {"role": "user", "content": "Which is bigger, a virus or a bacterium?"},
    {"role": "assistant", "content": "A bacterium."}
]

dataset = Dataset.from_dict({"chat": [chat1, chat2]})
dataset = dataset.map(lambda x: {"formatted_chat": tokenizer.apply_chat_template(x["chat"], tokenize=False, add_generation_prompt=False)})
print(dataset['formatted_chat'][0])
<|user|>
Which is bigger, the moon or the sun?</s>
<|assistant|>
The sun.</s>

После этого шага вы можете продолжить следование рецепту обучения для каузальных языковых моделей, используя столбец formatted_chat.

Update on GitHub