乐趣区

关于人工智能:StackLLaMA-用-RLHF-训练-LLaMA-的手把手教程

如 ChatGPT,GPT-4,Claude 语言模型 之弱小,因为它们采纳了 基于人类反馈的强化学习 (Reinforcement Learning from Human Feedback, RLHF) 来使之更合乎咱们的应用场景。

本博客旨在展现用 RLHF 训练一个 LLaMA 模型,以答复 Stack Exchange 上的问题。具体而言,蕴含以下几个方面:

  • 有监督的微调 (Supervised Fine-tuning,SFT)。
  • 处分 / 偏好建模 (Reward / preference modeling,RM)。
  • 基于人类反馈的强化学习 (RLHF)。

摘自 InstructGPT 论文,Ouyang, Long, et al.“Training language models to follow instructions with human feedback.”arXiv preprint arXiv:2203.02155 (2022).

联合了上述办法,咱们公布了 StackLLaMA 模型,该模型在 🤗 Hub 上开源 (拜访链接查看 Meta 的原始 LLaMA),整个 训练的流程 曾经集成到了 Hugging Face TRL 库中。你能够通过上面的 demo 来尝试该模型。

LLaMA 模型

在实际 RLHF 时,选取一个适合的模型很重要: RLHF 只是一个让模型满足咱们交互模式的需要的微调过程。所以咱们选取了最近上线的 LLaMA 模型。LLaMA 模型是 Mata AI 最近推出的大语言模型。其参数量大小涵盖 7B 到 65B,以及训练在 1T 和 1.4T 的 token 上,这让其很实用。咱们这里采纳 7B 的模型。(请填写 Meta AI 的这份 表单 来下载模型)。

Stack Exchange 数据集

收集人类的反馈数据集是很简单且低廉的劳动。为了做到这个,并且还能保障模型的有效性,咱们应用 StackExchange 数据集。该数据集涵盖了 StackExchange 平台上的问题和答案 (蕴含 StackOverflow 的编程等话题下的)。这很适宜咱们的实际,因为其蕴含了每个答案的赞和踩的数量。

咱们依照 Askell et al. 2021 中的办法,给每个答案赋分:

score = log2 (1 + upvotes) rounded to the nearest integer, plus 1 if the questioner accepted the answer (we assign a score of −1 if the number of upvotes is negative).

对处分模型,咱们将看到每个问题总是须要两个答案比照。有些问题有很多答案,能够产生很多对,咱们只取十个以限度每个问题的数据量。最初,咱们把格局从 HTML 转化到 Markdown 以进步输入的可读性。你能够看到数据集和处理过程的 [笔记本]。(https://huggingface.co/datasets/lvwerra/stack-exchange-paired。)

高效训练策略

即便是最小 LLaMA 模型的训练,都须要大量内存。估算一下: 以 bf16 半精度,每个参数用 2 个字节 (以 fp32 精度四字节的规范),训练时须要 8 个字节 (例如 Adam 优化器,参见 Tramsformers 的 性能文档)。可见 7B 参数量的模型将用 (2+8)* 7B = 70 GB 的内存,并且还可能须要更多用于计算诸如注意力分数的两头值。所以很难在一张 80GB 显存的 A100 上训练。或者你能够应用一些技巧,比方用更高效的半精度训练的优化器来压缩内存,但溢出是迟早的。

另外的可能是 参数高效的微调 (Parameter-Efficient Fine-Tuning, PEFT) 技术,比方 peft 库,它能够对应用 8-bit 加载的模型做 低秩优化(Low-Rank Adaptation,LoRA)。

线性层的低秩优化: 额定参数 (橙色) 被加在 Frozen 层 (蓝色),编码后的暗藏状态与 Frozen 层的暗藏状态叠加在一起。

以 8bit 加载模型会大幅升高内存占用,因为每个参数只有一字节 (比方 7B LLaMA 是 7GB 内存)。与间接训练原始模型不同,LoRA 在特定层 (个别是注意力层) 增加大量新参数,大幅升高了须要训练的参数。

此情此景,一个衡量标准是 1B 的参数在整个微调过程中占 ~1.2-1.4GB (和具体 batch size 及序列长度无关)。在参考的博客中具体探讨了,这使得低成本下微调较大参数规模的模型成为可能 (比方在一张 A100 上微调 50-60B 的参数)。

这些技术能让微调大模型的工作,在生产级设施和 Google Colab 上执行。这里提供一些值得关注的演示 demo: facebook/opt-6.7b (在 float16 精度下 13GB) 和 openai/whisper-large
跑在 Google Colab (15GB 显存) 上。欲了解 peft 的应用,请参见 github 仓库 或者之前的 博客介绍: 在客户端训练 20B 参数量的模型。

当初咱们能在一张 GPU 上微调很大的模型了,但训练还是会很慢。此时最简略的策略便是并行化: 把一个训练同时放到不同的 GPU 上,各 GPU 承受不同的 batch。这样咱们能够并行执行前向流传和后向流传,通过减少 GPU 的数量实现并行能力晋升。

咱们能够选用 trainsformers.Traineraccelerate,因为它们都反对无代码变更进行数据并行化。只需注意调用 torchrun 或者 accelerate launch 脚本时的参数即可实现。比方以下就是在一个 8 显卡的机器上别离用 accelerate launchtorchrun的办法:

accelerate launch --multi_gpu --num_machines 1  --num_processes 8 my_accelerate_script.py
torchrun --nnodes 1  --nproc_per_node 8 my_torch_script.py

有监督的微调

在训练处分模型和用 RL 之前,模型若是曾经在咱们感兴趣的方面体现好将会很有帮忙。在咱们的示例中,咱们想要其能答复问题,而其余时候,咱们可能它能听指令 (这时对指令执行的微调是现实的)。实现这个最简略的办法便是面向该语言工作,用该工作和畛域的文本,持续训练。StackExchange 数据集 含 10M 的指令量,所以咱们能用其子集很容易地训练。

在用 RLHF 之前的模型微调没有特地的,就是个别的面向语言工作的预训练模型微调。为了高效利用数据,咱们采纳了称之为 打包 的技术: 与 batch 中的每个样本均由繁多文本组成,最初基于最长的文原本 padding (填充),咱们把很多文本拼接起来,用 EOS token 来隔开,而后宰割成一些 chunk (切块) 来做成 batch,防止 padding。

该办法大大提高了效率,因为模型输出的所有 token 都对 loss 有所训练,而非 padding 作为掩码被抛弃了。如果你没有足够数据,并且放心随便地离开 token 会失去上下文语义,你也能够用传统的数据加载器
ConstantLengthDataset 解决了 打包 技术,并且咱们能在用 peft 加载模型后用 Trainer。首先,咱们用 int8 加载模型,筹备训练,而后退出 LoRA 微调器。

# load model in 8bit
model = AutoModelForCausalLM.from_pretrained(
        args.model_path,
        load_in_8bit=True,
        device_map={"": Accelerator().local_process_index}
    )
model = prepare_model_for_int8_training(model)

# add LoRA to model
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

model = get_peft_model(model, config)

咱们依据相应的语言工作,对模型训练几千个 step (步),并保留模型。因为咱们将会有其余微调模型的目标,咱们将 LoRA 的微调器权重合并到原模型中。

申明: 因为 LLaMA 的许可证规定,咱们只能公布微调器的权重,你须要填 Meta AI 的 表格 来获取模型,而后用这个 脚本 来转成 🤗 Transformers 格局。留神 🤗 Transformers 应该从源码装置,或者 v4.28 版。

当初咱们曾经微调好了模型,能够训练处分模型了。

处分模型和人类偏好

原则上,咱们能够间接用人类标注来对模型做 RLHF 微调。然而,这将须要咱们给人类发送一些样本,在每轮优化后计分。这是贵且慢的,因为收敛须要的训练样本量大,而人类浏览和标注的速度无限。

一个比间接反馈更好的策略是,在进入 RL 循环之前用人类标注集来训练一个处分模型。处分模型的目标是模仿人类对文本的打分。构建处分模型有许多能用的策略: 最间接的便是预测标注 (比方依据好与坏,输入比分或者布尔值)。最佳实际是,预测后果的排序,即对每个 prompt (输出文本) 对应的两个后果 \((y_k, y_j) \)模型预测人类标注的比分哪个更高。

或者示意为 loss (损失) 函数:

$$
{loss}(\theta) = – E_{(x, y_j, y_k)~D} [{log}(\sigma( r_\theta (x, y_j) – r_\theta(x, y_k)) ) ]
$$

其中 \(r \)是模型对可能的标注 \(y_j \)的预测分数。

在 StackExchange 数据集上,咱们能失去两个答案的受欢迎水平。有了这个信息和下面的损失函数,咱们就能自定义 loss 来改 transformers.Trainer 了。


class RewardTrainer(Trainer):
    def compute_loss(self, model, inputs, return_outputs=False):
        rewards_j = model(input_ids=inputs["input_ids_j"], attention_mask=inputs["attention_mask_j"])[0]
        rewards_k = model(input_ids=inputs["input_ids_k"], attention_mask=inputs["attention_mask_k"])[0]
        loss = -nn.functional.logsigmoid(rewards_j - rewards_k).mean()
        if return_outputs:
            return loss, {"rewards_j": rewards_j, "rewards_k": rewards_k}
        return loss

咱们用数据集中的 100000 对,并在 50000 对上评估。在比拟小的 batch size,为 4 下,咱们用 LoRA 的 peft 微调器来训练 LLaMA 模型,在 BF16 精度下用 Adam 优化器。咱们的 LoRA 设置是:

peft_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    inference_mode=False,
    r=8,
    lora_alpha=32,
    lora_dropout=0.1,
)

训练用 Weights & Biases 来记日志,并在 🤗 训练集群上,用 8 卡 A-100,要数小时,最初准确率为 67%。只管看上去可能低了,但想想这个工作的难度。

如下文要细说的,训练后果将作为固定参数,以供上游应用。

基于人类反馈的强化学习

当初咱们手头有了微调的语言模型和处分模型,能够开始执行 RL 循环了: 这个过程大抵分为三步

  1. 生成对 prompt (输出文本) 的反馈。
  2. 用处分模型来对反馈评分。
  3. 对评分,进行一轮策略优化的强化学习。

在被 token 化并输出处分模型前,发问和答复的 prompt 模版如下:

Question: <Query>
Answer: <Response>

在有监督训练 (SFT),处分模型训练 (RM) 和 RLHF 的阶段都用此模版。

用 RL 训练语言模型呈现的常见问题是,模型可能学会胡言乱语以糊弄处分模型,后者可能给高分。为了衡量,咱们对处分减少惩办: 留一份没有训练的模型,如何比拟两者输入的 KL 散度

$$
{R}(x, y) = {r}(x, y) – \beta {KL}(x,y)
$$

其中 \(r \) 是处分模型的后果,\({KL}(x,y) \) 是以后模型和比照模型的 KL 散度差。

再提一遍,咱们用 peft 来实现内存高效的训练,其对 RLHF 阶段提供了劣势。这里参考的模型和训练的模型用同一个基底,也就是有监督训练 (SFT) 的后果,它是用 8-bit 来加载,并且一如既往是固定的。咱们仅用 PPO 办法优化最终模型的 LoRA 权重,同时全副共享一个基底模型。

for epoch, batch in tqdm(enumerate(ppo_trainer.dataloader)):
    question_tensors = batch["input_ids"]
        
    # sample from the policy and generate responses
    response_tensors = ppo_trainer.generate(
        question_tensors,
        return_prompt=False,
        length_sampler=output_length_sampler,
        **generation_kwargs,
    )
    batch["response"] = tokenizer.batch_decode(response_tensors, skip_special_tokens=True)

    # Compute sentiment score
    texts = [q + r for q, r in zip(batch["query"], batch["response"])]
    pipe_outputs = sentiment_pipe(texts, **sent_kwargs)
    rewards = [torch.tensor(output[0]["score"] - script_args.reward_baseline) for output in pipe_outputs]

    # Run PPO step
    stats = ppo_trainer.step(question_tensors, response_tensors, rewards)
    # Log stats to WandB
    ppo_trainer.log_stats(stats, batch, rewards)

咱们用 🤗 集群,在 3×8 A100-80GB 的机器上训练了 20h,但一个差不多的后果很快 (大略,在 8 A100-80GB 上训练 20h)。所有的训练过程都在 Weight & Biases 上找到。

每个 batch 的处分,对每步的训练,在 ~1000 步时模型的成果最好。

所以模型训好了无能啥嘞 ? 咱们刮目相待 !

只管咱们不该太置信其后果,至多目前。但后果曾经很好了,甚至附上了 Google 链接。咱们来看看训练时的挑战。

挑战,不稳固和突破口

用 RL 训练 LLM (Large Language Models,大语言模型) 不总是一帆风顺的,你看到的本文也是经验有数试验,有数失败和有数调参的。即便如此,该模型也不能说变现完满。这儿,咱们分享一些遇到的察看和问题。

处分更高代表更好体现?

天呐,这个试验必定体现很好 ! 看处分的曲线多甜啊 !

在 RL 中,一般而言,处分越高越好。在 RLHF 中,咱们用了一个处分模型,它不完满,所以留给了 PPO 算法捡漏的机会。这能导致处分忽然回升,然而当查看文本后果时,却充斥了字符“`”,因为处分模型对含有代码 stack exchange 的答案更信赖。侥幸的是,该问题碰到的很少,应该是采取的 KL 散度的惩办项起到了作用。

KL 散度总是正的?

如咱们后面所提到的,一个 KL 惩办项被用来保障训练后的散布和原始散布靠近。个别地 , KL 散度来度量两个散布的类似水平,并且总是正的。然而,在 trl 咱们用了一个 KL 的近似,期望值和真的 KL 散度雷同。

$$
KL_{pen} (x, y) = {log} (\pi_\phi^{RL}(y | x) / \pi^{{SFT}}(y|x))
$$

显然,当训练中一个 token 比原始模型概率低,这会导致 KL 散度为负,适合的取样和均匀总能失去正的。然而一些采样的生成策略导致了不匀称的采样。比方,当生成被 padding 的序列 batch 时和当设置 EOS token 被压缩的最小长度是,模型会有很大 / 很小的概率到负 KL 散度的 token。同时 PPO 算法是面向处分优化的,模型就会追赶负的惩办,导致训练不稳固。

对生成和采样,你须要特地小心。咱们倡议一开始用最简略的形式,如何在逐步简单。

任然存在的问题

任然有很多问题咱们不懂,比方上面,loss 间断地跳跃,导致之后的不稳固

一旦咱们解决了这些问题,咱们就会上传变动到 trl 上,以保障社区受害。

总结

在本博客,咱们走过了 RLHF 训练的整个流程,从筹备人类标注的数据集开始,调整语言模型到特定畛域,训练处分模型,并最终用 RL 训练一个模型。

通过应用 peft,任何人都能在一张 GPU 上跑咱们的试验 ! 如果训练慢了,能够用数据并行化的办法,不须要改任何代码,或者用多张 GPU 并行进步训练速度。

对理论利用,这仅仅是第一步 ! 一旦你有了模型,你就要和其余模型比拟优劣。这个能够用一个面向不同模型的排名生成做到,和咱们训练处分数据集相似。

一旦你退出了评估的步骤,好玩的就开始了: 你能够在原数据集上重复炼丹,也能够减少数据集或者对原数据集提纯。另外,你能够对处分模型和生成试不同大小和构造的模型,这须要工夫。

咱们在踊跃进步 TRL 以保障 RLHF 的每一步都可见,并且非常冲动能看到人们用它来构建的货色。如果你想有所奉献,欢送看咱们的 Github Issue。

援用

@misc {beeching2023stackllama,
    author = { Edward Beeching and
                     Younes Belkada and
                     Kashif Rasul and
                     Lewis Tunstall and
                     Leandro von Werra and
                     Nazneen Rajani and
                     Nathan Lambert
                   },
    title = {StackLLaMA: An RL Fine-tuned LLaMA Model for Stack Exchange Question and Answering},
    year = 2023,
    url = {https://huggingface.co/blog/stackllama},
    doi = {10.57967/hf/0513},
    publisher = {Hugging Face Blog}
}

感激

咱们感激 Philipp Schmid 分享了他对文本生成绝妙的 demo, 咱们的 demo 也是基于他的。咱们也感激 Omar Sanseviero 和 Louis Castricato 对咱们博客的草稿提供贵重详尽的反馈。


英文原文: https://hf.co/blog/stackllama

作者: Edward Beeching, Kashif Rasul, Younes Belkada, Lewis Tunstall, Leandro von Werra Nazneen Rajani, Nathan Lambert

译者: Vermillion-Qi(张奇), zhongdongy (阿东)

退出移动版