跳到主要内容

快速扩缩容

在生产环境中运行 LLM 推理与训练模型完全是两回事。与基于批处理、可预测的训练不同,推理是由实时用户需求驱动的。这种需求往往是突发性的、难以预测的,并且对延迟或停机毫不宽容。

这意味着系统需要在流量高峰期快速扩容,并在空闲时缩容到零以节省成本。这种弹性是效率的根本。

然而,许多组织像对待训练一样对待推理:他们通过长期承诺预先分配固定的 GPU 容量。这通常会导致:

  • 过度配置(over-provisioning): GPU 容量浪费,闲置成本高昂。
  • 配置不足(under-provisioning): 请求被丢弃、延迟飙升、用户体验糟糕。
  • 预算僵化: 刚性支出无法适应真实的使用模式。

为什么无服务器不是银弹

这个扩展问题看起来很熟悉——无服务器计算多年前就解决了它。像 AWS Lambda 这样的平台让按需扩展变得容易,但无服务器并不能很好地适用于 AI 工作负载。原因如下:

  • 不支持 GPU: 大多数无服务器平台不支持 GPU。这不仅仅是技术上的疏忽;它根植于架构与实践层面的考量。
  • GPU 难以切片: GPU 强大且高度并行,但在同时处理跨不同模型的许多推理任务时,它们不像 CPU 那样灵活。
  • 闲置 GPU 成本高昂: 它们是计算世界中的高性能跑车,擅长特定任务但维护成本高昂,尤其是不能持续使用时。

冷启动问题

推理工作负载需要能够快速扩展、控制成本并保持高性能的基础设施。扩展中的一个根本性挑战是冷启动(cold start)。

在容器中部署 LLM 的背景下,冷启动发生在 Kubernetes 节点此前从未运行过某个给定部署时。因此,容器镜像没有被本地缓存,所有镜像层都必须从头拉取并初始化。

这个问题分三个阶段呈现:

  1. 云资源调配(cloud provisioning): 这一步涉及云服务商分配新实例并将其接入 Kubernetes 集群所需的时间。根据实例类型和可用性,这可能从 30 秒到几分钟不等,对于像 NVIDIA A100 和 H100 这样的高需求 GPU,甚至可能长达数小时。
  2. 容器镜像拉取(container image pulling): 由于依赖众多且包含自定义库,LLM 镜像比典型的 Python 任务镜像大得多、复杂得多。尽管云服务商宣称拥有多千兆带宽,实际的镜像下载速度往往慢得多。因此,拉取镜像可能需要三到五分钟。
  3. 模型加载(model loading): 加载模型所需的时间在很大程度上取决于模型的大小。LLM 因其数十亿参数而引入了显著延迟。关键瓶颈包括:
    • 从模型中心下载缓慢: 像 Hugging Face 这样的平台并未针对高吞吐量的多部分下载进行优化,使得获取大型模型文件耗时较长。
    • 数据流串行: 模型文件通过多个环节传输:远程存储 → 本地磁盘 → 内存 → GPU。这些步骤之间几乎没有或根本没有并行化。每一步都会增加延迟,尤其是对于那些难以缓存或流式传输的大文件。
    • 缺乏按需流式传输: 模型文件必须完全下载并写入磁盘后才能开始推理。这引入了额外的 I/O 操作并延迟了启动。

冷启动问题的每个阶段都需要特定的策略来最大程度地减少延迟。

扩缩容指标

为 LLM 推理扩展基础设施不仅仅是对系统负载做出反应。选择合适的指标对于实现响应迅速、高效且经济高效的扩缩容至关重要。

  • CPU 利用率(CPU utilization): 它很简单,且有明确的阈值,但对于基于 Python 的工作负载,它并不能反映真实负载。全局解释器锁(GIL)限制了 CPU 并行性,尤其是在多核机器上,这使得该指标对扩缩容决策具有误导性。
  • GPU 利用率(GPU utilization): 理论上更相关的指标,但在实践中并不准确。像 nvml 这样的工具只要在采样窗口内有任何内核运行——即使是短暂的——就会将 GPU 报告为"已利用"。这并没有考虑批处理或实际吞吐量,导致过早扩容或对容量产生虚假信心。
  • QPS(每秒查询数): 在传统 Web 服务中被广泛使用,但对 LLM 推理不太有用。生成式请求在大小和计算成本上差异很大,取决于输入长度和生成的 token 数。因此,QPS 缺乏一致性,很难用于自动扩缩容调优。
  • 并发数(concurrency): 该指标表示正在排队或正在处理的活动请求数量,是反映系统负载的理想度量。并发数易于根据批大小配置,并与实际系统需求直接相关,从而实现精确扩缩容。然而,要让并发数起作用,你需要服务框架的支持,以便自动将并发数作为指标进行采集,并将其作为部署平台的扩缩容信号。