调查研究-223 一个请求在 vLLM 里的一生:从 HTTP 到 token streaming 一个请求在 vLLM 里的一生从 HTTP 到 token streamingTL;DR场景想把 vLLM 当成「给模型包个 HTTP 包装」用结果线上并发上来后才发现它在干一件完全不同的事 —— 围绕 token、显存和调度持续运转的系统。结论一个请求的真实链路是API Server → Tokenizer → Waiting Queue → Scheduler → Prefill → KV Cache Manager → Decode Loop → Sampler → Streamer每一段都有自己的瓶颈理解这段链路比死记--max-num-seqs、--gpu-memory-utilization重要得多。产出一张覆盖 12 节的请求生命周期导图 25 项能力版本矩阵 10 行错误速查卡可直接对照vllm serve启动参数、curl /v1/chat/completions请求、SSE 流式返回做排障。版本矩阵#功能 / 主题状态说明1OpenAI-Compatible API/v1/chat/completions、/v1/completions、/v1/embeddings✅ 已验证vLLM 完整映射 Chat/Completion/Embedding流式 采样参数全覆盖2Continuous Batchingiteration-level✅ 已验证vLLM 核心Scheduler 每 step 动态决定 running set3PagedAttention✅ 已验证默认 block size 16 tokenblock table 维护逻辑→物理映射4Chunked Prefill✅ 已验证0.6.5 默认开启--enable-chunked-prefill显式开关与 decode step 混批5Automatic Prefix Caching✅ 已验证v1 zero-overhead prefix caching长文本命中率极低也不会回退6Guided DecodingJSON Schema / Regex / EBNF / choice✅ 已验证--guided-decoding-backend可选 FSM/xgrammar/outlines7Speculative Decoding✅ 已验证EAGLE / Medusa / n-gram prompt lookup 三种草稿方式8Multi-LoRA✅ 已验证enable_loraTruemax_lora_rank支持运行时切换 adapter9OpenAI Streaming SSEstreamtrue✅ 已验证流式 chunk chat template 多轮10Function Calling / Tool Use✅ 已验证≥0.8.3 内置结构化 tool 描述11多 GPU 并行TP / PP / SP✅ 已验证张量并行 流水线并行 序列并行万亿参数模型可扩展12TTFT / TPOT / P95/P99 / queue time metrics✅ 已验证内置 Prometheus 指标可对接 Grafana13Preemptionrecompute / swap✅ 已验证显存不足时按优先级抢占14P/D Disaggregation✅ 已验证0.7 独立 prefill / decode 节点KV 跨节点传输15Multi-Step 调度✅ 已验证减少 GPU→CPU 同步次数提升吞吐但 TTFT/ITL 略增16KV Cache 量化FP8 / INT8 / INT4✅ 已验证--kv-cache-dtype可选 fp8 / int817Tokenizer 并行化✅ 已验证--tokenizer-pool-size起多个 tokenizer 进程18Streaming 取消客户端断开 / 超时✅ 已验证取消请求时及时释放 KV block避免泄漏19Prefix Cache 哈希 LRU 淘汰✅ 已验证hash 复用已计算 blockLRU 驱逐冷 block20vLLM V1 引擎✅ 已验证2025 默认 V1V0 已弃用V1 zero-overhead prefix caching 统一调度21token budget--max-num-batched-tokens✅ 已验证限制每轮 prefilldecode 总 token 数22Logits Processorrepetition / presence / frequency penalty✅ 已验证SamplingParams 内置23Cancel / timeout / 抢占 链路✅ 已验证Engine Core 统一处理24usage 统计prompt_tokens/completion_tokens/total_tokens✅ 已验证流式尾部 chunk 返回 usage 字段25Anatomy of vLLM 系统拆解博客2025-09-05✅ 已验证Aleksa Gordić 主笔commit 42172ad 为分析基准备注版本矩阵全部基于 vLLM 官方文档、Anatomy of vLLM 博客2025-09-05、vllm-project/vllm GitHub 主分支commit 42172ad 之后核查未单独标注「⚠️」的条目均为多源印证后的稳定事实。理解 vLLM 最好的方式不是先背配置参数而是跟踪一个请求从进入服务到流式返回 token 的全过程。一个 LLM 请求表面上只是 HTTP 调用实际内部会经过 API Server、Tokenizer、请求队列、Scheduler、KV Cache Manager、Model Executor、Decode Loop、Sampler、Streamer 等多个模块。vLLM 的价值就在于把这些环节组织成一个持续运行的高吞吐系统。如果只把 vLLM 看成“把模型包成 OpenAI API 的服务”很容易低估它真正解决的问题。普通 HTTP wrapper 做的是请求转发和模型调用vLLM 这类推理框架做的是持续调度、动态 batching、KV Cache 管理和 token streaming。这篇文章就沿着一个请求走一遍它从 HTTP 入口进来如何被 tokenizer 变成 token如何进入 waiting queue如何被 scheduler 安排 prefill 和 decode如何占用和释放 KV Cache最后又如何通过 streaming 返回给客户端。一、入口请求进入 API Server用户请求通常通过 OpenAI-compatible API 进入服务比如/v1/chat/completions或/v1/completions。请求里包含模型名、messages或prompt、temperature、top_p、max_tokens、stream、stop、seed等参数。API Server 首先做协议层处理解析请求、校验参数、构造内部 request object。如果是 chat 接口还要把messages应用 chat template转成模型实际输入文本。这里有一个容易被忽略的点HTTP 层不是瓶颈时它看起来不重要一旦并发上来API Server、JSON 序列化、SSE 返回、连接数、网关超时和客户端取消都会影响尾延迟。所以高性能 LLM Serving 不是只有 GPU。入口层也要稳定。API Server 之后请求文本要经过 tokenizer。模型不能直接处理字符串必须先转成 token ids。Tokenizer 往往运行在 CPU 侧。对于高并发短请求tokenizer 本身也可能成为瓶颈尤其是 chat template 很复杂、messages 很长、并发很高时。Tokenizer 完成后系统就知道了输入 token 数。这个数字对调度非常关键输入 token 数决定 prefill 成本输入 token 数影响 KV Cache 初始占用输入 token 数决定是否超过最大上下文长度输入 token 数影响是否应该做 chunked prefill输入 token 数会进入 usage 统计和容量规划。好的 Serving 系统不会只把请求看成“一个请求”而会把它看成“多少 prefill token 预计多少 decode token 什么优先级 什么采样约束”。二、等待队列请求不一定马上上 GPU完成预处理后请求进入 waiting queue。这一步很关键。请求进入队列不代表已经获得 GPU 执行资源也不代表已经拿到完整 KV Cache。Scheduler 会在每一轮调度时决定哪些 waiting 请求可以进入执行。等待队列不是简单 FIFO。生产系统里常见策略包括策略目的交互式请求优先控制用户感知延迟短 prompt 优先降低平均 TTFT高优先级租户优先支持业务等级和 SLA接近超时的请求优先减少无意义排队长 prompt 分块进入避免阻塞 decode超过 token budget 的请求继续等待防止本轮 GPU 负载失控vLLM 的调度器会结合 token budget、running requests、KV Cache 可用 block 等因素决定本轮执行内容。也就是说请求能不能执行不只取决于“GPU 现在忙不忙”还取决于“这一轮还有多少 token 预算”“KV Cache block 是否够”“已有 running 请求是否需要继续 decode”。这也是普通 Web 服务和 LLM Serving 的差异之一。普通 Web 服务里请求通常进入 worker 后一次性处理完成LLM Serving 里请求可能在系统中存在很久每个 decode step 都要重新参与调度。三、Scheduler 是整个系统的大脑Scheduler 每一轮要做的事情很多。它要决定哪些 running 请求继续 decode。它要决定是否从 waiting queue 中加入新请求做 prefill。它要检查 KV Cache block 是否足够。它要控制本轮总 token 数不让 GPU 负载失控。它要处理完成请求释放资源。它还要处理被取消、超时、抢占的请求。普通 HTTP wrapper 往往是“一个请求进来调用一次model.generate()等结果返回”。而成熟 LLM Serving 的调度发生在 token 级别或者 step 级别。因为每个请求不是一次性完成而是在 decode loop 中持续存在。可以把 Scheduler 看成系统的大脑waiting queue 提供候选请求running set 保存正在生成的请求KV Cache Manager 告诉它显存 block 是否够token budget 限制本轮 prefill / decode 工作量policy 决定优先级、抢占、拒绝和超时metrics 反馈 TTFT、TPOT、queue time 和 P99。Scheduler 的好坏直接决定吞吐、首 token 延迟、流式稳定性和尾延迟。很多 vLLM 参数看似零散本质上都是在影响这套调度系统允许多少 running requests、每轮 token budget 多大、prefill 是否切块、是否开启 prefix caching、KV cache 使用率到什么程度开始抢占。理解 Scheduler比死记参数更重要。四、Prefill建立请求的 KV Cache新请求第一次进入 GPU 执行时通常先做 prefill。Prefill 会把输入 prompt 喂给模型计算所有输入 token 的隐藏状态并为每一层 attention 建立 KV Cache。Prefill 结束后模型可以产生第一个输出 token或者至少准备好进入 decode 阶段。TTFT 很大程度受 prefill 影响。prompt 越长prefill 越重。RAG、Agent、长对话、代码仓库问答、长文档分析都会让 TTFT 增加。如果一个超长 prompt 一次性 prefill会阻塞其他 decode 请求。对于已经在流式输出的用户来说这会表现为 token 间隔突然变长对于新请求来说会表现为首 token 等待变久。为了缓解这个问题vLLM 支持 chunked prefill把长 prompt 切成多个块让调度器可以在 prefill 块之间插入 decode。这样长 prompt 不会一次性独占 GPU在线交互的 P95/P99 更容易控制。对于语音对话系统chunked prefill 很关键。用户听到首字的时间比总吞吐更敏感。一个长上下文请求不应该把整张 GPU 卡住让所有人的语音回复都变慢。Prefill 过程中还需要为输入 token 分配 KV Cache block。Decode 过程中每生成新 token也要追加新的 KV block 或写入已有 block。这就进入了 KV Cache Manager 的职责范围。五、KV Cache Manager调度必须看显存账本KV Cache Manager 的职责包括维护全局可用 block pool为请求分配物理 block维护请求的 block table请求结束后回收 block支持共享前缀缓存在资源不足时配合 scheduler 做抢占、等待、swap/offload 或拒绝。如果 KV Cache block 不够请求不能继续执行。此时系统可能让请求等待或者抢占低优先级请求或者触发 swap/offload或者直接返回资源不足。所以调度器不是只看 GPU 是否空闲还必须看 KV Cache 是否够。这也是很多自研系统容易踩的坑。只看 GPU util可能以为“还有算力”但 KV Cache 已经接近上限新的长上下文请求根本进不来。只看显存总占用也不够你还需要看 used/free blocks、prefix cache 命中率、preemption 次数、waiting queue 长度和 token 长度分布。LLM Serving 的资源不是静态的。请求进入、增长、取消、结束、共享前缀、分叉采样都会改变 KV Cache 账本。Scheduler 和 KV Cache Manager 必须协同工作。六、Decode Loop逐 token 生成Prefill 后请求进入 decode 阶段。Decode 是一个循环。每一轮每个 running 请求通常生成一个 token。生成后系统更新 KV Cache把 token 送入采样逻辑再判断是否结束。结束条件包括生成了 EOS token命中 stop sequence达到max_tokens客户端取消请求超时策略约束停止。每一轮 decode 后Scheduler 都可以重新组织 batch。完成的请求被移除新请求可以加入。这就是 continuous batching。对用户来说decode 阶段表现为 token streaming。模型一边生成服务一边通过 SSE 或其他流式协议返回。这里也能看到 vLLM 和普通 wrapper 的差异。普通 wrapper 往往把生成看成一次函数调用vLLM 把生成看成持续循环中的很多 step。每个 step 都可能重新组合 batch、释放完成请求、加入新请求、追加 KV block、处理取消和 stop 条件。这就是高吞吐 Serving 的核心让 GPU 持续有活干同时不让长输出请求把短请求拖死。七、Sampler采样不是小事模型输出 logits 后还要经过采样策略产生 token。常见参数包括temperature、top_p、top_k、repetition penalty、presence penalty、frequency penalty、guided decoding、grammar constraint 等。采样通常不是最大的算力瓶颈但会影响系统复杂度。尤其是结构化输出、JSON schema、正则约束、函数调用格式约束会给 decode 阶段增加额外逻辑。如果不同请求的采样参数差异很大batching 的实现也会更复杂。成熟框架需要支持同一 batch 中不同请求有不同采样参数。这也是为什么“所有请求一起 batch”在 LLM Serving 里并不简单。请求的 prompt 长度、输出长度、采样参数、stop 条件、streaming 状态、优先级都可能不同。八、Streamer把 token 持续返回给客户端如果streamtrue服务端不会等完整回答生成完而是每产生一个 token 或一小段文本就返回。这对用户体验很重要。即使总生成时间是 5 秒只要 300ms 内看到首 token用户感知会好很多。但流式返回也增加系统负担连接会持续占用网络层要处理大量小包或 SSE chunk客户端取消后服务端要及时停止生成并释放 KV Cache如果后端生成快但前端消费慢可能出现背压网关、负载均衡和客户端超时都要处理长连接。所以 Streamer 不是简单print(token)而是 Serving 系统的一部分。请求结束后系统还要做清理释放 KV Cache block从 running set 移除请求关闭 stream记录 metrics返回最终 usage 信息如prompt_tokens、completion_tokens、total_tokens。如果启用了 prefix caching请求的一部分 KV Cache 不一定立即释放而是进入缓存池等待复用。如果启用了 LoRA 或多模型服务还可能释放 adapter 相关状态。资源释放是否及时直接影响系统能否承载下一批请求。很多自研系统的线上问题不是“执行慢”而是取消请求、异常请求、超时请求后资源没有正确释放。九、vLLM 请求链路的简化图可以把整个链路理解成Client - API Server - Tokenizer - Waiting Queue - Scheduler - Prefill - KV Cache Manager - Decode Loop - Sampler - Streamer - Client但这条链路不是一次性直线而是一个循环系统。Prefill 把请求带入系统。Decode 让请求在系统中持续存在。Scheduler 每轮重新决定执行集合。KV Cache Manager 决定资源是否足够。Streamer 持续把结果吐出去。真正理解 vLLM就要理解这条循环。十、哪些地方最容易成为瓶颈第一长 prompt 导致 TTFT 高。解决方向是 chunked prefill、prefix caching、prompt 压缩、RAG 控制和上下文裁剪。第二decode 并发过高导致 TPOT 变差。解决方向是限制 running requests、优化 batch size、扩 decode GPU、降低上下文长度。第三KV Cache 不足导致排队和抢占。解决方向是减少max_model_len、使用 paged cache、限制max_tokens、量化 KV、增加显存。第四CPU 侧 tokenizer 或 scheduler 成为瓶颈。解决方向是 tokenizer 并行化、减少 chat template 开销、优化调度器实现。第五流式连接过多导致网关压力。解决方向是连接池、网关限流、超时控制、合理 chunk flush 和及时处理取消。这些瓶颈对应到指标上可以看指标可能问题TTFT 高prefill 重、排队久、tokenizer 慢TPOT 高decode 拥挤、上下文长、KV 读取压力大queue time 高scheduler 预算不足、KV block 不够preemption 多KV Cache 压力大或优先级策略激进stream cancel 多前端超时、用户等待过久、网关策略不匹配十一、对自研系统的启发如果自己设计 Serving不要只写一个model.generate()包装 HTTP。至少要拆出这些模块请求解析、tokenizer、等待队列、调度器、KV Cache 管理器、执行器、采样器、streamer、metrics、限流和取消机制。其中最关键的是 Scheduler 和 KV Cache Manager。前者决定吞吐和延迟后者决定并发上限和稳定性。好的系统还需要把指标和链路对齐。不要只看 GPU util也不要只看平均 tokens/s。要把 TTFT、TPOT、P95/P99、queue time、KV Cache usage、waiting/running requests、token 长度分布、取消率和错误率放在一起看。否则调参只是在碰运气。十二、核心结论vLLM 的请求生命周期不是简单的函数调用而是一个持续调度系统。请求进入后先经过 API Server、Tokenizer 和 waiting queue再由 Scheduler 安排 prefill 和 decode。KV Cache Manager 管理显存 blockDecode Loop 每轮生成 tokenSampler 决定下一个 tokenStreamer 持续返回结果。请求结束后系统还要及时释放资源、记录 metrics、处理 usage 和缓存复用。理解这条链路才能真正理解 vLLM 的参数、瓶颈和优化方向。当你把一个请求从 HTTP 追踪到 token streaming再追踪到资源释放你会发现LLM Serving 不是“模型调用”而是一个围绕 token、显存、调度和连接持续运转的系统。参考资料vLLM Documentation: https://docs.vllm.ai/vLLM Blog, Inside vLLM: Anatomy of a High-Throughput LLM Inference System: https://vllm.ai/blog/2025-09-05-anatomy-of-vllmvLLM Documentation, Chunked Prefill: https://docs.vllm.ai/en/latest/performance/optimization.htmlvLLM Documentation, Automatic Prefix Caching: https://docs.vllm.ai/en/latest/features/automatic_prefix_caching/vLLM Documentation, OpenAI-Compatible Server: https://docs.vllm.ai/en/latest/serving/openai_compatible_server.htmlvLLM Documentation, Offline Inference / LLM Engine Examples: https://docs.vllm.ai/en/latest/getting_started/examples.htmlPaper, Efficient Memory Management for Large Language Model Serving with PagedAttention: https://arxiv.org/abs/2309.06180Hugging Face Text Generation Inference: https://github.com/huggingface/text-generation-inference3. 错误速查卡#症状根因定位方法修复方案1TTFT P99 飙到秒级prefill 重 / 排队久 / tokenizer 慢看vllm:request_success_total{finished_reasonstop,ttft_bucket}分位 Prometheus TTFT 曲线开 chunked prefill、--enable-prefix-caching、限制max_model_len、--tokenizer-pool-size提到 4-82TPOT 抖动 / decode step 间隔变大decode 拥挤 / 长上下文 KV 读压 / prefill 抢断 decode监控 TPOT P95/P99 观察单请求 decode step 间隔限制--max-num-seqs、降低max_model_len、扩 decode GPU3queue time 累计上升 / 新请求迟迟不进scheduler token budget 不够 / KV block 不足看e2e_request_latency_seconds - ttft - tpot、queue depth 指标提--gpu-memory-utilization到 0.9、量化 KV、降并发或加卡4preemption 频繁recompute / swapKV 压力大 / 优先级策略激进监控preemptions_total{reason}、swap / recompute 计数加max_tokens上限、启用 prefix caching、扩显存5stream cancel 率飙升网关超时 / 用户等待过久 / 客户端 SDK retrycancel reason 分布 网关 504 / 499 计数调网关 timeout、流式 chunk flush、加速 TTFT6tokenizer CPU 单核满高并发 复杂 chat template单核 CPU util 100% tokenizer 阶段耗时陡升--tokenizer-pool-size提到 4-8、简化 chat template7长 prompt 一次性 prefill 阻塞 decode没开 chunked prefill max_num_batched_tokens偏大decode step 间隔出现几十秒级长尾开 chunked prefill、限制--max-num-batched-tokens8prefix cache 命中率长期低于 10%prompt 结构不稳定 / prompt 模板随机化prefix cache hit rate 指标 hash 分布稳定 system prompt 用户前缀开启 prefix caching9OOM / KV block 不足报错显存规划不足 /max_model_len太大KV cache usage 90%、OOM 异常减max_model_len、限max_tokens、--kv-cache-dtype fp810网关 / LB 连接耗尽流式连接占用过多 客户端慢消费网关 active connection SSE chunk 大小网关超时、限流、客户端 SDK backoff作者武子康的个人博客