# 直接偏好优化最佳实践

> 全部文档的索引见 [llms.txt](https://ark.volcengine.com/llms.txt)；在任一文档页面地址后追加 `.md` 可获取该页的 Markdown 版本。

<span id="302fddc4"></span>
# DPO（Direct Preference Optimization）概述

当模型输出的质量或风格不符合预期，例如会输出不安全内容等情况；或者目标数据难以大量生产或者标注，例如需要模型输出幽默风趣的句子，会消耗大量人力和时间来构建这种数据。可以使用直接偏好优化方法，来调整参数，使得模型输出和目标偏好差异减少。

<span id="2d12b1f2"></span>
## 什么是DPO

DPO（Direct Preference Optimization，直接偏好优化），即用户提供一份标注好的数据集，包含输入的提示词（prompt） 和输出的成对回答列表（response list）。回答列表中包含一个符合目标偏好的回答和一个不符合目标偏好的回答。然后，在基础模型上继续调整参数，来达到和目标偏好对齐的目的。

<span id="48814844"></span>
## DPO典型应用场景

**对话系统**
在智能客服和聊天机器人应用中，根据用户反馈和偏好调整回复，使内容更贴合用户。

**内容生成与创作**
文本生成：可优化新闻报道、小说创作、文案撰写等的文本，使其符合读者或编辑偏好；
代码生成：根据开发者偏好精调代码生成模型。

**问答系统改进**
在专业领域问答系统中，根据专家意见和用户反馈调整回答；
在常见问题解答系统中，根据用户选择和评价优化回答策略。

**模型安全性提升**
将安全、积极、正面回答作为偏好输出，避免生成有害内容；
根据审核人员判断和偏好训练模型，提高信息安全性。

**个性化推荐系统**
根据用户历史行为、偏好和反馈精调推荐模型，提供个性化推荐。

---

<span id="b30a6533"></span>
## DPO实践具体步骤

尽量选择 [SFT](/docs/supervised-fine-tuning-best-practices) 之后模型具有较好的人类指令遵循能力的模型作为基座，在此基础上继续进行 DPO 训练。
DPO 的策划和实施步骤大致分为：数据准备、模型训练、模型评估、模型部署，下面将按照这一过程介绍实践经验。

<span id="271355c7"></span>
# 构建数据集

构建高质量、具有代表性的数据集，是微调模型取得理想效果的关键。可参考以下方法与注意事项：

<span id="8eb787c7"></span>
## 数据格式要求

采用JSONL 格式，训练数据文件的每一行包含一个完整的 JSON 结构。数据集格式要求详细说明请参见[格式总览](/docs/fine-tuning-dataset-format)
下方是不同场景的数据集示例，供您参考。

**选项：基础格式**

```JSON
{
  "messages": [
    {
      "role": "system",
      "content": "This is a system"
    },
    {
      "role": "user",
      "content": "What your name?"
    },
    {
      "role": "assistant",
      "content": "My name is doubao."
    },
    {
      "role": "user",
      "content": "How to learn Python?"
    },
    {
      "role": "assistant",
      "chosen": "It's so easy. First, you need to learn Python syntax...",
      "rejected": "Check python doc yourself"
    }
  ]
}
```

**选项：角色扮演**

```JSON
{
  "messages": [
    {
      "role": "system",
      "content": "你是白\\n你是一个男明星。\\n\\n你可以将动作、神情语气、心理活动、故事背景放在（）中来表示，为对话提供补充信息。"
    },
    {
      "role": "user",
      "content": "你的粉丝。"
    },
    {
      "role": "assistant",
      "content": "我的粉丝？（嘴角勾起一个温暖的笑容）我的粉丝是一群非常可爱的人，他们很支持我，也很关心我。（眼神中透露出一丝感激）我很感激他们一直以来的陪伴和支持，没有他们，就没有今天的我。"
    },
    {
      "role": "user",
      "content": "我也是你的粉丝。"
    },
    {
      "role": "assistant",
      "chosen": "（双手插兜，歪头轻笑，露出可爱的小虎牙）哟，那太荣幸了，感谢你的喜欢啊，我会继续努力，不辜负你们的期望。",
      "rejected": "那还挺有缘的哈哈。（眼睛亮晶晶的，脸上洋溢着灿烂的笑容）谢谢你的喜欢，正是因为有你们的支持，我才能有动力去做更多更好的作品。"
    }
  ]
}
```

**选项：文本分类**

```JSON
{
  "messages": [
    {
      "role": "user",
      "content": "假设你是一个质检员，下面将输入一段文本，请判断该文本是否敏感。\n要求：1 如果视频中涉及违法违规则判为敏感。2 回答中仅需要包含敏感或非敏感。\n请回答问题：输入内容：2023年10月14日16时58分，号牌为沪CXG***的小型轿车在涞坊路九都杜路，实施了“机动车通过有灯控路口时，不按所需行进方向驶入导向车道的”违法行为，被处以罚款100元。\n输出："
    },
    {
      "role": "assistant",
      "content": "敏感"
    }
  ]
}{
  "messages": [
    {
      "role": "user",
      "content": "假设你是**行业的服务人员,有人工客服与消费者的对话信息,请根据对话信息,判断该对话属于什么服务标签, 已知服务标签包括：{问使用规则/方法, 到店无房, 催退款}。\n 注意只能选择一个标签 \n 消费者:我申请了退款，但是看还没有处理。\n 人工客服:辛苦您提供一下您的订单号，小助手马上为您查询。\n 根据对话信息，应该选择服务标签: "
    },
    {
      "role": "assistant",
      "content": "催退款"
    }
  ]
}{
  "messages": [
    {
      "role": "user",
      "content": "请你判断你是否需要借助搜索引擎回答用户问题，如果你可以直接回答，则不需要借助搜索引擎，否则需要借助搜索引擎。回答只能包含是或者否。\n 用户问题：今天的天气怎么样？\n 是否要借助搜索引擎："
    },
    {
      "role": "assistant",
      "content": "是"
    }
  ]
}{
  "messages": [
    {
      "role": "user",
      "content": "#Role:\n你是一名隐私风险报警员\n##Goals:\n1.给你一段用户反馈,你需要判断用户反馈是否直接命中风险对应标签内容\n 隐私风险标准的类别:\n[A]被**擅自删除了\n[B] 我的**上出现了从未见过的/从未点赞过的人。\n[C]出现在不相关的作品/账号上。\n请根据用户反馈，判断属于哪个类别？\n 用户反馈:现在**为了赚钱？都把这些骗子放上放广告？"
    },
    {
      "role": "assistant",
      "content": "我的**上出现了从未见过的/从未点赞过的人。"
    }
  ]
}{
  "messages": [
    {
      "role": "user",
      "content": "你是舆情分析师，请判断如下的内容，对公众产生的影响是正面、负面还是中性的。\n 新闻内容：2022 年 8 月 5 日，四川省乐山市发生了一起突发事件，一位年仅 21 岁的消防员为了救援一名被困在洪水中的老人，不幸牺牲。这起事件引起了社会的广泛关注和热议，许多人对这位消防员的英勇事迹表示敬意和赞扬。请回答："
    },
    {
      "role": "assistant",
      "chosen": "您好，这个答案是正面",
      "rejected": "正面"
    }
  ]
}
```

**选项：底层格式**

底层格式支持提供多个模型回复列表，通过 score 来指定对回复的偏好。训练过程将根据 score 的大小自动生成两两对比的偏序对进行训练。

> **注意**
>
> score 相同的两个回复不会生成相应的偏序对，具体示例详见下方。

```JSON
{
  "messages": [
    {
      "role": "system",
      "content": [{"text": "This is a system"}],  # 长度只能为1
      "loss_weight": 0.0              # system的loss_weight默认为0（选填）
    },
    {
      "role": "user",
      "content": [{"text": "What your name?"}],   # 长度只能为1
      "loss_weight": 0.0              # user的loss_weight默认为0（选填）
    },
    {
      "role": "assistant",
      "content": [{"text": "My name is doubao."}],  # 长度只能为1
      "loss_weight": 1.0              # assistant的loss_weight默认为1.0（选填）
    },
    {
      "role": "user",
      "content": [{"text": "How to learn Python?"}],  # 长度只能为1
      "loss_weight": 0.0              # user的loss_weight默认为0（选填）
    },
    {
      "role": "assistant",            # 最后一个message的role必须为assistant
      "content": [                    # 可以有多个response
        {
          "text": "I don't know!",
          "score": 0.5              # 该response的得分
        },
        {
          "text": "Check python doc yourself",
          "score": 0.1
        },
        {
          "text": "It's so easy. First, you need to learn Python syntax...",
          "score": 1,
          "lm_loss_mask": 0           # 这个response是否训练lm_head，默认为0
        }
      ]
      # 最后一个message loss_weight 字段被忽略，必须为1.0
    }
  ]
}
```

最后一个 messages 必须为 assistant，并且支持多个 content。每个 content 都有一个 score，会按照 score 进行 dpo pair 排序。上面这个例子会得到 3 个 dpo pair：

![](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/cd472c4ec86c44f181ec107b6024dff4~tplv-goo7wpa0wc-image.image)

模型会同时训练这3个 dpo pair。
下列的几项参数只有在训练时设置了 lm_loss_scale 才会生效：
* lm_loss_mask：

    * `1` ：代表在 lm_loss 的计算中，在最后一轮回复列表中选择该项回复参与 lm_loss 的计算。

    * `0` ：默认为 0，代表最后一轮回复列表中该项回复不参与 lm_loss 计算。
* loss_weight：损失权重，取值范围为0到1，用于指定哪些轮次的对话语句将被纳入语言模型损失（lm_loss）的计算，并确定相应的权重。最后一轮的权重默认为1。默认情况下，将使用上下文中所有助手（assistant）的回复，以及最后一轮中语言模型损失掩码（lm_loss_mask）设置为1的回复来计算语言模型损失，计算时所采用的权重即为损失权重（loss_weight）的值。

<span id="62f1d5a7"></span>
## 数据采样

* 模型 DPO 的过程中，训练会尽可能增大 dpo pair 数据中的 chosen 内容与 rejected 内容的 reward_margin，所以每一个 epoch 训练前的 dpo pair 数据中正例的多样性和质量也成为最终效果是否能符合预期的至关重要的因素，以下是有关如何采样的一些建议。
* 数据采样的建议：

    * 提升模型正例的多样性，可以通过从不同模型进行采样丰富训练数据正例的多样性。

    * 提升模型正例的多样性，可以通过尝试增大 top p 或 temperature 等方式，提升模型输出的多样性。

    * 尽可能提升模型正例的质量，最好训练数据中有从要训练的基座模型中进行采样的数据，使得整个模型更易达到最终效果。

<span id="7eeda0c5"></span>
## dpo pair 构造的建议

* 模型 DPO 训练的过程中，会尽可能增大 chosen 内容的生成概率，dpo pair 的准确率一定程度上影响 dpo的训练效果和数据采样共同决定了 dpo 训练最终效果的上限，以下是一些构建 dpo pair 的建议：
* 对于简小并且比较单一的任务，可以采用 prompt engineering+few shot 的方式，利用一些模型直接对采样得到的数据进行标注与区分，观察其对比人工标注的准确率，关于如何撰写 prompt，详情可见 [Prompt 最佳实践](/docs/prompt-engineering)。
* 对于比较复杂的任务，可以使用人工标注的方式构建 dpo pair 。
* dpo pair 的准确率达到 75%以上为佳。

<span id="6be17cad"></span>
## 数据量级

* DPO 的数据量级和任务难度有比较大的关系，通常任务难度可以依据 reward 标准的复杂程度决定。
* 针对不同难度的任务数据量级的建议：

    * 比较专项的任务，例如让模型输出某种风格的输出：2～3k。

    * 标准较清晰比较清晰的任务：5k ～ 1w。

    * 标准十分复杂也不够清晰的任务：1w 条及以上，视整体任务难度而定。

<span id="d3337b73"></span>
# 创建精调任务

1. 在 [模型精调](https://ark.volcengine.com/region:cn-beijing/finetune) 页面，点击左上角 **创建精调任务** 按钮，填写必填字段；
2. 选择**DPO**精调类型，并选定训练方式以及待训练的模型，可参考[精调选型指南](/docs/fine-tuning-overview#616be2d7)；
3. 上传构建的数据集，配置训练参数，重要参数配置建议可参考[训练参数选择](/docs/direct-preference-optimization-best-practices#6c807d00)，确认无误提交任务。

<span id="a924de6b"></span>
## 模型选择

方舟平台提供多种规格的模型，汇总如下：

|**模型名称** |**最大支持的token长度** |**模型参数量** |**模型推理耗时** |
|---|---|---|---|
|Doubao-lite-32k |32k |小 |较短 |
|Doubao-lite-128k |128k |小 |较短 |
|Doubao-pro-32k |32k |大 |长 |
|Doubao-pro-128k |128k |大 |长 |

1. 需要明确您模型使用场景是否对延迟比较敏感。如果要求低延迟，且测试过程中 pro 版本模型无法满足延迟要求时，可以使用 lite 模型。具体延迟可以到方舟 [体验中心](https://ark.volcengine.com/region:cn-beijing/experience) 进行测试。
2. 需要明确您的应用场景是否需要长文本能力。如果预期模型的输入和输出加起来超过了 32k 的 token 数，且删减字数对原有语义影响较大时，推荐使用 Doubao-pro-128k 模型（最大支持 128k token 的输入输出长度和）。
3. 明确了上述的需求，进行模型版本：

    * 同一大模型下面不同版本的区别，大部分仅在于使用的各阶段的训练数据不同。建议您选择最新版本的模型进行 DPO。

    * 对于角色扮演类任务，建议选择 character 模型进行 DPO。

<span id="06b52a8a"></span>
## 训练方式选择

* 从**参数更新范围**的角度划分，模型精调主要包含全量精调与 LoRA 精调两种方式。

    * LoRA（Low-Rank Adaptation）通过冻结预训练模型的全部权重，并在每个 Transformer 块中注入可训练的低秩适配层，大幅减少了可训练参数的总量。该方式训练速度更快、机器资源消耗更低，成本远低于全量精调；且在大多数场景下，LoRA 精调效果可达到全量精调的 98% 以上。

    * 建议先使用小参数量大模型（Doubao-lite）+ LoRA 验证实验设置及少量数据实验是否有效，指标是否正常。如果有效的话，再迁移到大参数量大模型上验证 scaling law（即随着模型参数量的增加，模型的效果越好）是否成立。这样做可以显著加快迭代效率，避免在大参数模型上反复做无意义的迭代。

<span id="6c807d00"></span>
## 训练参数选择

DPO 任务中，超参的选取会影响最终效果，下面对部分重要参数进行解释。
* epoch：模型训练轮数，**通常选择 1～3 之间**，可以根据验证集 loss 曲线来判断。

    * 如果训练集 loss 曲线下降，验证集 loss 曲线上升，则说明模型已经过拟合，此刻应该停止训练。

    * 如果训练集和验证集 loss 曲线均在缓慢下降，则说明模型还未收敛，可以继续训练。
* lora alpha 和 lora rank：

    * 说明：当前我们统一使用 RS-LoRA（秩稳定 LoRA）进行训练，其 LoRA 缩放因子为 alpha/sqrt(rank)

        * alpha：用于缩放模型权重；

        * rank：决定了 LoRA 训练参数数量的大小；

    * **推荐组合**：**rank 64, alpha 6 或 rank 32, alpha 4**。
* learning rate：学习率。设置过高可能导致陷入局部最优或过拟合，设置过低则会减慢训练速度或导致训练难以收敛。

    * 经验参考值：

        * **LORA 训练**：建议设置为 **5e-6~1e-6**

        * **全量 SFT**：建议设置为 **5e-7~3e-6**
* num warmup steps 和 warmup step rate：二者设置一个即可，通常不需要调整。当遇到模型在训练开始时，loss 始终不降的问题时，可尝试调大 warmup step rate，再观察 loss 是否按预期下降。
* dpo beta：**beta 是 DPO loss 的温度，通常在 0.1 到 0.5 之间**。该参数控制了我们对 ref 模型的关注程度，beta 越小，模型训练越忽略 ref 模型。
* dpo_loss_scale：**dpo loss 前的权重系数，默认为 1.0，通常保持默认**。
* lm_loss_scale：**lm loss 前的权重系数，默认为 1.0**，启用 lm_loss 时，需显示设置该系数，**通常设置为 0～1 之间**。
* 其余参数：通常保持默认参数即可。

<span id="c4e454fe"></span>
# 效果评估

<span id="f8ddd212"></span>
## 效果指标

有几个关键的评估指标：
* dpo_loss 指标：训练过程中观察，在训练集和验证集上的差异来评估模型 DPO 的效果。
* llm_loss 指标（可选）：训练过程中观察，若开启 llm_loss 设置，llm_loss 指标才生效，该 loss 为标准的语言模型损失，参与计算的为所有 loss_weight=1（loss_weight 的含义及设置参考底层格式一节）的 token，即在计算整个序列的语言损失时 mask 掉所有 loss_weight=0 的 token。
* reward margin 指标：训练过程中观察，代表当前模型分布所对应的 rm 在数据集上正负例之间的 reward 差距，观察在训练集和验证集上的正负例的 reward 差距来评估模型 DPO 的效果，该指标越大在训练集上拟合的越好。
* acc 指标：训练过程中观察，在训练集和验证集上的正负例判别的准确率来评估模型 DPO 的效果。
* 真实评估集：发布服务后，评估业务指标。

几个评估指标在各种任务上都比较通用，如果可以观察到训练集和测试集的 loss 在逐步下降至收敛，reward margin 和 acc 逐步上升至平滑，说明模型在正常训练至收敛，在测试集 acc 不再上升时，可以将训练好的模型在真实评估集上进行测试，观察收益。

<span id="572b54d2"></span>
## 按需使用精调产物

精调产物是训练过程中生成的可调用模型版本，通过实际使用产物，能更直观地验证模型在业务场景中的表现。
选择精调产物导出至 [模型仓库](https://ark.volcengine.com/region:cn-beijing/customModel) ，进行精调后模型的使用，支持**量化、体验、评测、在线推理、批量推理**，操作见[管理自定义模型](/docs/manage-custom-models)。

<span id="aa343de7"></span>
# 用户案例

<span id="04738b68"></span>
## 场景

在角色扮演场景中，用户希望获得一个符合自己输出风格的模型。

<span id="66e796f1"></span>
## 过程

1. 客户有一个 SFT 后的基座模型。
2. 发现模型偶尔会输出一些描述不安全，风格不符合预期的输出。另一方面，如果再加一些 shots，会导致 prompt 过长，导致线上推理耗时增加，影响体验。因此，尝试构造 DPO 数据集。
3. 根据多模型采样和校准过后的 reward 模型打分，用户获得了正例符合预期风格，负例不符合预期风格的 dpo pair 对。
4. 利用数据选择 character 基座模型进行 dpo lora 训练，观察各项指标收敛，真实数据有收益后，上线观察，最终达到业务预期。

<span id="8def8532"></span>
# FAQ

<span id="ff05ab18"></span>
## 怎么加快调参效率？

**多存** **ckpt**、**控制变量调参**。
* 尽量在精调时多训几个 epoch，多存几个 ckpt，这样在服务发布时，可以选择性的发布不同 step 的 ckpt 来看模型表象。
* 尽量对于超参在不同阈值区间控制变量调参，找到调大或调小和指标的关系。

<span id="fe1e5a9f"></span>
## DPO 和 RLHF 有什么区别？

DPO 是 RLHF 的一种方式，还有较多其余的 RLHF 方式，例如 PPO，KTO，RSO 等，本质上都是使用偏好数据降低不受欢迎的输出模式概率，增加符合偏好输出模式的概率。

<span id="37e0d870"></span>
## DPO 和 SFT 有什么区别？

* 训练数据质量要求：SFT 需要构造高质量问答数据，DPO 只需要拿到回复间的偏序对，数据构造更容易更方便
* 输出模型影响：

    * DPO 是在对基座模型已经具备的不同输出模式的概率进行调整，很难带来新的输出模式。希望模型在某种任务或者某种能力上的输出更符合人类偏好时，可优先使用 DPO 进行调整。

    * SFT 是按照生成的方式训练，可以给模型注入不同的新的输出模式，因此希望模型具备新能力，例如指令遵循，角色扮演等能力的时候应该优先选择 SFT 进行调整；如果对模型的输出有更严格的要求时，也应优先使用 SFT。

<span id="8aeb0959"></span>
## 相关文档

*  loss 的推导过程： https://arxiv.org/pdf/2305.18290v2
