监督微调训练器
监督微调(或简称 SFT)是 RLHF 中的关键步骤。在 TRL 中,我们提供了一个易于使用的 API 来创建您的 SFT 模型,并使用数据集中的几行代码对它们进行训练。
查看 examples/scripts/sft.py
中的完整灵活示例。视觉语言模型的实验支持也包含在示例 examples/scripts/vsft_llava.py
中。
快速开始
如果您在 🤗 Hub 上托管了一个数据集,您可以轻松地使用 TRL 的 SFTTrainer 来微调您的 SFT 模型。我们假设您的数据集是 imdb
,您想要预测的文本在数据集的 text
字段中,并且您希望微调 facebook/opt-350m
模型。以下代码段将为您处理所有数据预处理和训练
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer
dataset = load_dataset("stanfordnlp/imdb", split="train")
sft_config = SFTConfig(
dataset_text_field="text",
max_seq_length=512,
output_dir="/tmp",
)
trainer = SFTTrainer(
"facebook/opt-350m",
train_dataset=dataset,
args=sft_config,
)
trainer.train()
确保传递 max_seq_length
的正确值,因为默认值将设置为 min(tokenizer.model_max_length, 1024)
。
您还可以在训练器之外构建一个模型,并按如下方式传递它
from transformers import AutoModelForCausalLM
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer
dataset = load_dataset("stanfordnlp/imdb", split="train")
model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m")
sft_config = SFTConfig(output_dir="/tmp")
trainer = SFTTrainer(
model,
train_dataset=dataset,
args=sft_config,
)
trainer.train()
上述代码段将使用 SFTConfig 类的默认训练参数。如果您想要修改默认值,请将您的修改传入 SFTConfig
构造函数,并通过 args
参数将它们传递给训练器。
高级用法
只对补全内容进行训练
您可以使用 DataCollatorForCompletionOnlyLM
仅针对生成的提示训练您的模型。请注意,这仅在 packing=False
时有效。若要为指令数据实例化该整理器,请传递一个响应模板和一个标记工具。这是在 CodeAlpaca 数据集上微调 opt-350m
以仅对补全内容进行训练的一个示例
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer, DataCollatorForCompletionOnlyLM
dataset = load_dataset("lucasmccabe-lmi/CodeAlpaca-20k", split="train")
model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m")
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-350m")
def formatting_prompts_func(example):
output_texts = []
for i in range(len(example['instruction'])):
text = f"### Question: {example['instruction'][i]}\n ### Answer: {example['output'][i]}"
output_texts.append(text)
return output_texts
response_template = " ### Answer:"
collator = DataCollatorForCompletionOnlyLM(response_template, tokenizer=tokenizer)
trainer = SFTTrainer(
model,
train_dataset=dataset,
args=SFTConfig(output_dir="/tmp"),
formatting_func=formatting_prompts_func,
data_collator=collator,
)
trainer.train()
若要为助理式会话数据实例化该整理器,请传递一个响应模板、一个指令模板和一个标记工具。这是在 Open Assistant Guanaco 数据集上微调 opt-350m
以仅对助理补全内容进行训练的一个示例
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer, DataCollatorForCompletionOnlyLM
dataset = load_dataset("timdettmers/openassistant-guanaco", split="train")
model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m")
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-350m")
instruction_template = "### Human:"
response_template = "### Assistant:"
collator = DataCollatorForCompletionOnlyLM(instruction_template=instruction_template, response_template=response_template, tokenizer=tokenizer, mlm=False)
trainer = SFTTrainer(
model,
args=SFTConfig(
output_dir="/tmp",
dataset_text_field = "text",
),
train_dataset=dataset,
data_collator=collator,
)
trainer.train()
确保有一个不同于 eos_token_id
的 pad_token_id
,它可能导致模型在生成过程中无法正确预测 EOS(句子结束)令牌。
直接为 response_template 使用 token_ids
某些分词器(例如 Llama 2 (meta-llama/Llama-2-XXb-hf
))对序列的标记化方式因其是否有上下文而异。例如
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
def print_tokens_with_ids(txt):
tokens = tokenizer.tokenize(txt, add_special_tokens=False)
token_ids = tokenizer.encode(txt, add_special_tokens=False)
print(list(zip(tokens, token_ids)))
prompt = """### User: Hello\n\n### Assistant: Hi, how can I help you?"""
print_tokens_with_ids(prompt) # [..., ('▁Hello', 15043), ('<0x0A>', 13), ('<0x0A>', 13), ('##', 2277), ('#', 29937), ('▁Ass', 4007), ('istant', 22137), (':', 29901), ...]
response_template = "### Assistant:"
print_tokens_with_ids(response_template) # [('▁###', 835), ('▁Ass', 4007), ('istant', 22137), (':', 29901)]
在这种情况下,由于 response_template
中缺少上下文,相同的字符串 (”### Assistant:”) 标记化方式会有所不同
- 文本(带上下文):
[2277, 29937, 4007, 22137, 29901]
response_template
(不带上下文):[835, 4007, 22137, 29901]
当 DataCollatorForCompletionOnlyLM
未能从数据集 示例文本中找到 response_template
时,这会导致一个错误
RuntimeError: Could not find response key [835, 4007, 22137, 29901] in token IDs tensor([ 1, 835, ...])
要解决这个问题,可以使用与数据集中相同的上下文对 response_template
进行标记化,根据需要对其进行截断,并将 token_ids
直接传递给 DataCollatorForCompletionOnlyLM
类的 response_template
参数。例如
response_template_with_context = "\n### Assistant:" # We added context here: "\n". This is enough for this tokenizer
response_template_ids = tokenizer.encode(response_template_with_context, add_special_tokens=False)[2:] # Now we have it like in the dataset texts: `[2277, 29937, 4007, 22137, 29901]`
data_collator = DataCollatorForCompletionOnlyLM(response_template_ids, tokenizer=tokenizer)
增加特殊令牌用于聊天格式
为语言模型添加特殊令牌对于训练聊天模型至关重要。这些令牌被添加到对话中的不同角色之间,例如用户、助手和系统,并帮助模型识别对话的结构和流程。此设置对于使模型能够在聊天环境中生成连贯且上下文适当的响应至关重要。trl
中的 setup_chat_format()
函数可以轻松地为会话式 AI 任务设置模型和分词器。此函数
- 向分词器添加特殊令牌,例如
<|im_start|>
和<|im_end|>
,表示会话的开始和结束。 - 调整模型的嵌入层的大小,以适应新令牌。
- 设置分词器的
chat_template
,用于将输入数据格式化为类似聊天的格式。OpenAI 的默认值是chatml
。 - 可选 ,可以传递
resize_to_multiple_of
,将嵌入层的大小调整为resize_to_multiple_of
参数的倍数,例如 64。如果您希望在将来看到更多受支持的格式,请在 trl 上打开一个 GitHub 问题
from transformers import AutoModelForCausalLM, AutoTokenizer
from trl import setup_chat_format
# Load model and tokenizer
model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m")
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-350m")
# Set up the chat format with default 'chatml' format
model, tokenizer = setup_chat_format(model, tokenizer)
在设置好我们的模型和分词器之后,我们现在可以在会话数据集上微调我们的模型。以下是数据集如何才能格式化以进行微调的示例。
数据集格式支持
SFTTrainer 支持流行的数据集格式。这使你能够将数据集直接传递给训练器,而无需进行任何预处理。支持以下格式
- 对话格式
{"messages": [{"role": "system", "content": "You are helpful"}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "..."}]}
{"messages": [{"role": "system", "content": "You are helpful"}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "..."}]}
{"messages": [{"role": "system", "content": "You are helpful"}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "..."}]}
- 指令格式
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
如果你的数据集使用上述格式之一,你可以直接将其传递给训练器,而无需进行预处理。SFTTrainer 随后将使用模型的分词器的已定义格式使用 apply_chat_template 方法为你格式化数据集。
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer
...
# load jsonl dataset
dataset = load_dataset("json", data_files="path/to/dataset.jsonl", split="train")
# load dataset from the HuggingFace Hub
dataset = load_dataset("philschmid/dolly-15k-oai-style", split="train")
...
sft_config = SFTConfig(packing=True)
trainer = SFTTrainer(
"facebook/opt-350m",
args=sft_config,
train_dataset=dataset,
)
如果数据集不是上述格式之一,则你可以对数据集进行预处理以匹配格式,或将格式化功能传递给 SFTTrainer 以为你完成这项工作。我们来看看。
格式化您的输入提示
对于指令微调,在数据集中有 2 列的情况非常常见:一列是提示,另一列是响应。这允许人们这样格式化示例,如 Stanford-Alpaca 所做的那样
Below is an instruction ...
### Instruction
{prompt}
### Response:
{completion}
让我们假设您的数据集有两个字段,question
和 answer
。因此,您只需运行
...
def formatting_prompts_func(example):
output_texts = []
for i in range(len(example['question'])):
text = f"### Question: {example['question'][i]}\n ### Answer: {example['answer'][i]}"
output_texts.append(text)
return output_texts
trainer = SFTTrainer(
model,
args=sft_config,
train_dataset=dataset,
formatting_func=formatting_prompts_func,
)
trainer.train()
要正确格式化您的输入,请确保通过循环处理所有示例并返回处理后的文本列表。查看有关如何在 alpaca 数据集上使用 SFTTrainer 的完整示例 在此
打包数据集 ( ConstantLengthDataset )
SFTTrainer 支持示例打包,其中多个短示例打包在同一个输入序列中以提高训练效率。这是使用 ConstantLengthDataset
实用程序类完成的,该实用程序类从示例流中返回固定长度的标记块。要启用此数据集类的使用,只需将 packing=True
传递给 SFTConfig 构造函数。
...
sft_config = SFTConfig(packing=True, dataset_text_field="text",)
trainer = SFTTrainer(
"facebook/opt-350m",
train_dataset=dataset,
args=sft_config
)
trainer.train()
请注意,如果你使用打包数据集并且在训练参数中传递了max_steps
,那么你可能会在超过几个 epoch 的时间内训练你的模型,这取决于你配置打包数据集和训练协议的方式。仔细检查你是否知道并理解你所做的事。如果你不想打包你的eval_dataset
,你可以将eval_packing=False
参数传递给SFTConfig
初始方法。
使用打包数据集自定义提示
如果你的数据集中有几个你想要组合的字段,例如,如果数据集有question
和answer
字段,并且你想要组合它们,你可以将一个格式化函数传递给该训练器,该函数将负责执行这项操作。例如
def formatting_func(example):
text = f"### Question: {example['question']}\n ### Answer: {example['answer']}"
return text
sft_config = SFTConfig(packing=True)
trainer = SFTTrainer(
"facebook/opt-350m",
train_dataset=dataset,
args=sft_config,
formatting_func=formatting_func
)
trainer.train()
你还可以通过直接将参数传递给SFTConfig构造函数来进一步自定义ConstantLengthDataset
。请参阅该类的签名以获取更多信息。
控制预训练模型
你可以直接将from_pretrained()
方法的参数传递给SFTConfig。例如,如果你想将模型加载成不同的精度,类似于
model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m", torch_dtype=torch.bfloat16)
...
sft_config = SFTConfig(
model_init_kwargs={
"torch_dtype": "bfloat16",
},
output_dir="/tmp",
)
trainer = SFTTrainer(
"facebook/opt-350m",
train_dataset=dataset,
args=sft_config,
)
trainer.train()
请注意,支持from_pretrained()
的所有关键字参数。
训练适配器
我们还支持与 🤗 PEFT 库的紧密集成,以便任何用户都可以方便地训练适配器并在 Hub 上共享,而不是训练整个模型
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer
from peft import LoraConfig
dataset = load_dataset("stanfordnlp/imdb", split="train")
peft_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
trainer = SFTTrainer(
"EleutherAI/gpt-neo-125m",
train_dataset=dataset,
args=SFTConfig(output_dir="/tmp"),
peft_config=peft_config
)
trainer.train()
您还可以继续训练您的 PeftModel
。为此,请首先在 SFTTrainer
之外加载一个 PeftModel
,然后将其直接传递给 trainer,而无需传递 peft_config
参数。
使用基本 8 位模型训练适配器
为此,您需要首先在 Trainer 之外加载您的 8 位模型,并将 PeftConfig
传递给 trainer。例如
...
peft_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = AutoModelForCausalLM.from_pretrained(
"EleutherAI/gpt-neo-125m",
load_in_8bit=True,
device_map="auto",
)
trainer = SFTTrainer(
model,
train_dataset=dataset,
args=SFTConfig(),
peft_config=peft_config,
)
trainer.train()