# 广义知识蒸馏训练器

[![model badge](https://img.shields.io/badge/All_models-GKD-blue)](https://huggingface.co/models?other=gkd,trl)

## 概述

广义知识蒸馏（GKD）由 Rishabh Agarwal、Nino Vieillard、Yongchao Zhou、Piotr Stanczyk、Sabela Ramos、Matthieu Geist 和 Olivier Bachem 在 [On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes](https://huggingface.co/papers/2306.13649) 中提出。

该论文的摘要如下：

> 知识蒸馏（KD）被广泛用于通过训练较小的学生模型来压缩教师模型，从而降低其推理成本和内存占用。然而，当前针对自回归序列模型的知识蒸馏方法存在训练期间看到的输出序列与学生模型在推理期间生成的序列之间的分布不匹配问题。为了解决这一问题，我们提出了广义知识蒸馏（GKD）。GKD 不再仅仅依赖固定的输出序列集合，而是利用教师模型对这些序列的反馈，在学生模型自生成的输出序列上训练学生模型。与有监督的知识蒸馏方法不同，GKD 还提供了在学生模型和教师模型之间使用替代损失函数的灵活性，当学生模型缺乏模仿教师模型分布的表达力时，这一点尤为有用。此外，GKD 促进了蒸馏与强化学习微调（RLHF）的无缝集成。我们展示了 GKD 在摘要、翻译和算术推理任务上蒸馏自回归语言模型的有效性，以及用于指令微调的任务无关蒸馏。

GKD 的关键方面包括：

1. 它通过在自生成的输出序列上训练学生模型，解决了自回归序列模型中训练与推理之间的分布不匹配问题。
2. GKD 允许通过广义 Jensen-Shannon 散度（JSD）灵活选择学生模型与教师模型之间的不同散度度量，当学生模型缺乏完全模仿教师模型的能力时，这一点尤为有用。

此训练后方法由 [Kashif Rasul](https://huggingface.co/kashif) 和 [Lewis Tunstall](https://huggingface.co/lewtun) 贡献。

## 使用技巧

[experimental.gkd.GKDTrainer](/docs/trl/main/zh/gkd_trainer#trl.experimental.gkd.GKDTrainer) 是 [SFTTrainer](/docs/trl/main/zh/sft_trainer#trl.SFTTrainer) 类的封装，它接受一个教师模型参数。需要通过 [experimental.gkd.GKDConfig](/docs/trl/main/zh/gkd_trainer#trl.experimental.gkd.GKDConfig) 设置三个参数，分别是：

* `lmbda`： 控制学生数据比例，即 on-policy 学生生成输出的占比。当 `lmbda=0.0` 时，损失退化为监督式 JSD，学生使用教师的 token 级概率进行训练。当 `lmbda=1.0` 时，损失退化为 on-policy JSD，学生生成输出序列，并由教师针对这些序列提供 token 级反馈。对于 [0, 1] 之间的值，每个 batch 会根据 `lmbda` 的值在两种模式之间随机选择。
* `seq_kd`：控制是否执行序列级 KD（可视为在教师生成的输出上进行的监督式 FT）。当 `seq_kd=True` 且 `lmbda=0.0` 时，损失函数简化为监督式 JSD，其中教师生成输出序列，学生从教师处获得针对这些序列的 token 级反馈。
* `beta`：控制广义 Jensen-Shannon 散度中的插值。当 `beta=0.0` 时，损失近似于前向 KL 散度；当 `beta=1.0` 时，损失近似于反向 KL 散度。对于 [0, 1] 之间的值，它在这两者之间进行插值。

作者发现，on-policy 数据（较高的 `lmbda`）表现更好，且最优的 `beta` 值会因任务和评估方法的不同而有所变化。

> [!WARNING]
> 在训练 [Gemma 模型](https://huggingface.co/models?other=gemma2) 时，请确保 `attn_implementation="kernels-community/flash-attn2"`。否则，由于该架构采用了 [soft capping 技术](https://huggingface.co/blog/gemma2#soft-capping-and-attention-implementations)，您将在 logits 中遇到 NaN。

基本 API 如下：

```python
from datasets import Dataset
from transformers import AutoModelForCausalLM, AutoTokenizer
from trl.experimental.gkd import GKDConfig, GKDTrainer

NUM_DUMMY_SAMPLES = 100

tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-0.5B-Instruct")
# The model to optimise
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-0.5B-Instruct")
# The teacher model to calculate the KL divergence against
teacher_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-1.5B-Instruct")

train_dataset = Dataset.from_dict(
    {
        "messages": [
            [
                {"role": "user", "content": "Hi, how are you?"},
                {"role": "assistant", "content": "I'm great thanks"},
            ]
        ]
        * NUM_DUMMY_SAMPLES
    }
)
eval_dataset = Dataset.from_dict(
    {
        "messages": [
            [
                {"role": "user", "content": "What colour is the sky?"},
                {"role": "assistant", "content": "The sky is blue"},
            ]
        ]
        * NUM_DUMMY_SAMPLES
    }
)

training_args = GKDConfig(output_dir="gkd-model", per_device_train_batch_size=1)
trainer = GKDTrainer(
    model=model,
    teacher_model=teacher_model,
    args=training_args,
    processing_class=tokenizer,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
)
trainer.train()
```

### 预期的数据集类型

数据集应格式化为一个“messages”列表，其中每条消息是一个包含以下键的字典列表：

* `role`：可以是 `system`、`assistant` 或 `user`
* `content`：消息内容

## GKDTrainer[[trl.experimental.gkd.GKDTrainer]]

#### trl.experimental.gkd.GKDTrainer[[trl.experimental.gkd.GKDTrainer]]

```python
trl.experimental.gkd.GKDTrainer(model: typing.Union[transformers.modeling_utils.PreTrainedModel, torch.nn.Module, str, NoneType] = None, teacher_model: typing.Union[transformers.modeling_utils.PreTrainedModel, torch.nn.Module, str] = None, args: trl.experimental.gkd.gkd_config.GKDConfig | None = None, data_collator: collections.abc.Callable[[list[typing.Any]], dict[str, typing.Any]] | None = None, train_dataset: datasets.arrow_dataset.Dataset | None = None, eval_dataset: datasets.arrow_dataset.Dataset | dict[str, datasets.arrow_dataset.Dataset] | None = None, processing_class: transformers.tokenization_utils_base.PreTrainedTokenizerBase | transformers.image_processing_utils.BaseImageProcessor | transformers.feature_extraction_utils.FeatureExtractionMixin | transformers.processing_utils.ProcessorMixin | None = None, compute_metrics: collections.abc.Callable[[transformers.trainer_utils.EvalPrediction], dict] | None = None, callbacks: list[transformers.trainer_callback.TrainerCallback] | None = None, optimizers: 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 = None, peft_config: PeftConfig | None = None, formatting_func: collections.abc.Callable | None = None)
```

[源代码](https://github.com/huggingface/trl/blob/main/trl/experimental/gkd/gkd_trainer.py#L54)

**参数：**

model ([PreTrainedModel](https://huggingface.co/docs/transformers/main/en/main_classes/model#transformers.PreTrainedModel) 或 `torch.nn.Module` 或 `str`，*可选*)：要训练的模型，或从预训练模型实例化模型的字符串标识符。

teacher_model ([PreTrainedModel](https://huggingface.co/docs/transformers/main/en/main_classes/model#transformers.PreTrainedModel) 或 `torch.nn.Module` 或 `str`，*optional*) : 用于知识蒸馏的教师模型，或从预训练模型实例化模型的字符串标识符。

args （[experimental.gkd.GKDConfig](/docs/trl/main/zh/gkd_trainer#trl.experimental.gkd.GKDConfig)，*可选*）：训练参数。

data_collator (`DataCollator`, *可选*) : 用于将数据集中的样本进行批处理的 Data collator。默认使用 `processing_class` 的 `experimental.utils.DataCollatorForChatML`。

train_dataset ([Dataset](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset)，*可选*)：用于训练的 Dataset。

eval_dataset ([Dataset](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset) 或 [Dataset](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset) 的 `dict`，*可选*)：用于评估的数据集。

processing_class ([PreTrainedTokenizerBase](https://huggingface.co/docs/transformers/main/en/internal/tokenization_utils#transformers.PreTrainedTokenizerBase)、[BaseImageProcessor](https://huggingface.co/docs/transformers/main/en/main_classes/image_processor#transformers.BaseImageProcessor)、[FeatureExtractionMixin](https://huggingface.co/docs/transformers/main/en/main_classes/feature_extractor#transformers.FeatureExtractionMixin) 或 [ProcessorMixin](https://huggingface.co/docs/transformers/main/en/main_classes/processors#transformers.ProcessorMixin)，*可选*)：用于处理数据的类。

compute_metrics (`Callable`，*可选*)：用于在评估时计算指标的函数。必须接收一个 [EvalPrediction](https://huggingface.co/docs/transformers/main/en/internal/trainer_utils#transformers.EvalPrediction) 并返回一个从字符串到浮点数的字典。

callbacks （`list` of [TrainerCallback](https://huggingface.co/docs/transformers/main/en/main_classes/callback#transformers.TrainerCallback)，*可选*）：训练期间使用的回调。

optimizers (`tuple`，包含 `torch.optim.Optimizer` 和 `torch.optim.lr_scheduler.LambdaLR`，*可选*，默认为 `(None, None)`)：包含训练所使用的优化器和学习率调度器的元组。

preprocess_logits_for_metrics (`Callable`，*可选*)：用于在计算指标之前对 logits 进行预处理的函数。必须接收 `logits` 和 `labels`，并返回用于指标计算的 logits。

peft_config ([PeftConfig](https://huggingface.co/docs/peft/main/en/package_reference/config#peft.PeftConfig)，*可选*)：用于训练时启用 PEFT 的 PEFT 配置。如果为 `None`，则不使用 PEFT。如果提供，`model` 将被指定的 PEFT 适配器包装。

formatting_func (`Callable`，*可选*)：用于格式化数据集的函数。必须接收一个示例并返回一个示例。

用于语言模型广义知识蒸馏（GKD）的 Trainer。

有关 GKD 的详细信息，请参阅论文：[语言模型的在线策略蒸馏：从自生成错误中学习](https://huggingface.co/papers/2306.13649)。

#### train[[trl.experimental.gkd.GKDTrainer.train]]

```python
train(resume_from_checkpoint: str | bool | None = None, trial: optuna.Trial | dict[str, Any] | None = None, ignore_keys_for_eval: list[str] | None = None)
```

[源代码](https://github.com/huggingface/trl/blob/main/transformers/trainer.py#L1447)

**参数：**

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。

主要训练入口。

#### save_model[[trl.experimental.gkd.GKDTrainer.save_model]]

```python
save_model(output_dir: str | None = None, _internal_call: bool = False)
```

[源代码](https://github.com/huggingface/trl/blob/main/transformers/trainer.py#L4036)

将保存模型，以便您可以使用 `from_pretrained()` 重新加载它。

仅从主进程保存。

#### push_to_hub[[trl.experimental.gkd.GKDTrainer.push_to_hub]]

```python
push_to_hub(commit_message: str | None = 'End of training', blocking: bool = True, token: str | None = None, revision: str | None = None, **kwargs)
```

[源代码](https://github.com/huggingface/trl/blob/main/transformers/trainer.py#L4307)

**参数：**

commit_message (`str`，*可选*，默认为 `"End of training"`)：推送时提交的说明信息。

blocking (`bool`, *可选*, 默认为 `True`) : 函数是否应在 `git push` 完成后才返回。

token (`str`，*可选*，默认为 `None`)：具有写入权限的令牌，用于覆盖 Trainer 的原始参数。

revision (`str`, *可选*) : 要从中提交内容的 git 修订版本。默认为 "main" 分支的头部。

kwargs (`dict[str, Any]`，*可选*)：传递给 `~Trainer.create_model_card` 的额外关键字参数。

**返回：**

如果 `blocking=False`，则返回推送模型所在存储库的 URL；如果 `blocking=True`，则返回一个跟踪提交进度的 `Future` 对象。

将 `self.model` 和 `self.processing_class` 上传到 🤗 模型库中的仓库 `self.args.hub_model_id`。

## GKDConfig[[trl.experimental.gkd.GKDConfig]]

#### trl.experimental.gkd.GKDConfig[[trl.experimental.gkd.GKDConfig]]

```python
trl.experimental.gkd.GKDConfig(output_dir: str | None = None, per_device_train_batch_size: int = 8, num_train_epochs: float = 3.0, max_steps: int = -1, learning_rate: float = 2e-05, lr_scheduler_type: transformers.trainer_utils.SchedulerType | str = 'linear', lr_scheduler_kwargs: dict | str | None = None, warmup_steps: float = 0, optim: transformers.training_args.OptimizerNames | str = 'adamw_torch_fused', optim_args: str | None = None, weight_decay: float = 0.0, adam_beta1: float = 0.9, adam_beta2: float = 0.999, adam_epsilon: float = 1e-08, optim_target_modules: None | str | list[str] = None, gradient_accumulation_steps: int = 1, average_tokens_across_devices: bool = True, max_grad_norm: float = 1.0, label_smoothing_factor: float = 0.0, bf16: bool | None = None, fp16: bool = False, bf16_full_eval: bool = False, fp16_full_eval: bool = False, tf32: bool | None = None, gradient_checkpointing: bool = True, gradient_checkpointing_kwargs: dict[str, typing.Any] | str | None = None, torch_compile: bool = False, torch_compile_backend: str | None = None, torch_compile_mode: str | None = None, use_liger_kernel: bool = False, liger_kernel_config: dict[str, bool] | None = None, use_cache: bool = False, neftune_noise_alpha: float | None = None, torch_empty_cache_steps: int | None = None, auto_find_batch_size: bool = False, logging_strategy: transformers.trainer_utils.IntervalStrategy | str = 'steps', logging_steps: float = 10, logging_first_step: bool = False, log_on_each_node: bool = True, logging_nan_inf_filter: bool = True, include_num_input_tokens_seen: str | bool = 'no', log_level: str = 'passive', log_level_replica: str = 'warning', disable_tqdm: bool | None = None, report_to: None | str | list[str] = 'none', run_name: str | None = None, project: str = 'huggingface', trackio_space_id: str | None = None, trackio_bucket_id: str | None = None, trackio_static_space_id: typing.Union[str, NoneType, typing.Literal[False]] = None, eval_strategy: transformers.trainer_utils.IntervalStrategy | str = 'no', eval_steps: float | None = None, eval_delay: float = 0, per_device_eval_batch_size: int = 8, prediction_loss_only: bool = False, eval_on_start: bool = False, eval_do_concat_batches: bool = True, eval_use_gather_object: bool = False, eval_accumulation_steps: int | None = None, include_for_metrics: list[str] = <factory>, batch_eval_metrics: bool = False, save_only_model: bool = False, save_strategy: transformers.trainer_utils.SaveStrategy | str = 'steps', save_steps: float = 500, save_on_each_node: bool = False, save_total_limit: int | None = None, enable_jit_checkpoint: bool = False, push_to_hub: bool = False, hub_token: str | None = None, hub_private_repo: bool | None = None, hub_model_id: str | None = None, hub_strategy: transformers.trainer_utils.HubStrategy | str = 'every_save', hub_always_push: bool = False, hub_revision: str | None = None, load_best_model_at_end: bool = False, metric_for_best_model: str | None = None, greater_is_better: bool | None = None, ignore_data_skip: bool = False, restore_callback_states_from_checkpoint: bool = False, full_determinism: bool = False, seed: int = 42, data_seed: int | None = None, use_cpu: bool = False, accelerator_config: dict | str | None = None, parallelism_config: accelerate.parallelism_config.ParallelismConfig | None = None, dataloader_drop_last: bool = False, dataloader_num_workers: int = 0, dataloader_pin_memory: bool = True, dataloader_persistent_workers: bool = False, dataloader_prefetch_factor: int | None = None, dataloader_multiprocessing_context: str | None = None, dataloader_in_order: bool = True, remove_unused_columns: bool = True, label_names: list[str] | None = None, train_sampling_strategy: str = 'random', length_column_name: str = 'length', ddp_find_unused_parameters: bool | None = None, ddp_bucket_cap_mb: int | None = None, ddp_broadcast_buffers: bool | None = None, ddp_static_graph: bool | None = None, ddp_backend: str | None = None, ddp_timeout: int = 1800, fsdp: str | None = None, fsdp_config: dict[str, typing.Any] | str | None = None, deepspeed: dict | str | None = None, debug: str | list[transformers.debug_utils.DebugOption] = '', skip_memory_metrics: bool = True, do_train: bool = False, do_eval: bool = False, do_predict: bool = False, resume_from_checkpoint: str | None = None, local_rank: int = -1, model_init_kwargs: dict[str, typing.Any] | str | None = None, trust_remote_code: bool = False, router_aux_loss_coef: float | None = None, chat_template_path: str | None = None, dataset_text_field: str = 'text', dataset_kwargs: dict[str, typing.Any] | str | None = None, dataset_num_proc: int | None = None, eos_token: str | None = None, max_length: int | None = 1024, truncation_mode: str = 'keep_start', shuffle_dataset: bool = False, packing: bool = False, packing_strategy: str = 'bfd', padding_free: bool = False, pad_to_multiple_of: int | str | None = 'auto', eval_packing: bool | None = None, completion_only_loss: bool | None = None, assistant_only_loss: bool = False, loss_type: str = 'nll', activation_offloading: bool = False, pad_token: str | None = None, temperature: float = 0.9, lmbda: float = 0.5, beta: float = 0.5, max_new_tokens: int = 128, teacher_model_name_or_path: str | None = None, teacher_model_init_kwargs: dict[str, typing.Any] | str | None = None, disable_dropout: bool = True, seq_kd: bool = False)
```

[源代码](https://github.com/huggingface/trl/blob/main/trl/experimental/gkd/gkd_config.py#L22)

**参数：**

temperature (`float`, *可选*，默认为 `0.9`)：采样温度。温度越高，生成的补全内容越随机。

lmbda (`float`, *可选*，默认为 `0.5`)：Lambda 参数，用于控制学生数据比例（即 on-policy 学生生成输出的占比）。

beta (`float`, *可选*，默认为 `0.5`) : 广义 Jensen-Shannon 散度损失的插值系数，取值范围为 `0.0` 到 `1.0`。当 beta 为 `0.0` 时，损失为 KL 散度。当 beta 为 `1.0` 时，损失为逆 KL 散度。

max_new_tokens (`int`，*可选*，默认为 `128`)：每次补全生成的最大 token 数。

teacher_model_name_or_path (`str`, *可选*) : 教师模型的名称或路径。如果为 `None`，则教师模型将与正在训练的模型相同。

teacher_model_init_kwargs (`dict[str, Any]`, *可选*) : 从字符串实例化教师模型时，传递给 `AutoModelForCausalLM.from_pretrained` 的关键字参数。

disable_dropout (`bool`，*可选*，默认为 `True`)：是否禁用模型中的 dropout。

seq_kd (`bool`, *可选*, 默认为 `False`) : Seq_kd 参数，用于控制是否执行序列级 KD（可视为在教师模型生成的输出上进行监督式 FT）。

[experimental.gkd.GKDTrainer](/docs/trl/main/zh/gkd_trainer#trl.experimental.gkd.GKDTrainer) 的配置类。

此类仅包含 GKD 训练特有的参数。有关完整的训练参数列表，请参阅 [TrainingArguments](https://huggingface.co/docs/transformers/main/en/main_classes/trainer#transformers.TrainingArguments) 和 [SFTConfig](/docs/trl/main/zh/sft_trainer#trl.SFTConfig) 的文档。

