Skip to content

AI Infrastructure

从 Ingress 到推理网关:Gateway API 与 Inference Extension

Kubernetes 网络如何具备模型感知能力:Gateway API 的资源模型、Inference Extension 的 InferencePool,以及不再轮询,而是依据 KV 缓存和队列指标做路由的端点选择器。

Ivan Porta

Founder & Principal Engineer

13 分钟阅读
#gateway-api#inference#kubernetes#vllm#envoy#platform-engineering
从 Ingress 到推理网关:Gateway API 与 Inference Extension

传统的负载均衡假设所有后端都是一样的,请求交给哪个后端处理都无所谓。LLM 流量改变了这一点。举例来说,一个 vLLM 副本可能为一段进行中的对话保留着已经预热的 KV 缓存,另一个副本却要从零开始计算。有的请求只是一句简短的提问,有的请求则塞进了一整个庞大的代码库。轮询式的负载均衡器分不出这些差别,于是请求可能被送到繁忙的副本上,而握着缓存成果的副本却闲在一边。结果就是用户感受到更高的延迟,GPU 资源被白白浪费。

Kubernetes 用两层来解决这个问题。Gateway API 正在取代 Ingress,成为流量进入集群的主要方式。在它之上,由同一个社区(SIG-Network 联合 WG-Serving)开发的 Gateway API Inference Extension 增加了一组 API 和一个协议。这让网关不再把模型服务器当成普通的 HTTP 后端,而是可以依据队列深度、缓存状态、已加载的适配器等因素来路由流量。

从 Ingress 到 Gateway API

在进入推理话题之前,先快速过一下 Gateway API 是什么,以及它对今天的 Kubernetes 网络为什么重要。它与 Ingress 控制器最主要的区别在于支持协作的方式。在 Ingress 里,API 层面没有任何机制阻止多个团队使用同一个主机名。Kubernetes 会接受这些互相冲突的对象,接下来发生什么取决于控制器。例如,ingress-nginx 会合并所有使用同一主机的 Ingress 资源,路径重复时最早的规则优先。这意味着一个团队可以往另一个团队视为己有的主机名上添加路由或设置。还有命名空间绑定的问题:Ingress 只能使用自己命名空间里的 TLS Secret。这意味着平台工程师要么冒着私钥泄露的风险把证书复制到每一个应用命名空间,要么让每个团队自己管理证书的生命周期。最后,Ingress 的规范不覆盖超时、认证、限流、重写、金丝雀这些能力。这些功能通常通过各厂商私有的注解来处理,分散在应用、基础设施、运维等不同层面。

为了用一个有类型、区分角色、可扩展的 API 取代这堆混乱的注解组合,SIG-Network 团队在 KubeCon San Diego 2019 上提出了 Gateway API。它在 2023 年 10 月达到正式可用(GA)。但真正的转折点,是被广泛使用的 NGINX Ingress Community 控制器宣告退役。面对不再有版本发布、不再有修复、不再有安全补丁的现实,世界各地的团队意识到,迁移到 Gateway API 不再是可选项,而是一场需要规划并完成的紧迫迁移。

Gateway API 的设计思路,是按照每项决定归谁所有来划分职责,从而拆解这个不堪重负的 Ingress 对象:

  • GatewayClass 声明平台提供的某一类负载均衡器(内部 L7、外部 L7、某个厂商的实现)。
  • Gateway 将其中一类实例化。创建它的动作,就是在制备监听器、主机名、TLS 等资源。
  • 应用团队在自己命名空间里拥有的 HTTPRoute 对象挂接到那个 Gateway 上,声明「匹配这些条件的请求都发到我的后端」。

网关如何变成推理网关

Gateway API Inference Extension 的核心,是构建在 Envoy 外部处理过滤器(ext-proc)之上的一组新 CRD 和控制器。

每个进入的请求都会依次经过 Envoy 的 HTTP 过滤器链。当请求到达 ext-proc 过滤器时,Envoy 会向一个外部服务器(也就是这个扩展的控制器)打开一条双向 gRPC 流。Envoy 会把请求头发过去;当过滤器的处理模式有要求时,请求体也会一到达就发出。外部服务器回以指令,Envoy 照做执行:改写头、修改体、附加元数据,或者直接返回即时响应。由于处理逻辑运行在独立的服务器里,它可以独立部署、扩缩和更新,还可以用任何语言编写。ext-proc 这份契约由 Envoy 定义,但并不限于 Envoy。遵循同一外部处理协议的网关还包括基于 Envoy 的 Istio、Agentgateway 和 NGINX Gateway Fabric。

请求到达 Envoy 后,首先进行的仍是常规的路由匹配。不同的是,匹配到的 HTTPRoute 现在指向的不是普通 Service,而是 InferencePool。InferencePool 是一个新的 CRD,列出模型服务器的 Pod 以及管理它们的 ext-proc 服务器。Envoy 的职责到此为止。路由逻辑由这个 ext-proc 服务器承担,它被称为端点选择器(Endpoint Picker, EPP):收集指标,为运行推理引擎的候选 Pod 打分,并决定哪个 Pod 来处理这个请求。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: llama3-route
  namespace: llm
spec:
  parentRefs:
  - name: inference-gateway
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - group: inference.networking.k8s.io
      kind: InferencePool
      name: vllm-llama3-8b
---
apiVersion: inference.networking.k8s.io/v1
kind: InferencePool
metadata:
  name: vllm-llama3-8b
  namespace: llm
spec:
  selector:
    matchLabels:
      app: vllm-llama3-8b
  targetPorts:
  - number: 8000
  endpointPickerRef:
    name: vllm-llama3-8b-epp
    port:
      number: 9002
    failureMode: FailClose

加入资源池的 Pod 需要遵循项目的模型服务器协议。这个协议很简单:提供 OpenAI 兼容的 Completions 和 Chat 两个 API,并暴露队列深度、运行中请求数、KV 缓存利用率等 Prometheus 指标。EPP 用这些指标做路由决策。目前 vLLM、SGLang 以及 Triton 的 TensorRT-LLM 后端都支持它,只是各自的指标名称不同。

端点选择器(llm-d-router)如何工作

到 2026 年,EPP 的代码库已经迁移到 llm-d 项目的 llm-d/llm-d-router(原名 llm-d-inference-scheduler)。留在 Gateway API Inference Extension 里的版本很快会被归档。本文将以 llm-d-router 的 EPP 为准展开,它的调度逻辑在迁移之后有了显著改进。

EPP 与插件

EPP 的可配置性非常高,如今支持超过 80 个插件,横跨调度、请求控制、流控、请求处理和数据层五个类别。这些插件在配置中启用,并在启动时加载。例如:

kind: ConfigMap
apiVersion: v1
metadata:
  name: vllm-llama32-1b-epp
data:
  default-plugins.yaml: |
    apiVersion: llm-d.ai/v1alpha1
    kind: EndpointPickerConfig
    plugins:
    - type: queue-scorer
    - type: kv-cache-utilization-scorer
    - type: prefix-cache-scorer
    - type: metrics-data-source
      parameters:
        scheme: "http"
        path: "/metrics"
        insecureSkipVerify: true
    - type: core-metrics-extractor
    schedulingProfiles:
    - name: default
      plugins:
      - pluginRef: queue-scorer
        weight: 2
      - pluginRef: kv-cache-utilization-scorer
        weight: 2
      - pluginRef: prefix-cache-scorer
        weight: 3

除了你在配置里定义的插件,EPP 还会加载若干默认插件。比如,某个 profile 没有指定 picker 时它会补上 max-score-picker;没有配置解析器时,它会带上 OpenAI、Anthropic 和 vLLM-HTTP 三个解析器。它还会加载 global-strict-fairness-policystatic-usage-limit-policy 等等,其中包括为你配置的插件提供数据所需的那些插件。举个例子,prefix-cache 插件需要分词后的提示词,即使 ConfigMap 里没有定义,EPP 也会在启动时把所需的分词插件准备好。

{"level":"info","ts":1785950733.484222,"caller":"loader/configloader.go:164","msg":"Instantiated all plugins and applied system defaults. Effective raw configuration","config":"{Plugins: [{Name: queue-scorer, Type: queue-scorer} {Name: kv-cache-utilization-scorer, Type: kv-cache-utilization-scorer} {Name: prefix-cache-scorer, Type: prefix-cache-scorer} {Name: metrics-data-source, Type: metrics-data-source, Parameters: {\"insecureSkipVerify\":true,\"path\":\"/metrics\",\"scheme\":\"http\"}} {Name: core-metrics-extractor, Type: core-metrics-extractor} {Name: single-profile-handler, Type: single-profile-handler} {Name: max-score-picker, Type: max-score-picker} {Name: fcfs-ordering-policy, Type: fcfs-ordering-policy} {Name: global-strict-fairness-policy, Type: global-strict-fairness-policy} {Name: static-usage-limit-policy, Type: static-usage-limit-policy} {Name: openai-parser, Type: openai-parser} {Name: anthropic-parser, Type: anthropic-parser} {Name: vllmhttp-parser, Type: vllmhttp-parser} {Name: utilization-detector, Type: utilization-detector}], SchedulingProfiles: [{Name: default, Plugins: [{PluginRef: queue-scorer, Weight: 2.00} {PluginRef: kv-cache-utilization-scorer, Weight: 2.00} {PluginRef: prefix-cache-scorer, Weight: 3.00} {PluginRef: max-score-picker}]}], DataLayer: {Sources: [{PluginRef: metrics-data-source, Extractors: [{PluginRef: core-metrics-extractor}]}], Discovery: <nil>}, FlowControl: {MaxBytes: unlimited, MaxRequests: unlimited, SaturationDetector: {PluginRef: utilization-detector}}, RequestHandler: {Parsers: [{PluginRef: openai-parser}, {PluginRef: anthropic-parser}, {PluginRef: vllmhttp-parser}]}}"}

EPP 根据每个插件生产和使用的数据键构建一张依赖图。对每个请求,它都按这张图的正确顺序运行那些生产数据的插件。

EPP 在带外做的事

在主请求流之外,EPP 会在 Kubernetes API 上对 InferencePoolPodInferenceObjectiveInferenceModelRewrite 资源建立监视。它从池中每个 Pod 抓取指标,输出自己的指标;在启用精确前缀缓存感知时,还会订阅每个 Pod 的 KV 缓存事件流。

精确的前缀缓存感知之所以特别有用,是因为 Prometheus 指标只能显示 KV 缓存有多满,说不出哪些块在哪个 Pod 上。通过 ZeroMQ 的 PUB 套接字订阅推理引擎的通知,就能收到块(vLLM 的 KV 缓存单位,默认固定为 16 个 token,由其内容连同之前所有内容的链式哈希来标识)被添加或被逐出的更新。这些更新由引擎适配器解码后存放在 EPP 的内存里。记住,推理引擎必须开启 KV 事件发布。vLLM 默认不发送这些事件,因此每个 Pod 都需要类似 --kv-events-config '{"enable_kv_cache_events": true, "publisher": "zmq", "endpoint": "tcp://*:5557"}' 的启动参数。

当请求到来时

一个新请求经由 ext-proc 流到达 EPP 后,EPP 会缓冲各个分块,直到收到流结束标志,等待完整的请求体到齐。这样它手里才有完整的 JSON 可以处理。接下来,它根据 URL 路径的后缀选择解析器。默认设置了三个解析器,各自认领自己的后缀;若都不匹配,请求会被拒绝:

  • openai-parser 认领 completionschat/completionsembeddingsresponses 及其同类;
  • anthropic-parser 认领 messagesmessages/count_tokens
  • vllmhttp-parser 认领 inference/v1/generate

被选中的解析器把原始字节转换成带类型的请求,包括模型名、消息或提示词以及流式标志,并把它存进一个按请求划分的状态对象。

{"caller":"handlers/server.go:435","msg":"EPP received request","x-request-id":"a8a70838-..."}
{"caller":"handlers/server.go:448","msg":"Incoming body chunk","EoS":false}
{"caller":"handlers/server.go:448","msg":"Incoming body chunk","EoS":true}
{"caller":"handlers/server.go:454","msg":"decoding"}

这种设计的代价是内存:EPP 在处理期间会把整个请求体保存在内存里,非流式的响应体也同样会被完整缓冲,而且它自身没有大小上限,因此超长的提示词会消耗网关内存。流式响应不受影响;EPP 会把 SSE 分块一到达就转发给客户端。

接下来,EPP 查找与资源池和所请求模型匹配的 InferenceModelRewrite 资源,并应用其规则。这个资源允许你定义多个带权重的目标;可以把它理解成 HTTPRoute 式的流量拆分,只不过对象是模型。例如:

apiVersion: llm-d.ai/v1alpha2
kind: InferenceModelRewrite
metadata:
  name: canary-model-split
spec:
  poolRef:
    name: production-llm-pool
  rules:
  - matches:
    - model:
        value: "llama3"  
    targets:
    - modelRewrite: "llama3-stable"
      weight: 90
    - modelRewrite: "llama3-canary"
      weight: 10

一旦匹配,EPP 就把请求体里的 model 字段改成选中的目标并重新序列化请求体。在响应返回时,它再把响应体里的模型名换回原来的名字,因此客户端完全看不到这次改写。日志里,incomingModelName 是客户端请求的名字,targetModelName 是应用规则后实际使用的名字:

{"caller":"requestcontrol/director.go:220","msg":"No associated InferenceObjective found, using default","objectiveKey":""}
{"caller":"requestcontrol/director.go:281","msg":"LLM request assembled","incomingModelName":"meta-llama/Llama-3.2-1B-Instruct","targetModelName":"meta-llama/Llama-3.2-1B-Instruct","priority":0}

接下来,在 EPP 执行分词、哈希、打分这些任务之前,默认的准入控制器会检查资源池是否饱和,并把通过 InferenceObjective 将优先级设为低于 0 的请求以 429 状态码拒绝。优先级为 0 或更高的请求(没有匹配的 InferenceObjective 时的默认值)总是能通过这道检查。

注意:这种要么接受、要么拒绝的行为只是默认值。启用实验性的流控(flow control)功能后,请求会按优先级分层排队,并在每层内部得到公平处理。随着容量释放,它们按优先级顺序被派发。饱和信号本身也是一个插件,默认叫 utilization-detector,其阈值可以配置。

# PASS
{"level":"trace","caller":"requestcontrol/admission.go:116","msg":"Executing LegacyAdmissionController","objectiveKey":"sheddable-batch","priority":-1,"fairnessID":"default-flow"}
{"level":"trace","caller":"requestcontrol/admission.go:127","msg":"Request admitted","requestID":"4eb39a51-..."}
Rejection, mid-burst. Probes 1–5 got HTTP 429, and each produced this pair:
 
# DROPPED
{"level":"trace","caller":"requestcontrol/admission.go:76","msg":"Request rejected: system saturated and request is sheddable","x-request-id":"98bbfafe-...","objectiveKey":"sheddable-batch","priority":-1}
{"level":"error","caller":"handlers/server.go:478","msg":"Error handling request","error":"inference error: ResourceExhausted - system saturated, sheddable request dropped"}

请求被接受后,EPP 开始评估池中的 Pod。它先对所有就绪的 Pod 拍一张快照,然后按配置和依赖图依次运行各个插件。以前面那份 ConfigMap 为例,流程从分词开始:EPP 把提示词切成 4 字节的伪 token,再分组成块,然后把每个块与之前所有块一起做哈希。这样一来,每个块的哈希都代表到该处为止的整段前缀,所用的链式方法与 vLLM 相同。

注意:换一套配置,这个阶段可能改变或被跳过。如果使用精确的 prefix-cache 插件,这里的估算就变成对带外一节里那个 KV 事件索引的查询。如果彻底移除前缀评分器,这一步什么都不会发生。

随后 EPP 按顺序拿这些块哈希去比对自己的索引。它在第一个没有任何 Pod 被记录在案的块处停下,统计索引归到每个 Pod 名下的块数,并为每个候选 Pod 记下这些数字。

然后轮到调度器接手。profile 处理器选出适用的调度 profile(single-profile-handler 永远回答 default),该 profile 的过滤器再对快照做裁剪:

{"caller":"scheduling/scheduler.go:69","msg":"Running profile handler, Pick profiles","plugin":"single-profile-handler/..."}
{"caller":"scheduling/scheduler_profile.go:162","msg":"Completed running filter plugins","remainingEndpoints":2}

接着,每个评分器为剩下的每个 Pod 打出 0 到 1 之间的分数。例如:

  • queue-scorer:按等待队列在 Pod 之间做相对排名,快照里队列最短的 Pod 得 1,最长的得 0;所有队列一样长时,大家都得 1。
  • kv-cache-utilization-scorer:奖励空闲的缓存空间;上报 KV 缓存用了 83% 的 Pod 得 0.17,完全空闲的得 1。
  • prefix-cache-scorer:按 Pod 已经持有的本条提示词块的比例奖励缓存成果,50 块里有 40 块得 0.8,一块没有得 0。
{"caller":"scheduling/scheduler_profile.go:210","msg":"Calculated score","plugin":"queue-scorer/...","endpoint":"...8p55s...","score":1}
{"caller":"scheduling/scheduler_profile.go:210","msg":"Calculated score","plugin":"queue-scorer/...","endpoint":"...4r286...","score":1}
{"caller":"scheduling/scheduler_profile.go:210","msg":"Calculated score","plugin":"kv-cache-utilization-scorer/...","endpoint":"...8p55s...","score":1}
{"caller":"scheduling/scheduler_profile.go:210","msg":"Calculated score","plugin":"kv-cache-utilization-scorer/...","endpoint":"...4r286...","score":1}
{"caller":"scheduling/scheduler_profile.go:210","msg":"Calculated score","plugin":"prefix-cache-scorer/...","endpoint":"...8p55s...","score":0}
{"caller":"scheduling/scheduler_profile.go:210","msg":"Calculated score","plugin":"prefix-cache-scorer/...","endpoint":"...4r286...","score":0}

每个分数乘以 ConfigMap 里的权重后求和。本例中的 picker 是 max-score-picker,它会先打乱候选者以随机打破平局,再选出总分最高的那个。

{"caller":"scheduling/scheduler_profile.go:303","msg":"Candidate pods for picking","endpoints-weighted-score":[{"...8p55s...","Score":4},{"...4r286...","Score":4}]}
{"caller":"maxscore/picker.go:88","msg":"Selecting endpoints from candidates sorted by max score","max-num-of-endpoints":1,"num-of-candidates":2}
{"caller":"requestcontrol/director.go:488","msg":"Request handled","endpoint":"10.20.0.2:8000"}

EPP 把选中 Pod 的地址放进 x-gateway-destination-endpoint 头,同时写入同名键的动态元数据,一并发给 Envoy。Envoy 随即把请求体转发给那个 Pod。

微调、LoRA 适配器与 Inference Extension

如今,越来越多的公司不再只依赖 Claude 或 ChatGPT 这类服务,而是用开放权重模型在自己的基础设施上跑推理。这条路线有实打实的好处:规模上去之后成本更低,对数据的掌控更强,还能把通用模型适配到公司的具体需求上,而不必为每个用例完整微调并部署一个单独的模型。Meta 的 llama-cookbook、Microsoft 的 LoRA、Hugging Face 的 PEFT 这些项目让这种专业化变得容易得多,过去需要大量算力、数据和 ML 专业知识的技术,现在普通工程团队也能用上了。

什么是 LoRA

LoRA 背后的主要思想很直接。与其更新大模型里的每一个权重,不如冻结基础模型,只训练成对的小型低秩矩阵,即所谓「适配器」(adapter),把它们挂到为特定用例选定的层上。这样产出的工件通常只有几十到几百 MB,而不是几个 GB。在每个目标层上,输入既经过被冻结的原始权重,也经过适配器的低秩路径,两条路径的输出相加,得到专业化的结果。

这个方法免去了为每种专业化单独复制一份数 GB 模型的麻烦。推理时,适配器既可以合并进基础权重,也可以保持独立。这让 vLLM 这类支持 LoRA 的推理引擎能够在同一个共享基础模型之上,动态套用不同的适配器。

推理引擎启动时,vLLM 通过若干配置项为 LoRA 适配器准备内存。比如,--max-loras 设定同一个 GPU 批次里最多同时使用多少个适配器,--max-cpu-loras 设定多少份适配器权重可以缓存在主机内存里、按需搬进 GPU。适配器用 --lora-modules 注册,请求便可以通过 model 字段选中某个适配器,同时依旧共享同一个已加载的基础模型。

spec:
  template:
    spec:
      containers:
      - name: vllm
        image: vllm/vllm-openai:latest
        args:
        - --model
        - Qwen/Qwen2.5-3B-Instruct
        - --port
        - "8000"
        - --max-model-len
        - "2048"
        - --gpu-memory-utilization
        - "0.85"
        - --max-num-seqs
        - "16"
        - --enable-lora
        - --max-loras
        - "2"
        - --max-cpu-loras
        - "3"
        - --max-lora-rank
        - "32"
        - --lora-modules
        - medical=calebking/qwen2.5-3b-instruct-medical-lora
        - korean-law=kim0924/qwen2.5-3b-korean-law-lora
        - reasoning=namanadep/qwen2.5-3b-instruct-reasoning-lora-bf16

EPP 与 LoRA

发送一个指向 LoRA 适配器的请求时,EPP 流程的开头没有变化。请求进入 Envoy,经 ext-proc 流交给 EPP,OpenAI 解析器从 model 字段提取模型,并像之前一样应用任何匹配的 InferenceModelRewrite

curl -s http://35.216.6.64/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"korean-law","messages":[{"role":"user","content":"전세 계약이 무엇인가요?"}],"max_tokens":64}'

差别出现在 EPP 评估 InferencePool 里各端点的时候。不是每个副本都能同样好地服务一个 LoRA 适配器请求。可能一个副本上 korean-law 适配器已经处于激活状态,另一个还有余量可以加载它,还有一个的 LoRA 容量已经打满。EPP 借助推理引擎的 lora_requests_info 指标,弄清每个副本上哪些适配器处于激活或等待状态,以及一个副本最多能承载多少个。

{"msg":"Refreshed metrics", "ts":1787719680.300, "endpoint":{"name":"vllm-qwen25-3b-lora-6888d54787-wjcb4-rank-0"}, "metrics":["…", "vllm:lora_requests_info", "…"], "updated":"{ActiveModels:map[korean-law:0] WaitingModels:map[korean-law:0] MaxActiveModels:2 …}"}

为此,需要把 lora-affinity-scorer 插件加进 EndpointPickerConfig。配置好之后,这个插件会给每个候选端点打分:

  • 1.0 适配器已经在这个端点上激活
  • 0.8 有空闲适配器槽位:active + waiting < max_lora
  • 0.6 适配器已在这个端点上排队等待加载
  • 0.0 端点已满:请求要等槽位腾出来

注意:这并不会取代其他调度信号。调度器仍然会考虑队列深度、KV 缓存利用率、prefix-cache 局部性,以及其他已配置的评分器。LoRA 亲和只是多加了一项分数,用来表明每个副本有多适合服务所请求的适配器。

评分器跑完之后,就没有新东西了。各项分数乘以配置的权重后求和,picker 选出胜出的端点,EPP 把那个 Pod 的地址放进 x-gateway-destination-endpoint 返回给 Envoy。

一个网关,多个模型

State of AI Engineering 2026 统计,使用多个模型如今已是常态。在数千家将 AI 投入生产的组织里,超过 70% 使用三个或更多模型,运行超过六个模型的组织占比一年间几乎翻倍。团队在不断新增模型,而不是替换模型。在同一个集群里,你可能用一个网关同时挡在 Llama 池、Qwen 池和 Mistral 池前面,各自专注不同的任务。

不过,路由因此变得更复杂:OpenAI API 的模式把模型名放在请求体里,而请求体字段不是网关通常能用来路由的东西:网关一般只匹配路径和头。基于请求体的路由(body-based routing)解决了这个问题。这个扩展从 JSON 请求体里提取模型名,把它设置成 X-Gateway-Model-Name 头,HTTPRoute 就能像匹配任何其他头一样匹配它。之后用普通的 HTTPRoute 把请求路由到正确的 InferencePool 即可。例如:

rules:
- matches:
  - path:
      type: PathPrefix
      value: /
    headers:
    - type: Exact
      name: X-Gateway-Model-Name
      value: meta-llama/Llama-3.1-8B-Instruct
  backendRefs:
  - group: inference.networking.k8s.io
    kind: InferencePool
    name: vllm-llama3-8b

运维现实

  • 尽管扩展里还内置着自己的 EPP,最好还是用 llm-d 的路由器。 Gateway API Inference Extension 自带的端点选择器仍然能用,继续用它也不会坏掉什么。但代码已经迁到了 llm-d/llm-d-router,新的调度工作都发生在那里,旧版本将被归档。新建的资源池从一开始就指向 llm-d 的 EPP;至于存量池的切换,插件配置并不能直接平移,所以要当作一次有计划的迁移来做,而不是简单换个镜像。

  • 你可能正在运行自己从未声明过的插件。 不管 ConfigMap 里列了什么,加载器都会在其上叠加自己的插件:给没有 picker 的 profile 补一个 picker,加上各个解析器和策略,还有你所要求的插件自身依赖的插件。真实配置要到运行时才能确定。EPP 会在启动时打印一次实际生效的配置,紧接着还会把自动创建的数据生产者以单独的 "auto-created default producer" 日志行记录下来。两者都要截取、存档,并在每次升级后做对比。

  • 默认的前缀评分只是估算,不是精确查询。 没有 KV 事件跟踪时,EPP 看不见 Pod 缓存的内部。它用 4 字节伪 token 为提示词生成指纹,再按它推测每个 Pod 持有多少指纹来打分。这个排名有用,但没有保证。命中率要看推理引擎的数据,而不是评分器的;两者对不上时,就切换到精确跟踪。

一条务实的建议

如果你已经在一个普通 Service 后面运行着某个模型的多个副本,那就本周把这个池挪到 InferencePool 后面,剩下的交给你自己的流量来决定。试点一个下午就够:只带 queue-scorerkv-cache-utilization-scorer 部署 EPP,把一条 HTTPRoute 指向资源池,同时保留原来的 Service 路由以便随时切回。把生产环境的提示词长度分布在两套配置上重放,比较 p95 TTFT、每秒输出 token 数,以及引擎的预填充缓存命中率。前缀评分器留到第二轮再加,否则你分不清是哪个改动带来了差异。前缀命中不是让缓存部分的预填充变快,而是把那段计算直接跳过:仍要跑预填充的只有未缓存的尾部。在一台一年花费超过十万美元的 H100 节点上,这个差别不容小觑。如果在你的流量里看不到改善,那就不要采用它。

FAQ

关于推理网关,我们最常被问到的三个问题。

推理引擎只有一个副本,还需要推理网关吗?

不需要。EPP 的职责是依据队列深度、KV 缓存利用率和前缀局部性在多个副本之间做选择;只有一个 Pod 时无从选起。在你为同一个模型运行多个副本之前,继续用普通的 Service 就好。

哪些推理引擎可以配合它使用?

任何遵循模型服务器协议的引擎都可以:提供 OpenAI 兼容的 Completions 和 Chat 两个 API,并暴露队列深度、运行中请求数、KV 缓存利用率等 Prometheus 指标。目前 vLLM、SGLang 以及 Triton 的 TensorRT-LLM 后端都满足条件。

EPP 宕机会发生什么?

由 InferencePool 的 failureMode 决定。FailClose 会在选择器恢复之前拒绝请求;FailOpen 则让 Envoy 退回到对整个池的自带负载均衡,智能调度没有了,但流量能继续流动。

本网站使用Cookie进行分析。