使用本指南训练出的模型实际运行效果。来自 SkyPilotVicuna 团队。

Meta 在两周前发布了 Llama 2,并在 AI 社区引起了巨大反响。我们认为,其最大的影响在于该模型现已在 宽松许可 下发布,允许商业使用模型权重1。这与 Llama 1 不同,Llama 1 不能用于商业用途。

简单来说:组织现在可以在完全私密的环境中,使用这个基础模型并根据自己的数据(无论是内部文档、客户对话还是代码)对其进行微调,并将其用于商业场景。

在这篇博文中,我们提供了一个分步教程,教您如何做到这一点:在您现有的云环境中,使用 100% 开源工具,根据您自己的数据微调 Llama 2。

为什么?

我们提供一份LLM 微调操作指南,具有以下特点

  • 完全开源:尽管出现了许多托管式微调服务,但本指南仅使用包括 SkyPilot 在内的开源、Apache 2.0 软件。因此,本教程可用于任何环境,无论是研究还是商业用途。
  • 一切都在您自己的云中:所有计算资源、数据和训练好的模型都保留在您自己的云环境(VPC、VM、存储桶)中。您拥有完全控制权,无需信任第三方托管解决方案。
  • 自动多云:相同的教程适用于所有超大规模云提供商(AWS、GCP、Azure、OCI 等)或 GPU 云(Lambda)。请参阅 SkyPilot 支持的 7+ 云提供商
  • 高 GPU 可用性:通过使用您有权访问的所有区域/云,SkyPilot 会自动为用户作业找到最高的 GPU 可用性。无需手动操作控制台。
  • 最低成本:SkyPilot 自动寻找最便宜的区域/可用区/云。本教程支持在 Spot 实例上进行微调,并具有自动恢复功能,可将成本降低 3 倍。

通过本教程,用户不仅可以以最小的努力入门,还可以确保其数据和模型检查点不会被任何第三方托管解决方案看到。

教程:在 Llama 2 上训练您自己的 Vicuna

Vicuna 是首批基于 Llama 1 微调的高质量 LLM 之一。我们(Wei-Lin 和 Zhanghao)作为 Vicuna 的共同创建者,更新了用于训练 Vicuna 的精确教程,使其基于 Llama 2,并由此产生了这份微调指南。

在本教程中,我们将展示如何使用 SkyPilot 在 Llama 2 上训练您自己的 Vicuna,轻松找到云上的可用 GPU,同时将成本降低至仅约 300 美元。

本教程(从 GitHub 下载)的编写方式使您可以轻松复制粘贴并运行。有关详细说明,请参阅下一节

前提条件

  1. 申请访问 Llama-2 模型

前往申请页面并申请访问模型权重。

  1. 从 HuggingFace 获取访问令牌

在 HuggingFace 上此处生成一个只读访问令牌。前往 HuggingFace 上 Llama-2 模型的页面此处并申请访问。确保您的 HuggingFace 邮箱与 Meta 申请中的邮箱一致。可能需要 1-2 天才能获批。

  1. 下载本教程并安装 SkyPilot
git clone https://github.com/skypilot-org/skypilot.git
cd skypilot
pip install -e ".[all]"
cd ./llm/vicuna-llama-2

将访问令牌粘贴到 train.yaml

envs:
  HF_TOKEN: <your-huggingface-token>  # Change to your own huggingface token

训练数据和模型身份

默认情况下,我们使用 ShareGPT 数据以及 hardcoded_questions.py 中的身份问题。

可选:要使用自定义数据,您可以更改 train.yaml 中的以下行

setup: |
  ...
  wget https://hugging-face.cn/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json  -O $HOME/data/sharegpt.json
  ...  

上述 json 文件是一个数组,每个元素的格式如下(对话可以在 humangpt 之间进行多轮)

{
  "id": "i6IyJda_0",
  "conversations": [
    {
      "from": "human",
      "value": "How to tell if a customer segment is well segmented? In 3 bullet points."
    },
    {
      "from": "gpt",
      "value": "1. Homogeneity: The segment should consist of customers who share similar characteristics and behaviors.\n2. Distinctiveness: The segment should be different from other segments in terms of their characteristics and behaviors.\n3. Stability: The segment should remain relatively stable over time and not change drastically. The characteristics and behaviors of customers within the segment should not change significantly."
    }
  ]
},

可选:要让模型了解其身份,您可以更改 hardcoded_questions.py 中的硬编码问题

注意:使用 ShareGPT 数据训练的模型可能存在商业使用限制。将其替换为您自己的数据以便进行商业使用。

在任何云上启动训练

使用一条命令开始训练

sky launch --down -c vicuna train.yaml \
  --env ARTIFACT_BUCKET_NAME=<your-bucket-name> \
  --env WANDB_API_KEY=<your-wandb-api-key>

这将在拥有 8x A100-80GB Spot GPU 可用的最便宜的云上启动训练作业。

提示:您可以在 https://wandb.ai/settings 获取 WANDB_API_KEY。要禁用 Weights & Biases,只需省略该 --env 标志。

提示:您可以将 ARTIFACT_BUCKET_NAME 设置为一个新的存储桶名称,例如 <whoami>-tmp-bucket,SkyPilot 将为您创建该存储桶。

改用按需实例以解锁更多云:在 train.yaml 中,我们请求使用 Spot 实例

resources:
  accelerators: A100-80GB:8
  disk_size: 1000
  use_spot: true

然而,目前 Spot A100-80GB:8 仅在 GCP 上受支持。按需版本在 AWS、Azure、GCP、Lambda 等上受支持。(提示:查看 sky show-gpus A100-80GB:8 的便捷输出!)

要使用这些云,添加 --no-use-spot 标志以请求按需实例

sky launch --no-use-spot ...

Optimizer

可选:尝试训练 13B 模型

sky launch -c vicuna train.yaml \
  --env ARTIFACT_BUCKET_NAME=<your-bucket-name> \
  --env WANDB_API_KEY=<your-wandb-api-key> \
  --env MODEL_SIZE=13

使用 Spot 实例将成本降低 3 倍

SkyPilot Managed Spot 是构建在 SkyPilot 之上的一个库,可帮助用户在 Spot 实例上运行作业而无需担心中断。这是 LMSYS 组织用于训练 Vicuna 第一个版本(更多细节可在其发布博文示例中找到)的工具。通过它,训练成本可以从 1000 美元降低到300 美元

要使用 SkyPilot Managed Spot,只需在上述命令中将 sky launch 替换为 sky spot launch

sky spot launch -n vicuna train.yaml \
  --env ARTIFACT_BUCKET_NAME=<your-bucket-name> \
  --env WANDB_API_KEY=<your-wandb-api-key>

部署您的模型

训练完成后,您可以使用一条命令在您自己的云环境中部署您的模型

sky launch -c serve serve.yaml --env MODEL_CKPT=<your-model-checkpoint>/chatbot/7b

serve.yaml 中,我们指定启动一个 Gradio 服务器来部署位于 <your-model-checkpoint>/chatbot/7b 的模型检查点。

demo
使用 Gradio 部署训练好的模型。

提示:您也可以切换到更便宜的加速器,例如 L4,以节省成本,只需在上述命令中添加 --gpus L4

教程详解

让我们来详解 train.yaml

资源供应

resources 字典指定了微调作业所需的资源

resources:
  accelerators: A100-80GB:8
  disk_size: 1000
  use_spot: true

SkyPilot 根据这个云无关的规格,自动寻找能够满足要求的最佳(最便宜且可用)云位置和实例类型。然后它会供应资源,并通过重试来处理容量不足错误,最后启动作业。

对用户而言,不再需要手动操作不同的云控制台来查找哪些区域有可用的 GPU VM。

提示:如果您愿意,仍然可以硬编码指定要使用的特定云/区域/可用区或实例类型。请查看 YAML 规范了解所有可设置的参数。

最大化云 GPU 可用性

每个资源供应请求(sky launchsky spot launch)都会在给定的搜索空间中自动寻找资源。这种自动故障转移机制是最大化 GPU 可用性的关键

SkyPilot&rsquo;s auto-failover across regions and clouds to improve GPU availability
SkyPilot 在跨区域和跨云之间进行自动故障转移以提高 GPU 可用性。

您给予 SkyPilot 的搜索空间与它能帮助找到的 GPU 可用性之间存在权衡。例如,假设您的笔记本电脑可以访问 AWS 和 GCP(请参阅 sky check 的有用输出)。那么,如果您在 resources 字典中指定

  • cloud: aws, region: us-east-1:SkyPilot 只会搜索该区域。如果该区域没有所需的 GPU 可用,则资源供应请求将失败。(您可以传递 -r/--retry-until-up 来持续重试。)
  • cloud: aws:SkyPilot 将在 AWS 的所有可用区/区域中搜索。
  • 无位置限制:SkyPilot 将在“天空”中搜索:所有您有权访问的可用区/区域/云(本例中为 AWS 和 GCP)。

正如您所见,最后一种情况真正实现了透明的多云使用,并应提供最高的可用性。话虽如此,如果用户在特定位置有特殊配额或折扣,也有参数可以缩小位置范围。

使用哪些 GPU

关键要求是 GPU 显存必须足够大。在 YAML 中我们使用了 A100-80GB:8,这对于无性能损失地微调 LLM 至关重要。

然而,如果您选择使用 QLoRA 或其他 PEFT(参数高效微调)方法,则可以显著放宽 GPU 要求。请参阅此处使用 A10:1 (24GB) 微调 7B 基础模型的一个示例,或 Tobi Lütke 的针对 Llama1 的 QLoRA 教程

要使用不同的 GPU 类型/数量,只需更改 YAML 中的 resources.accelerators 字段,或通过 --gpus <name>:<count> 标志覆盖 CLI 命令。

使用这个便捷工具查找不同云中的 GPU 及其实时价格

sky show-gpus

检查点保存到云存储

在 YAML 的 file_mounts 部分,我们指定了一个名为 $ARTIFACT_BUCKET_NAME(通过环境变量传入)的存储桶应该被挂载到 VM 内部的 /artifacts 路径

file_mounts:
  /artifacts:
    name: $ARTIFACT_BUCKET_NAME
    mode: MOUNT

启动作业时,我们只需将 /artifacts 传递给其 --output_dir 标志,训练程序会将所有检查点和其他产物写入此处

torchrun ... --output_dir /artifacts/chatbot/${MODEL_SIZE}b ...

换句话说,您的训练程序使用这个挂载路径,就像它在 VM 本地一样!写入挂载目录的文件/目录会自动同步到云存储桶。

提示:您可以传入一个新名称(例如,<whoami>-tmp-bucket)让 SkyPilot 自动为您创建一个新存储桶,也可以使用现有存储桶的名称。

提示:所有创建的存储桶都是私有存储桶,位于您自己的云账户中。

您可以从相应的对象存储中检查或下载输出。Google Cloud Storage (GCS) 上的示例存储桶

Example bucket

要了解更多关于与云存储交互的信息,请参阅 SkyPilot 存储文档

如何处理 Spot 实例中断

Spot GPU 具有极高的成本效益,在主要云上比按需实例便宜约 2.5 倍–3 倍

» sky show-gpus A100-80GB:8

GPU        QTY  CLOUD   INSTANCE_TYPE              DEVICE_MEM  vCPUs  HOST_MEM  HOURLY_PRICE  HOURLY_SPOT_PRICE  REGION
A100-80GB  8    Azure   Standard_ND96amsr_A100_v4  -           96     1924GB    $ 32.770      $ 12.977           eastus
A100-80GB  8    GCP     a2-ultragpu-8g             -           96     1360GB    $ 40.222      $ 12.866           asia-southeast1
...

有趣的是,我们观察到Spot GPU 有时比按需 GPU 更容易获得。最近在 GCP 的 A100 上观察到了这种情况。

问题是:我们如何轻松处理 Spot 中断?

托管式 vs. 非托管式

SkyPilot 支持两种使用 Spot 实例的模式

  • 托管式 Spot:使用 sky spot launch 启动的作业。SkyPilot 通过在下一个最便宜且可用的云位置重新启动新的 Spot 集群来自动恢复中断(这就是您给 SkyPilot 的搜索空间越大,GPU 可用性越高的原因!)。托管作业会在恢复的集群上自动重启。
  • 非托管式 Spot:使用 sky launch 启动并请求 Spot 实例的作业(可以是 YAML 中的 resources.use_spot 字段或 CLI 的 --use-spot 标志)。在此模式下,Spot 中断不会自动恢复。

我们建议在开发中使用非托管式 Spot,因为它允许轻松登录和调试(ssh <my cluster>),但要注意突然中断。一旦您验证训练正常进行,切换到托管式 Spot 进行完整运行以启用自动恢复。

处理部分检查点

回想一下,我们将检查点保存到云存储桶。这非常方便,因为当 Spot 作业在新的恢复实例上重新启动时,它可以从存储桶中重新加载最新的检查点并从那里恢复训练。

我们教程中的 train.py 使用了 HuggingFace 的 Trainer,它本身支持从检查点恢复

    trainer.train(resume_from_checkpoint=resume_from_checkpoint)

然而,需要处理一个边缘情况:在写入检查点期间,实例可能会突然被中断,导致只有部分状态写入云存储桶。发生这种情况时,从损坏的部分检查点恢复将导致程序崩溃。

为了解决这个问题,我们添加了一个简单的 transformers.TrainerCallback,它会

  1. 在每个检查点保存后写入一个 complete 指示文件
  2. 程序重启时,删除存储桶中任何不完整的检查点

相关代码片段

class CheckpointCallback(transformers.TrainerCallback):

    def on_save(self, args, state, control, **kwargs):
        """Add complete indicator to avoid incomplete checkpoints."""
        if state.is_world_process_zero:
            ckpt_path = os.path.join(args.output_dir,
                                     f'checkpoint-{state.global_step}')
            with open(os.path.join(ckpt_path, 'complete'), 'w') as f:
                f.write('')
            print(f'Checkpoint {state.global_step} saved.')
        torch.distributed.barrier()


def cleanup_incomplete_checkpoints(output_dir):
    """Remove incomplete checkpoints."""
    checkpoints = list(pathlib.Path(output_dir).glob('checkpoint-*'))
    ...  # Sort by step
    for checkpoint in checkpoints:
        if not (checkpoint / 'complete').exists():
            print(f'Removing incomplete checkpoint {checkpoint}')
            shutil.rmtree(checkpoint)
        else:
            ...
            break

在程序启动时将这些连接起来

def train():
    ...
    if local_rank == 0:
        cleanup_incomplete_checkpoints(training_args.output_dir)
    torch.distributed.barrier()
    ...
    trainer.add_callback(CheckpointCallback)
    trainer.train(resume_from_checkpoint=resume_from_checkpoint)
    ...

就是这样!只需大约 30 行代码,我们的训练程序现在就可以完全抵抗 Spot 中断。您现在可以使用 sky spot launch 进行所有微调运行,从而实现高成本节省和 GPU 可用性。

监控

本教程已经设置了 Weights & Biases 集成以监控指标。

如何轻松地在同一图表中查看同一作业的不同恢复过程?在 YAML 中,我们将 --run_name $SKYPILOT_TASK_ID 传递给主程序,并且此环境变量保证在同一 Spot 作业的不同恢复过程中保持一致。(您也可以指定自己的运行名称。)

通过此设置,您可以使用 W&B 的筛选功能对 run_name 进行筛选,以查看同一运行的不同恢复过程

Example bucket

在这里,作业被中断了一次,导致产生了 2 个片段。注意到重叠了吗?这是由于恢复时固有的一些进度损失。例如,第一个片段保存了一个检查点,取得了一些进展(但未到达下一个检查点),然后被中断。第二个片段必须重新加载最后一个检查点并重新执行一些工作。

结论

借助 Llama 2 和 SkyPilot,您现在可以在您自己的云账户中使用您的私有数据微调您自己的 LLM,并且成本效益高。我们希望本教程能帮助实践者在私有环境中释放 LLM 的力量。祝您微调愉快!

后续步骤

  • 微调完成后,在您自己的云中托管一个微调后的 Llama 2 LLM:示例
  • 使用 vLLM 项目将您的 LLM 服务提速高达 24 倍:示例博客

有问题或反馈?请通过 SkyPilot GitHubSlack 联系我们!


  1. 从技术上讲,只要您的月活跃用户不超过 7 亿即可。请参阅许可。 ↩︎