如何实现真正的无服务器GPU(20分钟阅读)

TLDR AI 新闻

摘要

Modal介绍了他们开发的四个关键要素,可在几秒而非几分钟内启动无服务器GPU推理副本,从而实现对多变AI工作负载的高效GPU分配。

推理工作负载比训练工作负载更多变且更不可预测。这使得它们非常适合无服务器计算。然而,无服务器计算只有在新副本能随着需求变化快速启动时才有效。本文探讨了Modal如何将AI推理服务器的扩展时间从数千秒缩短到仅几十秒。
查看原文
查看缓存全文

缓存时间: 2026/05/14 00:10

# 如何实现真正无服务器的 GPU 来源:https://modal.com/blog/truly-serverless-gpus 图表展示了在基础云系统和 Modal 上扩展推理服务器的延迟对比 我们正处于推理时代。数十亿到万亿参数的神经网络在专用加速器上以每秒数千万亿次运算的速度运行,用于大规模生成媒体(https://modal.com/blog/runway-chooses-modal-to-power-real-time-inference-for-runway-characters)、编写软件(https://modal.com/blog/lovable-case-study)和折叠蛋白质(https://modal.com/blog/seamless-computational-bio-at-chai-discovery)。 推理工作负载比以往占主导地位的训练工作负载更具可变性和不可预测性。这使得它们天然适合*无服务器计算*——在这种模式下,应用程序在(虚拟)机器之上定义,以便可以更灵活地伸缩以处理可变负载。 但是,无服务器计算只有在新的副本能够快速启动时才有效——速度要与需求变化相匹配,而需求变化可能在秒级发生。天真地启动一个新实例,例如,在 B200 上运行一个服务于数十亿参数 LLM 的 SGLang,可能需要数十分钟,或者因 GPU 可用性问题而停滞数小时。 在 Modal,我们在过去五年中进行了深入的工程工作来解决这个问题。在这篇博客文章中,我们将介绍我们所做的事情。 有四个关键要素: - **云缓冲**:维护一个小的健康、空闲 GPU 缓冲池,以承接新的负载 - **自定义文件系统**:从内容寻址、多层云原生缓存中懒加载容器镜像 - **检查点/恢复**:通过将进程直接恢复到内存中,快进 CPU 端的初始化过程 - **CUDA 检查点/恢复**:通过将 CUDA 上下文直接恢复到内存中,快进 GPU 端的初始化过程 它们共同将 AI 推理服务器副本的扩展时间从数千秒缩短到仅几十秒。 我们此前已分享过这项工作的部分内容(https://www.youtube.com/watch?v=3jJ1GhGkLY0)(https://modal.com/blog/jono-containers-talk),因为我们相信保密不是好的护城河。而且,如果更多人学会如何高效使用 GPU,市场上就会有更多 GPU 可供我们使用! 但这篇博客文章是我们第一次将所有内容完整地整合在一起。我们希望它能让你相信,我们的系统值得投资(https://modal.com/signup)——或者加入我们(https://modal.jobs/)一起构建它。 ## 为什么要在意无服务器 GPU?为了最大化推理工作负载的 GPU *分配*利用率。 首先,让我们清晰地定义问题。GPU 既昂贵又稀缺,因此我们希望最大化其利用率(https://modal.com/blog/gpu-utilization-guide),其中“利用率”是以下无量纲量: ### **利用率 := 实际输出 ÷ 已支付容量** 衡量利用率的方法有很多种——定义输出和容量。其中最为复杂和严格的大概是“模型 FLOP/s 利用率”,它将原始算法操作需求除以总算术带宽。 这对工程师来说很有吸引力。它对于“英雄级”大规模训练也尤其关键,因此吸引了大量投资和关注,例如最近 everyone dunked on xAI's ~10% MFU(https://x.com/theinformation/status/2050606311440531809?s=20)。 但在技术栈的另一端,有一种更基本的利用率形式破坏了推理工作负载的实际输出与分配容量之间的关系,即 GPU *分配*利用率: ### **GPU 分配利用率 := 运行应用程序代码的 GPU 秒数 ÷ 已支付的 GPU 秒数** **关于“GPU 利用率”术语的说明** `nvidia-smi` 和类似工具报告的“GPU 利用率”介于这两个极端之间。它报告的是*kernel 代码*在 GPU 上运行的时间比例——确切地说,是在 GPU 上有 CUDA 流运行的时间比例。更多信息请阅读 此处(https://modal.com/blog/gpu-utilization-guide)。 推理应用具有高度可变的规模。与训练不同,容量的需求不在工程组织的直接控制和管理范围内。相反,它是由外部用户行为驱动的——市场、社交媒体算法或产品团队。 下面是一个来自时变泊松过程的每分钟请求样本轨迹,我们用它来建模推理应用。请注意不仅存在季节性变化(每日周期),而且随着平均需求的增加,需求的长期趋势也变得更加多变。 图表展示了模拟的推理流量轨迹,来自时变泊松过程,包含季节性变化和尖峰 尖峰需求带来了严重的工程问题。借用 AWS 的 Marc Brooker(https://brooker.co.za/blog/2023/03/23/economics.html)的话:“系统的成本与其(短期)峰值流量成比例,但对于大多数应用程序来说,系统产生的价值与其(长期)平均流量成比例。” 尖峰需求意味着高峰值-平均比,这对系统经济性构成了挑战。 具体来说,想象一下这样一个应用程序的容量规划。你的需求(以在延迟目标内服务请求所需的 GPU 数量衡量)可能如下图所示: ### 采用固定的、过度配置的 GPU 分配,利用率很低 为了正确服务预期的负载,你分配了(机架和堆叠,或在超大规模云上租赁)140 个 GPU。但这些 GPU 在大部分时间都处于闲置状态——GPU 分配利用率很低。 你可能指责我们是在兜售自己的方案。但并不是只有我们指出了这一点!请参见 Hebbia 的精彩博客文章(https://www.hebbia.com/blog/the-hidden-economics-of-llm-inference)。我们有数据支持,而不仅仅是感觉:根据 2024 年《大规模 AI 基础设施现状》报告(https://ai-infrastructure.org/the-state-of-ai-infrastructure-at-scale-2024/),大多数组织在*峰值需求运行*时 GPU 分配利用率低于 70%。实际的 GPU 分配利用率通常接近 10-20%。 采用固定分配,需求还可能在意料之外的尖峰期间超过供应。试图预见尖峰只会进一步增加成本——超过其带来的收入增长。 ## 无服务器 GPU 的难点在哪里?启动延迟。 直接的解决方案是配置自动伸缩容量:当需求增加时,增加你的供应。 如果天真地去做,实际上会使问题恶化: ### 如果分配速度慢,利用率和 QoS 都会变差 如果不进行优化,从超大规模云 API 请求到运行中的服务副本,可能需要几十分钟。 你需要完成以下步骤: - **启动一个新实例并进行健康检查(几分钟到几十分钟)** - **加载应用程序程序和文件系统状态(几分钟)** - **在主机上启动应用程序程序,准备好服务请求(几十秒)** - **在设备上启动应用程序程序,准备好服务请求(几分钟到几十分钟)** 在这段时间内,负载超过了容量,QoS 通常会下降(被更高的并发或队列吸收,导致尾延迟膨胀,更糟糕的是,出现 `503` 错误)。这意味着用户不满。如果容量上线时间太长,甚至可能错过一个瞬时的尖峰。但由于需求的不可预测性和分配的困难,这些容量通常会长时间保留下来,处于低利用率状态。 在 Modal(https://modal.com/),我们将推理应用在 GPU 上的启动时间从几十分钟优化到了几秒或几十秒。通过这些优化,各种 GPU 推理应用都可以“真正地无服务器”运行:供应的容量与系统需求紧密匹配。 ### 通过快速、自动的分配,利用率和 QoS 都可以很高 在本文的其余部分,我们将解释我们采取的工程方法,以及针对上述四个步骤实施的性能优化,这些优化涵盖了从**云存储系统**和**机器管理**到**本地磁盘**、**CPU**,当然还有**GPU**的整个技术栈。 这些优化共同使 Modal 上的推理启动速度提高了 40 倍:50 秒而不是 2000 秒。 图表展示了在基础云系统和 Modal 上扩展推理服务器的延迟对比在基础云系统中启动需要 2 千秒以上的推理服务器,在 Modal 上只需约 50 秒。实现这一加速的关键架构优化已按它们所针对的关键系统组件进行指示和颜色编码——GPU 和 GPU 内存、CPU 和 CPU 内存、本地固态硬盘(SSD)或机器/实例管理。我们在本文中始终使用这种颜色方案和示意图。 ## 通过将实例分配和健康检查移出热路径,可以消除数十分钟的延迟。 考虑副本启动的第一步: - **启动一个新实例并进行健康检查(几分钟到几十分钟)** 我们可以通过提前执行来将其移出热路径:运行一个由多个应用程序共享的空闲、健康 GPU 的缓冲池,将新副本调度到这些单元上,并异步地将新设备启动到缓冲池中。当缓冲池变得过大时,我们还可以在副本缩减时取消分配单元。管理这个缓冲池是一个有趣的线性规划问题,正如我们之前在其他地方(https://modal.com/blog/resource-solver)所写的那样。 在已分配的机器基础上维护一个小的、已准备好但未使用的机器缓冲池,可以快速将新副本调度到空闲机器上(用亮色表示)。从缓冲池中服务请求可以将副本启动的延迟减少数十分钟。 图表展示了通过云实例缓冲池将推理启动延迟减少数十分钟。 **关于系统级和应用级缓冲池的说明** 如果你运行的是单个工作负载,而不是多工作负载系统,你可能会问为什么我们只将实例分配移出“热路径”并放入缓冲池。难道我们不能将更多的设置工作也移到缓冲池中吗?可以!Modal 用户可以通过 `buffer_containers`(https://modal.com/docs/guide/cold-start#run-more-warm-containers)维护一个应用层缓冲池,其中包含准备好服务请求的副本。但即便如此,吸收给定大小尖峰所需的缓冲池大小与创建新副本的速度成比例,因此下面描述的优化对于单工作负载系统仍然很重要。 运行缓冲池会将峰值分配利用率限制在 100% 以下。这是一个合理的权衡,因为 100% 的利用率通常是一种幻象。考虑一下,当其他资源(如 CPU 或 IOPS)的利用率过高时,通常的做法是启动新副本甚至通知工程师! 这对于稳健性很重要。一个 100% 利用的系统没有错误余地,因此故障经常会演变成失效。我们可以个人建议在你的生活中增加更多缓冲——在浴室里多放一把牙刷;在家中、办公室和随身携带关键设备的充电器。 这个缓冲池对于在单个系统上容纳更多种类的工作负载尤其有用。在 Modal,我们已经倾向于(https://modal.com/blog/agents-devex)支持多种“开发”工作负载,而不仅仅是生产服务,因为我们可以快速创建新的开发环境。另一个额外的好处是,这些环境默认具有可重现性,并且运行在适合生产的基础设施上。缩小与生产基础设施的差距也提高了开发速度。 当然,魔鬼在于细节。一个关键点:健康检查对于 GPU 至关重要,因为 GPU 的故障率远高于其他硬件,包括像机械硬盘这样的出了名挑剔的组件。我们在此处(https://modal.com/blog/gpu-health)详细介绍了我们的 GPU 健康检查系统。概括地说,根据我们的经验,你需要在启动时运行一个短暂的主动健康检查,并监控之后出现的健康问题(https://modal.com/docs/guide/gpu-health),但可以将更密集的检查(如 `dcgmi diag`)推迟到更慢的频率(对我们来说,每周一次)。 图表显示了按(匿名)云分组的每个 GPU 每小时的关键级别 Xid 错误(https://modal.com/docs/guide/gpu-health)。故障率绝非可以忽视! ## 通过从内容寻址缓存中懒加载文件,可以将容器启动从几分钟缩短到几秒。 现在让我们考虑下一步: - **加载应用程序程序和文件系统状态(几分钟)** 在当代实践中,这通常意味着启动一个或多个容器或虚拟机。 大致来说,容器是一个支持具有有限权限的进程的根文件系统。对于多个容器的分布式部署,性能瓶颈在于在 worker 实例上构建根文件系统。 操作系统发行版的根文件系统非常臃肿——包含数万个文件,大小达数 GB。天真地使用诸如 `docker run` 之类的命令,你需要加载整个文件系统,通常以云以太网支持的每秒几 GB 的速度进行。更糟糕的是,容器镜像被分成多个层,必须按顺序应用。 解决方案是将容器*启动器*(Docker 的 `runc`,gVisor 的 `runsc`)与容器*镜像交付*分离。我们使用一个名为 `ImageFS` 的自定义文件系统,它基于 `libfuse` 构建,结合了懒加载和一个多层、内容寻址的缓存,该缓存旨在匹配云提供商的能力。 我们在自定义文件系统中实现快速容器启动的关键“技巧”是明智地偷懒(正如所有优秀工程师所做的那样)。容器镜像包含许多文件,比如整个世界的时区和区域设置信息,但大多数应用程序永远不会读取它们。你可以跳过在容器启动前加载整个文件系统,而只需在启动时阻塞以加载元数据(索引)。元数据只有几兆字节,因此可以在 100 毫秒或更短的时间内加载完毕,连同启动容器所需的其他一切。 图表展示了在启动过程中并发懒加载容器文件系统内容 其余部分可以与其他工作同时加载——或者根本不加载!大部分文件不会被读取,正如 USENIX FAST '16 论文 *Slacker* (https://www.usenix.org/conference/fast16/technical-sessions/presentation/harter) 中的图 5 所示,该图转载如下。 我们目前使用 `libfuse` (https://github.com/libfuse/libfuse) 实现这个文件系统。它是一个用于在用户空间中编写 Linux 文件系统的库。内核在一个使用标准系统调用访问文件的用户空间程序和另一个实现新文件系统的程序(最终通过自己的系统调用,其中一个最终通过内核返回给原始程序)之间进行中介。这样比在内核模块中实现自定义文件系统更简单地进行构建和分发。 图表展示了在启动过程中并发懒加载容器文件系统内容 代价是:你在用户空间和内核空间之间经历了双倍的上下文切换。这对于延迟敏感的工作负载(例如从终端字符设备读取)来说可能很痛苦,但对吞吐量敏感的工作负载(我们使用它的场景)影响较小。关于性能影响的详细分析,请参见 USENIX FAST '17 的 *To FUSE or Not to FUSE* (https://www.usenix.org/conference/fast17/technical-sessions/presentation/vangoor)。 但天下没有免费的午餐:容器*访问*的所有数据仍然需要加载。如果你天真地从对象存储中获取典型容器访问的数千个文件中的每一个,那么从“容器启动”到 `torch.cuda.is_available` 的路径……

相似文章

我的4.8万美元GPU服务器值吗?

Hacker News Top

一位前FAANG工程师讲述了为独立AI研究构建一台配备六张RTX 6000 Ada显卡、价值4.8万美元的GPU服务器的经历,详细介绍了构建过程、电源限制以及与云GPU租用的成本对比。

无GPU革命:高效AI模型如何让人工智能大众化

Reddit r/AI_Agents

一场静默的革命正在让强大的AI模型无需昂贵GPU即可在消费级硬件上运行,这得益于量化技术和优化实现(如llama.cpp的Gemma4 MTP支持)的突破,为爱好者、小型企业和边缘计算打开了大门。