1

最近发现了两个出现在知名组织下的新 AI 网关项目:

  • envoyproxy/ai-gateway
  • kubernetes-sigs/llm-instance-gateway

正好我也是做 AI 网关的业内人士,看到同类项目自然会拿来仔细分析一番。三人行必有我师,从别人的思路总是可以学到不少东西。需要注意的是,这两个项目都还处于非常早期的阶段,有可能在将来出现 180° 的变化,因此我这里的分析只能反映当前的状态,不代表后期演进的方向。

目标

作为老江湖,在分析一个项目之前,我都会先看下该项目当前核心开发者的背景。在分析代码之前,我一度以为这两个项目存在竞对关系,但实际上他们面向的是不同场景。

ai-gateway 基于 Envoy Gateway 开发,其背后开发者也一样来自于 Tetrate 公司。这个项目看上去像是 Envoy Gateway 的 addon,类似于标准版和 Pro for AI 版的区别。llm-instance-gateway 的几个开发者以Google 的开发者为主,其当前 POC 版本也是基于 Envoy Gateway 的。据我猜测,ai-gateway 的开发目标和现有的 API Gateway 抢占 AI 平台场景一样,是 Envoy Gateway 自然的延申,只是它单独拆分一个仓库出来而不是像 Kong/Higress 那样在主仓库里开发。ai-gateway 面向的更多是 AI 平台或 AI 应用的构建者,主打提供简化 AI 开发的功能。这个项目能走多远,可能取决于 Tetrate 的网关产品的发展情况。

而 llm-instance-gateway 则旨在提供一个面向内部推理服务的网关的标准实现,占住 K8S AI infra 的生态位。它更多地考虑怎么做不同推理服务节点之间的负载均衡,如何更好地让内部用户接入推理服务。这个项目能走多远,可能取决于它那一套调度的机制能否落地到更多企业。

实现

这两个项目的实现有个共同之处,都是 Envoy 上跑 extProc 来完成请求的处理。简单来说就是 Envoy 侧收到请求,发起一个对应的 grpc 请求到本地服务;本地服务里完成请求改写、通过设置特定请求头选择上游服务器等功能,再发回 Envoy;然后 Envoy 代理到目标上游服务器。

ai-gateway

ai-gateway 在 gateway api 上增加了 LLMRoute 和 LLMBackend 两个 CRD,分别是 HTTPRoute 和 Backend 的包装。目前就只有一个功能:根据配置的 schema 做协议转换(当下支持 OpenAI 变 AWS bedrock)。比较奇怪为什么它没有沿用 Envoy Gateway 内以 Policy Attachment 为主的设计,弄一个 LLMPolicy 来实现这个功能。

llm-instance-gateway

llm-instance-gateway 引入了以下几个 CRD:

  • InferencePool
  • InferenceModel

InferenceModel 描述了对外提供的模型,它有可能是基础模型,也有可能是 lora adapter。
InferencePool 类似于 Service,能够通过 selector 选中运行推理引擎(比如 vllm)的 pod。严格来说应该是运行推理引擎的推理服务器,比如跑 vllm 的 triton,不过太拗口了,所以让我们忽略掉中间推理服务器这一层。InferencePool 和 InferenceModel 是一对多的关系,llm-instance-gateway 假定 InferenceModel 里定义的模型能够运行在 InferencePool 上。换句话说,InferencePool 里的每个 pod 需要满足下面的条件:

  1. 启动时能够运行所有引用了该 InferencePool 的 InferenceModel,至少是其中的基础模型
  2. 支持动态加载 InferenceModel 里的 lora adapter。

InferenceModel 里面还有 TargetModels 字段,支持对不同版本的 model 做 traffic split。

实际上 llm-instance-gateway 的控制面流程并没有跑通,各个 CRD 间还没有串起来。如果按我的思路,后续有可能把这些 InferenceModel/InferencePool 合并拍平成一个内部结构,再翻译成 Service,这样就能以较低的成本跑在 k8s 上面。支持按不同的 Service 做 traffic split 本身也是大多数主流 gateway 的能力。

llm-instance-gateway 通过推理引擎(目前只支持 vllm)暴露出来的 metrics 做负载均衡。它很有创意的一点是引入了 load balancer filter chain 的概念,支持通过多组 predicate 和 filter 来做分流。如果像一般的负载均衡一样,每次都只选中一个节点,难免会出现惊群的情况(不同的网关同时选中一个最佳节点,导致这个节点负载飙升)。所以 llm-instance-gateway 的每个 filter 都是返回最佳范围内的多个节点,尽可能在选择中包含多个节点,并从中随机选中一个最终节点。具体的 filters 见下图:

schedular-flowchart.png

其中最为关键的是 leastQueuingFilterFunc 这个 filter,它负责选中当前排队最少的 pod。其选择规则为:

        if pod.WaitingQueueSize >= min && pod.WaitingQueueSize <= min+(max-min)/len(pods) {
            filtered = append(filtered, pod)
        }

如果存在没有排队的 pod,那么这些 pod 一定会被选上。另外有排队的 pod 里,排队最少的一批也会选上。

不过这个规则似乎有些 edge case。在所有 pod 都存在排队的情况下,<= min+(max-min)/len(pods) 不一定能得出足够多的候选 pod。

举个例子,假如 pod 的数量特别少,只有 2 个 pod 且它们上都有排队的请求。那么所有的网关都会调度到排队最少的那个 pod,导致它在下一轮成为排队最多的 pod,最后出现一种类似“跷跷板”的场景。另一种情况是,pod 的数量特别多且都有排队的请求。由于公式里会除以 pod 的数量,在 max-min 固定的情况下,当 pod 的数量特别大,选择的区间就会特别小。假设所有 pod 都排队,且推理任务的耗时呈正态分布,那么我们可以合理假设这个排队的分布也是正态分布的。只要 pod 的数量非常大,就仅有 3 sigma 以外的极少数 pod 能够被选中,结果导致这一轮里的请求都打在极少数 pod 上。

一种避免出现上述极端场景的保护方案可以是,加上至少选中 max(n, m% of the pods) 的条件。比如要求返回结果里有至少 4 个或至少 20% 的 pod。

当下 llm-instance-gateway 还处于 POC 阶段,许多地方特别依赖 vllm 的行为(尤其是 metrics 类别)而不是主流推理引擎(vllm,SGLang,TensorRT-LLM等)的交集。除了负载均衡做得比较完善外,作为网关其余部分还都是脚手架级别,离生产可用还有一段距离。


spacewander
5.6k 声望1.5k 粉丝

make building blocks that people can understand and use easily, and people will work together to solve the very largest problems.