TRL documentation
CPO 训练器
CPO 训练器
概述
由 Haoran Xu、Amr Sharaf、Yunmo Chen、Weiting Tan、Lingfeng Shen、Benjamin Van Durme、Kenton Murray 和 Young Jin Kim 在论文 Contrastive Preference Optimization: Pushing the Boundaries of LLM Performance in Machine Translation 中提出的对比偏好优化(Contrastive Preference Optimization,CPO)。从宏观层面来看,CPO 旨在训练模型,使其在机器翻译(MT)任务中避免生成尚可但并不完美的译文。然而,CPO 是 DPO 损失的一种通用近似,因此也可应用于其他领域,例如聊天。
CPO 旨在缓解 SFT 的两个根本性缺陷。首先,SFT 通过最小化预测输出与黄金标准参考之间的差异来训练模型,这种方法本质上将模型性能的上限锁定在训练数据的质量水平上。其次,SFT 缺乏一种机制来防止模型拒绝翻译中的错误。CPO 的目标函数是从 DPO 的目标函数推导而来的。
快速入门
本示例演示了如何使用 CPO 方法训练模型。我们使用 Qwen 0.5B 模型 作为基础模型。我们使用来自 UltraFeedback 数据集 的偏好数据。您可以在这里查看该数据集的数据:
以下是用于训练模型的脚本:
# train_cpo.py
from datasets import load_dataset
from trl.experimental.cpo import CPOConfig, CPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-0.5B-Instruct")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-0.5B-Instruct")
train_dataset = load_dataset("trl-lib/ultrafeedback_binarized", split="train")
training_args = CPOConfig(output_dir="Qwen2-0.5B-CPO")
trainer = CPOTrainer(model=model, args=training_args, processing_class=tokenizer, train_dataset=train_dataset)
trainer.train()使用以下命令执行该脚本:
torchrun --nproc_per_node 8 train_cpo.py
预期的数据集类型
CPO 需要一个偏好数据集。experimental.cpo.CPOTrainer 同时支持对话式和标准数据集格式。当提供对话式数据集时,训练器会自动将聊天模板应用于该数据集。
记录的指标
在训练和评估过程中,我们记录以下指标:
rewards/chosen:策略模型对已选响应的平均对数概率,按 beta 缩放rewards/rejected:策略模型对拒绝响应的平均对数概率,乘以 betarewards/accuracies:所选奖励大于对应被拒绝奖励的频率均值rewards/margins:所选奖励与对应被拒绝奖励之间的平均差值nll_loss:策略模型针对所选回复的平均负对数似然损失
CPO 变体
简单偏好优化(SimPO)
Simple Preference Optimization(SimPO)由 Yu Meng、Mengzhou Xia 和 Danqi Chen 提出,这是一种比 DPO 更简单且更有效的偏好优化算法,且无需使用参考模型。SimPO 的关键设计包括:(1) 使用长度归一化的对数似然作为隐式奖励,以及 (2) 在 Bradley-Terry 排序目标中引入目标奖励间隔。官方代码可在 princeton-nlp/SimPO 中找到。
该论文的摘要如下:
直接偏好优化(DPO)是一种广泛使用的离线偏好优化算法,它重新参数化了基于人类反馈的强化学习(RLHF)中的奖励函数,以提升简洁性和训练稳定性。在这项工作中,我们提出了 SimPO,一种更简单但更有效的方法。SimPO 的有效性归因于一个关键设计:使用序列的平均对数概率作为隐式奖励。这种奖励公式与模型生成过程更加一致,并消除了对参考模型的需求,从而在计算和内存方面更加高效。此外,我们在 Bradley-Terry 目标中引入了目标奖励间隔,以鼓励获胜响应和失败响应之间产生更大的间隔,从而进一步提升算法性能。我们在各种最先进的训练设置中,包括 Mistral 和 Llama3 等基础模型和指令微调模型,将 SimPO 与 DPO 及其最新变体进行了比较。我们在广泛的指令遵循基准上进行了评估,包括 AlpacaEval 2、MT-Bench 以及近期具有挑战性的 Arena-Hard 基准。我们的结果表明,SimPO 在不显著增加响应长度的情况下,持续且显著地优于现有方法。具体而言,SimPO 在 AlpacaEval 2 上比 DPO 高出多达 6.4 分,在 Arena-Hard 上高出多达 7.5 分。我们表现最佳的模型基于 Llama3-8B-Instruct 构建,在 AlpacaEval 2 上取得了 44.7 的长度控制胜率——超越了排行榜上的 Claude 3 Opus,并在 Arena-Hard 上取得了 33.8 的胜率——使其成为最强的 8B 开源模型。
SimPO 损失已集成到 experimental.cpo.CPOTrainer 中,因为它是一种替代损失,可添加奖励间隔,支持长度归一化,并且不使用 BC 正则化。要使用此损失,只需在 experimental.cpo.CPOConfig 中开启 loss_type="simpo" 和 cpo_alpha=0.0,并将 simpo_gamma 设置为推荐值。
CPO-SimPO
我们还提供了 CPO 和 SimPO 的组合使用方式,以实现更稳定的训练并提升性能。有关更多详细信息,请参阅 CPO-SimPO GitHub。要使用此方法,只需在 experimental.cpo.CPOConfig 中设置 loss_type="simpo" 以及非零的 cpo_alpha 来启用 SimPO 即可。
AlphaPO
Aman Gupta、Shao Tang、Qingquan Song、Sirou Zhu、Jiwoo Hong、Ankan Saha、Viral Gupta、Noah Lee、Eunki Kim、Jason Zhu、Natesh Pillai 和 S. Sathiya Keerthi 提出的 AlphaPO — Reward shape matters for LLM alignment(AlphaPO)方法也在 experimental.cpo.CPOTrainer 中实现了。AlphaPO 是一种替代方法,它在 SimPO 损失的上下文中对奖励函数的形状进行变换。该论文的摘要如下:
基于人类反馈的强化学习(Reinforcement Learning with Human Feedback,RLHF)及其变体在有效对齐大型语言模型(LLMs)以遵循指令并体现人类价值观方面取得了巨大进展。最近,直接对齐算法(Direct Alignment Algorithms,DAAs)应运而生,它们通过直接将奖励表征为正在学习的策略的函数,从而跳过了 RLHF 中的奖励建模阶段。DAAs 的一些流行示例包括直接偏好优化(Direct Preference Optimization,DPO)和简单偏好优化(Simple Preference Optimization,SimPO)。这些方法通常存在似然位移(likelihood displacement)问题,即偏好响应的概率往往会被不必要地降低。在本文中,我们认为对于 DAAs 而言,奖励(函数)的形状至关重要。我们提出了 AlphaPO,这是一种新的 DAA 方法,它利用 α 参数来帮助改变奖励函数的形状,使其超越标准的对数奖励。AlphaPO 有助于对似然位移和过度优化保持细粒度的控制。与表现最佳的 DAAs 之一 SimPO 相比,AlphaPO 在 Mistral-7B 和 Llama3-8B 的指令版本上,将对齐性能相对提升了约 7% 到 10%,而在相同模型上,相较于 DPO 实现了 15% 到 50% 的相对提升。本文展示的分析与结果凸显了奖励形状的重要性,以及如何系统地改变它来影响训练动态,并提升对齐性能。
要在论文中描述的方式下使用该损失函数,我们可以设置 loss_type="alphapo",它会自动将 loss_type="simpo" 和 cpo_alpha=0.0 设置为推荐值,同时将 alpha 和 simpo_gamma 设置为 experimental.cpo.CPOConfig 中的推荐值。或者,您可以手动设置 loss_type="simpo"、cpo_alpha=0.0,并将 alpha 和 simpo_gamma 设置为推荐值。该方法的其他变体也是可行的,例如将 loss_type="ipo" 和 alpha 设置为任意非零值。
损失函数
CPO 算法支持多种损失函数。可以通过 experimental.cpo.CPOConfig 中的 loss_type 参数来设置损失函数。支持的损失函数如下:
loss_type= | 描述 |
|---|---|
"sigmoid"(默认) | 给定偏好数据,我们可以根据 Bradley-Terry 模型拟合一个二分类器。事实上,DPO 的作者提出通过对归一化似然使用 logsigmoid 上的 sigmoid 损失来拟合逻辑回归。 |
"hinge" | RSO 的作者建议对 SLiC 论文中的归一化似然使用铰链损失。在这种情况下,beta 是间隔的倒数。 |
"ipo" | IPO 的作者们对 DPO 算法提供了更深入的理论理解,指出了过拟合问题,并提出了一种替代损失函数。在这种情况下,beta 是所选完成与拒绝完成对之间对数似然比差距的倒数,因此 beta 越小,该差距就越大。根据论文,该损失是对完成的对数似然取平均值(而 DPO 仅进行求和)。 |
"simpo" | SimPO 方法也在 experimental.cpo.CPOTrainer 中实现。SimPO 是一种替代损失函数,它引入了奖励间隔,支持长度归一化,并且不使用 BC 正则化。要使用此损失函数,只需在 experimental.cpo.CPOConfig 中设置 loss_type="simpo" 和 cpo_alpha=0.0,并将 simpo_gamma 设置为推荐值。 |
"alphapo" | AlphaPO 方法也在 experimental.cpo.CPOTrainer 中实现。这是一种语法糖,会自动设置 loss_type="simpo" 和 cpo_alpha=0.0。当 alpha 参数不为零时,AlphaPO 会在 SimPO 损失的上下文中对奖励函数的形状进行变换。 |
对于混合专家模型:启用辅助损失
当负载在专家之间大致均匀分布时,MOE 的效率最高。
为了确保在偏好调优期间以类似的方式训练 MOE,将负载均衡器的辅助损失添加到最终损失中是有益的。
通过在模型配置中设置 output_router_logits=True(例如 MixtralConfig)来启用此选项。
要调整辅助损失对总损失的贡献程度,请在模型配置中使用超参数 router_aux_loss_coef=...(默认值:0.001)。
CPOTrainer
类 trl.experimental.cpo.CPOTrainer
< source >( model: typing.Union[transformers.modeling_utils.PreTrainedModel, torch.nn.Module, str, NoneType] = Noneargs: trl.experimental.cpo.cpo_config.CPOConfig | None = Nonedata_collator: collections.abc.Callable[[list[typing.Any]], dict[str, typing.Any]] | None = Nonetrain_dataset: datasets.arrow_dataset.Dataset | None = Noneeval_dataset: datasets.arrow_dataset.Dataset | dict[str, datasets.arrow_dataset.Dataset] | None = Noneprocessing_class: transformers.tokenization_utils_base.PreTrainedTokenizerBase | transformers.image_processing_utils.BaseImageProcessor | transformers.feature_extraction_utils.FeatureExtractionMixin | transformers.processing_utils.ProcessorMixin | None = Nonemodel_init: collections.abc.Callable[[], transformers.modeling_utils.PreTrainedModel] | None = Nonecallbacks: list[transformers.trainer_callback.TrainerCallback] | None = Noneoptimizers: tuple[torch.optim.Optimizer, torch.optim.lr_scheduler.LambdaLR] = (None, None)preprocess_logits_for_metrics: collections.abc.Callable[[torch.Tensor, torch.Tensor], torch.Tensor] | None = Nonepeft_config: PeftConfig | None = Nonecompute_metrics: collections.abc.Callable[[transformers.trainer_utils.EvalLoopOutput], dict] | None = None )
参数
- model (PreTrainedModel) — 要训练的模型,最好是 AutoModelForSequenceClassification。
- args (experimental.cpo.CPOConfig) — 用于训练的 CPO 配置参数。
- data_collator (
DataCollator) — 用于训练的数据整理器。如果指定为 None,则使用默认的数据整理器(experimental.utils.DPODataCollatorWithPadding),该整理器会将序列填充到批次中序列的最大长度,适用于成对序列的数据集。 - train_dataset (Dataset) — 用于训练的 dataset。
- eval_dataset (Dataset) — 用于评估的数据集。
- processing_class (PreTrainedTokenizerBase、BaseImageProcessor、FeatureExtractionMixin 或 ProcessorMixin,可选) — 用于处理数据的处理类。如果提供,将用于自动处理模型的输入,并随模型一起保存,以便更容易地重新运行中断的训练或复用微调后的模型。
- model_init (
Callable[[], transformers.PreTrainedModel]) — 用于训练的模型初始化器。如果指定为 None,则使用默认的模型初始化器。 - callbacks (
list[transformers.TrainerCallback]) — 训练时使用的回调。 - optimizers (
tuple[torch.optim.Optimizer, torch.optim.lr_scheduler.LambdaLR]) — 用于训练的优化器和调度器。 - preprocess_logits_for_metrics (
Callable[[torch.Tensor, torch.Tensor], torch.Tensor]) — 用于在计算指标之前对 logits 进行预处理的函数。 - peft_config (PeftConfig, 可选) — 用于训练的 PEFT 配置。如果传入 PEFT 配置,模型将被包装为一个 PEFT 模型。
- compute_metrics (
Callable[[EvalPrediction], dict], 可选) — 用于计算指标的函数。必须接受一个EvalPrediction并返回一个从字符串到指标值的字典。
Initialize CPOTrainer
训练
< source >( resume_from_checkpoint: str | bool | None = Nonetrial: optuna.Trial | dict[str, Any] | None = Noneignore_keys_for_eval: list[str] | None = None ) → ~trainer_utils.TrainOutput
参数
- resume_from_checkpoint (
str或bool,可选) — 如果为str,则为之前Trainer实例保存的检查点的本地路径。如果为bool且等于True,则加载之前Trainer实例保存在 args.output_dir 中的最后一个检查点。如果存在,训练将从此处加载的模型/优化器/调度器状态中恢复。 - trial (
optuna.Trial或dict[str, Any],可选) — 用于超参数搜索的试验运行或超参数字典。 - ignore_keys_for_eval (
list[str],可选) — 在训练期间收集预测结果以进行评估时,应忽略的模型输出(如果输出是字典)中的键列表。
返回值
~trainer_utils.TrainOutput
包含全局步数、训练损失和指标的 Object。
主要训练入口。
将保存模型,以便您可以使用 from_pretrained() 重新加载它。
仅从主进程保存。
push_to_hub
< source >( commit_message: str | None = 'End of training'blocking: bool = Truetoken: str | None = Nonerevision: str | None = None**kwargs )
将 self.model 和 self.processing_class 上传到 🤗 模型库中的仓库 self.args.hub_model_id。
CPOConfig
类 trl.experimental.cpo.CPOConfig
< source >( output_dir: str | None = Noneper_device_train_batch_size: int = 8num_train_epochs: float = 3.0max_steps: int = -1learning_rate: float = 1e-06lr_scheduler_type: transformers.trainer_utils.SchedulerType | str = 'linear'lr_scheduler_kwargs: dict | str | None = Nonewarmup_steps: float = 0optim: transformers.training_args.OptimizerNames | str = 'adamw_torch_fused'optim_args: str | None = Noneweight_decay: float = 0.0adam_beta1: float = 0.9adam_beta2: float = 0.999adam_epsilon: float = 1e-08optim_target_modules: None | str | list[str] = Nonegradient_accumulation_steps: int = 1average_tokens_across_devices: bool = Truemax_grad_norm: float = 1.0label_smoothing_factor: float = 0.0bf16: bool | None = Nonefp16: bool = Falsebf16_full_eval: bool = Falsefp16_full_eval: bool = Falsetf32: bool | None = Nonegradient_checkpointing: bool = Truegradient_checkpointing_kwargs: dict[str, typing.Any] | str | None = Nonetorch_compile: bool = Falsetorch_compile_backend: str | None = Nonetorch_compile_mode: str | None = Noneuse_liger_kernel: bool = Falseliger_kernel_config: dict[str, bool] | None = Noneuse_cache: bool = Falseneftune_noise_alpha: float | None = Nonetorch_empty_cache_steps: int | None = Noneauto_find_batch_size: bool = Falselogging_strategy: transformers.trainer_utils.IntervalStrategy | str = 'steps'logging_steps: float = 10logging_first_step: bool = Falselog_on_each_node: bool = Truelogging_nan_inf_filter: bool = Trueinclude_num_input_tokens_seen: str | bool = 'no'log_level: str = 'passive'log_level_replica: str = 'warning'disable_tqdm: bool | None = Nonereport_to: None | str | list[str] = 'none'run_name: str | None = Noneproject: str = 'huggingface'trackio_space_id: str | None = Nonetrackio_bucket_id: str | None = Nonetrackio_static_space_id: typing.Union[str, NoneType, typing.Literal[False]] = Noneeval_strategy: transformers.trainer_utils.IntervalStrategy | str = 'no'eval_steps: float | None = Noneeval_delay: float = 0per_device_eval_batch_size: int = 8prediction_loss_only: bool = Falseeval_on_start: bool = Falseeval_do_concat_batches: bool = Trueeval_use_gather_object: bool = Falseeval_accumulation_steps: int | None = Noneinclude_for_metrics: list[str] = <factory>batch_eval_metrics: bool = Falsesave_only_model: bool = Falsesave_strategy: transformers.trainer_utils.SaveStrategy | str = 'steps'save_steps: float = 500save_on_each_node: bool = Falsesave_total_limit: int | None = Noneenable_jit_checkpoint: bool = Falsepush_to_hub: bool = Falsehub_token: str | None = Nonehub_private_repo: bool | None = Nonehub_model_id: str | None = Nonehub_strategy: transformers.trainer_utils.HubStrategy | str = 'every_save'hub_always_push: bool = Falsehub_revision: str | None = Noneload_best_model_at_end: bool = Falsemetric_for_best_model: str | None = Nonegreater_is_better: bool | None = Noneignore_data_skip: bool = Falserestore_callback_states_from_checkpoint: bool = Falsefull_determinism: bool = Falseseed: int = 42data_seed: int | None = Noneuse_cpu: bool = Falseaccelerator_config: dict | str | None = Noneparallelism_config: accelerate.parallelism_config.ParallelismConfig | None = Nonedataloader_drop_last: bool = Falsedataloader_num_workers: int = 0dataloader_pin_memory: bool = Truedataloader_persistent_workers: bool = Falsedataloader_prefetch_factor: int | None = Nonedataloader_multiprocessing_context: str | None = Nonedataloader_in_order: bool = Trueremove_unused_columns: bool = Truelabel_names: list[str] | None = Nonetrain_sampling_strategy: str = 'random'length_column_name: str = 'length'ddp_find_unused_parameters: bool | None = Noneddp_bucket_cap_mb: int | None = Noneddp_broadcast_buffers: bool | None = Noneddp_static_graph: bool | None = Noneddp_backend: str | None = Noneddp_timeout: int = 1800fsdp: str | None = Nonefsdp_config: dict[str, typing.Any] | str | None = Nonedeepspeed: dict | str | None = Nonedebug: str | list[transformers.debug_utils.DebugOption] = ''skip_memory_metrics: bool = Truedo_train: bool = Falsedo_eval: bool = Falsedo_predict: bool = Falseresume_from_checkpoint: str | None = Nonelocal_rank: int = -1max_length: int | None = 1024max_completion_length: int | None = Nonebeta: float = 0.1label_smoothing: float = 0.0loss_type: str = 'sigmoid'disable_dropout: bool = Truecpo_alpha: float = 1.0simpo_gamma: float = 0.5alpha: float = 0.0generate_during_eval: bool = Falseis_encoder_decoder: bool | None = Nonemodel_init_kwargs: dict[str, typing.Any] | str | None = Nonetrust_remote_code: bool = Falsedataset_num_proc: int | None = None )
参数
- max_length (
int或None,可选,默认为1024) — 批次中序列(提示 + 补全)的最大长度。如果您想使用默认的数据整理器,则必须提供此参数。 - max_completion_length (
int, 可选) — 补全的最大长度。如果您希望使用默认的数据整理器(data collator)且您的模型是编码器-解码器(encoder-decoder)模型,则此参数为必填项。 - beta (
float, 可选, 默认为0.1) — 控制与参考模型偏差的参数。β 值越高,与参考模型的偏差越小。对于 IPO 损失(loss_type="ipo"),β 是论文中用 τ 表示的正则化参数。 - label_smoothing (
float,可选,默认为0.0) — 标签平滑系数。如果您想使用默认的数据整理器,则必须提供此参数。 - loss_type (
str, 可选, 默认为"sigmoid") — 要使用的损失类型。可能的值包括: - disable_dropout (
bool,可选,默认为True) — 是否禁用模型中的 dropout。 - cpo_alpha (
float, 可选, 默认为1.0) — CPO 训练中 BC 正则项的权重。 - simpo_gamma (
float, 可选, 默认值为0.5) — SimPO 损失的目标奖励间隔,仅在loss_type="simpo"时使用。 - alpha (
float, 可选,默认为0.0) — 控制所有损失类型下奖励函数形状的 Alpha 参数。当 alpha=0(默认值)时,使用标准的对数概率奖励。当alpha != 0时,应用来自 AlphaPO 论文 的 AlphaPO 变换:r = (1 - p^(-alpha)) / alpha。此参数适用于所有损失类型。 - generate_during_eval (
bool,可选,默认为False) — 如果为True,则在评估期间生成模型补全并将其记录到 W&B 或 Comet。 - is_encoder_decoder (
bool, 可选) — 当使用model_init参数(可调用对象)来实例化模型,而不是使用model参数时, 您需要指定该可调用对象返回的模型是否为编码器-解码器模型。 - model_init_kwargs (
dict[str, Any], 可选) — 从字符串实例化模型时传递给AutoModelForCausalLM.from_pretrained的关键字参数。revision值在加载处理类时也会使用。 - trust_remote_code (
bool,可选,默认为False) — 是否允许加载从 Hub 提供自定义 Python 代码的模型。该参数会传递给 from_pretrained。 - dataset_num_proc (
int, 可选) — 用于处理数据集的进程数。
experimental.cpo.CPOTrainer 的配置类。
此类仅包含 CPO 训练特有的参数。完整的训练参数列表,请参阅 TrainingArguments 文档。请注意,此类中的默认值可能与 TrainingArguments 中的不同。
使用 HfArgumentParser,我们可以将该类转换为 argparse 参数,从而在命令行中指定。
这些参数的默认值与 TrainingArguments 不同:
logging_steps:默认值为10,而非500。gradient_checkpointing:默认值为True,而非False。bf16:如果未设置fp16,则默认值为True,而不是False。learning_rate:默认值为1e-6,而非5e-5。