TRL 文档

监督微调训练器

Hugging Face's logo
加入 Hugging Face 社区

获取增强文档体验的访问权限

开始

监督微调训练器

监督微调(或简称 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_idpad_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}

让我们假设您的数据集有两个字段,questionanswer。因此,您只需运行

...
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初始方法。

使用打包数据集自定义提示

如果你的数据集中有几个你想要组合的字段,例如,如果数据集有questionanswer字段,并且你想要组合它们,你可以将一个格式化函数传递给该训练器,该函数将负责执行这项操作。例如

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()

pip install -U git+https://github.com/huggingface/transformers.git

请注意,目前闪存注意力仅支持 GPU 运行,并且处于半精度模式下(使用适配器时,基本模型以半精度加载)。此外,请注意,这两个特性与量化等其他工具完全兼容。

使用闪存注意力 1

对于闪存注意力 1,您可以使用 BetterTransformer API 并且强制将 API 调度到使用闪存注意力内核。首先,安装最新的 Optimum 包

pip install -U optimum

加载模型后,将 trainer.train() 调用包装在 with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=False, enable_mem_efficient=False): 上下文管理器中

...

+ with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=False, enable_mem_efficient=False):
    trainer.train()

请注意,由于 torch.scaled_dot_product_attention 不支持使用填充令牌进行训练(如果您使用闪存注意力内核),因此您不能在任意数据集上使用闪存注意力 1 来训练模型。因此,您只能将该功能与 packing=True 结合使用。如果您的数据集包含填充令牌,请考虑切换到闪存注意力 2 集成。

以下是使用单个 NVIDIA-T4 16GB 时的闪存注意力 1 在速度提升和内存效率方面获得的一些数据。

use_flash_attn_1 model_name max_seq_len batch_size time per training step
x facebook/opt-350m 2048 8 ~59.1s
facebook/opt-350m 2048 8 OOM
x facebook/opt-350m 2048 4 ~30.3s
facebook/opt-350m 2048 4 ~148.9s

使用 Flash Attention-2

若要使用 Flash Attention 2,请首先安装最新的 flash-attn 程序包

pip install -U flash-attn

并调用 from_pretrained 时添加 attn_implementation="flash_attention_2"

model = AutoModelForCausalLM.from_pretrained(
    model_id,
    load_in_4bit=True,
    attn_implementation="flash_attention_2"
)

如果不使用量化,请确保以半精度加载模型,并将模型调度到支持的 GPU 设备上。在加载模型后,可以按原样对其执行训练,或在模型被量化的情况下附加适配器并在其上训练适配器。

与 Flash Attention 1 相反,该集成可以对任意数据集(包括填充标记)进行训练。

使用模型创建实用程序

我们包含了一个实用程序函数来创建模型。

class trl.ModelConfig

( model_name_or_path:可选 = 无 model_revision:str = 'main' torch_dtype:可选 = 无 trust_remote_code:bool = False attn_implementation:可选 = 无 use_peft:bool = False lora_r:int = 16 lora_alpha:int = 32 lora_dropout:float = 0.05 lora_target_modules:可选 = 无 lora_modules_to_save:可选 = 无 lora_task_type:str = 'CAUSAL_LM' use_rslora:bool = False load_in_8bit:bool = False load_in_4bit:bool = False bnb_4bit_quant_type:Literal = 'nf4' use_bnb_nested_quant:bool = False )

参数

  • model_name_or_path (Optional[str]可选,默认为 None) — 用于初始化权重的模型检查点。
  • model_revision (str可选,默认为 "main") — 特定的要使用的模型版本。它可以是分支名称、标签名称或提交 ID。
  • torch_dtype (Optional[Literal["auto", "bfloat16", "float16", "float32"]]可选,默认为 None) — 覆盖默认 torch.dtype,并且在此 dtype 下加载模型。可能的值有

    • "bfloat16"torch.bfloat16
    • "float16"torch.float16
    • "float32"torch.float32
    • "auto":根据模型权重自动派生 dtype。
  • trust_remote_code (布尔值可选,默认为 False) — 是否允许在自己的建模文件中使用在 Hub 上定义的自定义模型。只有在你信任的存储库并且你已阅读代码的情况下,才应将此选项设置为 True,因为它会在你的本地计算机上执行 Hub 上存在的代码。
  • attn_implementation (Optional[str], 可选,默认为 None) — 使用的注意力方法。你可以运行 --attn_implementation=flash_attention_2,在这种情况下你必须手动安装,方法是运行 pip install flash-attn --no-build-isolation
  • use_peft (bool, 可选,默认为 False) — 是否在训练中使用 PEFT。
  • lora_alpha (int, 可选项, 默认为 32) — LoRA alpha。
  • lora_dropout (float, 可选项, 默认为 0.05) — LoRa 放弃。
  • lora_modules_to_save (Optional[List[str]], 可选的, 默认为 None) — 需要解冻和训练的模型层
  • lora_task_type (str, 可选的, 默认为 "CAUSAL_LM") — 用于 LoRA 的任务类型传递 (使用 "SEQ_CLS" 进行奖励建模)
  • load_in_8bitbool可选,默认为 False)- 是否为基础模型使用 8 位精度。仅与 LoRA 协同使用。
  • load_in_4bitbool可选,默认为 False)- 是否为基础模型使用 4 位精度。仅与 LoRA 协同使用。
  • use_bnb_nested_quant (bool, 可选,默认为 False) — 是否使用嵌套量化。

模型的配置类。

使用 HfArgumentParser,我们可以将此类变成可以通过命令行指定的 argparse 参数。

from trl import ModelConfig, SFTTrainer, get_kbit_device_map, get_peft_config, get_quantization_config
model_config = ModelConfig(
    model_name_or_path="facebook/opt-350m"
    attn_implementation=None, # or "flash_attention_2"
)
torch_dtype = (
    model_config.torch_dtype
    if model_config.torch_dtype in ["auto", None]
    else getattr(torch, model_config.torch_dtype)
)
quantization_config = get_quantization_config(model_config)
model_kwargs = dict(
    revision=model_config.model_revision,
    trust_remote_code=model_config.trust_remote_code,
    attn_implementation=model_config.attn_implementation,
    torch_dtype=torch_dtype,
    use_cache=False if training_args.gradient_checkpointing else True,
    device_map=get_kbit_device_map() if quantization_config is not None else None,
    quantization_config=quantization_config,
)
model = AutoModelForCausalLM.from_pretrained(model_config.model_name_or_path, **model_kwargs)
trainer = SFTTrainer(
    ...,
    model=model_config.model_name_or_path,
    peft_config=get_peft_config(model_config),
)

使用 NEFTune 提升模型的性能

NEFTune 是一种提升聊天模型性能的技术,由 Jain 等人的论文 “NEFTune:有噪声的嵌入改进说明微调” 中提出。它包括在训练过程中向嵌入向量添加噪音。根据论文摘要

使用 Alpaca 对 LLaMA-2-7B 进行标准微调,在 AlpacaEval 上达到 29.79%,使用有噪声嵌入,上升至 64.69%。NEFTune 在现代说明数据集上还优于强大的基准。经过 Evol-Instruct 训练的模型有 10% 的提升,ShareGPT 有 8% 的提升,OpenPlatypus 有 8% 的提升。即使是 LLaMA-2-Chat 等使用 RLHF 进一步改进的强大模型也受益于使用 NEFTune 进行额外的训练。

要在 SFTTrainer 中使用,只需在创建 SFTConfig 实例时传递 neftune_noise_alpha。请注意,为了避免任何意外行为,在进行训练后 NEFTune 会被禁用,以便恢复嵌入层的原始行为。

from datasets import load_dataset
from trl import SFTConfig, SFTTrainer

dataset = load_dataset("stanfordnlp/imdb", split="train")

sft_config = SFTConfig(
    neftune_noise_alpha=5,
)
trainer = SFTTrainer(
    "facebook/opt-350m",
    train_dataset=dataset,
    args=sft_config,
)
trainer.train()

我们通过向 OpenAssistant 数据集 中训练 mistralai/Mistral-7B-v0.1 来测试 NEFTune,并验证了在 MT Bench 上使用 NEFTune 可将性能提升约 25%。

但是请注意,性能提升的程度取决于数据集,特别是将 NEFTune 应用于 UltraChat 这样的合成数据集通常会产生较小的收益。

使用 unsloth 加速微调 2 倍

你可以使用完全兼容于 SFTTrainerunsloth 库进一步加速 QLoRA / LoRA(快 2 倍,内存少 60%)。目前 unsloth 仅支持 Llama(Yi、TinyLlama、Qwen、Deepseek 等)和 Mistral 架构。下面列出了一些 1x A100 上的基准

1 A100 40GB 数据集 🤗 🤗 + Flash Attention 2 🦥 Unsloth 🦥 节省的 VRAM
代码 Llama 34b Slim Orca 1x 1.01x 1.94x -22.7%
Llama-2 7b Slim Orca 1x 0.96x 1.87x -39.3%
Mistral 7b Slim Orca 1x 1.17x 1.88x -65.9%
Tiny Llama 1.1b Alpaca 1x 1.55x 2.74x -57.8%

首先按照 官方文档 安装 unsloth。安装完成后,你可以用一种非常简单的方式将 unsloth 纳入你的工作流程中;你只需要加载 FastLanguageModel,而不必加载 AutoModelForCausalLM,如下所示

import torch
from trl import SFTConfig, SFTTrainer
from unsloth import FastLanguageModel

max_seq_length = 2048 # Supports automatic RoPE Scaling, so choose any number

# Load model
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/mistral-7b",
    max_seq_length=max_seq_length,
    dtype=None,  # None for auto detection. Float16 for Tesla T4, V100, Bfloat16 for Ampere+
    load_in_4bit=True,  # Use 4bit quantization to reduce memory usage. Can be False
    # token = "hf_...", # use one if using gated models like meta-llama/Llama-2-7b-hf
)

# Do model patching and add fast LoRA weights
model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],
    lora_alpha=16,
    lora_dropout=0,  # Dropout = 0 is currently optimized
    bias="none",  # Bias = "none" is currently optimized
    use_gradient_checkpointing=True,
    random_state=3407,
)

args = SFTConfig(
    output_dir="./output",
    max_seq_length=max_seq_length,
    dataset_text_field="text",
)

trainer = SFTTrainer(
    model=model,
    args=args,
    train_dataset=dataset,
)
trainer.train()

已保存的模型与 Hugging Face 的 transformers 库完全兼容。在他们的 官方仓库 中了解更多关于 unsloth 的信息。

Liger-Kernel:提升 20% 吞吐量,并为多 GPU 训练降低 60% 内存

Liger Kernel 是一组专为 LLM 训练设计的 Triton 核。它可以有效地将多 GPU 训练吞吐量提升 20%,并将内存用量降低 60%。通过这种方式,我们可以将上下文字数提升 4 倍,如下面的基准测试所示。他们已经实现了 Hugging Face 兼容的 RMSNormRoPESwiGLUCrossEntropyFusedLinearCrossEntropy 以及更多内容。该核可以与 Flash AttentionPyTorch FSDPMicrosoft DeepSpeed 完美结合。

通过大幅降低内存,你可以关闭 cpu_offloading 或梯度检查点来进一步提升性能。

速度提升 内存降低
Speed up Memory
  1. SFTTrainer 中使用 Liger-Kernel,首先通过以下方式进行安装
pip install liger-kernel
  1. 安装后,在 SFTConfig 中设置 use_liger。不需要其他更改!
config = SFTConfig(
  use_liger=True
)

要了解更多关于 Liger-Kernel 的信息,请访问他们的 官方存储库

最佳实践

使用那个训练器训练模型时,注意以下最佳实践

  • SFTTrainer 默认总是将序列填充到 SFTTrainermax_seq_length 参数。如果没有传递参数,则训练器将从 tokenizer 中检索该值。某些 tokenizer 不提供默认值,因此有一个检查来检索 2048 与该值之间的最小值。务必在训练前检查它。
  • 要使用 8 位训练适配器,你可能需要调整 PEFT 的 prepare_model_for_kbit_training 方法的参数,因此我们建议用户使用 prepare_in_int8_kwargs 字段,或在 SFTTrainer 外创建 PeftModel 并传递它。
  • 为了使用适配器进行更节省内存的训练,你可以使用 8 位加载基础模型,只需在创建 SFTTrainer 时添加 load_in_8bit 参数,或在训练器外创建 8 位基础模型并传递它。
  • 如果你在训练器外创建模型,请确保不要向训练器传递任何与 from_pretrained() 方法相关的其他关键字参数。

多 GPU 训练

Trainer(因此还有 SFTTrainer)支持多 GPU 训练。如果您通过 python script.py 运行脚本,它将默认使用 DP 作为策略,这可能 比预期慢。要使用 DDP(通常建议这样做,如 此处 所述了解更多信息),您必须通过 python -m torch.distributed.launch script.pyaccelerate launch script.py 启动该脚本。要让 DDP 生效,您还必须检查以下内容

  • 如果您正在使用 gradient_checkpointing,则将以下内容添加到 TrainingArguments:gradient_checkpointing_kwargs={'use_reentrant':False} (更多信息 此处
  • 确保将模型置于正确的设备上
from accelerate import PartialState
device_string = PartialState().process_index
model = AutoModelForCausalLM.from_pretrained(
     ...
    device_map={'':device_string}
)

GPTQ 转换

完成训练后,您可能会遇到一些 GPTQ 量化的问题。降低 gradient_accumulation_steps4 将在量化过程到 GPTQ 格式中解决大多数问题。

扩展 SFTTrainer 用于视觉语言模型

SFTTrainer 本身不支持视觉语言数据。不过,我们将提供一份指南,说明如何调整此训练器以支持视觉语言数据。具体而言,你需要使用与视觉语言数据兼容的自定义数据整理器。本指南概述了进行这些调整的步骤。如需具体示例,请参阅脚本来 examples/scripts/vsft_llava.py,该脚本演示了如何微调 LLaVA 1.5 模型到 HuggingFaceH4/llava-instruct-mix-vsft 数据集上。

准备数据

数据格式很灵活,前提是要与我们稍后将定义的自定义整理器兼容。一种常见方法是使用对话数据。由于数据同时包含文本和图像,因此需要相应调整格式。以下是包含文本和图像的对话数据格式示例

images = ["obama.png"]
messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "Who is this?"},
            {"type": "image"}
        ]
    },
    {
        "role": "assistant",
        "content": [
            {"type": "text", "text": "Barack Obama"}
        ]
    },
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "What is he famous for?"}
        ]
    },
    {
        "role": "assistant",
        "content": [
            {"type": "text", "text": "He is the 44th President of the United States."}
        ]
    }
]

要说明如何使用 LLaVA 模型处理此数据格式,你可以使用以下代码

from transformers import AutoProcessor

processor = AutoProcessor.from_pretrained("llava-hf/llava-1.5-7b-hf")
print(processor.apply_chat_template(messages, tokenize=False))

输出将格式化为如下所示

Who is this? ASSISTANT: Barack Obama USER: What is he famous for? ASSISTANT: He is the 44th President of the United States. 

用于处理多模态数据的一个自定义整理器

SFTTrainer 的默认行为不同,在数据整理过程中,多模态数据的处理是即时进行的。为此,你需要定义一个同时处理文本和图像的自定义整理器。此整理器必须以示例列表作为输入(参见上一部分中的数据格式示例),并返回一个经过处理的数据批。下面是一个这样的整理器的示例

def collate_fn(examples):
    # Get the texts and images, and apply the chat template
    texts = [processor.apply_chat_template(example["messages"], tokenize=False) for example in examples]
    images = [example["images"][0] for example in examples]

    # Tokenize the texts and process the images
    batch = processor(texts, images, return_tensors="pt", padding=True)

    # The labels are the input_ids, and we mask the padding tokens in the loss computation
    labels = batch["input_ids"].clone()
    labels[labels == processor.tokenizer.pad_token_id] = -100
    batch["labels"] = labels

    return batch

我们可以通过运行下面的代码来验证整理器是否按预期工作

from datasets import load_dataset

dataset = load_dataset("HuggingFaceH4/llava-instruct-mix-vsft", split="train")
examples = [dataset[0], dataset[1]]  # Just two examples for the sake of the example
collated_data = collate_fn(examples)
print(collated_data.keys())  # dict_keys(['input_ids', 'attention_mask', 'pixel_values', 'labels'])

训练视觉语言模型

现在我们已经准备好了数据并定义了整理器,我们可以继续训练模型了。为了确保数据不被视为纯文本处理,我们需要在 SFTConfig 中设置几个参数,特别是 dataset_text_fieldremove_unused_columns。我们还需要将 skip_prepare_dataset 设置为 True 以避免对数据集进行默认处理。下面是一个设置 SFTTrainer 的示例。

args.dataset_text_field = ""  # needs a dummy field
args.remove_unused_columns = False
args.dataset_kwargs = {"skip_prepare_dataset": True}

trainer = SFTTrainer(
    model=model,
    args=args,
    data_collator=collate_fn,
    train_dataset=train_dataset,
    tokenizer=processor.tokenizer,
)

可以在 examples/scripts/vsft_llava.py 脚本中找到在 HuggingFaceH4/llava-instruct-mix-vsft 数据集上训练 LLaVa 1.5 的完整示例。

SFTTrainer

class trl.SFTTrainer

< >

( model: Union = None args: Optional = None data_collator: Optional = None train_dataset: Optional = None eval_dataset: Union = None tokenizer: Optional = None model_init: Optional = None compute_metrics: Optional = None callbacks: Optional = None optimizers: Tuple = (None, None) preprocess_logits_for_metrics: Optional = None peft_config: Optional = None dataset_text_field: Optional = None packing: Optional = False formatting_func: Optional = None max_seq_length: Optional = None infinite: Optional = None num_of_sequences: Optional = None chars_per_token: Optional = None dataset_num_proc: Optional = None dataset_batch_size: Optional = None neftune_noise_alpha: Optional = None model_init_kwargs: Optional = None dataset_kwargs: Optional = None eval_packing: Optional = None )

参数

  • model (联合 [transformers.预训练模型, nn.模块, str]) — 要训练的模型,可以是 预训练模型, torch.nn.Module 或一个字符串,用于从缓存加载或者下载模型名称。如果将 PeftConfig 对象传递给 peft_config 参数,也可以将该模型转换为 PeftModel
  • args (Optional[SFTConfig]) — 需要修改的训练参数。如果未提供,将默认设置为一个基本 SFTConfig 实例,其中 output_dir 设为当前目录中名为 tmp_trainer 的目录。
  • data_collator (Optional[transformers.DataCollator]) — 用作训练的数据整理器。
  • train_dataset (Optional[datasets.Dataset]) — 用于训练的数据集。我们建议用户使用 trl.trainer.ConstantLengthDataset 创建自己的数据集。
  • eval_dataset (Optional[Union[datasets.Dataset, Dict[str, datasets.Dataset]]]) — 用于评估的数据集。我们建议用户使用 trl.trainer.ConstantLengthDataset 创建自己的数据集。
  • model_init (Callable[[], transformers.PreTrainedModel]) — 用于训练的模型初始化程序。如果未指定参数,将使用默认的模型初始化程序。
  • compute_metrics (Callable[[transformers.EvalPrediction], Dict]可选默认为 None) — 用于在评估期间计算度量的函数。它应该返回一个字典,将度量名称映射到度量值。如果未指定,则在评估期间仅计算损失。
  • callbacks (List[transformers.TrainerCallback]) — 用于训练的回调。
  • optimizers (Tuple[torch.optim.Optimizer, torch.optim.lr_scheduler.LambdaLR]) — 用于训练的优化器和调度程序。
  • peft_config (Optional[PeftConfig]) — 要用于初始化 PeftModel 的 PeftConfig 对象。
  • formatting_func (Optional[Callable]) — 用于创建 ConstantLengthDataset 的格式化函数。

监督微调训练器 (SFT 训练器) 的类定义。该类是 transformers.Trainer 类的包装器,并且继承了该类所有的属性和方法。此训练器负责在用户传递 PeftConfig 对象时正确初始化 PeftModel。

SFTConfig

trl.SFTConfig

< >

( output_dir: str overwrite_output_dir: bool = False do_train: bool = False do_eval: bool = False do_predict: bool = False eval_strategy: Union = 'no' prediction_loss_only: bool = False per_device_train_batch_size: int = 8 per_device_eval_batch_size: int = 8 per_gpu_train_batch_size: Optional = None per_gpu_eval_batch_size: Optional = None gradient_accumulation_steps: int = 1 eval_accumulation_steps: Optional = None eval_delay: Optional = 0 torch_empty_cache_steps: Optional = None learning_rate: float = 5e-05 weight_decay: float = 0.0 adam_beta1: float = 0.9 adam_beta2: float = 0.999 adam_epsilon: float = 1e-08 max_grad_norm: float = 1.0 num_train_epochs: float = 3.0 max_steps: int = -1 lr_scheduler_type: Union = 'linear' lr_scheduler_kwargs: Union = <factory> warmup_ratio: float = 0.0 warmup_steps: int = 0 log_level: Optional = 'passive' log_level_replica: Optional = 'warning' log_on_each_node: bool = True logging_dir: Optional = None logging_strategy: Union = 'steps' logging_first_step: bool = False logging_steps: float = 500 logging_nan_inf_filter: bool = True save_strategy: Union = 'steps' save_steps: float = 500 save_total_limit: Optional = None save_safetensors: Optional = True save_on_each_node: bool = False save_only_model: bool = False restore_callback_states_from_checkpoint: bool = False no_cuda: bool = False use_cpu: bool = False use_mps_device: bool = False seed: int = 42 data_seed: Optional = None jit_mode_eval: bool = False use_ipex: bool = False bf16: bool = False fp16: bool = False fp16_opt_level: str = 'O1' half_precision_backend: str = 'auto' bf16_full_eval: bool = False fp16_full_eval: bool = False tf32: Optional = None local_rank: int = -1 ddp_backend: Optional = None tpu_num_cores: Optional = None tpu_metrics_debug: bool = False debug: Union = '' dataloader_drop_last: bool = False eval_steps: Optional = None dataloader_num_workers: int = 0 dataloader_prefetch_factor: Optional = None past_index: int = -1 run_name: Optional = None disable_tqdm: Optional = None remove_unused_columns: Optional = True label_names: Optional = None load_best_model_at_end: Optional = False metric_for_best_model: Optional = None greater_is_better: Optional = None ignore_data_skip: bool = False fsdp: Union = '' fsdp_min_num_params: int = 0 fsdp_config: Union = None fsdp_transformer_layer_cls_to_wrap: Optional = None accelerator_config: Union = None deepspeed: Union = None label_smoothing_factor: float = 0.0 optim: Union = 'adamw_torch' optim_args: Optional = None adafactor: bool = False group_by_length: bool = False length_column_name: Optional = 'length' report_to: Union = None ddp_find_unused_parameters: Optional = None ddp_bucket_cap_mb: Optional = None ddp_broadcast_buffers: Optional = None dataloader_pin_memory: bool = True dataloader_persistent_workers: bool = False skip_memory_metrics: bool = True use_legacy_prediction_loop: bool = False push_to_hub: bool = False resume_from_checkpoint: Optional = None hub_model_id: Optional = None hub_strategy: Union = 'every_save' hub_token: Optional = None hub_private_repo: bool = False hub_always_push: bool = False gradient_checkpointing: bool = False gradient_checkpointing_kwargs: Union = None include_inputs_for_metrics: bool = False include_for_metrics: List = <factory> eval_do_concat_batches: bool = True fp16_backend: str = 'auto' evaluation_strategy: Union = None push_to_hub_model_id: Optional = None push_to_hub_organization: Optional = None push_to_hub_token: Optional = None mp_parameters: str = '' auto_find_batch_size: bool = False full_determinism: bool = False torchdynamo: Optional = None ray_scope: Optional = 'last' ddp_timeout: Optional = 1800 torch_compile: bool = False torch_compile_backend: Optional = None torch_compile_mode: Optional = None dispatch_batches: Optional = None split_batches: Optional = None include_tokens_per_second: Optional = False include_num_input_tokens_seen: Optional = False neftune_noise_alpha: Optional = None optim_target_modules: Union = None batch_eval_metrics: bool = False eval_on_start: bool = False use_liger_kernel: Optional = False eval_use_gather_object: Optional = False dataset_text_field: Optional = None packing: bool = False max_seq_length: Optional = None dataset_num_proc: Optional = None dataset_batch_size: int = 1000 model_init_kwargs: Optional = None dataset_kwargs: Optional = None eval_packing: Optional = None num_of_sequences: int = 1024 chars_per_token: float = 3.6 use_liger: bool = False )

参数

  • dataset_text_field (Optional[str], 可选,默认为 None) — 数据集文本字段的名称。如果提供,训练器将根据 dataset_text_field 自动创建一个 ConstantLengthDataset
  • packing (bool, 可选,默认为 False) — 控制 ConstantLengthDataset 是否打包数据集的序列。
  • dataset_num_proc (可选[int]可选,默认为 None) — 用于处理数据集的进程数。仅当 packing=False 时使用。
  • dataset_batch_size (Union[int, None]可选,默认为 1000) — 每批标记的示例数。如果 dataset_batch_size <= 0dataset_batch_size为空,那么将整个数据集标记为单个批处理。
  • neftune_noise_alpha (Optional[float], 可选,默认为 None) — NEFTune 嵌入的噪声比例。NEFTune 论文建议使用 515 之间的值。如果设置为 None,则不会激活 NEFTune。激活 NEFTune 可以显著提高指令精调的模型性能。
  • model_init_kwargs (Optional[Dict[str, Any]], 可选,默认为 None) — 从字符串实例化模型时,将关键字参数传递给 AutoModelForCausalLM.from_pretrained
  • dataset_kwargs (Optional[Dict[str, Any]], 可选,默认为 None) — 在创建打包或非打包数据集时传递的可选关键字参数字典。
  • eval_packing (Optional[bool], 可选,默认为 None) — 是否打包 eval 数据集。如果为 None,则使用与 packing 相同的值。
  • num_of_sequences (int, 可选,默认为 1024) — 在 ConstantLengthDataset 中使用的序列数。
  • chars_per_token (float, 可选,默认为 3.6) — 在 ConstantLengthDataset 中使用的每个标记的字符数。请参见 chars_token_ratio 了解更多详情。
  • use_liger (bool, 可选,默认为 False) — 用 Liger 内核替换模型以提高吞吐量并减少内存使用。

SFTTrainer 的配置类。

使用 HfArgumentParser,我们可以将此类变成可以通过命令行指定的 argparse 参数。

数据集

在 SFTTrainer 中,我们智慧地支持 datasets.IterableDataset 以及其他样式的数据集。如果你正在使用不希望全部保存在磁盘上的大型语料库,这将很有用。即使启用了打包,数据也会即时标记化并处理。

此外,在 SFTTrainer 中,如果它们是 datasets.Datasetdatasets.IterableDataset,我们支持预标记化数据集。换句话说,如果这样的数据集有 input_ids 列,则不会进行进一步处理(标记化或打包),数据集将按原样使用。如果你在此脚本之外对数据集进行了预标记化并希望直接重新使用它,这将很有用。

ConstantLengthDataset

trl.trainer.ConstantLengthDataset

< >

( tokenizer dataset dataset_text_field = None formatting_func = None infinite = False seq_length = 1024 num_of_sequences = 1024 chars_per_token = 3.6 eos_token_id = 0 shuffle = True append_concat_token = True add_special_tokens = True )

参数

  • tokenizer (transformers.PreTrainedTokenizer) — 用于处理数据的处理器。
  • dataset (dataset.Dataset) — 包含文本文件的 Dataset。
  • dataset_text_field (Optional[str]可选,默认 None) — 数据集中包含文本的字段名称。仅当 formatting_funcNone 时使用。
  • infinite (bool, 可选,默认为 False) —— 若为 True,则数据集到达末尾后迭代器将复位,否则停止。
  • seq_length (int, 可选,默认为 1024) —— 返回的标记序列的长度。
  • chars_per_token (int,可选,默认为3.6) — 用于估算文本缓冲区中令牌数量的每个令牌的字符数。
  • eos_token_id (int,可选,默认为0) — 如果传递的标记化器没有 EOS 令牌,则为序列标记的结尾标记 Id。
  • append_concat_token布尔值可选,默认为 True) — 如果为真,则在要打包的每个样本的末尾追加 eos_token_id
  • add_special_tokens布尔值可选,默认为 True) — 如果为真,分词器会向要打包的每个样本添加特殊标记。

可迭代数据集,从文本文件流中返回恒定长度的标记块。该数据集还使用用户提供的特定格式对文本进行格式化,然后再进行标记化。

< > 更新在 GitHub 上