在 AMD MI300X 上部署 LLM:最佳实践
概要: vLLM 在 AMD MI300X 上释放了惊人的性能,对于 Llama 3.1 405B 模型,实现了比 Text Generation Inference (TGI) 高 1.5 倍的吞吐量和快 1.7 倍的首个令牌生成时间 (TTFT)。对于 Llama 3.1 70B 模型,它也实现了比 TGI 高 1.8 倍的吞吐量和快 5.1 倍的 TTFT。本指南探讨了 8 个关键的 vLLM 设置,以最大化效率,向您展示如何在 AMD 上利用开源 LLM 推理的力量。如果您只想查看最佳参数,请跳转到快速入门指南。
vLLM 与 TGI 在 8 个 MI300X 上运行 Llama 3.1 405B 模型的性能比较(BF16 精度,32 QPS)。
vLLM 与 TGI 在 8 个 MI300X 上运行 Llama 3.1 70B 模型的性能比较(BF16 精度,32 QPS)。
引言
Meta 最近宣布,他们 100% 的 Llama 3.1 405B 模型实时流量都在 AMD MI300X GPU 上运行,展示了 AMD 的 ROCm 平台在大型语言模型 (LLM) 推理方面的强大性能和就绪状态。这个令人兴奋的消息恰逢 ROCm 6.2 的发布,新版本为 vLLM 支持带来了重大改进,使得利用 AMD GPU 的强大功能进行 LLM 推理变得前所未有的容易。
ROCm 是 AMD 对 CUDA 的回应,可能对某些人来说不太熟悉,但它正迅速成熟为一个强大且高性能的替代方案。借助 vLLM,驾驭这种力量比以往任何时候都更容易。我们将向您展示如何操作。
vLLM 与 TGI 的对比
vLLM 在 AMD MI300X 上释放了惊人的性能,对于 Llama 3.1 405B 模型,实现了比 Text Generation Inference (TGI) 高 1.5 倍的吞吐量和快 1.7 倍的首个令牌生成时间 (TTFT)。对于 Llama 3.1 70B 模型,它也实现了比 TGI 高 1.8 倍的吞吐量和快 5.1 倍的 TTFT。
在 Llama 3.1 405B 模型上,在各种每秒查询数 (QPS) 场景中,vLLM 在首个令牌生成时间 (TTFT) 和吞吐量方面都表现出明显优于 TGI 的性能。对于 TTFT,在优化的配置下,在 16 QPS 时,vLLM 的响应时间平均比 TGI 快约 3.8 倍。在吞吐量方面,vLLM 始终优于 TGI,在优化的设置中,在 1000 QPS 下,ShareGPT 数据集上的最高吞吐量为 5.76 请求/秒,而 TGI 为 3.55 请求/秒。
即使在默认配置下,vLLM 也显示出优于 TGI 的性能。例如,在 16 QPS 时,vLLM 的默认配置实现了 4.05 请求/秒的吞吐量,而 TGI 为 2.58 请求/秒。这种性能优势在不同的 QPS 水平上都得以保持,突显了 vLLM 在处理大型语言模型推理任务方面的效率。
vLLM 与 TGI 在 8 个 MI300X 上运行 Llama 3.1 405B 模型的性能比较(BF16 精度,QPS 为 16、32、1000;命令详见附录)。
如何以最佳性能运行 vLLM
关键设置和配置
我们已经广泛测试了各种 vLLM 设置,以确定 MI300X 的最佳配置。以下是我们所了解到的:
- 分块预填充:经验法则是,在大多数情况下,为了获得更好的性能,暂时在 MI300X 上禁用它。
- 多步调度:使用多步调度可以显著提高 GPU 利用率和整体性能。将
--num-scheduler-steps
设置为 10 到 15 之间的值,以优化 GPU 利用率和性能。 - 前缀缓存:将前缀缓存与分块预填充相结合可以提高特定场景下的性能。但是,如果用户请求的前缀缓存命中率较低,则可能建议禁用分块预填充和前缀缓存。
- 图形捕获:当处理支持长上下文长度的模型时,将
--max-seq-len-to-capture
设置为 16384。但是,请注意,增加此值并不总是保证性能提升,有时甚至可能由于次优的 bucket 大小而导致性能下降。 - AMD 特定优化:禁用 NUMA 平衡和调整
NCCL_MIN_NCHANNELS
可以进一步提高性能。 - KV 缓存数据类型:为了获得最佳性能,请使用默认的 KV 缓存数据类型,该类型会自动匹配模型的数据类型。
- 张量并行:为了优化吞吐量,请使用可容纳模型权重和上下文的最小张量并行度 (TP),并运行多个 vLLM 实例。为了优化延迟,请将 TP 设置为节点中 GPU 的数量。
- 最大序列数:为了优化性能,请根据 GPU 的内存和计算资源,将
--max-num-seqs
增加到 512 或更高。这可以显著提高资源利用率和吞吐量,尤其适用于处理较短输入和输出的模型。 - 使用 CK Flash Attention:CK Flash Attention 的实现比 Triton 实现快得多。
详细分析和实验
案例 1:分块预填充
分块预填充是 vLLM 中的一项实验性功能,它允许将大型预填充请求分成较小的块,与解码请求一起批量处理。这通过将计算密集型的预填充请求与内存密集型的解码请求重叠来提高系统效率。您可以通过在 LLM 构造函数中设置 --enable_chunked_prefill=True
或使用 --enable-chunked-prefill
命令行选项来启用它。
根据我们运行的实验,我们发现调整分块预填充值比禁用分块预填充功能略有改进。但是,如果您不确定是否启用分块预填充,只需从禁用它开始,通常您应该期望获得比使用默认设置更好的性能。这对于 MI300X GPU 来说是特定的。
案例 2:调度器步数
多步调度已在 vLLM v0.6.0 中引入,有望提高 GPU 利用率和整体性能。正如这篇博客文章中详细介绍的那样,这种性能提升背后的魔力在于它能够执行一次调度和输入准备,并连续运行模型多个步骤而不会中断 GPU。通过巧妙地将 CPU 开销分散到这些步骤中,它可以显著减少 GPU 空闲时间并大幅提升性能。
要启用多步调度,请将 --num-scheduler-steps
参数设置为大于 1 的数字,这是默认值(值得一提的是,我们发现使用多步调度可能会导致收益递减,其值越高,收益越小,因此,我们坚持使用 15 的上限)。
案例 3:分块预填充和前缀缓存
分块预填充和前缀缓存是 vLLM 中的优化技术,通过将大型预填充分解为更小的块以实现高效批处理,以及重用跨查询共享前缀的缓存 KV(键值)计算,从而提高性能。
默认情况下,如果模型的上下文长度超过 32k 个令牌,vLLM 将自动启用分块预填充功能。预填充分块的最大令牌数默认设置为 512。
在我们深入研究图表之前,我们将首先尝试解释实验中使用的术语。全新运行指的是前缀缓存内存完全未填充的情况。第二次运行指的是在全新运行后再次重新运行基准测试脚本。一般来说,当在第二次运行中重新运行 ShareGPT 基准数据集时,我们获得了大约 50% 的前缀缓存命中率。
查看下面的图表,我们可以对这个实验做出三个观察结果。
- 基于 Bar 2(红色)与基线(蓝色)的比较,性能有巨大提升。
- 基于 Bar 3(黄色)、Bar 5(橙色)和 Bar 6(青色)与基线的比较,分块预填充性能取决于用户请求输入提示长度分布。
- 在我们的实验中,我们发现 Bar 3(黄色)和 Bar 4(绿色)的前缀缓存命中率约为 0.9% 和 50%。基于 Bar 3(黄色)和 Bar 4(绿色)与基线和 Bar 2(红色)的比较,这告诉我们,如果用户请求没有较高的前缀缓存命中率,则可以考虑禁用分块预填充和前缀缓存,这可能是一个不错的经验法则。
案例 4:要捕获的最大序列长度
vLLM 中的 --max-seq-len-to-capture
参数控制 CUDA/HIP 图形可以处理的最大序列长度,这通过捕获和重放 GPU 操作来优化性能。如果序列超过此长度,系统将恢复为 eager 模式,逐个执行操作,这效率可能较低。这适用于常规模型和编码器-解码器模型。
我们的基准测试揭示了一个有趣的趋势:增加 --max-seq-len-to-capture
并不总是能提高性能,有时甚至会降低性能。这可能是由于 vLLM 如何为不同的序列长度创建 bucket 造成的。
原因如下:
- 分桶:vLLM 使用 bucket 对相似长度的序列进行分组,从而为每个 bucket 优化图形捕获。
- 最佳 Bucket:最初,bucket 是细粒度的(例如,[4, 8, 12,…, 2048, 4096]),允许为各种序列长度进行有效的图形捕获。
- 更粗糙的 Bucket:增加
--max-seq-len-to-capture
可能会导致更粗糙的 bucket(例如,[4, 8, 12, 2048, 8192])。 - 性能影响:当输入序列落入这些更大、不太精确的 bucket 中时,捕获的 CUDA/HIP 图形可能不是最佳的,可能会导致性能下降。
因此,虽然使用 CUDA/HIP 图形捕获更长的序列看起来是有益的,但至关重要的是要考虑对 bucketing 和整体性能的潜在影响。找到最佳的 --max-seq-len-to-capture
值可能需要进行实验,以平衡图形捕获效率和适合您特定工作负载的 bucket 大小。
案例 5:AMD 推荐的环境变量
为了进一步优化 vLLM 在 AMD MI300X 上的性能,我们可以利用 AMD 特定的环境变量。
- 禁用 NUMA 平衡:非均匀内存访问 (NUMA) 平衡有时会阻碍 GPU 性能。正如 AMD MAD 存储库中建议的那样,禁用它可以防止潜在的 GPU 挂起并提高整体效率。可以使用以下命令实现:
# disable automatic NUMA balancing sh -c 'echo 0 > /proc/sys/kernel/numa_balancing' # check if NUMA balancing is disabled (returns 0 if disabled) cat /proc/sys/kernel/numa_balancing 0
- 调整 NCCL 通信:NVIDIA 集体通信库 (NCCL) 用于 GPU 间通信。对于 MI300X,AMD vLLM 分支性能文档建议将
NCCL_MIN_NCHANNELS
环境变量设置为 112,以潜在地提高性能。
在我们的测试中,启用这两个配置产生了一点性能提升。这与 “NanoFlow:通过最佳的大型语言模型服务吞吐量”论文中的发现一致,该论文表明,虽然优化网络通信是有益的,但由于 LLM 推理主要由计算密集型和内存密集型操作主导,因此影响可能有限。
即使收益可能很小,微调这些环境变量也有助于从您的 AMD 系统中榨取最大性能。
案例 6:KVCache 类型 Auto/FP8
默认情况下,vLLM 将自动分配与模型数据类型匹配的 KV 缓存类型。但是,vLLM 也支持 MI300X 上的原生 FP8,我们可以利用它来减少 KVCache 的内存需求,从而增加模型的可部署上下文长度。
我们通过使用 Auto KVCache 类型和 KV 缓存类型 FP8 进行实验,并将其与默认基线进行比较。我们可以从下图看到,使用 Auto KVCache 类型(红色)比使用设置为 FP8 的 KV 缓存类型(黄色)实现了更高的每秒请求数。理论上,这可能是由于 Llama-3.1-70B-Instruct (bfloat16)
模型中的量化开销造成的,但由于开销成本似乎很小,因此在某些情况下,为了获得 KVCache 需求的巨大减少,这仍然可能是一个不错的权衡。
案例 7:TP 4 和 TP 8 之间的性能差异
张量并行是一种用于分配大型模型计算负载的技术。它的工作原理是将单个张量拆分到多个设备上,从而允许并行处理特定操作或层。这种方法减少了模型的内存占用,并实现了跨多个 GPU 的扩展。
虽然增加张量并行度可以通过提供更多计算资源来提高性能,但收益并不总是线性的。这是因为随着涉及更多设备,通信开销会增加,并且每个 GPU 上的工作负载会减少。考虑到 MI300X 的强大处理能力,每个 GPU 上较小的工作负载实际上可能导致利用率不足,从而进一步阻碍性能扩展。
因此,当优化吞吐量时,我们建议启动多个 vLLM 实例,而不是激进地增加张量并行度。这种方法往往会产生更线性的性能提升。但是,如果最大限度地减少延迟是首要任务,则增加张量并行度可能是更有效的策略。
案例 8:最大(并行)序列数的影响
--max-num-seqs
参数指定每次迭代可以处理的最大序列数。此参数控制批处理中并发请求的数量,从而影响内存使用和性能。在 ShareGPT 基准测试中,由于样本的输入和输出长度较短,托管在 MI300X 上的 Llama-3.1-70B-Instruct
可以处理每次迭代的大量请求。在我们的实验中,即使 --max-num-seqs
设置为 1024,--max-num-seqs
仍然是一个限制因素。
快速入门指南
如果您不确定部署设置和用户请求的分布,您可以
- 使用 CK Flash Attention*(虽然我们这里没有展示,但 CK Flash Attention 的实现比 Triton 对应实现快得多)
export VLLM_USE_TRITON_FLASH_ATTN=0
- 禁用分块预填充
--enable-chunked-prefill=False
- 禁用前缀缓存
- 如果模型支持长上下文长度,请将
--max-seq-len-to-capture
设置为 16384 - 将
--num-scheduler-steps
设置为 10 或 15。 - 设置 AMD 环境
sh -c 'echo 0 > /proc/sys/kernel/numa_balancing'
export NCCL_MIN_NCHANNELS=112
- 根据 GPU 的内存和 GPU 的计算资源,将
--max-num-seqs
增加到 512 及以上。
VLLM_USE_TRITON_FLASH_ATTN=0 vllm serve meta-llama/Llama-3.1-70B-Instruct --host 0.0.0.0 --port 8000 -tp 4 --max-num-seqs 1024 --max-seq-len-to-capture 16384 --served-model-name meta-llama/Llama-3.1-70B-Instruct --enable-chunked-prefill=False --num-scheduler-steps 15 --max-num-seqs 1024
为了快速设置,我们已将 vLLM 0.6.2 (commit: cb3b2b9ba4a95c413a879e30e2b8674187519a93) 的 Docker 镜像编译到 Github Container Registry。要下载镜像
# v0.6.2 post
docker pull ghcr.io/embeddedllm/vllm-rocm:cb3b2b9
# P.S. We also have compiled the image for v0.6.3.post1 at commit 717a5f8
docker pull ghcr.io/embeddedllm/vllm-rocm:v0.6.3.post1-717a5f8
要使用该镜像启动 docker 容器,请运行
sudo docker run -it \
--network=host \
--group-add=video \
--ipc=host \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--device /dev/kfd \
--device /dev/dri \
-v /path/to/hfmodels:/app/model \ # if you have pre-downloaded the model weight, else ignore
ghcr.io/embeddedllm/vllm-rocm:cb3b2b9 \
bash
现在使用我们找到的参数启动 LLM 服务器
VLLM_USE_TRITON_FLASH_ATTN=0 vllm serve meta-llama/Llama-3.1-70B-Instruct --host 0.0.0.0 --port 8000 -tp 4 --max-num-seqs 1024 --max-seq-len-to-capture 16384 --served-model-name meta-llama/Llama-3.1-70B-Instruct --enable-chunked-prefill=False --num-scheduler-steps 15 --max-num-seqs 1024
结论
本指南探讨了 vLLM 在 AMD MI300X GPU 上部署大型语言模型的强大功能。通过细致地调整关键设置,如分块预填充、多步调度和 CUDA 图形捕获,我们已经展示了如何实现相对于标准配置和替代服务解决方案的显著性能提升。vLLM 释放了显著更高的吞吐量和更快的响应时间,使其成为在 AMD 硬件上部署 LLM 的理想选择。
然而,重要的是要承认我们的探索主要集中在具有短输入和输出的通用聊天机器人使用上。还需要进一步研究以优化 vLLM 在特定用例(如摘要或长篇内容生成)中的应用。此外,深入研究 Triton 和 CK attention kernel 之间的性能差异可能会产生进一步的见解。
我们还要感谢 Leonard Lin 的 这篇精彩的博文,该博文介绍了如何进一步优化 vLLM for MI300X,包括 hipBLAS vs hipBLASLt、CK Flash Attention vs Triton Flash Attention、张量并行 vs 流水线并行等。
致谢
这篇博文由 Embedded LLM 团队起草,并感谢 Hot Aisle Inc. 赞助 MI300X 用于 vLLM 基准测试。
附录
服务器规格
以下是令人惊叹的 Hot Aisle 服务器的配置
- CPU:2 个 Intel Xeon Platinum 8470
- GPU:8 个 AMD Instinct MI300X 加速器 我们在基准测试中使用的模型和软件如下
- 模型:meta-llama/Llama-3.1-405B-Instruct 和 meta-llama/Llama-3.1-70B-Instruct
- vLLM (v0.6.2):vllm-project/vllm:用于 LLM 的高吞吐量和内存效率的推理和服务引擎 (github.com) commit: cb3b2b9ba4a95c413a879e30e2b8674187519a93
- 数据集:ShareGPT
- 基准测试脚本:存储库中的 benchmarks/benchmark_serving.py
我们已经从存储库中找到的 Dockerfile.rocm 构建了 ROCm 兼容的 vLLM docker 镜像(我们已经推送了我们用于运行基准测试的 vLLM 版本的 docker 镜像。通过 docker pull ghcr.io/embeddedllm/vllm-rocm:cb3b2b9
获取)。所有基准测试都在 docker 容器实例中运行,并使用 CK Flash Attention 和 VLLM_USE_TRITON_FLASH_ATTN=0
在 4 个 MI300X GPU 上运行。
详细基准测试配置
配置 | 命令 |
---|---|
vLLM 默认配置 | VLLM_RPC_TIMEOUT=30000 VLLM_USE_TRITON_FLASH_ATTN=0 vllm serve Llama-3.1-405B-Instruct -tp 8 --max-num-seqs 1024 --max-num-batched-tokens 1024 |
TGI 默认配置 | ROCM_USE_FLASH_ATTN_V2_TRITON=false TRUST_REMOTE_CODE=true text-generation-launcher --num-shard 8 --sharded true --max-concurrent-requests 1024 --model-id Llama-3.1-405B-Instruct |
vLLM(本指南) | VLLM_RPC_TIMEOUT=30000 VLLM_USE_TRITON_FLASH_ATTN=0 vllm serve Llama-3.1-405B-Instruct -tp 8 --max-seq-len-to-capture 16384 --enable-chunked-prefill=False --num-scheduler-steps 15 --max-num-seqs 1024 |
TGI(本指南) | ROCM_USE_FLASH_ATTN_V2_TRITON=false TRUST_REMOTE_CODE=true text-generation-launcher --num-shard 8 --sharded true --max-concurrent-requests 1024 --max-total-tokens 131072 --max-input-tokens 131000 --model-id Llama-3.1-405B-Instruct |