个人编程助手: 训练你自己的编码助手

发表于 2023年10月27日
在 GitHub 上更新

在编程和软件开发这个不断演变的领域中,对效率和生产力的追求催生了许多卓越的创新。其中一个显著的创新就是代码生成模型的出现,如 CodexStarCoderCode Llama。这些模型在生成类似人类编写的代码片段方面表现出惊人能力,显示出了作为编程助手的巨大潜力。

然而,虽然这些预训练模型在各种任务上已经表现出了卓越的性能,但在不远的未来,我们仍然可以期待一个令人兴奋的前景: 想象一下,你能够根据自己的特定需求定制代码生成模型,并且这种个性化的编程助手能够在企业规模上得到应用。

在这篇博客中,我们将展示如何创建 HugCoder 🤗,一个在 huggingface GitHub 组织 的公共仓库代码内容上进行微调的代码大模型。我们将讲述我们的数据收集工作流程、训练实验,以及一些有趣的结果。这将使你能够根据你的专有代码库创建自己的个人编程助手。我们还将为这个项目的进一步扩展留下一些实验的方向。

让我们开始吧 🚀

Using HugCoder in Visual Studio Code to help create a LoRA fine-tune

数据收集的工作流

我们想要的数据集在概念上非常简单,我们像下面所示那样构建它。

仓库名 仓库中的文件路径 文件内容

使用 Python GitHub API 从 GitHub 上抓取代码内容是直截了当的。然而,这取决于仓库的数量和仓库内代码文件的数量,通常情况,人们很容易会遇到 API 速率限制等问题。

为了防止这类问题发生,我们决定将所有公共仓库克隆到本地,并从中提取内容,而不是通过 API。我们使用 Python 的 multiprocessing 模块并行下载所有仓库,如 这个下载脚本

一个仓库通常可能包含非代码文件,如图片、演示文稿和其他资料。我们对抓取它们不感兴趣。我们为此创建了一个 扩展名列表 来过滤掉它们。为了解析除了 Jupyter Notebook 之外的代码文件,我们简单地使用了 “utf-8” 编码。对于 notebook,我们只考虑了代码单元。

我们还排除了所有与代码不直接相关的文件路径。这些包括: .git__pycache__xcodeproj

为了保持这些内容的序列化相对内存友好 (即处理代码时不会过多占用内存),我们使用了分块处理方法和 feather 格式 (储存序列化的数据)。完整实现请参见 这个脚本

最终的数据集 可在 Hub 上获取,它看起来像这个样子:

hf-stack-full

对于这篇博客,我们选取了基于点赞数排名前十的 HF中国镜像站 公共仓库。它们分别是:

['transformers', 'pytorch-image-models', 'datasets', 'diffusers', 'peft', 'tokenizers', 'accelerate', 'text-generation-inference', 'chat-ui', 'deep-rl-class']

这是我们用来生成这个数据集的代码,而 这是数据集在 Hub 上的链接。下面是它的一个快照:

hf-stack-v1

为了降低项目复杂性,我们没有考虑对数据集进行去重。如果你对在生产应用中应用去重技术感兴趣,这篇博客文章 是一个极佳的资源,它在代码大模型的内容中详细讨论了这个主题。

微调你的个人代码助手

在这一部分,我们将展示如何微调以下模型: bigcode/starcoder (15.5B 参数) 、bigcode/starcoderbase-1b (1B 参数) 和 Deci/DeciCoder-1b (1B 参数)。我们将使用一个带有 40GB 显存的 A100 Colab Notebook,并使用 🤗 PEFT (Parameter-Efficient Fine-Tuning,参数高效微调) 进行所有实验。此外,我们还将展示如何使用 🤗 Accelerate 的 FSDP (Fully Sharded Data Parallel,全分片数据并行) 集成,在一台配备 8 个 80GB 显存的 A100 GPU 的机器上完全微调 bigcode/starcoder (15.5B 参数)。训练目标是 fill in the middle (FIM) ,其中训练序列的一部分被移动到序列的末尾,并且重排序后的序列被自回归地预测。

为什么选择 PEFT ?因为全微调代价高昂。让我们来看一些数字以便更好地理解:

全微调所需的最小 GPU 内存:

  1. 参数权重: 2 字节 (混合精度训练)
  2. 参数权重梯度: 2 字节
  3. 使用 Adam 优化器时的优化器状态: 4 字节用于原始 FP32 权重 + 8 字节用于一阶和二阶矩估计
  4. 将以上所有内容加在一起的每个参数成本: 每个参数 16 字节
  5. 15.5B 模型 -> 248GB 的 GPU 内存,甚至还没有考虑存储中间激活值所需的巨大内存 -> 至少需要 4 个 A100 80GB GPU

由于硬件需求巨大,我们将使用 QLoRA 进行参数高效微调。下面是使用 QLoRA 进行 Starcoder 微调的最小 GPU 内存需求:

trainable params: 110,428,160 || all params: 15,627,884,544 || trainable%: 0.7066097761926236

  1. 基础模型权重: 0.5 字节 * 15.51B 冻结参数 = 7.755GB
  2. 适配器 (Adapter) 权重: 2 字节 * 0.11B 可训练参数 = 0.22GB
  3. 权重梯度: 2 字节 * 0.11B 可训练参数 = 0.22GB
  4. 使用 Adam 优化器时的优化器状态: 4 字节 * 0.11B 可训练参数 * 3 = 1.32GB
  5. 将以上所有内容加在一起 -> 9.51GB ~ 10GB -> 需要 1 个 A100 40GB GPU 🤯。选择 A100 40GB GPU 的原因是,训练时长序列长度为 2048,批量大小为 4,这会导致更高的内存需求。如下所示,所需的 GPU 内存为 26GB,可以在 A100 40GB GPU 上容纳。此外,A100 GPU 与 Flash Attention 2 具有更好的兼容性。

在上面的计算中,我们没有考虑中间激活值检查点所需的内存,这通常是相当巨大的。我们利用 Flash Attention V2 和梯度检查点来解决这个问题。

  1. 对于 QLoRA,加上 flash attention V2 和梯度检查点,单个 A100 40GB GPU 上模型占用的总内存为 26GB批量大小为 4
  2. 对于使用 FSDP 进行全微调,加上 Flash Attention V2 和梯度检查点,每个 GPU 上占用的内存在 70GB 到 77.6GB 之间, 每个 GPU 的批量大小为 1

请参考 model-memory-usage 以轻松计算在 🤗 HF中国镜像站 Hub 上托管的大型模型上进行训练和推理所需的 vRAM。

全微调

我们将探讨如何使用 PyTorch Fully Sharded Data Parallel (FSDP) 技术在 8 个 A100 80GB GPU 上完全微调 bigcode/starcoder (15B 参数)。欲了解更多关于 FSDP 的信息,请参阅 Fine-tuning Llama 2 70B using PyTorch FSDPAccelerate Large Model Training using PyTorch Fully Sharded Data Parallel

资源

  1. 代码库: 链接。它使用了 Transformers 中最近添加的 Flash Attention V2 支持。
  2. FSDP 配置: fsdp_config.yaml
  3. 模型: bigcode/stacoder
  4. 数据集: smangrul/hf-stack-v1
  5. 微调后的模型: smangrul/peft-lora-starcoder15B-v2-personal-copilot-A100-40GB-colab

启动训练的命令在 run_fsdp.sh 中给出。

accelerate launch --config_file "configs/fsdp_config.yaml" train.py \
    --model_path "bigcode/starcoder" \
    --dataset_name "smangrul/hf-stack-v1" \
    --subset "data" \
    --data_column "content" \
    --split "train" \
    --seq_length 2048 \
    --max_steps 2000 \
    --batch_size 1 \
    --gradient_accumulation_steps 2 \
    --learning_rate 5e-5 \
    --lr_scheduler_type "cosine" \
    --weight_decay 0.01 \
    --num_warmup_steps 30 \
    --eval_freq 100 \
    --save_freq 500 \
    --log_freq 25 \
    --num_workers 4 \
    --bf16 \
    --no_fp16 \
    --output_dir "starcoder-personal-copilot-A100-40GB-colab" \
    --fim_rate 0.5 \
    --fim_spm_rate 0.5 \
    --use_flash_attn

总的训练时间为 9 小时。根据 lambdalabs 的价格,8 个 A100 80GB GPU 的成本为每小时 $12.00,总成本将为 $108

PEFT

我们将探讨如何使用 🤗 PEFT 的 QLoRA 方法对 bigcode/starcoder (15B 参数) 进行微调,使用的硬件是单个 A100 40GB GPU。有关 QLoRA 和 PEFT 方法的更多信息,请参阅 Making LLMs even more accessible with bitsandbytes, 4-bit quantization and QLoRA🤗 PEFT: Parameter-Efficient Fine-Tuning of Billion-Scale Models on Low-Resource Hardware

资源

  1. 代码库: 链接。它使用了 Transformers 中最近添加的 Flash Attention V2 支持。
  2. Colab notebook: 链接。请确保选择带有 High RAM 设置的 A100 GPU。
  3. 模型: bigcode/stacoder
  4. 数据集: smangrul/hf-stack-v1
  5. QLoRA 微调模型: smangrul/peft-lora-starcoder15B-v2-personal-copilot-A100-40GB-colab

启动训练的命令在 run_peft.sh 中给出。总的训练时间为 12.5 小时。根据 lambdalabs 的价格,每小时 $1.10,总成本将为 $13.75。这真是太棒了🚀!从成本上讲,它比全微调的成本低了 7.8 倍

对比

下面的图展示了 QLoRA 与全微调的评估损失、训练损失和学习率调度器。我们观察到,全微调的损失略低,收敛速度也略快一些,与 QLoRA 相比。PEFT 微调的学习率是全微调的 10 倍。

plots

为了确保我们的 QLoRA 模型不会导致灾难性遗忘,我们在其上运行了 Python Human Eval。以下是我们得到的结果。 Pass@1 评估了单个问题的通过率,考虑了每个问题仅生成一个代码候选。我们可以观察到,在 humaneval-python 上,基础模型 bigcode/starcoder (15B 参数) 和微调后的 PEFT 模型 smangrul/peft-lora-starcoder15B-v2-personal-copilot-A100-40GB-colab 的性能是可比的。

模型 Pass@1
bigcode/starcoder 33.57
smangrul/peft-lora-starcoder15B-v2-personal-copilot-A100-40GB-colab 33.37

现在让我们来看一些定性的样本。在我们的手动分析中,我们注意到 QLoRA 导致了轻微的过拟合,因此我们通过使用 PEFT 的 add_weighted_adapter 工具,创建一个权重为 0.8 的新加权适配器 (Adapter) 来降低其权重。

我们将看两个代码填充的例子,其中模型的任务是填充由 <FILL_ME> 占位符表示的部分。我们将考虑从 GitHub Copilot、QLoRA 微调模型和全微调模型的填充完成。

qualitative_comparison_1

定性示例 1

在上面的示例中,GitHub Copilot 的补全是正确的,但帮助不大。另一方面,QLoRA 和全微调模型的补全正确地填充了整个函数调用及其必要的参数。然而,它们之后也添加了许多噪声。这可以通过后处理步骤来控制,以限制补全到闭括号或新行。注意,QLoRA 和全微调模型产生的结果质量相似。

qualitative_comparison_2

定性示例 2

在上面的第二个示例中, GitHub Copilot 没有给出任何补全。这可能是因为 🤗 PEFT 是一个最近的库,还没有成为 Copilot 训练数据的一部分,这 正是我们试图解决的问题类型。另一方面,QLoRA 和全微调模型的补全正确地填充了整个函数调用及其必要的参数。再次注意,QLoRA 和全微调模型提供的生成质量相似。全微调模型和 PEFT 模型的各种示例的推理代码分别可在 Full_Finetuned_StarCoder_Inference.ipynbPEFT_StarCoder_Inference.ipynb 中找到。

因此,我们可以观察到,两种变体的生成都符合预期。太棒了!🚀

怎么在 VS Code 中使用?

你可以轻松地使用 🤗 llm-vscode VS Code 扩展配置一个自定义的代码补全大模型,并通过 🤗 Inference EndPoints 托管模型。我们将在下面逐步介绍所需的步骤。你可以在 推理端点文档 中了解有关部署端点的更多详细信息。

设置推理端点

下面是我们创建自定义推理端点时遵循的步骤的截图。我们使用了我们的 QLoRA 模型,导出为一个可以轻松加载到 transformers 中的全尺寸的 merged 模型。

ie_1

ie_2

设置 VS Code 扩展

只需按照 安装步骤 操作。在设置中,将下面字段中的端点替换为你部署的 HF 推理端点的地址。

vs_code_endpoint

使用起来如下所示:

code_completion

微调你自己的代码聊天助手

到目前为止,我们训练的模型特别是作为代码完成任务的个人助手培训。它们没有被训练来进行对话或回答问题。 OctocoderStarChat 是这类模型的绝佳示例。本节简要描述了如何实现这一点。

资源

  1. 代码库: 链接。它使用了 Transformers 中最近添加的 Flash Attention V2 支持。
  2. Colab notebook: 链接。请确保选择带有 High RAM 设置的 A100 GPU。
  3. 模型: bigcode/stacoderplus
  4. 数据集: smangrul/code-chat-assistant-v1。混合了 LIMA+GUANACO 并以适合训练的格式正确格式化。
  5. 训练好的模型: smangrul/peft-lora-starcoderplus-chat-asst-A100-40GB-colab

LoRA 的组合

如果你曾经涉足 Stable Diffusion 模型和 LoRAs,以及用于制作你自己的 Dreambooth 模型,你可能会熟悉将不同的 LoRAs 与不同的权重结合起来的概念,使用一个与其训练基模型不同的 LoRA 模型。在文本/代码领域,目前仍是未被探索的领域。我们在这方面进行了实验,并观察到了非常有趣的发现。你准备好了吗?我们出发吧!🚀

混合匹配 LoRAs

PEFT 目前支持 3 种结合 LoRA 模型的方式,linearsvdcat 。更多细节,请参考 tuners#peft.LoraModel.add_weighted_adapter

我们的 notebook Dance_of_LoRAs.ipynb 提供了所有推理代码,并展示了多种 LoRA 模型的加载组合。例如,它展示了如何在 starcoder 模型上加载聊天助手适配器 (Adapter),尽管 starcoderplus 是我们用于微调的基础模型。

这里,我们将考虑 2 种能力 ( 聊天/问答代码完成 ) 在 2 种数据分布 ( 前 10 公共 hf 代码库通用代码库 ) 上。这给了我们 4 个轴,我们将在上面进行一些定性评估分析。

首先,让我们考虑聊天/问答 任务。

如果我们禁用适配器 (Adapter),我们观察到对于两个数据集来说任务都失败了,因为基模型 ( starcoder ) 仅用于代码完成,不适合 聊天/问答 。启用 copilot 适配器 (Adapter) 的表现类似于禁用的情况,因为这个 LoRA 也是专门为代码完成而微调的。

现在,让我们启用 assistant 适配器 (Adapter)。

assistant_chat_generic

基于生成代码的 QA

assistant_chat_hf

基于 HF 代码的 QA

我们可以观察到,关于 scrapy 的通用问题得到了妥善的回答。然而,它未能解答与 HF (HF中国镜像站) 代码相关的问题,因为这不是它预训练数据的一部分。

现在让我们考虑 代码补全 任务。

在禁用适配器 (Adapter) 时,我们观察到对于通用的两数之和问题,代码补全如预期般工作正常。然而,对于 HF 代码补全任务,由于基础模型在其预训练数据中未曾见过,所以在向 LoraConfig 传递参数时出现了错误。启用 assistant 的表现与禁用时相似,因为它是在自然语言对话的基础上训练的,这些对话中没有任何 HF中国镜像站 代码仓库的内容。

现在,让我们启用 copilot 适配器 (Adapter)。

copilot_code_generic

我们可以观察到,在两种情况下 copilot 适配器 (Adapter) 都得到了正确的结果。因此,无论是在处理 HF (HF中国镜像站) 特定代码库还是通用代码库时,它都能如预期地完成代码补全任务。

现在,作为用户,我希望能结合 assistantcopilot 的能力。这将使我能够在 IDE 中编码时使用它进行代码补全,同时也能将它作为聊天机器人来回答我关于 API、类、方法、文档的问题。它应该能够提供对问题的答案,如 我该如何使用 x ,请在我的代码的基础上 为 Y 编写一段代码片段

PEFT 允许你通过 add_weighted_adapter 来实现这一点。让我们创建一个新的适配器 code_buddy ,给予 assistantcopilot 适配器相同的权重。

combining_loras

结合多种适配器 (Adapter)

现在,让我们看看 code_buddy聊天/问答 任务上的表现。

混合聊天 _hf

我们可以观察到 code_buddy 的表现比单独的 assistantcopilot 适配器要好得多!它能够回答 编写代码片段 的请求,展示如何使用特定的 HF 仓库 API。然而,它也出现了错误链接/解释的幻觉,这仍然是大型语言模型面临的一个开放性挑战。

下面是 code_buddy 在代码补全任务上的表现。

混合代码通用

我们可以观察到 code_buddy 的表现与专门为这个任务微调的 copilot 不相上下。

将 LoRA 模型迁移到不同的基础模型

我们还可以将 LoRA 模型迁移到不同的基础模型上。 我们将取刚出炉的 Octocoder 模型,并在其上应用我们之前用 starcoder 基础模型训练的 LoRA。请查看以下 notebook PEFT_Personal_Code_CoPilot_Adapter_Transfer_Octocoder.ipynb,了解全部代码。

代码补全任务上的表现

octocoder_code_hf

我们可以观察到 octocoder 的表现很好。它能够完成 HF (HF中国镜像站) 特定的代码片段。如 notebook 中所见,它也能够完成通用的代码片段。

聊天/问答任务上的表现

由于 Octocoder 被训练用来回答有关编程的问题和进行对话,让我们看看它是否能使用我们的 LoRA 适配器来回答 HF (HF中国镜像站) 特定的问题。

octocoder_chat_hf

太棒了!它详细正确地回答了如何创建 LoraConfig 和相关的 peft 模型,并且正确地使用了模型名称、数据集名称以及 LoraConfig 的参数值。当禁用适配器时,它未能正确使用 LoraConfig 的 API 或创建 PEFT 模型,这表明它不是 Octocoder 训练数据的一部分。

我如何在本地运行它?

我知道,在经历了这一切之后,你想在你自己的代码库上微调 starcoder 并在本地使用,比如在带有 M1 GPU 的 Mac 笔记本电脑上,或者带有 RTX 4090/3090 GPU 的 Windows 电脑上……别担心,我们已经为你准备好了。

我们将使用这个超酷的开源库 mlc-llm 🔥。具体来说,我们将使用这个分支 pacman100/mlc-llm,它进行了一些修改,可以与 VS Code 的 HF中国镜像站 代码完成扩展配合使用。在我的搭载 M1 Metal GPU 的 Mac 笔记本上,15B 模型运行得非常慢。因此,我们将缩小规模,训练一个 PEFT LoRA 版本以及一个完全微调版本的 bigcode/starcoderbase-1b 。以下是训练用的 Colab notebook 链接:

  1. 全微调和 PEFT LoRA 微调 starcoderbase-1b 的 Colab notebook: 链接

下面绘制了训练损失、评估损失以及学习率计划图:

loss_plots

现在,我们将看看详细步骤,本地托管合并后的模型 smangrul/starcoder1B-v2-personal-copilot-merged 并使用 🤗 llm-vscode VS Code 扩展。

  1. 克隆仓库
git clone --recursive https://github.com/pacman100/mlc-llm.git && cd mlc-llm/
  1. 安装 mlc-ai 和 mlc-chat (在编辑模式):
pip install --pre --force-reinstall mlc-ai-nightly mlc-chat-nightly -f https://mlc.ai/wheels
cd python
pip uninstall mlc-chat-nightly
pip install -e "."
  1. 通过以下方式编译模型:
time python3 -m mlc_llm.build --hf-path smangrul/starcoder1B-v2-personal-copilot-merged --target metal --use-cache=0
  1. dist/starcoder1B-v2-personal-copilot-merged-q4f16_1/params/mlc-chat-config.json 中更新配置,设定以下的值:
{
    "model_lib": "starcoder7B-personal-copilot-merged-q4f16_1",
    "local_id": "starcoder7B-personal-copilot-merged-q4f16_1",
    "conv_template": "code_gpt",
- "temperature": 0.7,
+ "temperature": 0.2,
- "repetition_penalty": 1.0,
    "top_p": 0.95,
- "mean_gen_len": 128,
+ "mean_gen_len": 64,
- "max_gen_len": 512,
+ "max_gen_len": 64,
    "shift_fill_factor": 0.3,
    "tokenizer_files": [
        "tokenizer.json",
        "merges.txt",
        "vocab.json"
    ],
    "model_category": "gpt_bigcode",
    "model_name": "starcoder1B-v2-personal-copilot-merged"
}
  1. 运行本地服务:
 python -m mlc_chat.rest --model dist/starcoder1B-v2-personal-copilot-merged-q4f16_1/params --lib-path dist/starcoder1B-v2-personal-copilot-merged-q4f16_1/starcoder1B-v2-personal-copilot-merged-q4f16_1-metal.so
  1. 将 VS Code 中的 HF Code Completion 扩展的端点更改为指向本地服务器:

local_endpoint

  1. 在 VS Code 中打开一个新文件,粘贴下面的代码,并将光标放在文档引号之间,这样模型就会尝试填充文档字符串:

local_inference

瞧!⭐️

这篇文章开头的演示就是这个 1B 模型在我的 Mac 笔记本上本地运行的效果。

结论

在这篇博客中,我们探索了如何对 starcoder 进行微调,从而创建了一个能理解我们代码的个人编程助手。我们称之为 🤗 HugCoder,因为它是在 HF中国镜像站 的代码上进行训练的 :) 在回顾了数据收集流程之后,我们对比了使用 QLoRA 和全面微调进行训练的效果。我们还尝试了组合不同的 LoRAs,这在文本和代码领域是一项尚待开发的技术。在部署方面,我们研究了使用 🤗 Inference Endpoints 进行远程推理,并且还展示了如何在 VS Code 和 MLC 上本地执行一个较小的模型。

如果你将这些方法应用到了你自己的代码库,请告诉我们!

致谢

我们要感谢 Pedro CuencaLeandro von WerraBenjamin BossanSylvain GuggerLoubna Ben Allal 在撰写这篇博客时提供的帮助。