vLLM Blog · 149 篇文章
📖 学习摘要 · 深度分析
vLLM prefill 搭配 TileRT 专用 decode:零修改 vLLM、按流量路由,主打每用户低延迟。

要点速览

  • PD 分离让 decode 侧变得可插拔:prefill 池、调度器、缓存层与 serving API 原地不动,decode 池成为一项可替换的选择。
  • TileRT 是面向每用户 decode 速度打造的专用推理运行时,与 vLLM 原生 decode 在同一吞吐–延迟前沿上取不同点,二者可以组合。
  • 集成完全落在 vLLM V1 公开扩展面:KVConnectorBase_V1 + MultiConnector 插件化加载,对 vLLM 零修改。
  • 路由器设 max_tokens=1 并透传 kv_transfer_params;TileRT connector 只认领带标记请求,其余严格 no-op。
  • 状态经 RDMA 单边写直达预注册 GPU 缓冲(Mooncake/NIXL),提取与 prefill 重叠,注入运行中引擎并启用 MTP。

解决什么问题

PD 分离已成大规模 LLM 服务的标准模式,但 agentic loop、交互式编码助手、实时语音等延迟敏感负载关心的是每个用户拿到 token 的速度而非聚合吞吐,而 vLLM 原生 decode 面向高吞吐批处理、覆盖海量模型与硬件,不是为该场景极致优化。实际的问题是:能否采用专用 decode 引擎的同时,不放弃 OpenAI 兼容 API、调度、prefix caching、tool calling 与 vLLM 的运维成熟度。本文给出肯定答案并展示如何落地。

核心思路

核心设计原则是对 vLLM 零改动:无 fork、无 patch、不包装内部 worker。集成完全落在 vLLM V1 的公开扩展面上——一个 KVConnectorBase_V1 实现(TileRTConnector),在 MultiConnector 下与其他 connector 组合,经标准 kv_connector_module_path 机制以插件加载。轻量 router 前置在 TileRT 池前:为每个请求设置 max_tokens=1(vLLM 完成首 token),并把目标 decode 节点放进标准透传字段 kv_transfer_params;TileRT connector 只认领带标记的请求、对其余严格 no-op。connector 仅充当 kv_producer,不碰调度与采样;状态以 RDMA 单边写进入 decode 节点上预注册的 GPU 缓冲(传输引擎可选 Mooncake 或 NIXL),提取发生在 forward 窗口内、由后台 sender 完成网络传输,与 prefill 完全重叠。

关键要点

  1. PD 分离使 decode 侧可插拔 — 分离 compute-bound 的 prefill 与 memory-bandwidth-bound 的 decode 已成标准模式,vLLM 通过一等公民的 connector 接口支持。这一架构转变带来一个容易被忽视的好处:一旦分离,decode 侧就可插拔——不同 serving 场景奖励不同引擎设计,而 prefill 池、调度器、缓存层与 serving API 保持原样。vLLM 原生 decode 仍是高吞吐批处理跨模型跨硬件的正确默认;TileRT 则是围绕“把每用户 decode 速度推向硬件极限”这一单一目标构建的新运行时,两者取吞吐–延迟前沿上的不同点,因此可以组合。
  2. 零修改 vLLM 的集成原则 — 核心设计原则是 zero changes to vLLM:无 fork、无 patch、不包装内部 worker。集成完全落在 vLLM V1 的公开扩展面上——一个 KVConnectorBase_V1 实现,在 MultiConnector 下组合,经标准 kv_connector_module_path 机制加载。这不仅是工程审美问题:新增 TileRT decode 池不会破坏已在运行的 vLLM 部署,升级 vLLM 也不需要重新移植 fork。经实际验证的配置是 NIXL 端到端运行:原生池用 vLLM 标准 NixlConnector,TileRT 池用 NIXL 模式的 TileRT connector,共享 prefill 只用一套传输库,只有 prefill 的 --kv-transfer-config 有所不同。
  3. 路由与认领过滤 — 一个轻量 router 前置在 TileRT 池前:为每个请求设置 max_tokens=1(vLLM 完成 prefill 并吐出第一个 token),并把目标 decode 节点放进标准透传字段 kv_transfer_params = {tilert_host, tilert_ctrl_port};发往原生池的流量照常走通常的分离代理、不做修改。TileRT connector 只认领携带标记的请求,对其余一切是严格 no-op,因此两个 decode 池可以共享同一个 prefill 实例(甚至同一个 forward batch):为部分流量采用 TileRT 对其余流量毫无影响。
  4. 纯 producer 角色 — TileRT connector 只充当 kv_producer:它从不触碰调度与采样,只在 prefill 之后抽取并发送状态。除此之外,prefill 实例就是一个原封不动的 stock vLLM server——调度、chunked prefill、prefix caching 等全部能力保持不变。这保证了集成面最小、行为可预期。
  5. 数据面:RDMA 单边写直达 GPU — prefill 完成后,请求的 attention 状态——compressed KV、sparse-attention index caches 以及少量 metadata——以 RDMA 单边写进入 decode 节点上预注册的 GPU 缓冲,传输引擎可选 Mooncake 或 NIXL;没有中间序列化,也不经 host memory 中转。handoff 协议本身独立于底层传输引擎,引擎的职责只是搬字节。为让跨引擎分离真正实用,三件事必须成立:传输要快、不能拖慢 prefill 节点、decode 引擎要能精确从 prefill 停下的地方继续。
  6. 与 prefill 完全重叠的状态提取 — 状态提取发生在 forward 窗口内:请求状态在其 cache blocks 可被回收之前先拷入 staging buffer,由后台 sender 执行实际网络传输。一个发往 TileRT 的请求绝不阻塞下一个 prefill 迭代,对同一 batch 里共享的 native-pool 请求也一样。到达 decode 节点后,状态被转换为 TileRT 的 native layout 并直接注入运行中的引擎,解码立即开始,多 token speculative decoding 从第一步就处于激活状态。
  7. 选型指引、局限与部署要点 — 选型:当 per-user token speed 是硬约束(如 interactive agents、real-time assistants、latency-SLO 推理)且模型受支持时,路由到 TileRT decode;追求最大聚合吞吐、高并发 batching 与长尾模型/特性覆盖时留在原生 decode——两个栈暴露同一 OpenAI 兼容面,迁移是路由变更而非客户端变更。本版限制:一个 TileRT decode 节点一次只服务一个 in-flight 请求,由 router 提供 gated dispatch 与 back-pressure;模型覆盖 GLM-5/5.1 与 DeepSeek-V3.2。部署需先用 weight_converter 把 HF checkpoint 转成 TileRT 权重格式;decode 侧带 --with-mtp 启动;prefill 侧必须配 MTP speculative config——prefill 要填充 decode 侧投机解码恢复所依赖的 draft-layer KV。

关键数据

指标数值条件
MTP 平均接受长度与峰值平均 3.2;最佳情形峰值 4.0GLM-5.1-FP8 在 8× NVIDIA B200 上的 TileRT v0.1.5 token 生成速度评测,对比无 MTP / 平均 / 最佳三档
评测输入/输出长度输出 1K token;输入 1K–192K tokenGLM-5.1-FP8 token 生成速度评测的工作负载设定
评测硬件与引擎版本8× NVIDIA B200;TileRT v0.1.5GLM-5.1-FP8 token 生成速度评测环境
路由器为请求设置的 max_tokens1(vLLM 完成首 token)TileRT PD router 对发往 TileRT 池的每个请求,目标 decode 节点经 kv_transfer_params 透传
单 decode 节点并发能力一次 1 个 in-flight 请求本版 TileRT decode 节点;router 提供 gated dispatch 与 back-pressure
本版模型覆盖GLM-5/5.1 与 DeepSeek-V3.2TileRT 0.1.5 release 的模型支持范围
安装环境要求Python 3.12;CUDA 13 wheelsTileRT 0.1.5 的 PyPI 安装(pip install tilert),prefill 与 decode 节点都要安装
“Bars compare TileRT without MTP, with MTP at average acceptance length 3.2, and the peak under best-case MTP acceptance 4.0.”
“Output length 1K, input length 1K–192K.”
“GLM-5.1-FP8 token generation speed on 8× NVIDIA B200 with TileRT v0.1.5.”

前置知识

prefill(compute-bound)与 decode(memory-bandwidth-bound)分离的基本动机 · vLLM V1 connector 接口:KVConnectorBase_V1 与 kv_role(producer/consumer) · vLLM MultiConnector 组合多个 connector 的机制 · RDMA 单边写与 NIXL/Mooncake 传输引擎 · speculative decoding 与 MTP(Multi-Token Prediction)draft 层 · prefix caching 与 OpenAI 兼容 serving API

术语表

  • TileRT: 面向每用户 decode 速度优化的新推理运行时,本篇引入的专用 decode 引擎,随 0.1.5 版本提供 vLLM 集成。
  • KVConnectorBase_V1: vLLM V1 的公开 KV/状态传输连接器基类接口,本集成唯一的扩展点,无需修改 vLLM 源码。
  • MultiConnector: vLLM 中组合多个 connector 的机制,让 TileRT decode 池与原生 decode 池共享同一个 prefill 实例。
  • kv_producer: connector 角色之一:只在 prefill 后抽取并发送状态,不参与调度与采样;TileRT connector 即此角色。
  • claim filtering: TileRT connector 只认领携带 kv_transfer_params 标记的请求,对其他请求是严格 no-op 的过滤机制。
  • MTP (Multi-Token Prediction): 利用模型自带 draft 层做多 token 投机解码;本集成中 decode 侧从第一步即激活,prefill 侧需配置以填充 draft-layer KV。
  • Mooncake: 用于 KV/状态传输的 RDMA 传输引擎,本集成中与 NIXL 二选一,handoff 协议独立于引擎。
  • NIXL: vLLM 标准的跨节点内存传输库;TileRT connector 也有 NIXL 模式,可与原生 NixlConnector 共用一套传输库。
  • RDMA one-sided write: 远程直接内存访问的单边写:数据直接写入对端预注册的 GPU 缓冲,无需中间序列化或经 host memory 中转。
📍 学习路径定位:『PD 分离服务』路径的生态应用篇:先理解 PD 分离动机与 vLLM V1 connector 接口,再读本文看 decode 侧可插拔的实际落地;三篇中最易读,适合作为路径收官时对“分离改变推理栈形态”的总结案例。
2026-07-147 min read 进阶 官方原文 ↗

vLLM x TileRT: Specialized Decode for Latency-Critical Serving

Disaggregated serving, which separates the compute-bound prefill phase from the memory-bandwidth-bound decode phase, has become an increasingly standard pattern for serving large language models at scale, and vLLM supports it through a first-class connector interface.

That architectural shift carries a benefit that is easy to overlook: once prefill and decode are separated, the decode side becomes pluggable. Different serving regimes reward different engine designs. The prefill pool, the scheduler, the caching layer, and the serving API stay exactly where they are, while the decode pool becomes a deliberate choice.

Today, we are introducing exactly such a choice: vLLM prefill paired with TileRT decode, integrated through vLLM V1's public connector interface and shipping with TileRT 0.1.5. For latency-critical workloads, this pairing delivers TileRT's native per-user decode speed, while everything else about the deployment remains stock vLLM.

Why a second decode option?

vLLM's native decode is, and remains, the right default: it is built for high-throughput batched serving across a huge range of models and hardware. But there is a growing class of workloads, e.g., agentic loops, interactive coding assistants, real-time voice, where the metric that matters is not aggregate throughput but how fast tokens reach each individual user. These workloads are latency-bound, and they call for a decode engine designed from the ground up for exactly that regime. Native decode and TileRT target different points on the same throughput–latency frontier, which is why they compose.

TileRT is such an engine: a new inference runtime built around the single goal of pushing per-user decode speed toward the limits of the hardware. We have written elsewhere about why we believe speed is becoming its own scaling dimension.

This post is not about the engine, though. It is about a more practical question: can you adopt a specialized decode engine without giving up the ecosystem you depend on, e.g., OpenAI-compatible APIs, scheduling, prefix caching, tool calling, and the operational maturity of vLLM?

This integration is designed to make that trade-off as small as possible:

  • Prefill is vLLM. Scheduling, chunked prefill, prefix caching — untouched.
  • The serving surface is vLLM. Same APIs, same request format, same tooling.
  • Only decode changes, and only for the traffic you send there. The TileRT-paired stack runs alongside your existing vLLM deployment; each workload picks its endpoint.

Architecture: coexistence by design

The core design principle is zero changes to vLLM: no fork, no patches, no wrapped internal workers. The integration lives entirely behind vLLM V1's public extensibility surface: a KVConnectorBase_V1 implementation, composed under MultiConnector and loaded through the standard kv_connector_module_path mechanism. This matters beyond engineering aesthetics: adding a TileRT decode pool cannot destabilize a vLLM deployment you already run, and upgrading vLLM does not mean re-porting a fork.


Coexistence by design: latency-critical traffic is marked by the TileRT PD router and claimed by the TileRT connector, while general traffic flows through the native disaggregation path, both served by a single stock vLLM prefill pool composed under MultiConnector.

Routing. A lightweight router fronts the TileRT pool. For each request it sets max_tokens=1 (vLLM performs the prefill and emits the first token) and attaches the target decode node in the standard pass-through field: kv_transfer_params = {"tilert_host": ..., "tilert_ctrl_port": ...}. Traffic for the native pool flows through the usual disaggregation proxy, unmodified.

Claim filtering. The TileRT connector claims only requests carrying the mark and is a strict no-op for everything else, so the two decode pools can share one prefill instance (even a single forward batch): adopting TileRT for some traffic changes nothing for the rest.

A pure producer. The connector acts as a kv_producer only, it never touches scheduling or sampling; it extracts and ships state after prefill. In every other respect the prefill instance is a stock vLLM server.

How the handoff works

For cross-engine disaggregation to be practical, three things have to be true: the transfer must be fast, it must not slow the prefill node down, and the decode engine must pick up exactly where prefill left off.

Data plane. After prefill, the request's attention state (compressed KV, the sparse-attention index caches, and a small amount of metadata) moves to the decode node as RDMA one-sided writes into pre-registered GPU buffers, with either Mooncake or NIXL as the transfer engine. No intermediate serialization, no staging through host memory. The handoff protocol itself is independent of the underlying transfer engine, whose job is only to move bytes.

Fully overlapped with prefill. State extraction happens inside the forward window: the request's state is copied to a staging buffer before its cache blocks can be recycled, and a background sender performs the actual network transfer. A request bound for TileRT never blocks the next prefill iteration, including for native-pool requests sharing the same batch.

Injection into a live engine. On arrival, the state is converted to TileRT's native layout and injected directly into a running engine; decoding begins immediately, with multi-token speculative decoding active from the first step.

Evaluation


GLM-5.1-FP8 token generation speed on 8× NVIDIA B200 with TileRT v0.1.5. Output length 1K, input length 1K–192K. Bars compare TileRT without MTP, with MTP at average acceptance length 3.2, and the peak under best-case MTP acceptance 4.0.

Choosing your decode pool

Route to TileRT decode when per-user token speed is the binding constraint, e.g., interactive agents, real-time assistants, latency-SLO inference, and the model is one TileRT supports.

Stay on native vLLM decode for maximum aggregate throughput, high-concurrency batching, and the long tail of models and features that general-purpose decode covers.

Both stacks expose the same OpenAI-compatible surface, so moving a workload between them is a routing change, not a client change.

Current limitations. In this release a TileRT decode node serves one in-flight request at a time, with the router providing gated dispatch and back-pressure. Model coverage in this release is GLM-5/5.1 and DeepSeek-V3.2, with more to come.

Getting started

TileRT 0.1.5 is available on PyPI (pip install tilert; Python 3.12, CUDA 13 wheels) and the TileRT repository. Install it on both the prefill and decode nodes; the prefill side needs it for the connector plugin.

# 0. One-time: convert the HF checkpoint to TileRT's weight format
python -m tilert.models.preprocess.weight_converter \
    --model_type glm-5 \
    --model_dir /path/to/GLM-5.1 \
    --save_dir /path/to/tilert-glm5.1-weights

# 1. TileRT decode node
python -m tilert.pd_vllm.decode_server \
    --engine tilert --model glm5 \
    --model-weights-dir /path/to/tilert-glm5.1-weights \
    --with-mtp --max-seq-len 202752 \
    --kv-cache-dtype fp8 \
    --ctrl-port 5556 --http-port 5557

# 2. vLLM prefill (stock vLLM; the connector loads as a plugin).
#    The MTP speculative config is required: prefill populates the
#    draft-layer KV that decode-side speculation resumes from.
vllm serve /path/to/GLM-5.1 \
    --served-model-name glm5.1 \
    --port 8000 \
    --tensor-parallel-size 8 \
    --enforce-eager \
    --trust-remote-code \
    --return-tokens-as-token-ids \
    --gpu-memory-utilization 0.8 \
    --kv-cache-dtype fp8_ds_mla \
    --speculative-config '{"method": "mtp", "num_speculative_tokens": 1}' \
    --kv-transfer-config '{
        "kv_connector": "TileRTConnector",
        "kv_connector_module_path": "tilert.pd_vllm.prefill_connector",
        "kv_role": "kv_producer",
        "kv_connector_extra_config":{
            "tilert_host":"[TILERT_DECODE_SERVER_IP]",
            "tilert_ctrl_port":5556,
            "tilert_model":"glm5",
            "tilert_max_seq_len":202752
        }
    }'

# 3. Router: OpenAI-compatible ingress for the TileRT pool
python -m tilert.pd_vllm.pd_router \
    --vllm-url http://prefill-node:8000 \
    --decode decode-node:5556:5557 \
    --model-path /path/to/GLM-5.1 \
    --port 23333

To run the TileRT pool and a native vLLM decode pool behind one shared prefill instance, compose both connectors under MultiConnector. The configuration we validated runs NIXL end to end (vLLM's standard NixlConnector for the native pool, the TileRT connector in NIXL mode for the TileRT pool), so the shared prefill uses a single transfer library; only the prefill's --kv-transfer-config changes.

Looking ahead

We think disaggregation is quietly changing what an inference stack is: less a single engine, and more a composition of specialized engines behind a shared serving layer. vLLM's connector interface is what makes that composition possible today, and this integration is one concrete example. It is also why an engine like TileRT can afford to specialize this deeply: with the serving layer shared and the interfaces open, going deep on one dimension no longer means rebuilding everything else.

We would love feedback from the community: on the integration surface, on the workloads where this helps, and on which models to support next.

Acknowledgements

We thank the vLLM community for designing the V1 connector interface that made a zero-modification integration possible, and the Mooncake and NIXL projects for the RDMA transfer engines. We also appreciate Inferact Inc. for the collaboration to improve vLLM-TileRT integration.