Press "Enter" to skip to content

对Llama-2 7B模型进行微调以用于Python代码生成

使用PEFT,QLoRa和Huggingface工具演示如何微调新的Llama-2模型

由Leonardo.ai中的作者创建的图像

大约两周前,生成式人工智能领域被Meta公司发布的新Llama-2 AI模型所震惊。它的前身Llama-1在LLM行业是一个重要的突破,随着其权重的发布以及新的微调技术,出现了大量的开源LLM模型,从而催生了高性能模型,如Vicuna、Koala等。

在本文中,我们将简要讨论该模型的一些相关要点,但重点是展示如何使用这个领域中标准的库和工具快速训练模型以完成特定任务。我们不会对新模型进行详尽的分析,因为已经有很多关于这个主题的文章发表。

新的Llama-2模型

在7月中旬,Meta发布了其新的预训练和微调模型系列,称为Llama-2,具有开源和商业性质以便于使用和扩展。基础模型发布了聊天版本和大小为7B、13B和70B的模型。与这些模型一起,还发表了相应的论文,描述了它们的特点和学习过程的相关要点,这提供了非常有趣的信息。

Llama 1的更新版本,使用新的公开可用数据进行训练。预训练语料库的大小增加了40%,模型的上下文长度加倍,并采用了分组查询注意力。发布了参数为7B、13B和70B的变体,以及论文中报告但未发布的34B变体。

在预训练阶段,使用了40%更多的标记,达到了2T,上下文长度加倍,并应用了分组查询注意力(GQA)技术来加速更重的70B模型上的推理。在标准Transformer架构上,使用了RMSNorm归一化,SwiGLU激活和旋转位置嵌入,上下文长度达到4096个标记,并应用了Adam优化器,具有余弦学习率调度,0.1的权重衰减和梯度裁剪。

监督微调(SFT)阶段的特点是优先选择高质量的示例,因为众多报告显示使用高质量数据会改善最终模型的性能。最后,采用人类反馈强化学习(RLHF)步骤来使模型与用户的偏好保持一致。收集了大量示例,其中注释员在二元比较中选择他们更喜欢的模型输出。这些数据用于训练奖励模型,重点是有用性和安全性。

简而言之

· 训练了2T个标记

· 允许商业使用

· 用于对话场景的聊天模型

· 默认上下文窗口为4096(可增加)

· 7B、13B和70B参数版本

· 70B模型采用了分组查询注意力(GQA)

· 聊天模型可以使用工具和插件

· LLaMA 2-CHAT与OpenAI ChatGPT一样好

微调的数据集

对于我们的微调过程,我们将使用包含大约18,000个示例的数据集,其中模型被要求构建解决给定任务的Python代码。这是原始数据集[2]的一个提取,只选择了Python语言的示例。每行包含要解决的任务的描述,如果适用,还提供了任务的数据输入示例和生成的解决任务的代码片段[3]。

# 从hub加载数据集
dataset = load_dataset(dataset_name, split=dataset_split)
# 显示数据集大小
print(f"数据集大小:{len(dataset)}")
# 显示一个示例
print(dataset[randrange(len(dataset))])

创建提示

为了进行指令微调,我们必须将每个数据示例转换为一条指令,并按照以下方式概述其主要部分:

def format_instruction(sample): return f"""### 指令: 使用下面的任务和给定的输入编写响应,响应是一个可以解决以下任务的编程代码:### 任务:{sample['instruction']}### 输入:{sample['input']}### 响应:{sample['output']}"""

输出:

### 指令: 使用下面的任务和给定的输入编写响应,响应是一个可以解决以下任务的编程代码:### 任务:开发一个Python程序,每次运行时都打印"Hello, World!"### 输入:### 响应:#Python程序打印"Hello World!"print("Hello, World!")
Irvan Smith拍摄的照片

模型微调

为了进行这个阶段,我们使用了Google Colab环境,在其中开发了一个笔记本,可以以交互方式运行训练,还可以运行一个Python脚本以无人值守模式运行训练。对于最初的测试运行来说,使用具有高内存容量的T4实例就足够了,但是当涉及到运行整个数据集和时期时,我们选择使用A100实例以加快训练速度,并确保其执行时间合理。

为了能够共享模型,我们将使用适当的令牌登录到Huggingface hub,以便在整个过程结束时,我们将上传模型文件,以便与其他用户共享。

from huggingface_hub import loginfrom dotenv import load_dotenvimport os# 加载环境变量load_dotenv()# 登录Hugging Face Hublogin(token=os.getenv("HF_HUB_TOKEN"))

微调技术:PEFT、Lora和QLora

最近几个月,一些论文显示如何使用PEFT技术训练大型语言模型,可以大幅减少RAM需求,从而允许在一块合理大小的单个GPU上对这些模型进行微调。训练LLM的常规步骤首先是对数十亿或数万亿个标记进行密集的预训练,以获取基础模型,然后在该模型上执行微调,以使其专注于下游任务。在这个微调阶段,PEFT技术发挥了作用。

参数高效微调(PEFT)允许我们通过仅微调少量附加参数来大大减少RAM和存储需求,几乎所有模型参数都保持冻结状态。PEFT已经被发现在相对较小的数据集上产生良好的泛化效果。此外,它增强了模型的可重用性和可移植性,因为可以轻松将小的检查点添加到基础模型中,并且可以通过添加PEFT参数轻松微调和重复使用基础模型,以适应多种情景。最后,由于基础模型没有调整,因此在预训练阶段获得的所有知识都得以保留,从而避免了灾难性遗忘。

最广泛使用的PEFT技术旨在保持预训练的基础模型不变,并在其之上添加新的层或参数。这些层被称为“适配器”,其调整技术称为“适配器微调”,我们将这些层添加到预训练的基础模型中,仅训练这些新层的参数。然而,这种方法的一个严重问题是,这些层会导致推理阶段的延迟增加,从而使得该过程在许多场景下效率低下。在LoRa技术中,即大型语言模型的低秩适应,其思想是不添加新的层,而是以一种避免推理阶段延迟问题的方式添加参数的值。LoRa在冻结预训练模型的所有权重的同时,训练和存储附加权重的变化。因此,我们用预训练模型矩阵中的变化训练一个新的权重矩阵,然后将这个新矩阵分解为2个低秩矩阵,如下所述:

将LLM中的所有参数放在矩阵W0中,将额外的权重变化放在矩阵∆W中,最终的权重变为W0 + ∆W。 LoRA的作者提出,权重变化矩阵∆W可以分解为两个低秩矩阵A和B。 LoRA不直接训练∆W中的参数,而是训练A和B中的参数。因此,可训练的参数数量要少得多。假设A的维度为100 * 1,B的维度为1 * 100,则∆W中的参数数量为100 * 100 = 10000。在A和B中只有100 + 100 = 200个可训练的参数,而不是∆W中的10000个可训练的参数

[4]. Dr. Dataman在Fine-tuning a GPT – LoRA中的解释

这些低秩矩阵的大小由r参数定义。该值越小,要训练的参数越少,因此工作量越小,速度越快,但另一方面,可能会丢失信息和性能。如果您想要更详细的解释,可以参考原始论文,或者有很多详细解释的文章,例如[4]。

最后,QLoRa [6]是将量化应用于LoRa方法,允许4位正常量化nf4,这是一种针对正态分布权重进行优化的类型;双重量化以减少内存占用和优化NVIDIA统一内存。这些是用于优化内存使用以实现“更轻”和更便宜的训练的技术。

作者:来自Leonardo.ai的图像

在我们的实验中实施QLoRa需要指定BitsAndBytes配置,下载4位量化的预训练模型,并定义一个LoraConfig。最后,我们需要检索分词器。

# 获取类型compute_dtype = getattr(torch, bnb_4bit_compute_dtype)# BitsAndBytesConfig int-4 配置bnb_config = BitsAndBytesConfig(    load_in_4bit=use_4bit,    bnb_4bit_use_double_quant=use_double_nested_quant,    bnb_4bit_quant_type=bnb_4bit_quant_type,    bnb_4bit_compute_dtype=compute_dtype)# 加载模型和分词器model = AutoModelForCausalLM.from_pretrained(model_id,   quantization_config=bnb_config, use_cache = False, device_map=device_map)model.config.pretraining_tp = 1# 加载分词器tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)tokenizer.pad_token = tokenizer.eos_tokentokenizer.padding_side = "right"

定义参数,

# 激活4位精度基础模型加载use_4bit = True# 4位基础模型的计算数据类型bnb_4bit_compute_dtype = "float16"# 量化类型(fp4或nf4)bnb_4bit_quant_type = "nf4"# 激活4位基础模型的嵌套量化(双重量化)use_double_nested_quant = False# LoRA注意力维度lora_r = 64# LoRA缩放的Alpha参数lora_alpha = 16# LoRA层的Dropout概率lora_dropout = 0.1

下一步对于所有Hugging Face用户都是众所周知的,设置训练参数并创建一个Trainer。由于我们正在执行指令微调,因此调用方法封装了PEFT模型定义和其他步骤。

# 定义训练参数args = TrainingArguments(    output_dir=output_dir,    num_train_epochs=num_train_epochs,    per_device_train_batch_size=per_device_train_batch_size, # 6 if use_flash_attention else 4,    gradient_accumulation_steps=gradient_accumulation_steps,    gradient_checkpointing=gradient_checkpointing,    optim=optim,    logging_steps=logging_steps,    save_strategy="epoch",    learning_rate=learning_rate,    weight_decay=weight_decay,    fp16=fp16,    bf16=bf16,    max_grad_norm=max_grad_norm,    warmup_ratio=warmup_ratio,    group_by_length=group_by_length,    lr_scheduler_type=lr_scheduler_type,    disable_tqdm=disable_tqdm,    report_to="tensorboard",    seed=42)# 创建训练器trainer = SFTTrainer(    model=model,    train_dataset=dataset,    peft_config=peft_config,    max_seq_length=max_seq_length,    tokenizer=tokenizer,    packing=packing,    formatting_func=format_instruction,    args=args,)# 训练模型trainer.train() # 由于禁用了tqdm,所以不会有进度条# 在本地保存模型trainer.save_model()

参数可以在我的GitHub存储库上找到,其中大部分在LLMs的其他微调脚本中都是常用的,以下是这些参数:

# 训练轮数num_train_epochs = 1# 启用fp16/bf16训练(对于A100,将bf16设置为True)fp16 = Falsebf16 = True# 每个GPU的训练批次大小per_device_train_batch_size = 4# 累积梯度的更新步数gradient_accumulation_steps = 1# 启用梯度检查点gradient_checkpointing = True# 最大梯度范数(梯度裁剪)max_grad_norm = 0.3# 初始学习率(AdamW优化器)learning_rate = 2e-4# 应用于所有层的权重衰减,除了偏差/LayerNorm权重weight_decay = 0.001# 使用的优化器optim = "paged_adamw_32bit"# 学习率调度lr_scheduler_type = "cosine" #"constant"# 线性预热的步数比例(从0到学习率)warmup_ratio = 0.03# 将序列按相同长度分组# 节省内存并大大加快训练速度group_by_length = False# 每X个更新步骤保存一次检查点save_steps = 0# 每X个更新步骤记录一次日志logging_steps = 25# 禁用tqdmdisable_tqdm= True

合并基模型和适配器权重

正如我们所提到的,我们在基模型上训练了“修改权重”,我们的最终模型需要将预训练模型和适配器合并为一个单独的模型。

from peft import AutoPeftModelForCausalLMmodel = AutoPeftModelForCausalLM.from_pretrained(    args.output_dir,    low_cpu_mem_usage=True,    return_dict=True,    torch_dtype=torch.float16,    device_map=device_map,    )# 合并LoRA和基模型merged_model = model.merge_and_unload()# 保存合并后的模型merged_model.save_pretrained("merged_model",safe_serialization=True)tokenizer.save_pretrained("merged_model")# 将合并后的模型推送到Hubmerged_model.push_to_hub(hf_model_repo)tokenizer.push_to_hub(hf_model_repo)

您可以在我的Hugging Face帐户edumunozsala/llama-2–7b-int4-python-code-20k中找到并下载该模型。 试试吧!

推断或生成Python代码

最后,我们将向您展示如何从Hugging Face Hub下载模型并调用模型生成准确的结果:

import torchfrom transformers import AutoModelForCausalLM, AutoTokenizer# 获取标记器tokenizer = AutoTokenizer.from_pretrained(hf_model_repo)# 加载模型model = AutoModelForCausalLM.from_pretrained(hf_model_repo, load_in_4bit=True,                                              torch_dtype=torch.float16,                                             device_map=device_map)# 创建指令instruction="优化用Python编写的代码片段。代码片段应该创建一个从0到10之间可以被2整除的数字列表。"input=""prompt = f"""### 指令:使用下面的任务和给定的输入来编写响应,响应是一个可以解决任务的编程代码。### 任务:{instruction}### 输入:{input}### 响应:"""# 对输入进行标记input_ids = tokenizer(prompt, return_tensors="pt", truncation=True).input_ids.cuda()# 运行模型以推断输出outputs = model.generate(input_ids=input_ids, max_new_tokens=100, do_sample=True, top_p=0.9,temperature=0.5)# 打印结果print(f"提示:\n{prompt}\n")print(f"生成的指令:\n{tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True)[0][len(prompt):]}")

提示:### 指令:使用下面的任务和给定的输入来编写响应,响应是一个可以解决任务的编程代码。### 任务:优化用Python编写的代码片段。代码片段应该创建一个从0到10之间可以被2整除的数字列表。### 输入:arr = []for i in range(10): if i % 2 == 0: arr.append(i)### 响应:生成的指令:arr = [i for i in range(10) if i % 2 == 0]Ground truth:arr = [i for i in range(11) if i % 2 == 0]

感谢Maxime Labonne提供的优秀文章[9]和Philipp Schmid提供的鼓舞人心的代码[8]。

这就是我要提到的全部内容了,希望你会发现这篇文章有用,欢迎鼓掌!你可以关注我并订阅我的文章,甚至可以通过Linkedin与我联系。代码可以在我的Github仓库中找到。

参考资料

[1] Llama-2 论文

[2] Huggingface hub 上原始数据集的链接

[3] Huggingface hub 上使用的数据集的链接

[4] Chris Kuo/Dr. Dataman 的 GPT — LoRA 微调

[5] Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, & Weizhu Chen. (2021). LoRA: 大型语言模型的低秩适应. arXiv:2106.09685

[6] QLoRa: 高效量化语言模型的微调

[7] 少样本参数高效微调比上下文学习更好更便宜

[8] 扩展指南:通过 Philipp Schmid 对 Llama 2 进行指令调整

[9] 在 Colab 笔记本中微调自己的 Llama 2 模型,由 Maxime Labonne

[10] 我的 Github 仓库

Leave a Reply

Your email address will not be published. Required fields are marked *