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

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

在不断发展的编程和软件开发领域,对效率和生产力的追求催生了非凡的创新。其中一项创新便是代码生成模型的出现,例如 CodexStarCoderCode Llama。这些模型在生成类人代码片段方面展现了卓越的能力,从而显示出作为编码助手的巨大潜力。

然而,虽然这些预训练模型可以在一系列任务上表现出色,但一个激动人心的可能性正悄然出现:根据你的特定需求定制代码生成模型。想象一下,可以在企业范围内利用个性化的编码助手。

在这篇博文中,我们将展示我们如何创建了 HugCoder 🤗,一个在 huggingface GitHub 组织的公开仓库代码内容上微调的代码 LLM。我们将讨论我们的数据收集工作流、训练实验以及一些有趣的结果。这将使你能够基于自己的专有代码库创建个人 copilot。我们还会为你留下几个该项目的进一步扩展方向以供实验。

让我们开始吧 🚀

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

对于这篇博客,我们考虑了基于 stargazers 数量排名前 10 的 Hugging Face 公开仓库。它们是以下这些:

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

这是我们用来生成该数据集的代码,而这是 Hub 上的数据集。以下是它的一个快照:hf-stack-v1

为了降低项目复杂性,我们没有考虑对数据集进行去重。如果你有兴趣为生产应用应用去重技术,这篇博文是在代码 LLM 背景下关于该主题的绝佳资源。

微调你自己的个人 Copilot

在本节中,我们将展示如何微调以下模型:bigcode/starcoder (155 亿参数)、bigcode/starcoderbase-1b (10 亿参数)、Deci/DeciCoder-1b (10 亿参数)。我们将在所有实验中使用单个 A100 40GB Colab Notebook,并采用 🤗 PEFT(参数高效微调)。此外,我们还将展示如何在一台拥有 8 个 A100 80GB GPU 的机器上,使用 🤗 Accelerate 的 FSDP 集成来完全微调 bigcode/starcoder (155 亿参数) 模型。训练目标是中间填充 (FIM),即将训练序列的某些部分移到末尾,然后自回归地预测重新排序后的序列。

为什么选择 PEFT?全量微调成本高昂。让我们用一些数字来直观感受一下:

全量微调所需的最低 GPU 内存

  1. 权重:2 字节 (混合精度训练)
  2. 权重梯度:2 字节
  3. 使用 Adam 优化器时的状态:原始 FP32 权重需要 4 字节 + 一阶和二阶矩估计需要 8 字节
  4. 将上述所有相加,每个参数的成本:每个参数 16 字节
  5. 155 亿参数模型 -> 248GB GPU 内存,这还不包括存储中间激活所需的大量内存 -> 至少需要 4 块 A100 80GB GPU

由于硬件要求巨大,我们将使用QLoRA进行参数高效微调。以下是使用 QLoRA 微调 StarCoder 的最低 GPU 内存要求:

可训练参数:110,428,160 || 总参数:15,627,884,544 || 可训练百分比:0.7066097761926236

  1. 基础模型权重:0.5 字节 * 155.1 亿冻结参数 = 7.755 GB
  2. 适配器权重:2 字节 * 1.1 亿可训练参数 = 0.22GB
  3. 权重梯度:2 字节 * 1.1 亿可训练参数 = 0.12GB
  4. 使用 Adam 优化器时的状态:4 字节 * 1.1 亿可训练参数 * 3 = 1.32GB
  5. 将以上所有相加 -> 9.51 GB ~ 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 上,模型占用的总内存为 26 GB批量大小为 4
  2. 对于使用 FSDP 加上 Flash Attention V2 和梯度检查点的全量微调,每块 GPU 占用的内存介于 70 GB 到 77.6 GB 之间,每 GPU 批量大小为 1

请参考模型内存使用工具,轻松计算在 🤗 Hugging Face Hub 上托管的模型进行训练和大型模型推理需要多少 vRAM。

全量微调

我们将探讨如何使用 PyTorch 全分片数据并行(FSDP)技术,在 8 块 A100 80GB GPU 上对 bigcode/starcoder(150 亿参数)进行全量微调。有关 FSDP 的更多信息,请参考使用 PyTorch FSDP 微调 Llama 2 70B使用 PyTorch 全分片数据并行加速大模型训练

资源

  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

我们将探讨如何使用 QLoRA 在单个 A100 40GB GPU 上使用 🤗 PEFT 对 bigcode/starcoder(150 亿参数)进行微调。有关 QLoRA 和 PEFT 方法的更多信息,请参考使用 bitsandbytes、4 位量化和 QLoRA 让 LLM 更易于使用🤗 PEFT:在低资源硬件上对十亿级模型进行参数高效微调

资源

  1. 代码库:链接。它使用了 Transformers 中最近添加的 Flash Attention V2 支持。
  2. Colab notebook:链接。请确保选择 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 (150 亿参数) 和微调后的 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 的新加权适配器来降低其影响。

我们将看两个代码填充的例子,其中模型的任务是填充由 <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 托管模型,轻松在 VS Code 中配置自定义代码补全 LLM。我们将在下面介绍所需的步骤。你可以在推理端点文档中了解有关部署端点的更多详细信息。

设置推理端点

以下是我们创建自定义推理端点所遵循的步骤截图。我们使用了我们的 QLoRA 模型,将其导出为完整的*合并*模型,以便在 transformers 中轻松加载。ie_1 ie_2

设置 VS Code 扩展

只需按照安装步骤操作。在设置中,替换下面字段中的端点,使其指向你部署的 HF 推理端点。

vs_code_endpoint

使用起来会像下面这样:

code_completion

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

到目前为止,我们训练的模型都是专门为代码补全任务训练的个人 copilot。它们没有经过训练来进行对话或问答。OctocoderStarChat 是这类模型的优秀例子。本节简要介绍如何实现这一点。

资源

  1. 代码库:链接。它使用了 Transformers 中最近添加的 Flash Attention V2 支持。
  2. Colab notebook:链接。请确保选择 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 模型和 LoRA 来制作你自己的 Dreambooth 模型,你可能熟悉将不同 LoRA 以不同权重组合,或者将一个 LoRA 模型用于与其训练时不同的基础模型的概念。在文本/代码领域,这仍然是未被探索的领域。我们在这方面进行了实验,并观察到了非常有前景的发现。你准备好了吗?我们开始吧!🚀

混合搭配 LoRA

PEFT 目前支持 3 种组合 LoRA 模型的方式:linearsvdcat。更多详情请参考 tuners#peft.LoraModel.add_weighted_adapter

我们的 notebook Dance_of_LoRAs.ipynb 包含了所有的推理代码和各种 LoRA 加载组合,比如在 starcoder 上加载聊天助手而不是我们微调时使用的基础模型 starcodeplus

在这里,我们将考虑 2 种能力(聊天/问答代码补全)在 2 种数据分布(前 10 名 hf 公开代码库通用代码库)上的表现。这给了我们 4 个维度来进行一些定性评估分析。

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

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

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

assistant_chat_generic 基于通用代码的问答

assistant_chat_hf 基于 HF 代码的问答

我们可以观察到,关于 scrapy 的通用问题得到了正确的回答。然而,对于 HF 代码相关的问题,它却失败了,因为这部分内容不在其预训练数据中。

现在我们来考虑一下代码补全任务。

禁用适配器后,我们观察到通用的 two-sum 代码补全按预期工作。然而,HF 代码补全失败,给出了错误的 LoraConfig 参数,因为基础模型在其预训练数据中没有见过它。启用 assistant 的表现与禁用情况类似,因为它是在自然语言对话上训练的,其中不包含任何 Hugging Face 代码库。

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

copilot_code_generic

我们可以观察到,copilot 适配器在两种情况下都正确了。因此,它在处理 HF 特定代码库以及通用代码库时,都能按预期进行代码补全。

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

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

combining_loras 组合多个适配器

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

mix_chat_hf

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

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

mix_code_generic

我们可以观察到,code_buddy 的表现与专门为此任务微调的 copilot 相当。

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

我们也可以将 LoRA 模型迁移到不同的基础模型上。我们将采用新鲜出炉的 Octocoder 模型,并将我们上面用 starcoder 基础模型训练的 LoRA 应用于其上。请参阅以下 notebook PEFT_Personal_Code_CoPilot_Adapter_Transfer_Octocoder.ipynb 以获取完整代码。

在代码补全任务上的表现

octocoder_code_hf

我们可以观察到 `octocoder` 的表现非常出色。它能够补全 HF 特定的代码片段。正如 notebook 中所示,它也能够补全通用的代码片段。

在聊天/问答任务上的表现

由于 Octocoder 经过训练可以回答问题并进行编码相关的对话,让我们看看它是否能使用我们的 LoRA 适配器来回答 HF 特定的问题。

octocoder_chat_hf

太棒了!它详细地正确回答了如何创建 LoraConfig 和相关的 peft 模型,并正确使用了模型名称、数据集名称以及 LoraConfig 的参数值。禁用适配器后,它无法正确使用 LoraConfig 的 API 或创建 PEFT 模型,这表明这部分内容不在 Octocoder 的训练数据中。

如何在本地运行它?

我知道,经过这一切,你想在你的代码库上微调 starcoder,并在你的消费级硬件上本地使用它,比如带 M1 GPU 的 Mac 笔记本、带 RTX 4090/3090 GPU 的 Windows 电脑……别担心,我们已经为你准备好了。

我们将使用这个超酷的开源库 mlc-llm 🔥。具体来说,我们将使用这个 fork pacman100/mlc-llm,它做了一些修改以使其能与 VS Code 的 Hugging Face 代码补全扩展一起工作。在我的带 M1 Metal GPU 的 Mac 笔记本上,15B 模型速度非常慢。因此,我们将选择小一点的模型,训练一个 PEFT LoRA 版本以及一个 bigcode/starcoderbase-1b 的全量微调版本。训练的 colab notebook 链接如下:

  1. starcoderbase-1b 全量微调和 PEFT LoRA 微调的 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 代码补全扩展的端点,使其指向本地服务器

local_endpoint

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

local_inference

搞定!⭐️

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

结论

在这篇博文中,我们看到了如何微调 starcoder 来创建一个了解我们代码的个人 copilot。我们称之为 🤗 HugCoder,因为我们是在 Hugging Face 的代码上训练它的 :) 在介绍了数据收集工作流之后,我们比较了使用 QLoRA 和全量微调的训练。我们还通过组合不同的 LoRA 进行了实验,这在文本/代码领域仍然是一种未被充分探索的技术。在部署方面,我们研究了使用 🤗 Inference Endpoints 进行远程推理,并展示了在 VS Code 和 MLC 上运行一个较小模型的设备端执行。

如果你在自己的代码库上使用这些方法,请告诉我们!

致谢

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

社区

注册登录 以发表评论