经济实惠地自托管 LLaMA 3.1 70B(或任何约 70B 的大型语言模型)

社区文章 发布于 2024 年 8 月 20 日

llama featured image

如果您正在阅读本指南,那么 Meta 的 Llama 3 系列模型无需介绍。它们于 2024 年 4 月 发布,是目前最好、最可靠的开源大型语言模型,可用于生产环境,直接与 OpenAI 的 GPT-4o 和 Anthropic 的 Claude 3.5 Sonnet 等闭源替代方案竞争。人们经常问我如何托管这些模型用于个人用途、创建合成数据集以及用于私有数据的 RAG。不幸的是,我家里没有强大的 GPU(或多个 GPU)来运行 70B 这样的大型模型,所以我只能求助于使用云计算并积极寻找降低成本的方法。如果您也面临同样的情况,或者只是想知道如何实现,那么本文就是为您准备的。

作为一个有趣的福利,我还会展示如何在本地运行一个类似 ChatGPT 的用户界面来与您的私有模型进行交互。

友情提示:这将是一个直接明了的指南,所以不要指望我详细解释每个步骤。我的简短回答是“阅读文档”

简而言之

  • 云服务:Runpod(我将在另一个教程中介绍 GCP)
  • 推理引擎:vLLM(我将在未来的教程中介绍 TGI
  • 监控与代理:LiteLLM
  • 数据库:PostgreSQL 容器 或 Supabase
  • GPU:4x A40 或 2x A100
  • Runpod 模板:vLLM 最新版

我创建了 Runpod 模板,请使用它。它也能帮我获得一些积分。

我们应该部署在哪里?

在尝试了几种选择之后,我发现最经济实惠的方法是将模型托管在 Runpod 上。它是一个专为 AI 和机器学习应用程序设计的云计算平台,允许用户通过其 Pod 和无服务器计算选项执行 GPU 和 CPU 资源的代码。以下是我对 Runpod 的看法:

  • 非常容易上手
  • 极其经济实惠
  • 灵活使用我们自己的 Docker 镜像,实现开发环境的无缝迁移(这是我使用它的一个重要原因)
  • 提供各种规格的 GPU

在本文中,首先,在决定我们需要什么 GPU 之前,我建议遵循以下粗略指南。

1. 模型大小:

既然我们讨论的是 70B 参数模型,要以 16 位浮点精度部署,我们需要约 140GB 的内存。这足够大,无法在市场上任何单个 GPU 中运行。但对于 4 位(INT4)模型,我们只需要约 45GB 的内存,但这也会导致质量略有下降,请注意这一点。在本指南中,我将向您展示如何部署一个 70B 模型,以:

现在我们已经知道了这些模型的大致内存/磁盘需求,最好还是查看模型在 Huggingface 上的页面,以获取权重的确切大小,因为一个 70B 模型通常不完全是 70B,例如 LLaMA 3.1 70B 模型是 70.6B。

因此,16 位精度大约需要 148GB(高估了),8 位精度大约是其一半,而对于 4 位精度,由于我们使用的是 AWQ,最好也查看一下 -> hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4,它大约是 45GB。

2. 选择 GPU:技术考量

选择用于托管 LLaMA 3.1 70B 等大型语言模型的 GPU 时,需要考虑几个技术因素:

注意:如果您已经了解这些内容,只是将本文作为部署指南,请直接跳到 2.8

2.1 显存容量

主要考虑因素是 GPU 的显存(视频内存)容量。LLaMA 3.1 70B,顾名思义,有 700 亿个参数。在 FP16 精度下,这需要大约 148GB 的内存来存储模型权重。但是,还需要额外的内存用于:

  • 上下文窗口
  • KV 缓存

根据经验,您需要至少模型大小的 1.2 倍到 1.5 倍的显存。但对于更长的上下文,您需要的远不止经验法则,一个 70B 模型,如果上下文长度为 32k,大约需要 14GB 的显存,并且它会随着上下文长度线性增加。

2.2 计算能力 (FLOPS)

FLOPS(每秒浮点运算次数)衡量 GPU 的原始计算能力。对于大型语言模型 (LLM),我们特别关注张量 FLOPS,它表示 GPU 高效执行矩阵乘法运算的能力。

几种不同 GPU 的比较(前两种是目前最划算的!)

GPU 型号 内存 TF32 (TFLOPS) FP16/BF16 (TFLOPS) INT8 (TOPS) FP8 (FLOPS)
A100 80GB 156 312 624 不适用
H100 80GB 989 1979 3958 3958
A40 48GB 37.4 149.7 299.4 不适用

更高的 FLOPS 通常意味着更快的推理时间(每秒更多令牌)。约 300 TFLOPS 意味着每秒 300 万亿次操作。

顺便说一句,在大多数 GPU 中,BF16 比 FP16 快得多...如果您使用的是 Ampere 系列或更高版本的 GPU,请随意将 torch_dtype 属性设置为 bfloat16

2.3 内存带宽

内存带宽对大型模型至关重要,因为它决定了数据在显存和 GPU 核心之间移动的速度。它以 GB/s 为单位衡量。

  • A100:最高 1935 GB/s
  • H100:最高 3350 GB/s
  • A40:696 GB/s

更高的内存带宽减少了等待数据的时间,从而提高了整体性能。

2.4 Tensor 核心

现代 NVIDIA GPU 包含 Tensor 核心,这是用于矩阵乘法和 AI 工作负载的专用单元。Tensor 核心的数量和效率显著影响 LLM 的性能。

2.5 精度支持

考虑您将运行模型的精度:

  • FP32(32 位浮点):最高精度,但内存占用最大
  • FP16(16 位浮点):精度和内存效率之间的良好平衡
  • FP8(8 位浮点):精度降低,但仍优于 INT8,但并非所有 GPU 都支持此功能。(据我所知,只有 Nvidia Hopper 系列 GPU 支持此功能)
  • INT8(8 位整数量化):精度降低,但内存占用显著减少

一些 GPU(例如 A100)提供混合精度功能,可优化性能。

2.6 多 GPU 设置

对于像 LLaMA 3.1 70B 这样大的模型,通常需要多 GPU 设置。考虑:

  • NVLink 支持高带宽 GPU 到 GPU 通信
  • PCIe 带宽用于 GPU 和 CPU 之间的数据传输

2.7 成本-性能权衡

在寻求经济实惠的托管方案时:

  1. 考虑使用多个消费级 GPU(例如 RTX 4090),而不是单个高端数据中心 GPU。
  2. 探索量化技术以减少内存需求。
  3. 寻找为 AI 工作负载提供有竞争力的定价的 GPU 云服务商。(因此选择了 Runpod,JarvisLabs.ai 也是我的最爱之一)

通过平衡这些因素,您可以找到最具成本效益的 GPU 解决方案来托管 LLaMA 3.1 70B,同时保持可接受的性能。

2.8 GPU 的选择

考虑到这些因素,以及之前使用这些 GPU 的经验,确定我的个人需求,并查看 Runpod 上 GPU 的成本(可以在这里找到),我决定为每种部署类型选择以下 GPU Pod:

  • Llama 3.1 70B FP16:4x A40 或 2x A100
  • Llama 3.1 70B INT8:1x A100 或 2x A40
  • Llama 3.1 70B INT4:1x A40

此外,在撰写本文时,A40 的价格仅为每小时 0.35 美元,非常经济实惠。

A40 Price in Runpod

如果您的预算充足,我推荐选择 H100 等 Hopper 系列显卡。如果预算有限,A100、A6000、A6000-Ada 或 A40 应该足够好。如果您仍想进一步降低成本(假设 A40 pod 的价格上涨),可以尝试使用 8x 3090。

推理引擎

vLLM 是目前在自定义硬件上托管大型语言模型的热门选择。这是因为它包含了许多已被发现的优化:

  • 使用分页注意力机制实现高效的内存管理
  • CUDA/HIP 图形执行
  • Flash Attention
  • 以及更多。

这也允许我们公开一个遵循 OpenAI API 格式的 API,这使得与使用相同格式的预编写工作流程集成变得更容易。

创建 Runpod 实例

在 Runpod 上创建实例相当简单,如果您遇到任何问题,请参阅他们的官方文档。

  1. 在控制面板上,进入 Pods -> Deploy
  2. 在这里,我们将不得不选择我们需要的 GPU 实例,我将选择 4x A40 用于 16 位模型。
  3. 您还需要选择正确的模板,然后编辑环境变量,并检查是否正确设置了 HF_TOKEN 密钥。
  4. 同时,请确保 TCP 端口 8000 已代理,否则 API 将无法对外暴露。
  5. 编辑存储(卷磁盘),将其设置为 180GB,这对于 16 位模型应该足够了。

您的配置应类似于:Final Config

但这还不是全部,您还需要编辑容器启动命令 (CMD),它会告诉 vLLM 要拉取哪个模型并进行推理设置。您可以通过点击 Edit Template(编辑模板)然后编辑 Container Start Command(容器启动命令)来完成此操作。

LLaMA 3.1 70B 16 位配置:

--host 0.0.0.0 --port 8000 --model meta-llama/Meta-Llama-3.1-70B-Instruct --dtype bfloat16 --enforce-eager --gpu-memory-utilization 0.95 --api-key sk-IrR7Bwxtin0haWagUnPrBgq5PurnUz86 --max-model-len 8128 --tensor-parallel-size 4

这里重要的参数有(嗯……所有)

  • 模型:您当然要选择正确的模型
  • 数据类型 (dtype):如果您使用的是 Ampere 系列 GPU 或更高版本(我们正在使用),请使用 bfloat16;否则,请使用 float16
  • 强制即时执行 (enforce-eager):我曾遇到过一些 AsyncEngineDead 问题,如果没有此设置。目前 vLLM 似乎没有针对此问题的修复,因此您必须启用即时模式。
  • GPU 内存利用率 (gpu-memory-utilization):这取决于您希望有多少余量,我通常将其设置为 0.90.95
  • API 密钥 (api-key):我提供了一个示例 API 密钥,您可以随意编辑。
  • 最大模型长度 (max-model-len):这是模型的最大上下文长度(输入 + 输出),这再次取决于内存和您的用例。
  • 张量并行大小 (tensor-parallel-size):这是您用于分布式推理的 GPU 数量。

LLaMA 3.1 70B 8 位配置:

--host 0.0.0.0 --port 8000 --model meta-llama/Meta-Llama-3.1-70B-Instruct --dtype bfloat16 --enforce-eager --gpu-memory-utilization 0.95 --api-key sk-IrR7Bwxtin0haWagUnPrBgq5PurnUz86 --max-model-len 8128 --tensor-parallel-size 2 --quantization bitsandbytes --load-format bitsandbytes

变更点

  • 量化:我们将其设置为 bitsandbytes
  • 加载格式:我们也将此设置为 bitsandbytes
  • 张量并行大小:通过使用 8 位,我们将内存需求减半。

注意:我在 vLLM 中使用 8 位模型时遇到过问题,如果您遇到任何问题,请参阅文档或他们的 GitHub issues 线程。

LLaMA 3.1 70B 4 位配置:

--host 0.0.0.0 --port 8000 --model hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4 --enforce-eager --gpu-memory-utilization 0.98 --api-key sk-IrR7Bwxtin0haWagUnPrBgq5PurnUz86 --max-model-len 8128 --quantization awq

变更点

  • 模型:我们使用的是来自 HuggingFace 的预量化模型,采用 AWQ 格式。
  • 量化:我们将其设置为 awq,因为我们的源模型使用了这种量化方法。
  • 张量并行大小:这不再需要,因为 4 位模型可以放入单个 A40 GPU 的内存中。

现在,我们终于完成了所有配置步骤。您可以启动实例了。

注意:根据模型的不同,vLLM 可能需要一些时间来下载模型并提供服务。您可以在 Runpod 的日志选项卡中查看日志。

使用自托管模型

您可以选择直接使用您的自托管端点,或者使用 LiteLLM 设置代理,您可以在其中拥有多个部署实例、用于不同用例的多个模型等……此步骤是完全可选的,但它可以为您的 API 提供良好的可追溯性和治理。如果您打算以团队形式使用这些 API,它会特别有用。

直接使用 API

要查找 API 的 URL,请点击实例上的“连接”,然后点击端口 8000,您将获得一个 {"detail": "Not Found"} 响应,这是因为 API 实际上存在于 /v1/ 路由中。所以复制 URL 并在末尾添加 /v1

RunPod connect

之后使用 API 应该就相当简单了

import openai

OPENAI_BASE_URL = "https://runpod-instance-id-8000.proxy.runpod.net/v1" # Note the /v1 at the end
OPENAI_API_KEY = "sk-ABCDEFGHIJKLMNOPQRSTUVWZ" # Make sure to replace with the right one

SYSTEM_PROMPT = "You are a helpful AI assistant"
TEST_PROMPT = "What is Entropy?"

client = openai.OpenAI(
    api_key=OPENAI_API_KEY, 
    base_url=OPENAI_BASE_URL,
)

response = client.chat.completions.create(
    model="meta-llama/Meta-Llama-3.1-70B-Instruct",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": TEST_PROMPT}
    ],
)

通过 LiteLLM Proxy 使用

根据我的经验,使用 LiteLLM 有以下优点(即使是个人使用,我也推荐):

  • 统一接口:通过单个 API 简化与多个 LLM 提供商的集成。
  • 效率:优化架构降低了计算需求,使其更易于访问。
  • 可扩展性:良好地适应各种硬件配置,而不会损失性能。
  • 强大的功能:支持文本生成和图像创建等多种任务。
  • 错误处理:自动重试和回退机制确保连续性。
  • 成本效益:通过最大限度地减少对高端资源的需求来降低运营成本。

LiteLLM 是一个强大、易于访问的工具,可以高效地利用多个语言模型。

您可以将 LiteLLM 部署到任何您想部署的地方。我将在我的一个 Linux 服务器上以 Docker 方式运行它。我建议您查阅 LiteLLM 的文档以获取更多详细信息。

首先,我们必须创建一个包含所有模型端点的 yaml 配置文件。我的配置如下所示:

model_list:
  - model_name: vllm-llama3.1-8b
    litellm_params:
      model: openai/meta-llama/Meta-Llama-3.1-8B-Instruct
      api_base: https://runpod-instance-id-8000.proxy.runpod.net/v1 # Make sure to use the right URL
      api_key: "os.environ/VLLM_API_KEY"

  - model_name: vllm-llama3.1-70b
    litellm_params:
      model: openai/meta-llama/Meta-Llama-3.1-70B-Instruct
      api_base: https://runpod-instance-id-8000.proxy.runpod.net/v1 # Make sure to use the right URL
      api_key: "os.environ/VLLM_API_KEY"

  - model_name: vllm-llama3.1-70b-4bit
    litellm_params:
      model: openai/hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4
      api_base: https://runpod-instance-id-8000.proxy.runpod.net/v1 # Make sure to use the right URL
      api_key: "os.environ/VLLM_API_KEY"

而且,我们还需要一个 Docker Compose 文件(docker-compose.yaml)来创建在本地运行的 LiteLLM 服务,并使用 Postgres 数据库。(当然,请确保您的机器上已安装 Docker)

version: '3.8'

services:
  litellm-database:
    image: ghcr.io/berriai/litellm-database:main-latest
    container_name: litellm-database
    ports:
      - "4000:4000"
    volumes:
      - ./config.yaml:/app/config.yaml
    environment:
      LITELLM_MASTER_KEY: sk-YOUR-KEY-HERE # This is the key with which you can access all your models
      DATABASE_URL: postgres://postgres:yourpassword@postgres:5432/litellmdb
      GROQ_API_KEY: gsk_yougrokapikeyhere
      VLLM_API_KEY: sk-yourvllmapikeyhere
    depends_on:
      - postgres
    command: ["--config", "/app/config.yaml", "--detailed_debug"]

  postgres:
    image: postgres:15
    container_name: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: yourpassword
      POSTGRES_DB: litellmdb
    volumes:
      - litellm_postgres_data:/var/lib/postgresql/data

volumes:
  litellm_postgres_data:

如果您不想使用本地数据库,您随时可以用 Supabase 替换它。

现在您已经创建了 compose 文件和配置文件,是时候运行它了。

sudo docker compose up -d

然后,检查容器日志,看看一切是否顺利。如果一切正常,您可以通过 http://0.0.0.0:4000 访问代理,并通过 http://0.0.0.0:4000/ui 访问用户界面。您可以在用户界面中做很多事情,请查阅文档。

现在您可以通过代理使用您的 API 了

import openai

OPENAI_BASE_URL = "http://0.0.0.0:4000/v1" # If you've hosted it on a cloud server use that IP/DNS here
OPENAI_API_KEY = "sk-ABCDEFGHIJKLMNOPQRSTUVWZ" # Make sure to replace with the right one

SYSTEM_PROMPT = "You are a helpful AI assistant"
TEST_PROMPT = "What is Entropy?"

client = openai.OpenAI(
    api_key=OPENAI_API_KEY, 
    base_url=OPENAI_BASE_URL,
)

response = client.chat.completions.create(
    model="meta-llama/Meta-Llama-3.1-8B-Instruct",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": TEST_PROMPT}
    ],
    # Here you can add tags like these for monitoring API usage
    extra_body={
        "metadata": {
            "tags": ["taskName:simple_api_test", "provider:vllm_runpod"]
        }
    }
)

福利:通过聊天界面使用 API

我们将使用 OpenWebUI 来实现这一点,步骤非常简单。您需要再次为 UI 设置一个 Docker 容器。这是我的 docker-compose.yaml 文件:

version: '3'

volumes:
  open-webui:

services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    environment:
      - OPENAI_API_KEY=sk_ABCDEFGHIJKLMNOPQRSTUVWXYZ # This is our LiteLLM / Hosted server API key
      - OPENAI_API_BASE_URL=https://api.together.xyz/v1 # This is our LiteLLM / Hosted server's base URL
      - DATABASE_URL=postgres://YOURPOSTGRESURLHERE:5432/postgres
    ports:
      - "3000:8080"
    volumes:
      - open-webui:/app/backend/data
    restart: always

基本上就是这样,您可以在自己的用户界面上与您的模型进行聊天了。

结论

恭喜!您现在已经设置好自己的 LLaMA 3.1 70B(或任何大约 70B 的大型语言模型)实例,并学习了如何高效地与其交互。让我们回顾一下我们所取得的成就:

  1. 我们探讨了托管大型语言模型的技术考虑因素,重点关注 GPU 选择和内存需求。
  2. 我们使用 Runpod 建立了经济高效的云部署,利用其 GPU 实例支持不同的量化级别(FP16、INT8 和 INT4)。
  3. 我们配置了 vLLM 作为我们的推理引擎,利用其对大型语言模型的优化。
  4. 我们实现了 LiteLLM 代理,以更好地进行 API 管理、可扩展性和监控。

通过遵循本指南,您现在拥有一个强大、可定制且相对经济实惠的设置,可用于运行最先进的语言模型。这为个人项目、研究甚至小规模生产部署开辟了无限可能。

请记住,人工智能领域发展迅速,因此请密切关注可能进一步提高性能或降低成本的新优化、量化技术和硬件选项。

祝您在您新部署的 LLM 上愉快地进行实验!无论您是将其用于创意写作、代码生成,还是构建您的下一个大型人工智能驱动应用程序,您现在都拥有了按照自己的方式和基础设施实现这些目标的工具。

社区

注册登录 以评论