当大语言模型引擎进程发生故障时,传统恢复流程需要经历冷启动:从存储加载权重到高带宽内存(HBM)、编译内核、捕获 NVIDIA CUDA 计算图。对于大型模型而言,初始化过程可能长达数分钟,期间存活的工作节点必须承担全部被转移的流量。

NVIDIA Dynamo 预览特性中提供的影子引擎恢复功能,将大部分恢复工作从服务路径中剥离出来。它在与主动引擎相同的 GPU 上保留一个完全初始化的空闲影子引擎,并通过 GPU 内存服务(GMS)在两个引擎之间共享已有权重,无需在 HBM 中另行复制。一旦主动进程发生故障,影子引擎可在数秒内接管服务,而重新初始化过程则完全在后台、脱离服务路径进行。

实测效果

为量化该功能的效果,研究团队对一个双工作节点的 GLM-5.2 部署主动终止其中一个节点。在未启用影子引擎恢复的情况下,剩余节点在长达 283 秒的冷启动期间独自承担全部流量,导致首 Token 生成时间(TTFT)显著上升,每用户解码速率大幅下降。启用影子引擎恢复后,第二个工作节点仅需 7.3 秒即可恢复服务,速度提升近 39 倍,服务质量受到的影响大幅降低。

为何普通冷启动无法绕过初始化开销

生产环境中的大语言模型引擎常会遭遇可恢复的软件故障,包括进程崩溃、可恢复的 CUDA 错误以及短暂的集体通信故障。在这些情况下,硬件、驱动和节点本身均保持健康,只有持有损坏状态的进程会丢失,替代引擎通常可在同一 GPU 上启动。

然而,新引擎仍无法跳过初始化开销,原因有二:

其一,权重与引擎进程绑定。GPU 内存与引擎的 CUDA 上下文绑定,而 CUDA 上下文又与引擎进程绑定。进程退出时,驱动会释放所有资源,包括已驻留于 GPU 内存中的权重。因此,替代引擎进程必须重新执行完整的权重加载流程。

其二,部分初始化状态不可转移。NCCL 和 torch.distributed 通信器绑定于特定的运行进程,CUDA 计算图也固定于捕获时的虚拟地址。这些状态无法从旧引擎移交,每次重启都必须重新创建。

影子引擎恢复针对上述两个问题分别采取了优化措施:将权重生命周期与引擎进程解耦,并在故障发生前提前完成不可转移的初始化。

核心组件

GPU 内存服务与权重持久化

GPU 内存服务(GMS)独立于引擎进程管理特定内存区域(如权重)。通过使用一个独立于引擎的进程来持有这些内存区域,权重在引擎重启时始终驻留于内存中,新引擎可直接附加到已有内存。

GMS 是一个每 GPU 部署的 Sidecar 进程,代表推理引擎持有物理 GPU 内存。它大部分时间处于休眠状态,自身不持有 CUDA 上下文,仅负责分配物理内存页、分发句柄,并仲裁哪些引擎可在何时读写。引擎在启动时连接并导入句柄,将底层内存页映射至自身 CUDA 上下文中的虚拟地址,此后 GMS 不再介入任何后续访问。

该功能基于 CUDA 虚拟内存管理 API 构建,物理 GPU 内存与其关联的虚拟地址可拥有独立的生命周期。由于物理分配采用引用计数,只要有任何进程持有映射,内存页便会持续驻留。两个引擎映射同一权重张量时,各自使用本地虚拟地址访问同一物理字节,内核读取权重时与引擎自行分配的访问开销相同。

该架构带来两大优势:第一,权重在引擎故障后持续存在,新引擎可立即完成映射;第二,权重可在并发引擎之间共享,因此同一 GPU 上的辅助引擎的权重边际成本为零。

将 GMS 集成到推理框架仅需极少改动。vLLM、SGLang 和 NVIDIA TensorRT-LLM 均通过绑定到权重内存池的自定义 torch.cuda.CUDAPluggableAllocator 集成 GMS,引擎内部的权重仍以普通 torch.Tensor 形式存在,只需在启动时开启一个标志即可。

影子引擎机制

影子引擎是一个完全初始化、处于空闲状态并与主动引擎共驻于同一 GPU 上的引擎进程。权重共享使这一配置成为可能——若无共享,第二个引擎将需要另一份完整的权重副本,大幅压缩可用于处理请求的内存空间。

影子引擎与主动引擎经历完全相同的启动流程:连接本地 GMS 并导入权重映射、建立通信器(NCCL 及用于工作节点间 KV 传输的 NIXL)、捕获 CUDA 计算图并执行必要的预热。完成启动后,它不会开始服务,而是进入驻留状态:释放可物化部分的内存,并阻塞等待接管时机。

影子引擎在驻留前已预计算完成的内容:CUDA 上下文、捕获的计算图和通信器(因为这些不可转移的组件在激活时必须就绪);以及权重映射(GMS 句柄已导入,唤醒时只需重新映射至初始化时建立的虚拟地址)。

影子引擎推迟处理的内容:KV 缓存物化(KV 缓存是引擎持有的最大可回收分配,影子引擎驻留时仅保留地址范围而不分配物理内存,激活时再完成物化)。

因此,驻留的影子引擎仅持有 CUDA 上下文、捕获的计算图、通信器和权重映射,既没有独立的权重副本,也没有 KV 缓存,占用空间足够小,可与主动引擎共存于同一设备上,从而实现秒级恢复。

工作节点结构

每个工作节点 Pod 包含两个引擎容器、一个用于仲裁 GPU 内存访问的 GMS Sidecar,以及一个用于选举主动引擎的共享锁。

稳态下,一个引擎持有锁并处于唤醒状态,连接至 GMS,持有已物化的 KV 缓存,并已向前端路由器注册;另一个引擎完全初始化并已连接 GMS,但处于休眠状态,不持有 KV 缓存,等待获取锁。

恢复流程

工作节点在恢复至稳态前经历四个阶段:

T? 稳态:引擎 A 持有锁并处于唤醒状态,已向路由器注册;引擎 B 处于休眠状态,阻塞于锁上。

T? 故障:引擎 A 进程退出(无论是直接崩溃,还是存活探针检测到其挂起后将其终止)。进程被回收时,内核释放其持有的锁,工作节点短暂处于不可路由状态,直至影子引擎完成注册。

T? 切换:引擎 B 获取锁,唤醒后通过 GMS 重新映射权重,物化 KV 缓存,并重新向路由器注册;同时编排器重启引擎 A 的容器。

T? 重启完成:引擎 A 完成初始化,进入影子状态;稳态恢复,两者角色互换。

影子引擎的优势在于进入 T? 时已完成初始化,关键路径上仅需获取锁、重新映射权重和物化 KV 缓存三步操作。

同步机制

工作节点需要同时保证互斥性(确保同一时刻只有一个引擎处于唤醒状态)和可靠释放(确保主动引擎故障时备用引擎能够接管)。共享文件上的 POSIX flock 提供了上述保证:当主动进程因关闭、段错误或 SIGKILL 退出时,内核回收其文件描述符,影子引擎随即获取锁并开始服务。

因此,每个引擎的启动路径本质上是一次简短的主节点选举:

await engine.initialize() # 权重加载、torch.compile、自动调优、CUDA 计算图捕获

... # 等待获取锁时使引擎进入休眠

await engine.sleep()

lock = FlockFailoverLock(lock_path)

await lock.acquire(engine_id=engine.id) # 等待锁以唤醒引擎

await engine.wake()

若进程仍存活但引擎死锁,Kubernetes 存活探针将发出 SIGKILL,同样触发内核管理的锁释放。

内存管理

在单张 GPU 上容纳两个引擎进程且不耗尽 HBM,需要在整个生命周期中对内存进行精细核算:

权重:由 GMS 一次性分配,每个引擎以只读方式映射,永不复制。

KV 缓存:目前仅由主动引擎持有,唤醒时物化,进程终止时释放,供影子引擎接管。

缓冲区与计算图:NCCL 缓冲区、CUDA 上下文和捕获的计算图,每个引擎即使在休眠时也持有这些资源,构成驻留影子引擎的全部常驻开销。

实测数据

测试在两个工作节点上运行 NVFP4 量化的 GLM-5.2,每个工作节点部署于一个 NVIDIA B200 节点,TP=8,最大上下文长度 200K,使用 FP8 KV 缓存。单个前端以轮询方式将请求分发至两个工作节点,合成负载为每请求 32,000 个输入 Token、1,000 个输出 Token,到达速率为每秒 0.7 个请求。

两组测试使用完全相同的引擎构建和配置,唯一区别在于是否启用影子引擎。基线组禁用影子引擎,故障工作节点执行冷启动;影子引擎恢复组中,每个工作节点 Pod 托管一个预初始化的影子引擎,可在主动引擎故障时接管。

故障在工作负载达到稳态后注入:对其中一个工作节点发送 SIGKILL,随后观察 600 秒。两节点集群中失去一个节点后,存活节点将承担全部请求,直至伙伴节点恢复。

测试结果显示,与冷启动基线的 283 秒相比,影子引擎恢复仅需 7.3 秒(1.7 秒用于检测故障,5.6 秒用于提升影子引擎),故障后的 TTFT 和解码速率显著改善,基本避免了 SLA 违约。

后续计划与当前限制

在验证了大语言模型推理工作负载在 Kubernetes 上快速恢复的可行性后,团队正在努力稳定实现并扩展对更多工作负载的支持,影子引擎恢复功能将在未来数月内逐步推出。

当前预览版存在以下限制和部署要求:

影子引擎恢复仅针对常见引擎进程故障,不涵盖硬件、节点或多节点故障,后者仍依赖标准重调度机制。

需要在 Kubernetes 上启用动态资源分配(DRA),集群需使用 Kubernetes 1.34 或更高版本,并启用 DRA 及安装 NVIDIA GPU DRA 驱动。

Dynamo Snapshot 可与恢复功能组合使用,以减少服务期间影子引擎初始化阶段的资源争用。

由于提升后的影子引擎以空 KV 缓存启动,切换后的 TTFT 会出现轻微抖动。跨提升传递缓存状态(包括前缀缓存索引和缓存内存本身)是当前的主要研究方向。

目前主要支持 vLLM 作为后端。

如需体验影子引擎恢复功能,可从 Kubernetes 快速入门开始创建运行中的部署,然后按照影子引擎恢复部署工作流进行操作,并参考 vLLM 故障转移示例获取完整配置清单。欢迎访问 ai-dynamo/dynamo 仓库提问、报告问题或参与贡献。

Q&A

Q1:NVIDIA Dynamo 影子引擎恢复和普通冷启动有什么区别?

A:普通冷启动需要从存储重新加载权重到 HBM、编译内核、捕获 CUDA 计算图,对于大型模型可能长达数分钟。影子引擎恢复则在故障发生前就保留一个完全初始化的备用引擎,通过 GPU 内存服务共享权重,故障后仅需数秒即可接管,实测从 283 秒缩短至 7.3 秒,速度提升近 39 倍。

Q2:GPU 内存服务(GMS)是如何实现权重共享的?

A:GMS 基于 CUDA 虚拟内存管理 API,将物理 GPU 内存的生命周期与引擎进程解耦。物理内存页采用引用计数,只要有任何进程持有映射就不会释放。主动引擎和影子引擎各自通过虚拟地址映射到同一份物理权重数据,实现零额外内存开销的共享,即使主动引擎进程崩溃,权重也会持续驻留供新引擎使用。

Q3:影子引擎恢复有哪些使用限制?

A:目前该功能仅覆盖引擎进程级故障,不支持硬件故障、节点故障或多节点故障的恢复。部署上需要 Kubernetes 1.34 或更高版本并启用动态资源分配(DRA)及安装 NVIDIA GPU DRA 驱动。此外,影子引擎提升后初始 KV 缓存为空,会导致切换后 TTFT 轻微抖动,跨提升传递缓存状态的功能仍在开发中。目前主要支持 vLLM 后端。

NVIDIA