2.1 一个请求的一生:从 HTTP 到首 Token 的链路拆解 部署高并发服务,第一个要回答的问题是"慢在哪里"。很多团队一上来就调 、加卡、加显存,结果发现延迟纹丝不动——因为他们根本没看请求的时间都花在哪一环。这一节我们沿着一个请求的真实生命周期,把每一段延迟拆开,给你一张"延迟地图"。读完你应该能指着监控图说出"我们服务的首 token 慢,主要慢在调度等待和 prefill",而不是只会说"服务器好慢"。 2.1.
部署高并发服务,第一个要回答的问题是"慢在哪里"。很多团队一上来就调 tensor-parallel-size、加卡、加显存,结果发现延迟纹丝不动——因为他们根本没看请求的时间都花在哪一环。这一节我们沿着一个请求的真实生命周期,把每一段延迟拆开,给你一张"延迟地图"。读完你应该能指着监控图说出"我们服务的首 token 慢,主要慢在调度等待和 prefill",而不是只会说"服务器好慢"。
当一个客户端向 vLLM 的 /v1/completions 或 /v1/chat/completions 发来一个请求,到它收到第一个 token(TTFT,Time To First Token),中间大致经历七个阶段:
假设你部署 DeepSeek V4 处理一段 2K token 的 prompt,在 4 卡 A100 上测到 TTFT 约 800ms。粗略分解可能是:网络+入口 20ms、分词 30ms、调度等待 250ms、KV 分配 5ms、Prefill 450ms、采样 10ms、解码回传 35ms。注意——调度等待占了近三分之一。这意味着在不碰 GPU 的情况下,把批预算调大、把等待队列理顺,就能直接砍掉这 250ms。
这正是"看链路"的价值:很多人以为 TTFT 全是 GPU 算的,于是盲目加卡;但实测发现调度等待才是瓶颈,加卡毫无用处,反而应该调 max_num_seqs 和 max_num_batched_tokens。我见过一个真实案例:某团队把 8 卡扩到 16 卡,TTFT 一点没降,后来发现是 max_num_seqs 默认 256 但他们的流量峰值远超此值,等待队列常年几百个请求。把预算调到 512 并配合 max_num_batched_tokens 后,TTFT 直接砍半——这钱本来可以省下来。
DeepSeek V4 是 MoE(混合专家)架构:每一层不是所有参数都激活,而是路由出少数几个专家。这带来两个对链路的影响,部署者必须心里有数:
tensor-parallel-size 的选择和单机多卡 vs 多机多卡的决策,也影响 prefill 时卡的通信量。一个实用判断:如果你的 prefill 慢且 GPU 的"计算利用率"看起来不高、但"显存带宽利用率"很满,那基本就是 MoE 的访存瓶颈,而不是你卡不够——此时更应该优化批大小让每次搬运更"值",或考虑用更宽的卡间互联,而不是无脑加卡。这个判断省下了我们团队当初一大笔无效的扩容预算。
不要凭感觉定位瓶颈。vLLM 提供了多种可观测手段,部署当天就该把这几条接上:
queue_time(调度等待)、prefill_time、inference_time,这是最直接的"延迟地图"数据源。vllm:time_to_first_token、vllm:num_requests_waiting 等,前者看延迟分布,后者看队列是否成为瓶颈。这张示意图表达的是:TTFT 随 prompt 长度近似线性上升。当你发现长 prompt 把整批拖慢时,就应该考虑"分块 prefill"或限制单请求最大 prompt,把长任务和普通任务分流——这是第三章进阶内容,这里先埋下伏笔。重要的是,这条曲线要你自己测出来,因为不同模型、不同硬件的斜率天差地别,别直接套用别人的数字。
总结本节的落地建议,也是我反复踩坑后的结论:
nvidia-smi dmon 或 DCGM 看的是带宽利用率而不是 SM 利用率,这两个指标会骗你。光讲原理不够,讲两个我们团队真金白银换来的教训,帮你少走弯路。
案例一:system prompt 每次都变,前缀缓存全失效。 我们最初在前端拼 system 时,顺手加了一个时间戳和请求 id,导致每个请求的 system 前缀都不一样。结果前缀缓存完全没命中,每个请求都要重做完整 prefill,TTFT 比预期高一倍。改成固定 system 后,缓存命中率升到 90%+,prefill 成本骤降。教训:任何你以为"无害"的差异(空格、顺序、随机串)都会让共享块失效,想用前缀缓存就必须让前缀字节级一致。
案例二:客户端不开流式,误判 TTFT。 有个同事抱怨首 token 要 3 秒,但我们服务端日志显示 prefill 只用了 600ms。排查发现他们的测试脚本用的是非流式调用,要等服务端把整段生成完、一次性回传才计时——3 秒里大部分是 decode 和网络回传,根本不是首 token 慢。教训:测 TTFT 必须用流式接口,且客户端要能逐 token 计时,否则你会把 decode 延迟算到 prefill 头上,调错方向。
把下面几条做成上线必查项,能挡掉 80% 的"莫名变慢"工单:
queue_time / prefill_time 指标?没有就先接,否则你永远在猜。拿到 2.1.2 那种分段数据后,不要眉毛胡子一把抓。遵循"先改配置、再改架构、最后加硬件"的顺序,性价比最高:
max_num_seqs / max_num_batched_tokens,零成本、立竿见影。这个顺序能帮你把每一分预算花在刀刃上。我见过太多团队一上来就加卡,结果延迟纹丝不动——因为他们的问题根本不在算力,而在调度等待那 250ms。
把 2.1 收个尾:链路拆解的本质,是让你从"服务器好慢"这种模糊抱怨,进化到"慢在调度等待 250ms、prefill 450ms"这种可操作的诊断。前者让人无从下手,后者直接指向参数。下一节 2.2 我们看显存,2.3 看调度,三者合起来你就有了一张完整的"高并发归因地图"。
最后澄清几个新手最常有的误解,免得你带着错误心智模型进下一节:
把这四条贴在工位上,能少踩一半的链路坑。
最后补一个容易被忽略的点:即使 TTFT 完全相同,开不开流式,用户体感也天差地别。非流式时,用户面对的是一段空白直到整段返回;流式时,第一个 token 到达就开始有内容滚动,焦虑感大幅下降。所以优化 TTFT 之外,务必默认开启流式输出——它不改服务器指标,却改了用户的主观"快不快"。把 TTFT、TPOT、流式三件事一起交付,才是完整的低延迟体验,而不是只盯着监控里的某个数字。要补充的是,流式对服务端的成本几乎为零——它只是把一次性返回改成边生成边回传,不会增加 GPU 开销,所以你没有任何理由不开它。
读到这里,你应该能指着延迟图说出::"我们服务的首 token 慢,主要慢在调度等待 250ms 和 prefill 450ms,前者调批预算,后者受 MoE 访存限制。"——这就是"读者带走的一句话"。如果你现在还只会说"服务器慢",请重读 2.1.2 的小结,直到你能拆出每一段。
下一节 2.2,我们钻进显存,看 PagedAttention 如何用分页把 KV Cache 管理得既省又灵活,从根本上支撑起这么大的并发。那是本章最硬核、也最值得反复读的一节。