引言

vLLM、SGLang 等推理引擎解决的核心问题,是如何在单个 Model Server 内高效完成模型加载、请求批处理、Prefill、Decode 与 KV Cache 管理。当系统从单实例扩展为由多个 Model Server 组成的推理集群后,新的问题随之出现:

  • 一个请求应当被发送到哪个 Model Server?
  • 如何避免部分实例过载而另一些实例空闲?
  • 如果某个实例已经缓存了请求前缀对应的 KV Cache,能否优先复用?
  • 当实例数量动态变化时,如何维护可用 Endpoint 的视图?

llm-d 关注的正是这一层问题。它并不替代 vLLM 或 SGLang,而是在多个推理实例之上提供面向 LLM 工作负载的请求路由、Endpoint 调度与 KV Cache 感知能力。

本文聚焦 llm-d 的核心架构与智能路由机制。P/D Disaggregation、KV Offloading、Autoscaling 与 Batch Serving 等能力将在下篇展开。


1. llm-d 的定位

可以将 llm-d 与推理引擎的职责划分为两个层次:

层次 主要职责 典型组件
单实例推理 模型加载、Continuous Batching、Prefill、Decode、KV Cache vLLM、SGLang、TensorRT-LLM
分布式 Serving Endpoint 发现、请求调度、负载感知、Prefix Cache 感知 llm-d

Model Server 是实际执行推理的计算层。以 vLLM 为例,一个 Model Server 会加载模型、管理 GPU 上的 KV Cache,并暴露 OpenAI-compatible API 以及 Queue Depth、Running Requests、KV Cache Utilization 等指标。

llm-d 并不直接执行模型计算,而是根据这些运行状态决定请求应当由哪个 Model Server 处理。


2. 核心组件:InferencePool、Proxy 与 EPP

llm-d 的整体关系需要区分两条路径:请求数据路径与调度控制路径。前者负责真正转发推理请求,后者负责维护候选 Endpoint 并完成选路。

%%{init: {"look": "handDrawn", "theme": "base", "handDrawnSeed": 7}}%%
flowchart TB
    subgraph CTRL["Scheduling / Control"]
        direction LR
        IP["InferencePool<br/>selector / targetPorts"]
        E["EPP<br/>Endpoint Picker"]
        IP -. "candidate endpoint definition" .-> E
    end

    subgraph DATA["Request Data Path"]
        direction LR
        C[Client] --> G["Gateway / Proxy"]
        G -->|"forward request"| M["Selected Model Server<br/>vLLM / SGLang"]
        M --> GPU[GPU]
    end

    G -. "scheduling request" .-> E
    E -. "selected endpoint" .-> G
    E -. "discover / observe endpoints" .-> M

    classDef entry fill:#EEE8FF,stroke:#7663C6,stroke-width:1.5px,color:#222
    classDef control fill:#D8F5F2,stroke:#3A9C96,stroke-width:1.5px,color:#222
    classDef compute fill:#FFF2BF,stroke:#B9952A,stroke-width:1.5px,color:#222

    class C,G,IP entry
    class E control
    class M,GPU compute

图中的实线表示请求数据路径:请求由 Client 进入 Gateway/Proxy,随后被转发至选定的 Model Server。虚线表示调度与状态关系:InferencePool 描述候选 Endpoint 集合,EPP 基于这些候选 Endpoint 及其运行状态做出选路决策,再将结果返回给 Proxy。

因此,InferencePool 并不是请求经过的数据面组件,EPP 也不承载模型推理流量。真正的数据转发仍由 Proxy 与 Model Server 完成。

2.1 Model Server

Model Server 是真正执行推理的组件,例如 vLLM 或 SGLang。它负责:

  • 加载模型并占用 GPU 等加速器资源;
  • 执行 Prefill 与 Decode;
  • 管理 KV Cache;
  • 暴露推理 API;
  • 暴露 Queue、Running Requests、KV Cache Utilization 等运行指标。

因此,llm-d 的调度对象并不是 GPU 本身,而是提供推理能力的 Endpoint。在多数普通部署中,一个 Endpoint 可以近似理解为一个 Model Server Pod 的 IP:Port。

2.2 InferencePool

InferencePool 描述一组可以被 EPP 调度的 Model Server Endpoint。

其核心职责有两项:

  1. Endpoint Discovery:通过 Kubernetes Label Selector 发现符合条件的 Model Server Pod,并跟踪其 Ready 状态及扩缩容变化;
  2. Gateway Integration:声明与 EPP 的关联,使 Gateway/Proxy 能够在转发请求前获得 EPP 的 Endpoint 选择结果。

需要注意,InferencePool 本身不执行推理,也不是请求必须“经过”的数据面代理。它更接近一份 可调度 Endpoint 集合的声明与发现机制。

2.3 Proxy 与 EPP

Proxy 与 EPP 的职责边界可以概括为:

  • Proxy 负责转发请求;
  • EPP 负责决定请求发往哪里。

当请求进入 Gateway/Proxy 后,Proxy 会将调度决策交给 EPP。EPP 从 InferencePool 对应的候选 Endpoint 中选择一个目标,随后 Proxy 再将原始请求转发至该 Model Server。

这使得数据转发与调度策略相互解耦:Proxy 无需理解 LLM 的 Queue、KV Cache 或 Prefix Cache,只需要执行 EPP 返回的路由决策。


3. EPP:面向推理请求的 Scheduler

EPP(Endpoint Picker)可以理解为 llm-d 的 Request Scheduler。

如果将 Kubernetes Scheduler 的职责概括为:

1
Pod -> Node

那么 EPP 的职责可以概括为:

1
Inference Request -> Model Server Endpoint

EPP 的核心调度模型采用:

1
Filter -> Score -> Pick

对应流程如下:

%%{init: {"look": "handDrawn", "theme": "base", "handDrawnSeed": 11}}%%
flowchart LR
    R["Inference Request"]
    C["Candidate Endpoints"]

    subgraph SP["SchedulingProfile"]
        direction LR
        F[Filters] --> S["Weighted Scorers"] --> P[Picker]
    end

    R --> F
    C --> F
    P --> O["Selected Endpoint"]

    classDef input fill:#EEE8FF,stroke:#7663C6,stroke-width:1.5px,color:#222
    classDef stage fill:#D8F5F2,stroke:#3A9C96,stroke-width:1.5px,color:#222
    classDef result fill:#FFF2BF,stroke:#B9952A,stroke-width:1.5px,color:#222

    class R,C input
    class F,S,P stage
    class O result

3.1 Filter:确定“谁有资格”

Filter 用于缩小候选 Endpoint 集合。

例如,在不同场景中可以根据以下条件过滤:

  • Endpoint 的角色或 Label;
  • SLO Headroom;
  • Prefix Cache Affinity;
  • Prefill / Decode 角色。

Filter 的目标不是判断哪个 Endpoint 最优,而是先排除当前请求不应选择的候选对象。

3.2 Score:确定“谁更合适”

通过 Filter 的 Endpoint 会进入 Scoring 阶段。EPP 可以组合多个 Scorer,例如:

  • Queue Depth;
  • Running Requests;
  • Token Load;
  • KV Cache Utilization;
  • Prefix Cache Match;
  • Predicted Latency。

不同 Scorer 可以配置不同权重,最终形成 Endpoint 的综合分数。

3.3 Pick:确定最终 Endpoint

Picker 根据评分结果选择最终 Endpoint。最常见的策略是选择最高分 Endpoint,也可以采用 Weighted Random 等方式降低大量请求同时集中到单个最高分 Endpoint 的风险。

因此,EPP 并不是固定的一套“最短队列算法”,而是一套可组合的 Request Scheduling Framework。


4. 智能路由:从 Load-Aware 到 KV Cache-Aware

传统 L7 Load Balancer 通常依据连接数、请求数或基础资源利用率进行流量分配。LLM Serving 的请求成本差异显著,并且不同 Endpoint 之间还存在 KV Cache Locality,因此仅使用传统指标不足以得到稳定的调度结果。

llm-d 的智能路由将 负载状态 与 KV Cache Locality 同时纳入调度决策。

4.1 Load-Aware Routing:识别真实 Serving 压力

传统 Round Robin 隐含了一个假设:不同请求的处理成本大致接近。但这一假设在 LLM Serving 中并不成立。

例如:

1
2
Request A: 500 input tokens
Request B: 20,000 input tokens

从 HTTP 层面看,两者都只是一个请求;从 GPU 计算量和 KV Cache 占用来看,两者的成本可能相差巨大。

因此,仅按照请求数量做均衡容易出现如下情况:

1
2
Pod A: 少量长请求,实际负载很高
Pod B: 较多短请求,仍有剩余容量

llm-d 可以利用 Model Server 暴露的运行状态进行 Load-Aware Routing,包括:

  • Queue Depth:等待处理的请求数量;
  • Running Requests:当前正在执行的请求数量;
  • Token Load:Endpoint 正在承担的 Token 工作量;
  • KV Cache Utilization:KV Cache 的占用压力。

其目标并不是简单选择“请求数最少”的 Endpoint,而是尽量将请求分配给具有更多实际 Serving Headroom 的实例。

4.2 Prefix / KV Cache-Aware Routing:利用 Cache Locality

Load-Aware Routing 解决的是“哪个 Endpoint 更空闲”,而 LLM Routing 还有另一个特殊维度:KV Cache Locality。

假设多个 Endpoint 均可以处理同一个模型:

1
2
3
Pod A: 已缓存当前请求的大部分 Prefix KV
Pod B: 只缓存少量 Prefix KV
Pod C: 完全没有相关 KV

如果将请求发送至 Pod A,就可以复用已有 Prefix KV,减少重复 Prefill;如果发送至 Pod C,则可能需要重新计算完整 Prompt。

因此,Prefix Cache-Aware Routing 的目标是:

在满足负载与服务质量约束的前提下,优先选择已经拥有更多可复用 Prefix KV 的 Endpoint。

其直接收益通常体现在减少重复 Prefill、降低 TTFT,并提升 GPU 的有效计算比例与整体吞吐。

但 Prefix Locality 不能成为唯一调度依据。如果某个 Endpoint 的 Cache Match 很高,但其 Queue 已严重堆积,继续将请求集中到该实例会形成热点。因此实际调度通常需要同时考虑:

1
Cache Locality + Current Load

这也是 llm-d 将 Prefix Cache Scorer 与 Queue、Token Load、KV Cache Utilization 等 Scorer 组合使用的原因。

4.3 Prefix Routing 的两种实现:Approximate 与 Precise

为了判断“某段 Prefix KV 位于哪个 Endpoint”,llm-d 提供两种不同实现路径。

4.3.1 Approximate Prefix Routing

Approximate 模式由 EPP 根据历史路由行为维护近似 Prefix 状态。

1
2
3
某个 Prefix 刚刚被路由到 Pod A
↓
EPP 推测 Pod A 很可能仍然持有对应 KV

这种方式的优势是轻量,不依赖 Model Server 主动汇报 KV Cache 变化。

其局限在于,EPP 的推测可能与真实状态发生偏离。例如 Pod A 已经因为内存压力淘汰了部分 KV Block,但 EPP 尚未感知这一变化。

4.3.2 Precise Prefix Routing

Precise 模式不再依赖“曾经把请求路由到哪里”的推测,而是使用 Model Server 实际产生的 KV Cache 状态变化事件维护缓存位置视图。

它依赖两个关键能力:

  • KVEvents:Model Server 对 KV Cache 状态变化发出的元数据事件;
  • KV Cache Indexer:消费这些事件并维护 KV Block 到 Endpoint 的位置索引。

与 Approximate 模式相比,Precise 模式能够更直接地反映 Model Server 当前的真实缓存状态。其完整的数据流将在后续 KVEvents 与 KV Cache Indexer 小节中展开。

4.4 KVEvents:KV Cache 的状态变化通知

KVEvents 并不是 KV 数据传输协议,也不会将真正的 K/V Tensor 发送给 EPP。

它更接近 Model Server 对自身 KV Cache 状态变化发布的元数据事件,例如:

  • BlockStored:新的 KV Block 被缓存;
  • BlockRemoved:某个 KV Block 被淘汰;
  • AllBlocksCleared:当前实例的 KV Cache 被整体清空。

真正的 KV Tensor 仍然位于 Model Server 所管理的 GPU、CPU 或其他 Cache Tier 中。

因此可以将两者区分为:

1
2
KV Cache = 真正的推理数据
KVEvents = KV Cache 的状态变更通知

vLLM、SGLang 等 Model Server 可以提供 KVEvents 能力,llm-d 则利用这些事件建立全局缓存位置视图。

4.5 KV Cache Indexer:从事件流构建全局 KV 视图

KV Cache Indexer 位于 EPP 内部,其核心数据结构可以抽象为:

1
Block Key -> Endpoints

例如:

1
2
3
Block A -> Pod 1, Pod 3
Block B -> Pod 1
Block C -> Pod 2, Pod 3

Indexer 同时承担两类工作:一类是持续消费 Model Server 的 KVEvents,更新缓存位置索引;另一类是在请求到达时,根据 Prefix 对索引进行查询,为调度器生成每个 Endpoint 的 Prefix Match 信息。

%%{init: {"look": "handDrawn", "theme": "base", "handDrawnSeed": 23}}%%
flowchart TB
    M["Model Servers<br/>vLLM / SGLang"] -->|"Write Path: KVEvents"| I[("KV Index<br/>block key -> endpoints")]

    R["Inference Request"] --> T["Token Producer"]
    T --> P["Precise Prefix Cache Producer"]
    P <-->|"Read Path: block lookup"| I
    P --> MI["PrefixCacheMatchInfo"]
    MI --> S["Prefix Cache Scorer"]

    classDef source fill:#EEE8FF,stroke:#7663C6,stroke-width:1.5px,color:#222
    classDef control fill:#D8F5F2,stroke:#3A9C96,stroke-width:1.5px,color:#222
    classDef data fill:#FFF2BF,stroke:#B9952A,stroke-width:1.5px,color:#222

    class M,R source
    class T,P,S control
    class I,MI data

4.5.1 Write Path:持续更新索引

Model Server 的 KV Cache 状态不断变化。每当 Block 被写入、淘汰或整体清空时,相应 KVEvents 会更新 Index。

例如:

1
2
3
4
5
6
7
8
Pod A: BlockStored(X)
=> X -> Pod A

Pod B: BlockStored(X)
=> X -> Pod A, Pod B

Pod A: BlockRemoved(X)
=> X -> Pod B

Write Path 的目标是让索引持续接近当前真实缓存状态。

4.5.2 Read Path:为当前请求查询 Cache Locality

当新请求到达后,Token Producer 先得到请求对应的 Token 序列,Precise Prefix Cache Producer 再构造可查询的 Block Key,并对 Index 执行查找,计算各 Endpoint 能够复用的连续 Prefix。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Request Prefix:
B0 B1 B2 B3 B4

Pod A:
B0 ✓ B1 ✓ B2 ✓ B3 ✓ B4 ✗
=> 连续命中 4 个 Block

Pod B:
B0 ✓ B1 ✓ B2 ✗
=> 连续命中 2 个 Block

Pod C:
B0 ✗
=> 连续命中 0 个 Block

这里关注的是 连续 Prefix,而不是任意 Block 的命中数量。由于自回归模型具有因果依赖,后续 KV Block 无法脱离前面的 Prefix 链独立复用。

Indexer 最终提供的是缓存位置与 Prefix Match 信息,而不是最终路由结果。真正的 Endpoint 选择仍由 EPP Scheduler 综合其他信号完成:

1
2
3
4
5
KV Cache Indexer
回答:“当前请求的 Prefix KV 在哪里?”

EPP Scheduler
回答:“综合 Cache、Load、Latency 等因素,最终选谁?”

4.6 最终路由决策:将多个信号汇入 EPP

前面的 Load-Aware、Prefix Cache-Aware 与 SLO 相关能力并不是彼此独立的路由器,而是共同为 EPP 的 SchedulingProfile 提供决策依据。不同信号可能进入 Filter,也可能进入 Scorer,最终统一经过 Pick 阶段得到目标 Endpoint。

%%{init: {"look": "handDrawn", "theme": "base", "handDrawnSeed": 31}}%%
flowchart LR
    R["Request Context<br/>model / objective / prompt"]
    L["Load Signals<br/>queue / running / token load / KV util"]
    K["Cache Signals<br/>prefix match / cache locality"]
    O["SLO / Policy<br/>latency headroom / role constraints"]

    E["EPP<br/>SchedulingProfile"]
    T["Selected Endpoint"]
    M["Model Server"]

    R --> E
    L --> E
    K --> E
    O --> E
    E --> T --> M

    classDef input fill:#EEE8FF,stroke:#7663C6,stroke-width:1.5px,color:#222
    classDef control fill:#D8F5F2,stroke:#3A9C96,stroke-width:1.5px,color:#222
    classDef result fill:#FFF2BF,stroke:#B9952A,stroke-width:1.5px,color:#222

    class R,L,K,O input
    class E control
    class T,M result

传统负载均衡主要关注连接数、请求数或基础资源利用率;llm-d 则进一步将 LLM Serving 特有的信息纳入请求调度,包括 Endpoint 当前负载、Token 工作量、KV Cache 压力、Prefix Cache Locality,以及请求对应的 SLO 或角色约束。

因此,llm-d Router 的核心价值并不是提供另一套通用 L7 负载均衡,而是把推理系统内部状态转化为可用于 Request Scheduling 的信号。


5. 总结

本文可以归纳为三层逻辑:

1
2
3
4
5
6
7
8
9
10
11
第一层:Endpoint Discovery
InferencePool
=> 哪些 Model Server 可以被调度

第二层:Request Scheduling
EPP
=> Filter -> Score -> Pick

第三层:LLM-Aware Signals
Load / Token / KV Cache / Prefix Locality
=> 让 Endpoint 选择更符合推理工作负载特征

其中,KV Cache Indexer 又将单实例内部的 Prefix Cache 能力扩展为可供 Router 使用的全局 Cache Locality 信息:

1
2
3
4
5
6
7
Model Server KV Cache
↓ KVEvents
KV Cache Indexer
↓
Prefix Match Information
↓
EPP Scheduling

这构成了 llm-d 上半部分最核心的架构闭环:发现可用 Endpoint、理解 Endpoint 状态,并基于负载与 KV Cache Locality 对请求进行智能调度。

下篇将在此基础上继续讨论:

  • Prefill / Decode Disaggregation;
  • P/D 请求编排与 KV Transfer;
  • KV Offloading 与 P2P Prefix Cache Sharing;
  • Autoscaling 与 SLO 驱动的容量管理;
  • Batch / Async Inference。

参考资料