TRL documentation
SDFT
SDFT
自蒸馏微调(SDFT)由 Idan Shenfeld、Mehul Damani、Jonas Hübotter 和 Pulkit Agrawal 在论文 Self-Distillation Enables Continual Learning 中提出。
持续学习使模型能够在不损害现有能力的前提下获取新技能和知识,这仍然是基础模型面临的一项根本性挑战。虽然基于策略的强化学习可以减少遗忘,但它需要通常不可用的显式奖励函数。从专家演示中学习是主要的替代方案,而监督微调(SFT)在其中占据主导地位,其本质上是离策略的。我们提出了自蒸馏微调(SDFT),这是一种简单的方法,能够直接从演示中进行基于策略的学习。SDFT 利用上下文学习,将基于演示条件的模型作为其自身的教师,生成能够保留先前能力同时获取新技能的基于策略的训练信号。在技能学习和知识获取任务中,SDFT 始终优于 SFT,在实现更高的新任务准确率的同时,大幅减少了灾难性遗忘。在顺序学习实验中,SDFT 使单个模型能够随时间积累多项技能而不出现性能回退,从而确立了基于策略的蒸馏作为从演示中进行持续学习的可行路径。
工作原理
普通的监督微调在演示文本上进行离策略训练,这往往会覆盖模型原有的能力。SDFT 则采用在策略学习:学生模型从普通的 prompt 生成内容,教师模型——即同一个模型,但额外看到了 prompt 以及示例的 privileged_context——对这些生成的 token 重新进行评分,并将其基于演示条件的分布蒸馏回学生模型。教师和学生是同一个网络,区别仅在于它们所看到的信息,从而形成了一个 自蒸馏循环。
选择教师模型
teacher_model_kind 用于选择哪个模型副本充当教师模型。"base"(默认值)将初始权重冻结为固定参考,与论文保持一致;"live" 复用当前的学生模型作为零延迟自教师;"ema" 维护一个指数移动平均,并每隔 teacher_sync_steps 步以 teacher_update_rate 的速率重新同步。在 PEFT 下,"base" 通过在教师前向传播期间禁用适配器来恢复基础权重,而采用纯 LoRA 训练的 "ema" 则在一个专用的 "teacher" 适配器中保存移动平均,而不是使用第二个模型副本。对于非纯 LoRA 的 PEFT 模型(例如使用 modules_to_save 或 bias),不支持 "ema",因为单独的 EMA 副本无法与学生模型进行参数匹配。
默认情况下,学生模型从纯提示词生成;设置 generate_from_teacher=True 可改为从演示条件提示词采样,以牺牲 on-policy 保真度为代价换取更高质量的 rollout。蒸馏目标由 distillation_mode(默认为 "topk_logits",也可选择 "full_logits" 或 "sampled_token")、distillation_alpha 和 distillation_topk 共同决定;num_loss_tokens_to_skip 用于从损失中丢弃开头的 completion token。训练仅涉及文本;生成默认通过 transformers 运行,当 use_vllm=True 时则使用 vLLM(colocate 或 server 模式)。
用法
from datasets import Dataset
from trl.experimental.sdft import SDFTConfig, SDFTTrainer
dataset = Dataset.from_dict(
{
"prompt": [[{"role": "user", "content": "Solve 2+2."}]],
"privileged_context": ["Example answer: 4."],
}
)
training_args = SDFTConfig(
output_dir="sdft-model",
distillation_alpha=0.5,
distillation_mode="topk_logits",
distillation_topk=5,
max_completion_length=64,
)
trainer = SDFTTrainer(
model="Qwen/Qwen2.5-1.5B-Instruct",
args=training_args,
train_dataset=dataset,
)
trainer.train()若要基于教师条件提示而非学生提示进行生成,请设置 generate_from_teacher=True。
若要自定义教师提示的构建方式,请在 experimental.sdft.SDFTConfig 上设置 teacher_prompt_template。
从 vLLM 服务器提供教师模型
当 teacher_model_kind="live" 时,教师模型即为当前的学生模型,其权重已由 vLLM 服务器 持有(每步生成时都会同步)。设置 use_teacher_server=True 可在同一服务器上计算教师对数概率,而无需运行单独的本地教师前向传播,从而将教师模型完全从训练步骤中移除:
training_args = SDFTConfig(
output_dir="sdft-model",
use_vllm=True,
vllm_mode="server",
teacher_model_kind="live",
use_teacher_server=True,
distillation_mode="sampled_token",
)使用教师服务器时:
use_vllm=True和vllm_mode="server"是必需的teacher_model_kind必须为"live"(服务器持有当前的学生模型权重)distillation_mode必须为"sampled_token"(对已实现的 token 计算反向 KL)或"topk_logits"。服务器返回教师模型自身的 top-k 对数概率,因此topk_logits是在教师模型的 top-k 支撑集上进行蒸馏(与本地目标函数不同,它无法使用学生模型的支撑集);当使用"live"教师模型时,这两个支撑集几乎重合。full_logits不可用。
预期的数据集列
每个示例必须提供:
prompt:面向学生的提示privileged_context:仅包含教师特有的额外信息,例如演示、提示或特权反馈
训练器的提示词处理同时支持标准文本提示词和对话式提示词。
回调
Trainer 会触发一组用于调试、可观测性和测试的回调钩子。这些钩子旨在作为实验性自蒸馏工作流的实用集成点。
共享自蒸馏钩子:
on_self_distillation_batch_prepared:当自蒸馏批次准备就绪时触发。当重要性采样裁剪输入可用时,负载包括prompt_ids、completion_ids和old_per_token_logps。on_generation_batch_built:当创建新的缓冲生成批次时触发。负载包括generate_every和steps_per_generation。
SDFT 专用钩子:
on_generation_prompts_selected:当 SDFT 为 on-policy 生成选择提示来源时触发。负载包含所选的generation_prompts及对应的generation_prompt_text。
示例脚本
使用 examples/sdft_privileged_context/sdft_privileged_context.py 可从命令行启动 SDFT 训练。该脚本支持 Hub 上的任意因果 LM,通过 --dataset_path 指定自定义本地数据集,并通过标准的 ModelConfig 标志启用 PEFT/LoRA。
python examples/sdft_privileged_context/sdft_privileged_context.py \
--model_name_or_path Qwen/Qwen3.5-0.8B \
--dataset_name your-org/your-dataset \
--output_dir outputs/sdft-qwen3.5-0.8b \
--per_device_train_batch_size 1 \
--gradient_accumulation_steps 16 \
--learning_rate 2e-5 \
--max_prompt_length 1024 \
--max_completion_length 512 \
--generate_from_teacher \
--teacher_model_kind ema \
--teacher_sync_steps 1 \
--teacher_update_rate 0.05 \
--eval_strategy steps \
--eval_steps 50 \
--report_to wandb原始实现可在 idanshen/Self-Distillation 中找到。
SDFTConfig
类 trl.experimental.sdft.SDFTConfig
< source >( output_dir: str | None = Noneper_device_train_batch_size: int = 8num_train_epochs: float = 3.0max_steps: int = -1learning_rate: float = 5e-05lr_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 = Falselabel_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 = -1model_init_kwargs: dict[str, typing.Any] | None = Nonetrust_remote_code: bool = Falsedisable_dropout: bool = Truemax_prompt_length: int | None = 512num_generations: int = 8num_generations_eval: int | None = Nonemax_completion_length: int | None = 256ds3_gather_for_generation: bool = Trueshuffle_dataset: bool = Truegeneration_batch_size: int | None = Nonesteps_per_generation: int | None = Nonetemperature: float = 1.0top_p: float = 1.0top_k: int = 0min_p: float | None = Nonegeneration_kwargs: dict[str, typing.Any] | None = Nonechat_template_kwargs: dict[str, typing.Any] | None = Nonerepetition_penalty: float = 1.0cache_implementation: str | None = Noneuse_vllm: bool = Falseuse_teacher_server: bool = Falsevllm_mode: str = 'colocate'vllm_model_impl: str = 'vllm'vllm_enable_sleep_mode: bool = Falsevllm_server_base_url: str | None = Nonevllm_server_host: str = '0.0.0.0'vllm_server_port: int = 8000vllm_group_port: int = 51216vllm_server_timeout: float = 240.0vllm_tensor_parallel_size: int = 1vllm_gpu_memory_utilization: float = 0.3vllm_max_model_length: int | None = Nonenum_iterations: int = 1teacher_model_kind: str = 'base'teacher_update_rate: float = 0.05teacher_sync_steps: int = 1distillation_alpha: float = 0.5distillation_mode: typing.Literal['sampled_token', 'full_logits', 'topk_logits'] = 'topk_logits'distillation_topk: int | None = 100distillation_is_clip: float | None = 2.0distillation_add_tail: bool = Falsegenerate_from_teacher: bool = Falseteacher_prompt_template: str = '{prompt}\n\n{privileged_context}'num_loss_tokens_to_skip: int = 0 )
控制 SDFT 损失的参数
- distillation_alpha (
float,可选,默认为0.5) — SDFT top-k logit 蒸馏的散度插值系数。 - distillation_mode (
Literal["sampled_token", "full_logits", "topk_logits"],可选,默认为"topk_logits") — 蒸馏目标模式。SDFT 默认使用 top-k logit 蒸馏。 - distillation_topk (
int,可选,默认为100) — 默认 SDFT top-k logit 目标函数所使用的 top token 数量。 - distillation_is_clip (
float,可选,默认为2.0) — 自蒸馏中重要性采样的截断系数。None表示禁用截断。 - distillation_add_tail (
bool,可选,默认为False) — 是否为非 top-k 概率质量添加尾部桶。 - num_loss_tokens_to_skip (
int,可选,默认为0) — 从蒸馏损失中排除的初始补全 token 数量。
控制 teacher 的参数
- teacher_model_kind (
str, 可选, 默认为"base") — SDFT 的语义教师选择。base使用初始学生模型,live使用当前学生模型,ema使用指数移动平均教师模型。 - teacher_update_rate (
float,可选,默认为0.05) — 当teacher_model_kind="ema"时使用的 EMA 更新率。值为1.0时,更新将变为硬覆盖,定期将教师模型重新同步到当前学生模型的权重。 - teacher_sync_steps (
int,可选,默认为1) — 教师模型更新之间的优化器步数。
控制教师条件生成的参数
- generate_from_teacher (
bool,可选,默认为False) — 是否应在 on-policy 生成中使用教师条件提示,而不是学生提示。 - teacher_prompt_template (
str, 可选, 默认为"{prompt}\n\n{privileged_context}") — 用于将学生提示词和特权上下文组合成教师提示词的模板。
控制模型的参数
- model_init_kwargs (
dict[str, Any], 可选) —transformers.AutoModelForCausalLM.from_pretrained的关键字参数,当SDFTTrainer的model参数以字符串形式提供时使用。加载处理类时也会使用revision的值。 - trust_remote_code (
bool, optional, defaults toFalse) — Whether to allow loading models and tokenizers that ship custom Python code from the Hub. Forwarded to from_pretrained and from_pretrained, for both the student and teacher. - disable_dropout (
bool,可选,默认为True) — 是否在学生模型和教师模型中禁用 dropout。
控制数据预处理的参数
- remove_unused_columns (
bool,可选,默认为False) — 是否仅保留训练器在数据集中所需的列。如果您提供了训练器所需的额外列(例如privileged_context),请保持此参数为False。 - max_prompt_length (
int,可选,默认为512) — 最大提示长度。较长的提示将从左侧截断。 - shuffle_dataset (
bool,可选,默认为True) — 是否打乱训练数据集。
控制生成的参数
- num_generations (
int,可选,默认为8) — 要采样的生成数量。有效批大小(num_processes per_device_batch_size gradient_accumulation_steps)必须能被该值整除。 - num_generations_eval (
int, 可选) — 评估期间采样的生成数量。这允许在评估期间使用更少的生成数量以节省计算资源。如果为None,则使用num_generations的值。 - max_completion_length (
int,可选,默认为256) — 生成补全的最大长度。 - temperature (
float,可选,默认为1.0) — 用于采样的温度。温度越高,生成的补全内容越随机。 - top_p (
float,可选,默认为1.0) — 用于控制所考虑的前几个 token 的累积概率的浮点数。取值必须在 (0, 1] 范围内。设置为 1.0 表示考虑所有 token。 - top_k (
int,可选,默认为0) — 用于 top-k 过滤时保留的最高概率词汇表 token 数量。如果为0,则禁用 top-k 过滤,并考虑所有 token。 - min_p (
float, 可选) — 最小 token 概率,该值会按最可能 token 的概率进行缩放。它必须是 0.0 到 1.0 之间的值。典型值在 0.01 到 0.2 范围内。 - repetition_penalty (
float,可选,默认为1.0) — 一个浮点数,根据新 token 是否出现在提示词和迄今为止生成的文本中来对其进行惩罚。 值 > 1.0 会鼓励模型使用新的 token,而值 < 1.0 会鼓励模型重复使用 token。 - cache_implementation (
str, 可选) — 当 use_vllm 设置为 False 时,用于加速生成的缓存方法实现。 - generation_kwargs (
dict[str, Any], 可选) — 在采样补全时传递给GenerationConfig(如果使用 transformers)或SamplingParams(如果使用 vLLM)的额外关键字参数。可用于进一步自定义生成行为,例如设置suppress_tokens、num_beams等。如果其中包含与其他生成参数(如min_p、top_p等)冲突的键,则会覆盖它们。 - chat_template_kwargs (
dict[str, Any], 可选) — 在生成补全时传递给apply_chat_template函数的其他关键字参数。 - ds3_gather_for_generation (
bool,可选,默认为True) — 此设置适用于 DeepSpeed ZeRO-3。如果启用,策略模型权重将在生成时进行聚合,从而提升生成速度。但是,禁用此选项允许训练超出单个 GPU 显存容量的模型,代价是生成速度变慢。禁用此选项与 vLLM 生成不兼容。
控制由 vLLM 驱动的生成加速的参数
- use_vllm (
bool,可选,默认为False) — 是否使用 vLLM 来生成补全。如果设置为True,训练器将使用 vLLM 进行生成,而不是默认的 model.generate()。需要安装vllm。 - use_teacher_server (
bool,可选,默认为False) — 从正在运行的 vLLM 生成服务器计算教师模型的 logprobs,而不是通过本地教师模型的前向传播。仅支持teacher_model_kind='live'且use_vllm=True以及vllm_mode='server',并且distillation_mode为 {‘sampled_token’, ‘topk_logits’}(服务器返回的是教师模型的 top-k logprobs,而非完整词表;topk_logits在教师模型自身的 top-k 支撑集上进行蒸馏)。 - vllm_mode (
str, 可选,默认为"colocate") — 当use_vllm设置为True时,用于 vLLM 集成的模式。必须是'server'或'colocate'之一。'server':训练器将生成请求发送到独立的 vLLM 服务器。请确保 vLLM 服务器正在运行(使用vllm serve启动)。'colocate':vLLM 将在同一进程中运行并共享 训练 GPU。这避免了单独服务器的需求,但可能会与训练产生资源争用。 - vllm_model_impl (
str,可选,默认为"vllm") — 用于 vLLM 的模型实现。必须是transformers或vllm之一。transformers:使用transformers后端作为模型实现。vllm:使用vllm库作为模型实现。 - vllm_enable_sleep_mode (
bool,可选,默认为False) — 启用 vLLM 睡眠模式,以便在优化器步骤期间卸载权重/缓存。这可以保持较低的 GPU 内存占用,但唤醒引擎会增加主机与设备之间的传输延迟。 - vllm_server_base_url (
str, 可选) — vLLM 服务器的基础 URL(例如 ‘http://localhost:8000’)。如果提供此参数,vllm_server_host和vllm_server_port将被忽略。 - vllm_server_host (
str,可选,默认为"0.0.0.0") — 要连接的 vLLM 服务器的主机地址。如果提供了 vllm_server_base_url,则忽略此参数。 - vllm_server_port (
int, 可选, 默认为8000) — 要连接的 vLLM 服务器端口。如果提供了 vllm_server_base_url,则忽略此参数。 - vllm_group_port (
int, 可选,默认为51216) — 权重更新组的端口号。用于与 vLLM 服务器通信。除非端口被占用,否则无需更改。 - vllm_server_timeout (
float, 可选, 默认值为240.0) — 等待 vLLM 服务器启动的总超时时间(秒)。如果超时后服务器仍未启动,则会引发ConnectionError。 - vllm_tensor_parallel_size (
int,可选,默认为1) — 控制 vLLM 的张量并行大小。此设置仅在vllm_mode设置为'colocate'时生效。如果您使用的是vllm_mode='server',则必须在通过--vllm_tensor_parallel_size标志启动 vLLM 服务器时单独传递此参数。 - vllm_gpu_memory_utilization (
float,可选,默认值为0.3) — 控制 vLLM 的 GPU 内存利用率。此设置仅在vllm_mode设置为'colocate'时生效。如果您使用的是vllm_mode='server',则必须在通过--vllm_gpu_memory_utilization标志启动 vLLM 服务器时单独传递此参数。 - vllm_max_model_length (
int, 可选) — vLLM 的上下文窗口。请将其设置为至少等于数据集中的最大提示长度加上max_completion_length;如果省略,则从模型配置中推断。
控制训练的参数
SDFTTrainer
类 trl.experimental.sdft.SDFTTrainer
< source >( model: typing.Union[str, transformers.modeling_utils.PreTrainedModel, torch.nn.Module]args: trl.experimental.sdft.sdft_config.SDFTConfig | None = Nonetrain_dataset: datasets.arrow_dataset.Dataset | datasets.iterable_dataset.IterableDataset | None = Noneeval_dataset: datasets.arrow_dataset.Dataset | datasets.iterable_dataset.IterableDataset | dict[str, datasets.arrow_dataset.Dataset | datasets.iterable_dataset.IterableDataset] | None = Noneprocessing_class: transformers.tokenization_utils_base.PreTrainedTokenizerBase | transformers.processing_utils.ProcessorMixin | None = Nonecallbacks: list[transformers.trainer_callback.TrainerCallback] | None = Noneoptimizers: tuple[typing.Optional[torch.optim.Optimizer], typing.Optional[torch.optim.lr_scheduler.LambdaLR]] = (None, None)peft_config: PeftConfig | None = None )
用于 SDFT 风格基于策略自蒸馏的 Trainer,支持显式教师提示。
训练
< 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。