Kubernetes 上 LLM 冷启动速度提升 25 倍 

更新于 2025 年 5 月 8 日 • 作者: Fog Dong 和 Sherlock Xu

您在 Kubernetes 上启动了一个 Llama 3.1 8B 容器,但启动却花费了 10 分钟。为什么?显然,容器基础设施在 GenAI 工作负载下表现不佳。

快速冷启动对于确保您的部署能够快速响应流量变化而不会出现大量延迟至关重要。通过按需启动实例,您可以动态扩展并避免过度配置计算能力。这种响应性在保持高水平服务的同时降低了成本。

认识到这一点,我们彻底重新设计了 LLM 冷启动策略,从拉取镜像到将模型权重加载到 GPU 内存。最终,我们开发了一种解决方案,实现了 25 倍的冷启动速度提升,并支持真正的按需模型加载

LLM-cold-starts-comparison.png

* 确切的 LLM 冷启动持续时间可能因您的网络或磁盘带宽、GPU 性能和模型大小而异

在这篇博文中,我们将分享我们是如何做到的。首先,让我们看一些基本问题。

LLM 容器包含什么?

用于服务 GenAI 工作负载的容器通常比简单的 Web 应用程序容器大得多。例如,一个 Llama 3.1 8B 容器可能包含

  • CUDA: 2.8GB
  • Torch: 1.4GB
  • 其他库: 1GB
  • 模型权重: 15GB
container-comparison.png

这将总大小增加到 20.2 GB,在许多情况下,随着模型大小的增长还会更高。相比之下,一个 python3.10-slim 容器可能只有大约 154 MB。

冷启动和热启动是什么意思?

在 LLM 容器部署的上下文中,冷启动意味着 Kubernetes 节点之前从未运行过此部署。因此,它没有本地容器镜像,并且必须从头下载和初始化镜像层。

相比之下,热启动意味着节点之前已经加载过模型或构建过容器镜像。在这种情况下,容器镜像已缓存在节点上,并且模型文件可能在页面缓存中可用。

启动 LLM 容器时会发生什么?

容器镜像由多个压缩层组成,每个层通常以压缩 tarball(例如 gzipzstd)的形式存储在注册中心(例如 Docker Hub)中。此外,镜像还包含一个 JSON 清单,列出了其层、基础镜像和配置详细信息。

这种分层架构虽然对于版本控制和共享公共组件很有效,但在扩展以容纳大型 AI 模型时会带来挑战。让我们详细分解一下 LLM 容器在 Kubernetes 上启动时究竟发生了什么。

  1. 下载: 从注册中心拉取所有镜像层。
  2. 解压: 解压缩层并将其写入磁盘。
  3. 启动: 设置挂载、网络、GPU 访问和其他配置。
  4. 加载模型: 将权重加载到 GPU 内存。

对于 Llama 3.1 8B,冷启动时间线可能如下所示

Deployment Timeline: Image Pull: 5–6 min ███████ Layer Extract: 3–4 min ████ Config & Start: ~2 min █ ───────────────────── Total: ~11 min

需要注意的是,延迟不是由计算或推理引起的,而仅仅是由于准备容器启动

什么在减慢您的 LLM 容器?

现在我们已经介绍了基础知识,让我们深入探讨减慢 LLM 容器启动速度的主要瓶颈。

1. 容器注册中心和运行时

容器注册中心和运行时最初是为小型镜像(例如 Web 应用程序)设计的。它们在处理 LLM 推理工作负载时可能面临性能挑战。

  • 单线程传输: 大多数注册中心通过单个 HTTP 连接提供镜像层服务。它们不支持多部分或并行分块下载,这使得大型文件下载极其缓慢。
  • 顺序解压: 下载后,层会逐个解压,而不是并行解压,这限制了整体吞吐量。由于容器层使用 gzipzstd 等压缩格式,解压步骤通常是 CPU 密集型的。仅解压一个 50GB 的模型在现代 CPU 上可能需要 5-8 分钟。
  • 写时复制开销: CoW(写时复制)操作增加了开销,特别是对于大型模型,因为运行时需要管理跨多个层的复杂文件映射。这引入了额外的磁盘 I/O 并减慢了文件访问速度。
  • 全有或全无的启动: 容器在所有镜像层下载和解包完成之前无法启动。

2. 存储驱动限制

大多数容器存储驱动不适用于处理大型 GenAI 模型文件

  • 针对小文件优化: 存储驱动针对包含大量小文件的工作负载进行了优化,例如二进制文件、库文件和配置文件。它们提供了元数据缓存和索引等功能。然而,这些优化对 LLM 的益处很小,因为 LLM 通常是大型的单一文件(例如,许多 5GB 的 .safetensors 文件)。
  • 双重写入惩罚: 层会被写入两次,首先是压缩形式(存储在注册中心缓存中),然后在解压到文件系统时再次写入。这意味着一个 15GB 的模型会暂时需要 30GB 的空间,使磁盘写入量翻倍并增加 I/O 负载。

3. 模型加载效率低下

LLM 在部署过程中模型文件的传输和加载方式带来了独特的挑战

  • 从模型中心下载慢: 与容器注册中心类似,Hugging Face 等模型平台并未针对高吞吐量的多部分下载进行优化。这使得直接拉取大型模型文件非常耗时。
  • 顺序模型加载: 模型数据按顺序流经多个环节:远程存储 → 本地磁盘 → 内存 → GPU。下载、解压和磁盘写入阶段之间几乎没有并行化。每个步骤都会增加延迟,特别是当文件太大而无法有效缓存或流式传输时。
  • 无按需流式加载: 模型文件必须完全写入磁盘后才能进行推理。这导致额外的磁盘读写,增加了 I/O 压力并延迟了启动。

这些限制加在一起可以极大地延长启动过程,即使在高带宽网络上也是如此。除了延迟之外,它们还会产生连锁问题:网络饱和、磁盘 I/O 峰值以及基础设施成本增加(由于过度配置资源来管理这些低效率)。

重新思考镜像拉取和模型加载

为了加速 LLM 容器启动,我们必须从根本上重新考虑容器镜像的拉取方式和模型权重的加载方式。

步骤 1:放弃注册中心,改用对象存储

第一个挑战是容器注册中心镜像下载速度慢。鉴于上述限制,我们探索了一种替代方案:直接从对象存储系统拉取容器镜像,例如 Google Cloud Storage (GCS) 和 Amazon S3。

对于 Llama 3.1 8B 容器,对象存储的速度明显更快,我们在测试中镜像拉取时间降至约 10 秒

方法速度 (取决于机器/网络)时间
云注册中心拉取 (GAR/ECR)60 MB/秒~350 秒
内部注册中心拉取 (Harbor)120 MB/秒~170 秒
直接 GCS/S3 下载2 GB/秒 或更高~10 秒

我们将速度提升归因于

  • 并行下载: 对象存储支持多部分并行 Range 请求,充分利用网络带宽。
  • 无需处理清单: 对象存储不需要解析 JSON 清单、单独验证每一层或顺序验证层。这意味着您可以直接将镜像作为原始数据检索。

尽管下载速度更快,但我们注意到解压步骤明显减慢了速度,常常将整体有效吞吐量减半。解压期间的磁盘 I/O 仍然是一个关键瓶颈。

这使得解压步骤成为我们的下一个优化目标

步骤 2:使用 FUSE 跳过解压

我们的突破性进展在于我们意识到可以通过重新思考容器如何访问其文件系统来完全绕过解压。这就是 FUSE(用户空间文件系统) 改变游戏规则的地方。

FUSE 允许非特权用户创建和挂载自定义文件系统,而无需内核级代码或 root 权限。基于 FUSE 的工具,如 stargz-snapshotter,允许容器按需访问镜像数据,无需预先解压所有层。这避免了 CPU 密集型的解压和过多的磁盘 I/O。

通过 FUSE,我们优化了工作流程,实现了模型的按需访问。我们没有将每一层都解压到磁盘,而是将镜像层以未压缩、可查找的 tar 格式保存在对象存储中。这使得容器可以像访问本地文件一样访问它们,将它们视为可查找、惰性加载的文件数据库,即使底层数据仍然远程存在。这意味着容器只有在实际需要时才会从远程存储流式传输单个文件块或数据块。

以可查找的 tar 格式为基础,我们将模型权重与容器镜像分离,并实现了直接加载到 GPU 内存

步骤 3:将模型直接加载到 GPU 内存

在未经优化的情况下,模型权重会被下载、写入磁盘,然后加载到 GPU 内存。这是一个缓慢的过程,因为它涉及顺序操作且 I/O 密集。

我们通过引入零拷贝、基于流的模型加载简化了这一步骤。这意味着模型文件可以直接从远程存储流式传输到 GPU 内存,无需中间的磁盘读写。通过这些优化,我们使 Llama 3.1 8B 的冷启动时间加快了 25 倍。请注意,确切的 LLM 冷启动持续时间可能因您的网络或磁盘带宽、GPU 性能和模型大小而异

LLM-cold-starts-comparison.png

结论

缩短 LLM 容器冷启动时间不仅是一项技术优化,也是您产品的战略优势。对于部署 LLM 的企业来说,更快的冷启动意味着更低的基础设施成本、更高的工程灵活性和更好的用户体验。

通过分享我们的经验,我们希望帮助其他在部署大规模 AI 工作负载时面临类似挑战的人。我们相信这些技术可以惠及更广泛的 AI 工程社区,支持在 Kubernetes 上进行更具可扩展性和成本效益的 LLM 部署。

查看以下资源了解更多信息