Taking vLLM Apart: A Practical Guide to Disaggregated Serving
Taking vLLM Apart: A Practical Guide to Disaggregated Serving
发表时间: 2026-09 · Blog post by Martin Hickey (IBM Research) (blog.vllm.ai)
原文: https://blog.vllm.ai/blog/2026-09-29-disaggregated-serving-guide
Martin Hickey (IBM Research)
速读
一句话结论
vLLM 通过将大模型推理拆分为独立的预填充(Prefill)、解码(Decode)和纯 CPU 前端渲染层,彻底消除了长提示词对并发生成任务的显存与算力抢占,在保持系统吞吐量的同时将长尾词间延迟降低了数倍。
要解决什么问题
传统的单体服务模式将三种特征完全不同的负载强行绑定在同一张 GPU 上,导致严重的资源互相干扰。首先,预填充阶段(处理提示词)是计算密集型的,需要一次性读取全部提示词,决定了首字母延迟(TTFT);而解码阶段(生成词)是访存带宽密集型的,逐词生成,决定了词间延迟(ITL)。当这两者共享 GPU 时,一旦系统接收到一个长提示词请求,庞大的预填充计算会瞬间霸占算力,导致该 GPU 上所有正在进行解码的并发流全部卡顿。其次,诸如聊天模板拼接、分词(Tokenization)、逆分词以及推理过程和工具调用的解析,本质上全是纯 CPU 计算,将这些任务留在 GPU 节点上运行,白白浪费了昂贵的加速器资源。
怎么做的
核心思路是实施分离式服务(Disaggregated Serving),将单体推理流水线物理切割为三个独立层级,各自独立扩展。第一部分是预填充与解码分离(P/D 分离)。预填充实例专门负责处理长提示词,计算出注意力机制所需的键值对(KV Cache),随后通过 KV 连接器(如 NIXL)经由 RDMA 或 PCIe 将这些显存块传输给解码实例。解码实例只负责运行纯粹的生成批处理,由于没有预填充任务的插入,生成流永远不会被中断。针对多轮对话,系统设计了双向传输机制,预填充节点可以从解码节点把上一轮的 KV 块拉回,只计算新增的 Token,避免重复计算历史上下文。第二部分是剥离无 GPU 需求的纯 CPU 前端。引入 /render 和 /derender 端点,将 OpenAI 格式的请求在 CPU 节点上直接转化为 Token ID,使得后端的 GPU 引擎变成纯粹的“进 Token、出 Token”黑盒。在实际调度中,代理路由会先调用渲染层获取 ID,接着向预填充节点下发仅生成 1 个 Token 的预算并要求其保留 KV 块,随后将指向这些块的参数(kv_transfer_params)传递给解码节点完成生成,最后由逆渲染层将输出的 ID 还原为包含内容、推理过程和工具调用的标准响应。
效果如何
核心对比实验在一台配备双 NVIDIA L40S GPU(48GB,PCIe,无 NVLink)的机器上进行。测试模型为 Qwen2.5-7B-Instruct,负载为 8K Token 提示词和 256 个输出 Token,按泊松分布发送 100 个请求。对比基线是传统的同位部署(Collocated)路线,即在两张卡上运行 vllm serve --data-parallel-size 2(代表数据并行的单体副本模式)。结果显示,在 0.4 req/s 的负载下,基线路线的 p99 词间延迟(ITL)从 23ms 暴增至 169ms,而分离式架构的 p99 ITL 始终稳定在 25 到 52ms 之间,长尾延迟降低了约 6 倍。在 AMD MI300X 集群上运行 Qwen3-235B-A22B-FP8 时,分离架构在满足严格延迟目标下的有效吞吐量(Goodput)是基线的 2.4 倍。但该方法有明确的代价和失效场景:KV 传输的耗时会全部叠加在首字母延迟(TTFT)上。在上述缺乏高速互联(如 NVLink 或 P2P)的 L40S 测试中,470MB 的 KV 传输耗时约 1.3 秒,导致分离架构的 TTFT 中位数高达 2.2 秒(基线仅为 0.7 秒),直接错失了 2 秒的 SLO 目标。此外,作者承认当前的流式逆渲染解析器存在严重的性能局限:由于推理和工具解析器的状态无法序列化,每个流式数据块都需要重建解析器并重放 Token 历史,其字符级计算复杂度达到了 $O(n^3)$,在长文本推理输出时会导致 CPU 负载飙升。
主要贡献
在默认情况下,单一的 vllm serve 进程需要同时处理三个互不相关的任务:处理提示词(Prefill阶段)、生成Token(Decode阶段)以及围绕这两者的大量CPU工作。由于Prefill阶段是计算密集型的,它会一次性读取整个提示词并决定了首Token时间(TTFT);而Decode阶段是内存带宽密集型的,每次只生成一个Token,决定了Token间延迟(ITL)。当它们共享同一块GPU时,一个长提示词的到达会阻塞正在进行的所有Decode流,导致输出严重卡顿。此外,如聊天模板化、分词、解分词以及推理和工具调用解析等纯CPU工作,也白白消耗了昂贵的GPU节点资源。
本文的核心贡献在于系统性地介绍了vLLM中的解耦服务(Disaggregated Serving),旨在将LLM推理的不同阶段分离开来:
- 分离Prefill与Decode(P/D解耦):只要KV Cache能在两者之间快速传输,就能阻止长提示词阻塞其他请求的输出。
- 分离无GPU前端(前端解耦):将分词和解析工作转移到纯CPU的前端(
/render和/derender),将这些工作从GPU节点卸载,使得底层引擎完全只处理Token IDs。
本文详细阐述了各种解耦模式的适用场景、在vLLM v0.31.0及以上版本中的具体运行方法、系统架构设计以及未来的改进方向。
背景知识与设计原则
解耦的维度与架构设计
- Prefill和Decode的分离:Prefill和Decode作为两个独立的实例运行。它们之间传递的是KV Cache,即Decode在输出任何内容前必须获取的每个提示词Token的Attention Keys和Values。KV Cache的体积非常庞大,例如在BF16精度下,Llama-3.1-70B每个Token占用320 KiB,一个10k Token的提示词会向Decode传递约3 GB的数据。在400 Gb/s的线速下,不计任何开销,这大约需要65毫秒,且所有这些时间都会算入TTFT中。数据移动由KV连接器(KV Connector)负责,通常通过RDMA进行。目前上游已有十几个连接器,包括NIXL、LMCache、Mooncake、FlexKV、AMD的MoRI-IO,以及一个可以将它们串联起来的
MultiConnector。 - 前端完全脱离GPU节点:
/render端点负责将OpenAI格式的请求转换为Token IDs,GPU引擎执行纯粹的“Token进/Token出”计算,随后/derender端点将输出的Token IDs转换回标准的OpenAI响应格式(包含拆分好的content、reasoning和tool_calls)。这最后一步近期才合并入主分支,完成了整个请求的闭环。 - 超越P/D的解耦扩展:除了基础的P/D解耦,多模态模型引入了编码器解耦(Encoder Disaggregation),MoE模型引入了用于分离Attention和FFN的AFD插件。两者都在模型的不同阶段采用了相同的解耦理念。此外,P/D解耦目前也已支持混合SSM模型,包含了Mamba状态的转移。
图1. 单一进程处理所有任务与同一流水线被切分为四层的对比。
解耦的收益与成本权衡
- 核心收益在于有效吞吐量(Goodput):解耦的卖点并不是峰值吞吐量。在没有延迟目标的情况下,将相同的GPU划分为Prefill和Decode池并不一定会提升每秒处理的Token数。它真正带来的是Goodput:即在所有请求仍能满足其TTFT和ITL目标的前提下,系统所能维持的最高请求速率。
- 支持独立调整TTFT和ITL:架构允许在每一层配置不同的并行度,以适应其运行的阶段。Prefill可以重度使用张量并行(TP),而Decode可以根据Batch大小进行尺寸调整。这两者的调整互不拖累。
- 负载攀升时维持极低的尾部延迟:一个只运行Decode Batch的Decode实例永远不会被冗长的Prefill过程阻塞。虽然分块Prefill(Chunked Prefill)能在一定程度上缓解该问题,但合适的Chunk大小取决于实际流量,导致运维人员需要不断重新调优。
- 每次首Token生成都需承担传输代价:解耦的优势建立在KV Cache能够快速移动的前提下(如通过InfiniBand/RoCE的RDMA,或节点内的NVLink/GPU P2P)。如果传输缓慢,由于传输延迟会直接叠加在TTFT上,不仅无法满足TTFT目标,还会限制整体吞吐量。因此,带宽本身并不能解释所有的延迟,大部分时间消耗在复制前后的开销上(例如Decode在其自身的前向步之间轮询时才注意到传输已完成)。
- CPU层成本极为低廉:一旦分词和解析任务移出GPU节点,就可以根据CPU负载来扩展它们,而无需购买昂贵的加速器时间。长提示词、多模态预处理以及推理或工具调用的解析是Render层真正消耗算力的地方,而这些完全不需要GPU。
- 系统部署建议:
- 如果ITL的p99在生产负载下未达到SLO:强烈建议解耦(这是主要用例)。
- 高并发下存在长提示词:建议解耦(前提是KV传输必须快,此处Prefill干扰最严重)。
- 上下文不断增长的聊天或Agent循环:建议解耦,并开启双向传输,配合KV卸载或共享KV Cache(如LMCache或Mooncake)。
- 模板化、分词或解析在GPU节点的CPU监控中显著:建议剥离Render层。
- TTFT是核心约束条件:建议保持共置或先进行测量(传输耗时会算入首Token)。
- KV传输极其缓慢:修复网络或保持共置。
- 低流量、突发性或对延迟不敏感的流量:建议保持共置。
方法细节
运行Prefill/Decode分离
- 环境与进程配置:解耦部署需要启动三个进程:Prefiller、Decoder和Proxy。Prefiller运行在一张GPU上,通过配置参数
--kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_producer"}'将其设定为KV Cache的生产者;Decoder运行在另一张GPU上,角色设定为kv_consumer。需要特别注意的是,主机上每个Worker的VLLM_NIXL_SIDE_CHANNEL_PORT必须唯一。
# Prefiller on GPU 0
CUDA_VISIBLE_DEVICES=0 UCX_NET_DEVICES=all VLLM_NIXL_SIDE_CHANNEL_PORT=5600 \
vllm serve Qwen/Qwen3-0.6B --port 8100 \
--kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_producer"}'
# Decoder on GPU 1
CUDA_VISIBLE_DEVICES=1 UCX_NET_DEVICES=all VLLM_NIXL_SIDE_CHANNEL_PORT=5601 \
vllm serve Qwen/Qwen3-0.6B --port 8200 \
--kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_consumer"}'
# Proxy
python tests/v1/kv_connector/nixl_integration/toy_proxy_server.py --port 8192 \
--prefiller-hosts localhost --prefiller-ports 8100 \
--decoder-hosts localhost --decoder-ports 8200
- Proxy的调度逻辑:Proxy是将两者结合的核心。对于每个到达的请求,Proxy首先调用Prefill,并设定
max_tokens=1以及附加参数kv_transfer_params: {"do_remote_decode": true}。Prefill计算完KV Cache后,会保留这些数据块,并返回指向这些数据块的kv_transfer_params。随后,Proxy将原始请求连同这些参数转发给Decode,Decode在开始生成Token前,会通过NIXL拉取这些数据块。 - 关键容错与生命周期配置:
kv_lease_duration(在kv_connector_extra_config中设置,默认30秒)控制Prefiller在等待Decoder收集数据块时保留这些块的时长。在高负载下,这是需要重点调优的超时参数。kv_load_failure_policy决定了传输失败时的行为:默认值为fail,会直接使请求报错;若设为recompute,则Decode会自行重新计算缺失的KV,虽然速度较慢,但能保证请求存活。 - 优化Token解析的冗余:当在
/v1/chat/completions接口上运行P/D解耦时,由于Prefill阶段已经对提示词进行了分词,Decode阶段无需重复此操作。可以在Prefill请求中要求return_token_ids,然后通过kv_transfer_params将这些Token IDs直接传递给Decode。
from openai import OpenAI
MODEL = "Qwen/Qwen3-0.6B"
messages = [{"role": "user", "content": "What is 17 * 23?"}]
prefill_client = OpenAI(base_url="http://localhost:8100/v1", api_key="EMPTY")
decode_client = OpenAI(base_url="http://localhost:8200/v1", api_key="EMPTY")
prefill = prefill_client.chat.completions.create(model=MODEL, messages=messages, max_tokens=1,
extra_body={"return_token_ids": True, "kv_transfer_params": {"do_remote_decode": True}})
# prefill的kv_transfer_params指向decode所需的块;在此基础上添加token IDs
decode = decode_client.chat.completions.create(model=MODEL, messages=messages, stream=True,
extra_body={"kv_transfer_params": {**prefill.kv_transfer_params,
"prompt_token_ids": prefill.prompt_token_ids}})
多轮对话:停止重新计算对话
- 双向KV传输机制:标准的P/D解耦是单向移动Cache的,这对于聊天和Agent循环来说极其浪费。在第二轮对话中,Decode仍然持有它刚刚生成的答案的KV,但Prefill从未计算过这部分内容,导致Prefill必须重新计算它。通过在两个实例上设置
bidirectional_kv_xfer,Prefill可以从Decode反向拉取这些数据块,从而只计算新增加的Token。Proxy通过客户端发送的conversation_id来追踪哪些数据块属于哪次对话。 - 推理模型的对齐警告:Decode的数据块包含了它生成的思考轨迹(Thinking Traces)。如果下一轮的提示词丢弃了这些轨迹(例如Qwen3的聊天模板默认行为),Prefill的提示词将不再与Decode的数据块对齐,这会导致输出内容错误而不仅仅是速度变慢。目前vLLM中尚无机制捕获这种不匹配,因此在开启此功能前必须严格检查聊天模板。
运行无GPU的前端
- 前端分离的执行逻辑:将CPU工作移出GPU节点意味着,一旦引擎只接收Token IDs并输出Token IDs,它就不再需要聊天模板或解析器。路由器(Router)可以先渲染请求,然后根据前缀缓存(Prefix Cache)对真实提示词Token的命中情况来选择副本,而不是通过文本去盲猜。RL(强化学习)和Eval(评估)流水线也能获取模型看到和生成的精确ID,这非常关键,因为解码后的文本在重新分词时并不总是能得到相同的ID。
- 多模态预处理的迁移:图像解码和模型的Processor同样迁移到了Render层。结合编码器解耦,Render层将处理后的张量发送给编码器,而仅仅将元数据发送给Prefill。由于处理后的有效载荷可能比源图像大得多,因此需要相应地调整请求大小的限制。
- 服务启动与三跳(Three Hops)请求流程:启动两个服务器,一个是没有GPU的Render层,另一个是只处理Token的引擎。解析器部署在Render服务器上,因为Derender也在此运行。如果要在同时处理普通OpenAI流量的服务器上开启
/render、/derender或/inference/v1/generate,需添加--enable-scale-out。请求流程分为三跳:1. 请求转为Token IDs(无GPU);2. Token IDs到Token IDs的生成(GPU);3. Token IDs转为标准响应(无GPU)。
vllm launch render Qwen/Qwen3-0.6B --port 8100 \
--reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser hermes
vllm serve Qwen/Qwen3-0.6B --tokens-only --port 8200
import httpx
MODEL = "Qwen/Qwen3-0.6B"
RENDER = "http://localhost:8100" # vllm launch render
ENGINE = "http://localhost:8200" # vllm serve --tokens-only
chat_request = {
"model": MODEL,
"messages": [{"role": "user", "content": "What is 17 * 23?"}],
"max_tokens": 2048,
}
with httpx.Client(timeout=60.0) as client:
# 1. request -> token IDs (no GPU)
generate_request = client.post(f"{RENDER}/v1/chat/completions/render", json=chat_request).json()
# 2. token IDs -> token IDs (GPU)
generate_response = client.post(f"{ENGINE}/inference/v1/generate", json=generate_request).json()
# 3. token IDs -> ChatCompletionResponse (no GPU)
response = client.post(f"{RENDER}/v1/chat/completions/derender", json={
"model": MODEL,
"generate_response": generate_response,
"prompt_tokens": len(generate_request["token_ids"]),
"chat_request": chat_request,
}).json()
message = response["choices"][0]["message"]
print(message["reasoning"], message["content"], sep="\n---\n")
- 上下文回传要求:必须将
chat_request传回Derender步骤。解析器需要原始上下文(如Tools、tool_choice、include_reasoning)来产生与普通vllm serve相同的content/reasoning/tool_calls拆分。如果遗漏,会导致400错误,而不是将<tool_call>标记泄漏到content中的静默失败。
流式输出(Streaming)
- 流式处理的状态维护:
/render和生成端点都支持stream: true。生成的每个Chunk都需要独立通过Derender。在首次调用时,发送stream_state为null,随后回传上一次响应给出的状态。因为服务器在调用之间不保留任何状态,所以chat_request和prompt_token_ids必须随每个Chunk一起发送。这也意味着任何Render副本都可以处理任何Chunk。
import json
chat_request = {**chat_request, "stream": True}
with httpx.Client(timeout=60.0) as client:
generate_request = client.post(f"{RENDER}/v1/chat/completions/render", json=chat_request).json()
prompt_token_ids = generate_request["token_ids"]
stream_state = None # 如果 n > 1,则每个choice index一个
with client.stream("POST", f"{ENGINE}/inference/v1/generate", json=generate_request) as stream:
for line in stream.iter_lines():
if not line.startswith("data: ") or line == "data: [DONE]":
continue
derendered = client.post(f"{RENDER}/v1/chat/completions/derender", json={
"stream": True,
"model": MODEL,
"generate_chunk": json.loads(line[len("data: "):]),
"stream_state": stream_state,
"prompt_tokens": len(prompt_token_ids),
"prompt_token_ids": prompt_token_ids,
"chat_request": chat_request,
}).json()
stream_state = derendered["stream_state"]
for choice in derendered["chunk"]["choices"]:
print(choice["delta"].get("content") or "", end="", flush=True)
部署Render层
- 无状态扩展与配置同步:由于Render层不持有每个请求的状态(即使在流式传输时),它可以像任何无状态Web服务一样进行扩展。将副本放置在普通负载均衡器之后,并独立于GPU池根据CPU负载进行扩展。必须保持配置同步:Render、Derender和引擎在模型、分词器、聊天模板和解析器标志上必须完全一致。如果不匹配不会引发错误,但会导致返回的字段拆分与预期不符。
- 调整线程池大小:模板化、分词、多模态预处理和流式解析器重放都运行在Renderer的Worker上,由
--renderer-num-workers设置(默认为1)。普通的渲染成本很低(9k Token约15ms),但通过解析器流式传输推理或工具调用模型的成本非常高。因此,必须提高Worker数量或增加副本以匹配并发解析流的数量。
将两种解耦结合(Putting Both Splits Together)
- 四层架构的编排:将前端解耦与P/D解耦结合,形成了四层流水线,但物理上只需要三个服务器(一个处理Render/Derender,一个Prefill,一个Decode)。
/inference/v1/generate接口同样接受kv_transfer_params。
# Render and derender, no GPU
vllm launch render Qwen/Qwen3-0.6B --port 8000 \
--reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser hermes
# Prefill on GPU 0
CUDA_VISIBLE_DEVICES=0 UCX_NET_DEVICES=all VLLM_NIXL_SIDE_CHANNEL_PORT=5600 \
vllm serve Qwen/Qwen3-0.6B --tokens-only --port 8100 \
--kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_producer"}'
# Decode on GPU 1
CUDA_VISIBLE_DEVICES=1 UCX_NET_DEVICES=all VLLM_NIXL_SIDE_CHANNEL_PORT=5601 \
vllm serve Qwen/Qwen3-0.6B --tokens-only --port 8200 \
--kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_consumer"}'
- 代码编排逻辑:开发者需要编写代码来扮演Proxy的角色,协调Render和Derender之间的流程:1. Render阶段将请求转为Token IDs;2. Prefill阶段分配1个Token的生成预算并启用
do_remote_decode,计算并保留KV Cache;3. Decode阶段通过NIXL拉取Prefill的数据块并进行生成;4. Derender阶段将Token IDs转换为最终响应格式。在生产环境中,这种编排工作通常由llm-d或Dynamo等框架接管。
实验环境
-
硬件配置:
- 单机测试平台:配备两张NVIDIA L40S GPU(48 GB内存,PCIe连接,无NVLink,
nvidia-smi topo -p2p r显示NS不支持点对点复制)。 - AMD测试平台:单台8卡MI300X节点。
- 集群测试平台:16张H200 GPU。
- 单机测试平台:配备两张NVIDIA L40S GPU(48 GB内存,PCIe连接,无NVLink,
-
模型与参数:
- 基准测试模型:Qwen2.5-7B-Instruct(长提示词测试)、Qwen3-235B-A22B-FP8(AMD测试)、gpt-oss-120b(集群测试)。
- 输入输出规模:约8k Token的提示词,256个输出Token。
-
软件配置:vLLM v0.31.0或更高版本,使用NIXL连接器。基线共置测试使用
vllm serve --data-parallel-size 2。测试时关闭前缀缓存(Prefix Caching off)以防止缓存命中掩盖Prefill的干扰。 - 负载设置:在各个请求速率下,模拟100个泊松到达(Poisson-arrival)的请求。
实验结果
-
实验组1:L40S双卡上的共置与P/D解耦对比 (Qwen2.5-7B-Instruct)
- 实验内容:对比单实例DP=2(共置)与一Prefill一Decode实例(P/D解耦)在0.2至2 req/s负载下的Token间延迟(ITL)。
- 实验结果:在高达1 req/s的负载下,两种设置的中位数ITL均保持在21–24 ms。然而,在0.4–0.6 req/s时,共置模式的p99 ITL飙升至169 ms(在2 req/s时甚至达到263 ms),而P/D解耦的p99 ITL稳定保持在29 ms,且整个测试过程中从未超过52 ms。
- 分析结论:共置模式下,8k Token的Prefill任务与Decode共享GPU,导致该GPU上的所有生成流停滞直到Prefill完成。P/D解耦将长Prefill完全隔离在Decode GPU之外,极大地稳定了尾部延迟。
-
实验组2:KV传输速度对TTFT的影响分析 (L40S平台)
- 实验内容:评估在不支持P2P的L40S硬件上,KV Cache传输对首Token时间(TTFT)的影响。
- 实验结果:由于缺乏高速互联,每个8k提示词约470 MB的KV Cache需要约1.3秒才能到达Decode端。在0.2 req/s时,P/D的中位数TTFT为2.2秒,而共置模式仅为0.7秒。
- 分析结论:缓慢的KV传输会直接导致TTFT目标失败。即使通过PCIe 4.0,理论上也只需几十毫秒,1.3秒的延迟主要来源于传输前后的开销(如Decode轮询等待)。因此,部署前必须验证传输速度。
-
实验组3:AMD MI300X上的高速传输基准测试 (Qwen3-235B-A22B-FP8)
- 实验内容:在具备快速KV传输能力的单节点8卡MI300X上测试MoRI-IO的Goodput。
- 实验结果:在8 req/s的负载下,100个请求中有73个同时满足了1秒的TTFT和50 ms的ITL目标;而共置服务仅有30个满足。
- 分析结论:在高速传输的支持下,P/D解耦在相同硬件上实现了约2.4倍的有效吞吐量(Goodput)。
-
实验组4:集群规模下的端到端延迟测试 (gpt-oss-120b on 16 H200s)
- 实验内容:llm-d框架在16张H200上进行的集群规模P/D解耦测试。
- 实验结果:与将GPU作为聚合副本运行相比,解耦架构使平均端到端延迟降低了约59%,P95延迟降低了67%。
补充细节:实际应用生态
目前,解耦服务已经在多个生产级框架中得到了实际应用:
- llm-d:使用了vLLM原生的远程Prefill/Decode和NIXL KV传输功能。其内部的路由器和EPP调度器负责选择Prefill/Decode端点,并编排它们之间的KV Cache交接。
- NVIDIA Dynamo:支持在Kubernetes上以聚合或解耦模式运行vLLM,并配备了自有的路由器和规划器。
- KServe:通过基于llm-d构建的
LLMInferenceService对外暴露解耦能力。 - vLLM生产栈 (Production Stack):通过Helm交付了解耦的Prefill能力,同时AIBrix覆盖了控制平面的相关工作。
- 连接器与异构引擎:Moonshot的Mooncake、LMCache和AMD的MoRI-IO均已合并至上游。由于Decode位于连接器之后,它甚至不需要是vLLM引擎——例如TileRT就将标准的vLLM Prefill与其自身针对延迟优化的Decode引擎配对使用。
结论与未来展望
vLLM的解耦服务通过分离Prefill、Decode以及纯CPU的前端任务,成功解决了长提示词阻塞并发生成以及CPU任务抢占GPU资源的核心痛点。在具备高速互联硬件的支持下,解耦架构不仅能显著压平尾部延迟,还能大幅提升系统在严格SLO下的有效吞吐量(Goodput)。
然而,该架构仍有一些亟待解决的问题与未来工作:
- 流式Derender的性能瓶颈:目前带有解析器的流式Derender开销极大。因为推理和工具解析器持有无法序列化的状态,每个Chunk都需要重建一个全新的解析器,并将Token历史重新通过它重放。传输复杂度为 $O(n)$,重放解析的复杂度为 $O(n)$,而像Hermes和DeepSeek-R1这样的解析器在重新扫描累积文本时,会导致整个长生成过程产生 $O(n^3)$ 的字符级工作量。基准测试表明,在并发解析流下,其CPU消耗是进程内处理的9倍。社区计划引入缓存层([#57571])来解决此问题。
- 网络传输的冗余:长提示词在网络传输中占据主导。解析器路径在每个Chunk上都会完整重发
prompt_token_ids。对于一个100k Token的提示词和1k的输出,提示词占据了每个请求体约99%的体积。 - 正确性与API完善:正在修复Batch Derender在解析工具调用时不总是返回
finish_reason: "tool_calls"的问题([#47931]),以及引擎返回的Logprobs为字符串占位符的问题(提议返回真实的整数Token IDs,[#57574])。同时,社区正在讨论/inference/v1/generate是否应该直接返回文本或Derender后的输出,以省去不需要此步骤的调用者的第三跳开销([#56851])。
未来的核心演进将围绕解分词批处理([#42729])、解分词流式处理([#47161])、Token进出机制([#22817])以及解耦前端([#34407])的RFC展开。
💬 评论讨论
欢迎在这里分享您的想法和见解!