背景问题:Serverless 省钱,但冷启动会放大首 Token 延迟
大模型推理团队越来越青睐 Serverless GPU 或弹性 GPU 池,因为它能把低峰期闲置成本降下来:没有请求时缩容,有突发流量时再拉起 GPU worker。问题在于,大模型服务的冷启动不是普通 Web 函数的冷启动。普通函数可能只是拉起容器、加载依赖;LLM 推理还要处理镜像启动、模型权重下载、权重从磁盘到 CPU 内存再到 GPU 显存、runtime 初始化、tokenizer 或推理服务预热、首个请求排队等步骤。
Modal 的冷启动文档把额外延迟拆成两类:一类是请求等待 warm container 的排队时间,另一类是新容器首次处理请求时必须完成的初始化工作;文档还明确指出,大模型权重如果在启动时从远端下载,可能把启动时间从秒级拉到分钟级,而提前下载或放入镜像 / Volume 可以显著降低启动耗时。Ray Serve 的 autoscaling 文档也强调,自动扩缩容会根据请求队列做决策,min_replicas=0 适合长时间无流量且能接受扩容尾延迟的场景。这两个事实放在一起,说明冷启动治理不能只靠”开自动扩容”,还要把模型生命周期拆开治理。
本文讨论的不是 Continuous Batching、PagedAttention、Prefix Caching 或 LoRA adapter 缓存,而是一个更靠近平台层的问题:当某个模型、租户或版本从冷状态被重新唤醒时,如何让首次请求不要被模型加载拖垮,同时又不把所有 GPU 长期预留成热实例。
冷启动到底慢在哪里
对 LLM Serverless 推理来说,冷启动可以拆成六段:
| 阶段 | 典型问题 | 关键治理动作 |
|---|---|---|
| 调度阶段 | 请求没有可用 warm worker,只能等待新 worker 启动 | Warm Pool、buffer worker、预测式预热 |
| 容器阶段 | 镜像拉取、依赖加载、Python import 慢 | 预构建镜像、裁剪依赖、Memory Snapshot |
| 权重阶段 | 模型权重从对象存储或远端仓库读取 | 本地 SSD 缓存、Volume、分片并行读取 |
| GPU 阶段 | 权重搬到显存、CUDA runtime 初始化 | 预加载、显存保留、分层驱逐 |
| 服务阶段 | tokenizer、server、路由、健康检查尚未就绪 | readiness gate、warmup request、影子探测 |
| 首请求阶段 | 首个真实请求承担编译、kernel、cache 初始化成本 | synthetic warmup、首请求隔离、冷/热指标拆分 |
很多团队只盯着”容器启动时间”,但大模型推理的更大成本经常在模型权重加载和GPU 显存恢复。ParaServe 论文指出,Serverless LLM serving 的冷启动延迟会受到大模型体积影响,其中从远端存储获取模型是主要瓶颈之一;它通过并行化模型参数获取、加载和 runtime 初始化来降低冷启动时间。Tangram 则从另一个角度处理问题:利用 GPU 显存复用和 GPU affinity,让冷模型再次启动时尽量少搬运参数。
核心原理:把冷启动看成”模型生命周期流水线”
冷启动治理的核心不是某个单独参数,而是把模型从”不可服务”到”稳定服务”的过程定义成明确状态机:
COLD -> IMAGE_READY -> WEIGHTS_ON_DISK -> WEIGHTS_IN_CPU_MEMORY
-> WEIGHTS_ON_GPU -> RUNTIME_READY -> WARMED -> SERVING
-> IDLE -> EVICTING -> COLD
每个状态都要有可观测指标、超时阈值和回退动作。例如,WEIGHTS_ON_DISK 超时通常意味着对象存储、镜像层或本地缓存异常;WEIGHTS_ON_GPU 超时可能是 GPU 内存不足、驱逐策略过慢或并发加载太多;RUNTIME_READY 到 WARMED 之间如果失败,往往说明健康检查过早放行,真实请求被迫承担预热成本。
把冷启动拆成状态机之后,平台就能做三件事:
- 把冷启动成本前移:在部署、扩容或流量预测阶段完成模型下载、权重加载和 warmup。
- 把冷启动成本隔离:不要让真实用户的第一个请求承担所有初始化成本。
- 把冷启动成本显性化:把 cold TTFT、warm TTFT、model load time、queue wait 分开统计。
Warm Pool:不是永远开着 GPU,而是保留”可快速接管”的热容量
Warm Pool 的常见误解是”把 min_replicas 调大”。这只是最粗的办法。真正的 Warm Pool 需要回答四个问题:哪些模型值得常驻、保留到生命周期的哪一层、由什么事件触发预热、什么时候驱逐。
| 层级 | 策略 | 适用对象 |
|---|---|---|
| Hot | 常驻 GPU,完整 warmed | 高频主模型、核心付费租户、低延迟 API |
| Warm | 容器或权重常驻,但不一定占满 GPU | 中频模型、工作时间高峰、可预测批量任务 |
| Cold | 完全按需加载 | 长尾模型、低频实验模型、内部离线任务 |
这不是简单的 QPS 排名,还要看模型大小、加载时间、显存占用、SLO 级别、租户价值和是否存在周期性流量。
不是所有模型都要做到 WEIGHTS_ON_GPU。有些模型只需要做到 WEIGHTS_ON_DISK,这样既能避免远端下载,又不占 GPU 显存;有些高价值模型需要做到 RUNTIME_READY 或 WARMED,保证真实请求进来时马上能服务。
warm_pool_policy:
hot_models:
target_state: WARMED
min_workers: 2
scaledown_window_seconds: 1800
warm_models:
target_state: WEIGHTS_ON_DISK
min_workers: 0
prefetch_window_minutes: 15
cold_models:
target_state: COLD
min_workers: 0
max_cold_start_seconds: 120
预热信号可以来自部署事件、历史流量、业务活动和实时队列。驱逐则不应该只看 idle time,还要看模型大小、冷启动代价、租户等级、当前 GPU 池压力和刚驱逐又被请求的概率。
Tangram 和 CrossPool 这类研究都指向一个共同事实:LLM 冷启动和多模型服务的核心瓶颈之一是 GPU 内存中”静态权重”和”动态请求状态”的管理。工程实现不一定照搬论文系统,但应该吸收它们的思想:让权重、KV cache、worker 生命周期和调度亲和性分开治理。
分阶段预热:不要让第一个真实请求做系统健康检查
一个可发布的 Serverless GPU 推理服务,至少应有四级预热。
构建期预热
构建期不跑 GPU,但要尽量把慢 IO 从运行时拿掉:固定镜像基础层、提前下载 tokenizer 和 config、把不变依赖固化到 image 或 Volume、记录模型 artifact digest,禁止运行时漂移。
启动期预热
启动期要把容器从”启动完成”推进到”可服务”。
class LLMWorker:
def __init__(self, model_path: str):
self.model_path = model_path
self.ready = False
self.model = None
def startup(self):
self.tokenizer = load_tokenizer(self.model_path)
self.model = load_model_from_local_cache(self.model_path)
self.model.to("cuda")
self.model.eval()
self.warmup()
self.ready = True
def warmup(self):
prompt = "warmup"
_ = self.model.generate(prompt, max_new_tokens=1)
注意,ready = True 必须放在 warmup 之后。否则服务发现层会把真实流量送进一个”容器活着但模型未热”的 worker。
发布期预热
新模型版本先进入 shadow warmup,不接真实流量;warmup 通过后再切 1% 流量;同时比较 cold TTFT、warm TTFT、错误率和 GPU memory peak。回滚时保留上一版本 warm worker 一段时间,避免回滚也冷启动。
流量期预热
流量期要面向突发:pending queue 达到阈值时,不只扩 replica,还要提前加载对应模型;对周期性流量使用 scheduled warmup;对高价值租户设置更高的 warm priority;对长尾模型使用本地权重缓存,而不是常驻 GPU。
工程落地架构
推荐把冷启动治理拆成五个组件:
- Traffic Classifier:识别模型、租户、SLO、请求类型。
- Warm Pool Manager:决定哪些模型保持在哪个状态。
- Artifact Cache Manager:管理镜像层、模型权重、本地 SSD、Volume。
- GPU Residency Controller:决定哪些权重留在 GPU、哪些降级到 CPU 或磁盘。
- Readiness & Warmup Gate:只有预热通过才允许真实流量进入。
Request -> Gateway -> Traffic Classifier -> Warm Pool Manager
-> Worker Pool -> Readiness Gate -> LLM Runtime
Artifact Store -> Local Cache -> CPU Memory -> GPU Memory
这套架构的关键是:网关不应该只知道有多少 worker,调度器也不应该只知道 GPU 是否空闲。它们都需要知道模型是否热、权重在哪里、当前冷启动成本是多少。
指标体系:必须区分 cold 和 warm
如果只看平均延迟,冷启动问题会被掩盖。建议至少拆出以下指标:
| 指标 | 含义 | 用途 |
|---|---|---|
cold_start_rate | 请求命中冷 worker 的比例 | 判断 Warm Pool 是否足够 |
cold_ttft_p95 | 冷启动请求的 P95 首 Token 延迟 | 衡量用户最差体验 |
warm_ttft_p95 | 热请求的 P95 首 Token 延迟 | 排除冷启动后的模型服务质量 |
model_load_time | 权重加载耗时 | 定位远端存储、磁盘、CPU/GPU 搬运问题 |
queue_wait_time | 请求等待 worker 的时间 | 判断扩容是否滞后 |
warmup_failure_rate | 预热失败比例 | 防止未就绪 worker 接流量 |
warm_pool_cost | warm worker 空闲成本 | 评估成本和延迟取舍 |
eviction_miss_rate | 刚驱逐又被请求的比例 | 调整驱逐策略 |
上线报表里要把这些指标按模型、租户、版本、GPU 池、区域拆开。冷启动通常不是全局平均问题,而是某几个模型或租户在特定时间段被反复击中。
适用场景
这套方法适合以下场景:
- 多模型平台,只有少数模型高频,其余模型长尾。
- 企业客户使用独立模型版本或专属 LoRA / adapter。
- 推理平台允许 scale-to-zero,希望降低 GPU 空闲成本。
- 模型体积较大,首次加载显著影响 TTFT。
- 线上有明显周期性流量,例如工作时间、活动时间、批处理窗口。
- 发布新模型时,首批用户经常遇到明显慢请求。
不适合的场景也很明确:如果服务必须全天候低延迟,且模型数量少、流量稳定,直接固定副本可能更简单。Warm Pool 的价值在于处理”成本不允许全热,但体验不允许全冷”的中间状态。
常见误区
误区一:只要自动扩容就能解决冷启动
自动扩容解决的是容量不足,不一定解决模型加载慢。Ray Serve 可以根据队列扩缩副本,但如果副本启动后还要从远端拉模型,用户仍然会等。
误区二:把所有模型都放进 GPU 才叫 Warm Pool
GPU 显存最贵。长尾模型更适合做到 WEIGHTS_ON_DISK 或 WEIGHTS_IN_CPU_MEMORY,只有高频模型才需要 WEIGHTS_ON_GPU 或 WARMED。
误区三:健康检查通过等于模型可服务
容器 alive 不代表模型 warmed。健康检查至少要区分 process_alive、runtime_ready、model_loaded、warmup_passed。
误区四:只看 P95 延迟就够了
冷启动通常影响小比例请求,但这些请求体验很差。必须单独统计 cold path 指标,否则平均值会误导决策。
上线检查清单
- 模型权重是否有固定 digest。
- 镜像是否包含必要依赖,避免启动时临时安装。
- tokenizer、config、权重路径是否可从本地缓存读取。
- warmup request 是否覆盖真实推理路径。
- readiness gate 是否在 warmup 之后才放行。
- 新旧版本是否能同时保留 warm worker。
- 是否定义 Hot / Warm / Cold 模型分层。
- 是否设置每层的目标状态和最大成本。
- 是否为高价值租户设置 warm priority。
- 是否有 GPU 显存水位和驱逐策略。
- 是否限制并发模型加载数量,避免 IO 风暴。
- 是否区分 cold TTFT 和 warm TTFT。
- 是否记录 model load time、queue wait、startup pending。
- 是否有 warmup failure 告警。
- 是否记录 warm pool 空闲成本。
参考资料
- Modal Docs: Cold start performance — https://modal.com/docs/guide/cold-start
- Ray Serve Autoscaling Guide — https://docs.ray.io/en/latest/serve/autoscaling-guide.html
- ParaServe: Towards Swift Serverless LLM Cold Starts — https://arxiv.org/abs/2502.15524
- Tangram: Accelerating Serverless LLM Loading through GPU Memory Reuse and Affinity — https://arxiv.org/abs/2512.01357
- Serverless Cold Starts and Where to Find Them — https://arxiv.org/abs/2410.06145
- CrossPool: Efficient Multi-LLM Serving for Cold MoE Models through KV-Cache and Weight Disaggregation — https://arxiv.org/abs/2606.24506