文章

LLM 冷启动治理生产实战:用 Local NVMe Cache、Weight Streaming 与 Warm Pool 缩短模型拉起时间

大模型服务扩容后迟迟不能 Ready,瓶颈常在模型权重下载、磁盘读取与 GPU 装载。本文从本地 NVMe 缓存、权重流式加载、Warm Pool 与模型本地性感知调度出发,给出一套可落地的 LLM 冷启动治理方案。

LLM 冷启动治理生产实战:用 Local NVMe Cache、Weight Streaming 与 Warm Pool 缩短模型拉起时间

很多团队优化 LLM Serving 时,把重点放在 HPA、KEDA、队列长度、GPU 利用率和副本数。这些机制解决的是「什么时候扩容、扩多少」,但在大模型场景里,一个新 Pod 被调度到 GPU 节点之后,距离真正进入 Ready 往往还隔着一条很长的启动链路:

Pod Scheduling -> Container Image Ready -> Model Weights Fetch -> Weights Read / Deserialize -> CPU to GPU Transfer -> Runtime Initialization -> CUDA Graph / Kernel Warmup -> Readiness

因此,真正应该度量的不是单一 Pod startup time,而是完整的冷启动总时长:

cold_start_total = scheduling_time + image_prepare_time + weight_fetch_time + weight_load_time + runtime_init_time + warmup_time

对几十 GB 甚至数百 GB 的模型来说,weight_fetch_timeweight_load_time 经常才是主导项。如果每次扩容都从对象存储或 Hugging Face Hub 重新下载模型,那么即使 Kubernetes 在几秒钟内完成调度,业务看到的仍然是分钟级的可服务延迟。

这也是冷启动治理和自动扩缩容治理的边界:Autoscaling 决定「要不要新副本」,Cold Start Engineering 决定「新副本多久能真正提供推理」。

核心原则:让权重离 GPU 更近,让加载路径更少串行等待

1. 用 Local NVMe 把「远程下载」变成「节点内读取」

KServe 的 Local Model Cache 直接针对这一问题:提前把模型权重缓存到 GPU 节点的本地磁盘,典型介质就是 NVMe。InferenceService 启动时,如果目标节点已经存在对应模型,就可以从本地缓存读取,而不是重新从远程仓库下载。

KServe 当前的模型缓存设计包含 LocalModelCacheLocalModelNamespaceCacheLocalModelNodeGroupLocalModelNode 等资源。其中 namespace-scoped cache 还能用于多租户隔离,避免所有命名空间无边界共享缓存。

一个简化配置示意如下:

apiVersion: serving.kserve.io/v1alpha1
kind: LocalModelCache
metadata:
  name: qwen-production-model
spec:
  sourceModelUri: "hf://your-org/your-model"
  modelSize: 40Gi
  nodeGroups:
    - gpu-workers

真正落地时,重点不只是「有没有缓存」,而是要定义清楚:

  • 哪些模型必须预热到所有 GPU 节点;
  • 哪些模型只在部分节点缓存;
  • 节点磁盘不足时谁先被淘汰;
  • 新版本权重发布后,旧缓存何时失效;
  • 模型文件校验失败时是否允许回源。

如果没有这些规则,本地缓存很容易从冷启动优化手段变成磁盘容量事故源。

2. 用 Weight Streaming 缩短「读文件 → CPU → GPU」的串行链路

即使权重已经在对象存储或高速文件系统里,传统加载方式仍可能经历多个阶段:先完整下载或 mmap,再由 CPU 读取,最后逐步送入 GPU。

vLLM 已支持 Run:ai Model Streamer。其核心思路是并发读取 tensor,并把权重流式送入 GPU;同时支持从 S3、GCS、Azure Blob 等对象存储直接加载,并可调节读取并发度、CPU buffer 大小以及 distributed streaming。

例如:

vllm serve s3://llm-models/qwen-prod \
  --load-format runai_streamer \
  --model-loader-extra-config '{"concurrency":16}'

对于多卡模型,还可以评估 distributed streaming 或预分片 checkpoint,使每个 rank 更接近「只加载自己真正需要的权重」。

这里要避免一个常见误区:Weight Streaming 不等于任何环境下都一定更快。 实际收益取决于对象存储吞吐、网络带宽、CPU 内存带宽、GPU H2D 带宽、checkpoint 格式和并发参数。生产上线前应按真实模型、真实节点和真实存储做基准,而不是照搬固定 concurrency。

3. Warm Pool 不应只有「GPU 常驻」一种形态

完全 Scale-to-Zero 最省钱,但它把所有冷启动阶段都留给首个请求;所有模型都常驻 GPU 又会造成显著闲置成本。更实用的做法是把模型划成多级温度状态:

Tier状态成本特征
Tier 0Active GPU Replica最高成本,最快启动
Tier 1CPU-resident / Sleeping Replica中等成本,较快唤醒
Tier 2Node-local NVMe Model Cache低成本,需重新装载
Tier 3Remote Object Storage最低成本,最慢回源

不同模型根据 SLO、调用频率和启动成本进入不同层级。例如,高频核心模型维持少量 GPU Warm Replica;中频模型可以保留容器与 CPU 侧权重;低频长尾模型只保留节点 NVMe 缓存;极低频模型才完全回落到远端对象存储。

vLLM 的 Sleep Mode 为 Tier 1 提供了一个很有价值的实现思路。Level 1 会把模型权重 offload 到 CPU 并丢弃 KV Cache,从而释放大量 GPU 内存,之后可以重新 wake up,而不必从远端重新获取同一套权重。

但要注意,vLLM 文档明确说明在线 Sleep/Wake 控制端点需要开发模式开启。生产环境不要把这类端点直接暴露到公网或租户侧,而应由内部生命周期控制器调用,并配合权限、超时和状态机。

4. 调度时考虑「模型在哪里」,而不只是「哪张 GPU 空闲」

冷启动优化最容易被忽略的一层,是 placement。假设集群里有两台空闲 GPU 节点:

  • Node A 已经在本地 NVMe 缓存目标模型;
  • Node B 完全没有该模型;
  • 两台机器 GPU 型号、显存和当前利用率完全相同。

传统调度器可能随机选择 B;从 GPU 资源角度看没有问题,但从模型启动时间看,这是明显的次优选择。

ServerlessLLM 的一个关键设计就是 startup-time-aware scheduling:根据 checkpoint 在不同服务器上的本地性状态,选择预计启动时间更短的服务器,而不是只看抽象计算资源。

在 Kubernetes 环境里,这个思想可以落成一个简单规则:

Score(node, model) = GPU_fit_score + model_locality_score - remote_fetch_penalty - cache_pressure_penalty

模型本地性应成为调度信号,而不是启动之后才被动发现的事实。

工程落地:建立一条可观测的冷启动状态机

建议把每个模型副本的启动过程拆成可观测状态,而不是只记录一个 Pod Ready 时间:

PENDING_GPU -> IMAGE_READY -> CACHE_LOOKUP -> FETCHING_WEIGHTS -> LOADING_WEIGHTS -> RUNTIME_INIT -> WARMING_UP -> READY

至少采集以下指标:

  • cold_start_total_seconds:从扩容决策到 Ready 的总时间;
  • model_fetch_seconds:远程获取权重时间;
  • model_load_seconds:权重读取与装载时间;
  • local_model_cache_hit_rate:节点本地模型缓存命中率;
  • remote_weight_bytes:每次启动从远端读取的权重字节数;
  • warm_pool_hit_rate:请求触发扩容时是否命中 Warm Pool;
  • sleep_wakeup_seconds:sleep replica 恢复到 Ready 的时间;
  • cold_start_failure_rate:下载、校验、OOM、初始化失败占比。

如果只监控 pod_startup_latency,你只能知道「慢」;拆开以后才能知道究竟是 Scheduler、Registry、S3、NVMe、CPU 读取、H2D 还是 runtime warmup 在拖延。

一个推荐的生产策略

可以按模型热度和 SLO 做三级策略。

层级策略要点
高频核心模型保留 1~N 个 Active Warm Replica;所有目标 GPU 节点预缓存模型到 NVMe;扩容优先调度到 cache hit 节点;重点优化 p95/p99 冷启动时间,而不是追求 Scale-to-Zero。
中频模型允许 GPU Scale Down;尽量保留 CPU-resident / Sleeping 状态;节点 NVMe 保留完整权重;需要时快速重新占用 GPU。
长尾模型不保留 GPU Warm Replica;只缓存最常用版本到部分 NVMe 节点;对极低频模型允许回源对象存储;给调用方暴露更宽松的冷启动 SLO。

这种策略本质上是在 GPU 成本、CPU 内存、NVMe 容量、网络流量和启动延迟之间做分层交换,而不是用一个统一的 minReplicas 参数解决所有模型。

适用场景

这套方法尤其适合以下场景:

  • 一个 GPU 集群托管大量不同基础模型;
  • 模型权重较大,扩容后加载时间明显高于 Pod 调度时间;
  • 需要 Scale-to-Zero 或低保有量运行长尾模型;
  • 对象存储与 GPU 节点之间网络距离较远;
  • 模型版本更新频繁,需要可控的缓存预热和失效机制;
  • 业务有明确 TTFT / availability SLO,但不愿长期保留大量空闲 GPU。

如果只有一个固定模型、流量长期稳定且所有 GPU 都常驻,那么冷启动治理的收益反而有限,优先优化运行态吞吐和成本更合理。

常见误区

误区一:把 minReplicas 调大就是解决冷启动。 它只能降低「冷启动被用户撞到」的概率,不能解决节点重建、故障迁移、模型版本切换和突发扩容时的权重加载问题。

误区二:只优化容器镜像。 大模型服务中,容器镜像通常远小于模型权重。Image Pull 优化重要,但不要把几百 MB 镜像问题当成几十 GB 权重问题。

误区三:共享网络盘等于本地缓存。 共享盘避免了重复从互联网下载,但不代表具备本地 NVMe 的数据路径。并发扩容时,网络存储本身可能成为新的热点。

误区四:缓存不做版本治理。 如果模型 URI、revision、tokenizer、config 与权重文件版本没有一起管理,很容易出现「权重命中缓存,但实际版本不一致」的隐蔽问题。缓存键至少应包含模型标识和不可变 revision。

误区五:Readiness 过早。 进程启动不代表模型可服务。Readiness 应在权重加载、runtime 初始化和必要 warmup 完成后才置为成功,否则流量会被送到一个「活着但还不能推理」的实例。

误区六:把开发模式 Sleep/Wake 端点直接对外。 Sleep/Wake 是生命周期控制能力,不是业务 API。生产环境必须放在内部控制面后面,并限制调用主体。

上线检查

上线前建议至少完成以下检查:

  • 模型权重是否使用不可变版本号或 revision;
  • GPU 节点 NVMe 容量是否有高水位与淘汰策略;
  • 是否能统计 Local Model Cache hit / miss;
  • cache miss 时的回源路径是否限流;
  • 模型加载并发是否压测过 CPU、网络和对象存储;
  • readiness 是否覆盖完整 warmup;
  • Warm Pool 是否设置最大数量和最大空闲时间;
  • 调度器是否优先 cache hit 节点;
  • 冷启动阶段是否能定位具体耗时分段;
  • 新模型发布前是否支持提前预热节点缓存;
  • 缓存文件是否有 checksum / 完整性校验;
  • 发生 OOM、下载中断或版本不一致时是否能自动回滚。

FAQ

LLM 冷启动为什么不能只靠增加 minReplicas 解决?

因为 minReplicas 只是在运行时保留更多常驻副本。只要发生节点故障、版本切换、突发扩容或新模型首次部署,权重获取和加载成本仍然存在。真正的治理需要同时处理模型本地性、加载路径和温度分层

本地 NVMe 缓存和共享网络盘有什么本质区别?

本地 NVMe 的优势不是「文件存在磁盘」,而是数据已经位于目标 GPU 节点附近。共享网络盘仍然要经过网络和共享存储层,在大规模并发加载时可能发生带宽竞争。

vLLM Sleep Mode 能直接当生产 Warm Pool 吗?

可以借鉴其 GPU 内存释放和快速恢复机制,但生产上应由内部控制面封装,而不是直接对外暴露开发模式端点。还需要额外处理副本状态、并发唤醒、失败恢复和资源配额。

参考资料

  1. USENIX OSDI 2024 — ServerlessLLM: Low-Latency Serverless Inference for Large Language Models:https://www.usenix.org/conference/osdi24/presentation/fu
  2. KServe — Local Model Cache:https://kserve.github.io/website/docs/next/model-serving/generative-inference/modelcache/localmodel
  3. vLLM — Loading models with Run:ai Model Streamer:https://docs.vllm.ai/en/v0.18.0/models/extensions/runai_model_streamer/
  4. vLLM — Sleep Mode:https://docs.vllm.ai/en/v0.13.0/features/sleep_mode/

常见问题

LLM 冷启动为什么不能只靠增加 minReplicas 解决?
minReplicas 只能减少部分扩容触发后的等待,并不能消除节点故障、模型切换、弹性扩容和长尾模型首次加载。更稳妥的方法是把权重缓存、加载路径、Warm Pool 和调度本地性一起治理。
本地 NVMe 缓存和共享网络盘有什么本质区别?
本地 NVMe 的核心价值是模型权重已经位于目标 GPU 节点附近,可避免扩容时再次跨网络拉取大文件;共享网络盘虽然避免重复下载,但仍可能受网络吞吐、并发争用和远端存储延迟影响。
vLLM Sleep Mode 能直接当生产 Warm Pool 吗?
它提供了很有价值的 GPU 内存释放和快速唤醒能力,但官方文档中的在线控制端点属于开发模式能力,不应直接暴露给用户。生产环境应由内部控制面封装生命周期和权限。