个人 Copilot:训练你自己的编码助手
在不断发展的编程和软件开发领域,对效率和生产力的追求催生了非凡的创新。其中一项创新便是代码生成模型的出现,例如 Codex、StarCoder 和 Code Llama。这些模型在生成类人代码片段方面展现了卓越的能力,从而显示出作为编码助手的巨大潜力。
然而,虽然这些预训练模型可以在一系列任务上表现出色,但一个激动人心的可能性正悄然出现:根据你的特定需求定制代码生成模型。想象一下,可以在企业范围内利用个性化的编码助手。
在这篇博文中,我们将展示我们如何创建了 HugCoder 🤗,一个在 huggingface
GitHub 组织的公开仓库代码内容上微调的代码 LLM。我们将讨论我们的数据收集工作流、训练实验以及一些有趣的结果。这将使你能够基于自己的专有代码库创建个人 copilot。我们还会为你留下几个该项目的进一步扩展方向以供实验。
让我们开始吧 🚀
数据收集工作流
我们期望的数据集在概念上很简单,我们这样构建它:
仓库名称 | 仓库中的文件路径 | 文件内容 |
--- | --- | --- |
--- | --- | --- |
使用 Python GitHub API 从 GitHub 抓取代码内容非常直接。但是,根据仓库数量和仓库内代码文件的数量,人们可能很容易遇到 API 速率限制问题。
为了防止此类问题,我们决定将所有公共仓库克隆到本地,并从本地提取内容,而不是通过 API。我们使用了 Python 的 multiprocessing
模块来并行下载所有仓库,如此下载脚本所示。
一个仓库通常可能包含非代码文件,如图像、演示文稿和其他资产。我们对抓取这些不感兴趣。我们创建了一个扩展名列表来将它们过滤掉。对于解析除 Jupyter Notebook 之外的代码文件,我们简单地使用了“utf-8”编码。对于 notebook,我们只考虑代码单元格。
我们还排除了所有与代码不直接相关的文件路径。这些包括:.git
、__pycache__
和 xcodeproj
。
为了使这些内容的序列化相对内存友好,我们使用了分块和 feather 格式。有关完整实现,请参阅此脚本。
最终的数据集可在 Hub 上找到,它看起来像这样:
对于这篇博客,我们考虑了基于 stargazers 数量排名前 10 的 Hugging Face 公开仓库。它们是以下这些:
['transformers', 'pytorch-image-models', 'datasets', 'diffusers', 'peft', 'tokenizers', 'accelerate', 'text-generation-inference', 'chat-ui', 'deep-rl-class']
这是我们用来生成该数据集的代码,而这是 Hub 上的数据集。以下是它的一个快照:
为了降低项目复杂性,我们没有考虑对数据集进行去重。如果你有兴趣为生产应用应用去重技术,这篇博文是在代码 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 内存
- 权重:2 字节 (混合精度训练)
- 权重梯度:2 字节
- 使用 Adam 优化器时的状态:原始 FP32 权重需要 4 字节 + 一阶和二阶矩估计需要 8 字节
- 将上述所有相加,每个参数的成本:每个参数 16 字节
- 155 亿参数模型 -> 248GB GPU 内存,这还不包括存储中间激活所需的大量内存 -> 至少需要 4 块 A100 80GB GPU
由于硬件要求巨大,我们将使用QLoRA进行参数高效微调。以下是使用 QLoRA 微调 StarCoder 的最低 GPU 内存要求:
可训练参数:110,428,160 || 总参数:15,627,884,544 || 可训练百分比:0.7066097761926236
- 基础模型权重:0.5 字节 * 155.1 亿冻结参数 = 7.755 GB
- 适配器权重:2 字节 * 1.1 亿可训练参数 = 0.22GB
- 权重梯度:2 字节 * 1.1 亿可训练参数 = 0.12GB
- 使用 Adam 优化器时的状态:4 字节 * 1.1 亿可训练参数 * 3 = 1.32GB
- 将以上所有相加 -> 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 和梯度检查点来克服这个问题。
- 对于 QLoRA 加上 Flash Attention V2 和梯度检查点,在单个 A100 40GB GPU 上,模型占用的总内存为 26 GB,批量大小为 4。
- 对于使用 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 全分片数据并行加速大模型训练。
资源
- 代码库:链接。它使用了 Transformers 中最近添加的 Flash Attention V2 支持。
- FSDP 配置:fsdp_config.yaml
- 模型:bigcode/stacoder
- 数据集:smangrul/hf-stack-v1
- 微调后的模型: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:在低资源硬件上对十亿级模型进行参数高效微调。
资源
- 代码库:链接。它使用了 Transformers 中最近添加的 Flash Attention V2 支持。
- Colab notebook:链接。请确保选择 A100 GPU 并设置高内存。
- 模型:bigcode/stacoder
- 数据集:smangrul/hf-stack-v1
- 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 倍。
为了确保我们的 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 微调模型和全量微调模型的填充结果。
在上面的例子中,GitHub Copilot 的补全方向是正确的,但帮助不大。另一方面,QLoRA 和全量微调模型的补全正确地填充了整个函数调用以及必要的参数。然而,它们之后也增加了很多噪音。这可以通过后处理步骤来控制,将补全限制在闭合括号或换行符处。请注意,QLoRA 和全量微调模型都产生了质量相似的结果。
在上面的第二个例子中,GitHub Copilot 没有给出任何补全。这可能是因为 🤗 PEFT 是一个较新的库,尚未成为 Copilot 训练数据的一部分,这正是我们试图解决的问题。另一方面,QLoRA 和全量微调模型的补全正确地填充了整个函数调用以及必要的参数。再次注意,QLoRA 和全量微调模型都给出了质量相似的生成结果。全量微调模型和 peft 模型的带有各种示例的推理代码分别在 Full_Finetuned_StarCoder_Inference.ipynb 和 PEFT_StarCoder_Inference.ipynb 中提供。
因此,我们可以观察到两种变体的生成结果都符合预期。太棒了!🚀
如何在 VS Code 中使用它?
你可以使用 🤗 llm-vscode VS Code 扩展,并结合 🤗 Inference EndPoints 托管模型,轻松在 VS Code 中配置自定义代码补全 LLM。我们将在下面介绍所需的步骤。你可以在推理端点文档中了解有关部署端点的更多详细信息。
设置推理端点
以下是我们创建自定义推理端点所遵循的步骤截图。我们使用了我们的 QLoRA 模型,将其导出为完整的*合并*模型,以便在 transformers
中轻松加载。
设置 VS Code 扩展
只需按照安装步骤操作。在设置中,替换下面字段中的端点,使其指向你部署的 HF 推理端点。
使用起来会像下面这样:
微调你自己的代码聊天助手
到目前为止,我们训练的模型都是专门为代码补全任务训练的个人 copilot。它们没有经过训练来进行对话或问答。Octocoder
和 StarChat
是这类模型的优秀例子。本节简要介绍如何实现这一点。
资源
- 代码库:链接。它使用了 Transformers 中最近添加的 Flash Attention V2 支持。
- Colab notebook:链接。请确保选择 A100 GPU 并设置高内存。
- 模型:bigcode/stacoderplus
- 数据集:smangrul/code-chat-assistant-v1。混合了
LIMA+GUANACO
,并以可直接训练的格式进行了适当的格式化。 - 训练好的模型:smangrul/peft-lora-starcoderplus-chat-asst-A100-40GB-colab
LoRA 之舞
如果你曾涉足 Stable Diffusion 模型和 LoRA 来制作你自己的 Dreambooth 模型,你可能熟悉将不同 LoRA 以不同权重组合,或者将一个 LoRA 模型用于与其训练时不同的基础模型的概念。在文本/代码领域,这仍然是未被探索的领域。我们在这方面进行了实验,并观察到了非常有前景的发现。你准备好了吗?我们开始吧!🚀
混合搭配 LoRA
PEFT 目前支持 3 种组合 LoRA 模型的方式:linear
、svd
和 cat
。更多详情请参考 tuners#peft.LoraModel.add_weighted_adapter。
我们的 notebook Dance_of_LoRAs.ipynb 包含了所有的推理代码和各种 LoRA 加载组合,比如在 starcoder
上加载聊天助手而不是我们微调时使用的基础模型 starcodeplus
。
在这里,我们将考虑 2 种能力(聊天/问答
和代码补全
)在 2 种数据分布(前 10 名 hf 公开代码库
和通用代码库
)上的表现。这给了我们 4 个维度来进行一些定性评估分析。
首先,让我们考虑聊天/问答
任务。
如果我们禁用适配器,我们观察到该任务在两个数据集上都失败了,因为基础模型(starcoder
)只用于代码补全,不适合聊天/问答
。启用 copilot
适配器的表现与禁用情况类似,因为这个 LoRA 也是专门为代码补全而微调的。
现在,让我们启用 assistant
适配器。
我们可以观察到,关于 scrapy
的通用问题得到了正确的回答。然而,对于 HF 代码相关的问题,它却失败了,因为这部分内容不在其预训练数据中。
现在我们来考虑一下代码补全
任务。
禁用适配器后,我们观察到通用的 two-sum 代码补全按预期工作。然而,HF 代码补全失败,给出了错误的 LoraConfig
参数,因为基础模型在其预训练数据中没有见过它。启用 assistant
的表现与禁用情况类似,因为它是在自然语言对话上训练的,其中不包含任何 Hugging Face 代码库。
现在,让我们启用 copilot
适配器。
我们可以观察到,copilot
适配器在两种情况下都正确了。因此,它在处理 HF 特定代码库以及通用代码库时,都能按预期进行代码补全。
现在,作为用户,我希望结合 assistant
和 copilot
的能力。这将使我能够在 IDE 中编码时使用它进行代码补全,同时也可以将它作为一个聊天机器人来回答我关于 API、类、方法、文档的问题。它应该能够回答诸如“我该如何使用 x”、“请为 Y 写一个代码片段”之类关于我的代码库的问题。
PEFT 允许你通过 add_weighted_adapter
来实现这一点。让我们创建一个新的适配器 code_buddy
,给予 assistant
和 copilot
适配器相等的权重。
现在,让我们看看 code_buddy
在聊天/问答
任务上的表现如何。
我们可以观察到,code_buddy
的表现比单独使用 assistant
或 copilot
适配器要好得多!它能够回答“写一个代码片段”的请求,以展示如何使用特定的 HF 仓库 API。然而,它也会幻觉出错误的链接/解释,这仍然是 LLM 面临的一个开放挑战。
以下是 code_buddy
在代码补全任务上的表现。
我们可以观察到,code_buddy
的表现与专门为此任务微调的 copilot
相当。
将 LoRA 迁移到不同的基础模型
我们也可以将 LoRA 模型迁移到不同的基础模型上。我们将采用新鲜出炉的 Octocoder
模型,并将我们上面用 starcoder
基础模型训练的 LoRA 应用于其上。请参阅以下 notebook PEFT_Personal_Code_CoPilot_Adapter_Transfer_Octocoder.ipynb 以获取完整代码。
在代码补全任务上的表现
我们可以观察到 `octocoder` 的表现非常出色。它能够补全 HF 特定的代码片段。正如 notebook 中所示,它也能够补全通用的代码片段。
在聊天/问答任务上的表现
由于 Octocoder 经过训练可以回答问题并进行编码相关的对话,让我们看看它是否能使用我们的 LoRA 适配器来回答 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 链接如下:
starcoderbase-1b
全量微调和 PEFT LoRA 微调的 Colab notebook:链接
训练损失、评估损失以及学习率调度如下所示:
现在,我们将详细介绍如何在本地托管合并后的模型 smangrul/starcoder1B-v2-personal-copilot-merged,并将其与 🤗 llm-vscode VS Code 扩展一起使用。
- 克隆仓库
git clone --recursive https://github.com/pacman100/mlc-llm.git && cd mlc-llm/
- 安装 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 "."
- 通过以下命令编译模型
time python3 -m mlc_llm.build --hf-path smangrul/starcoder1B-v2-personal-copilot-merged --target metal --use-cache=0
- 在
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"
}
- 运行本地服务器
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
- 在 VS Code 中更改 HF 代码补全扩展的端点,使其指向本地服务器
- 在 VS Code 中打开一个新文件,粘贴下面的代码,并将光标放在文档字符串引号之间,以便模型尝试填充文档字符串
搞定!⭐️
本文开头的演示就是这个 1B 模型在我的 Mac 笔记本上本地运行。
结论
在这篇博文中,我们看到了如何微调 starcoder
来创建一个了解我们代码的个人 copilot。我们称之为 🤗 HugCoder,因为我们是在 Hugging Face 的代码上训练它的 :) 在介绍了数据收集工作流之后,我们比较了使用 QLoRA 和全量微调的训练。我们还通过组合不同的 LoRA 进行了实验,这在文本/代码领域仍然是一种未被充分探索的技术。在部署方面,我们研究了使用 🤗 Inference Endpoints 进行远程推理,并展示了在 VS Code 和 MLC 上运行一个较小模型的设备端执行。
如果你在自己的代码库上使用这些方法,请告诉我们!
致谢
我们要感谢 Pedro Cuenca、Leandro von Werra、Benjamin Bossan、Sylvain Gugger 和 Loubna Ben Allal 在撰写这篇博文时提供的帮助。