大模型推理阶段
一、整体架构:九大阶段
vLLM V1 的推理生命周期分为 9 个核心阶段:
| 阶段 | 核心模块 | 职责 |
|---|---|---|
| ① HTTP入口 | FastAPI Route | 接收请求、参数校验、负载感知 |
| ② 分词/预处理 | OnlineRenderer + Tokenizer | 文本 → Token IDs |
| ③ 请求入队 | InputProcessor | 封装为 EngineCoreRequest |
| ④ 调度决策 | Scheduler | 选请求、分配 KV Cache |
| ⑤ Prefill | GPUModelRunner | 一次性处理所有输入 Token |
| ⑥ Decode循环 | GPUModelRunner + Scheduler | 逐 Token 生成 + 连续批处理 |
| ⑦ 采样 | Sampler | 从 logits 中选取下一个 Token |
| ⑧ 流式输出 | OutputProcessor + Detokenizer | Token → 文本,SSE 推送 |
| ⑨ 请求完成 | Scheduler + OutputProcessor | EOS 检测,释放资源 |
二、双进程架构(面试亮点)
vLLM V1 采用 前端进程 + 后端进程 的双进程设计,通过 ZMQ 进行跨进程通信:
-
前端进程(AsyncLLM):负责 HTTP 接入、请求构造、输出处理
-
后端进程(EngineCore):负责调度决策、模型执行
这种架构将 I/O 密集型工作(前端)与计算密集型工作(后端)隔离,避免相互阻塞。
三、各阶段关键技术点
① HTTP入口
-
使用 FastAPI 提供 OpenAI 兼容 API
-
load_aware_call装饰器在高负载时返回 503 -
流式/非流式自动分流
② 分词与预处理
-
vLLM V1 引入 Renderer 架构,统一处理分词和多模态
-
若客户端直接传入
prompt_token_ids,可跳过分词,减少约 5-10ms 延迟
③ 请求入队
-
EngineCoreRequest使用msgspec.Struct,序列化极快,适合 ZMQ 跨进程通信 -
请求同时注册到前端的 OutputProcessor 和后端的 EngineCore
④ 调度决策(核心)
Scheduler 维护 waiting / running / prefill 三类队列,调度约束条件包括:
-
KV Cache 容量:可用 GPU 块数
-
Token 预算:
max_num_scheduled_tokens -
请求数上限:
max_num_running_reqs -
策略优先级:FCFS 或优先级调度
max_num_scheduled_tokens是吞吐和延迟的"旋钮"——值越大吞吐越高但延迟也越高
⑤ Prefill 阶段
-
所有输入 Token 在 一次 forward pass 中完成
-
计算结果写入 PagedAttention 的 KV Cache
-
首 Token 延迟 (TTFT) 与输入长度成正比
-
vLLM V1 支持 Chunked Prefill,长输入可拆分并与 Decode 请求交错执行
⑥ Decode 循环(面试高频)
-
连续批处理 (Continuous Batching):每个 step 动态组合 Prefill 和 Decode 请求
-
请求完成立即移除,新请求立即加入
-
所有请求共享同一 KV Cache 块池
-
关键指标:TPOT(Time Per Output Token)
⑦ 采样
采样步骤按顺序执行:
-
logits 转 float32
-
应用 allowed token ids 白名单
-
应用 bad words 排除
-
应用惩罚(repetition / frequency / presence)
-
采样(greedy 或 random,含 temperature / top_k / top_p)
性能亮点:
TopKTopPSampler使用 fused CUDA kernel,将 top-k 和 top-p 合并为一次操作;greedy 采样(temperature=0)几乎是零开销的 argmax
⑧ 流式输出
-
使用
IncrementalDetokenizer实现增量反分词——每收到一个新 Token,只解码增量部分 -
需处理 UTF-8 边界问题:中文字符可能由多个 Token 组成,不完整字节需暂存
-
最终通过 SSE (Server-Sent Events) 格式推送给客户端
-
stream_interval可调,默认每 Token 推送
⑨ 请求完成
完成条件包括:EOS Token、达到 max_tokens、Stop String、客户端中断、重复检测
资源回收:释放 KV Cache 块、从 requests dict 移除、清理 RequestState
四、面试可展开的加分点
-
PagedAttention:vLLM 的核心创新,将 KV Cache 分页管理,类似操作系统虚拟内存,显著提高显存利用率
-
连续批处理 vs 静态批处理:传统框架需等所有请求完成再组新 batch,vLLM 每 step 动态调整
-
Prefill vs Decode 的计算差异:Prefill 是计算密集型(一次性处理所有输入 Token),Decode 是内存带宽密集型(逐 Token 生成,受 KV Cache 读取限制)
-
vLLM V1 相比 V0 的改进:更简洁的双进程架构、更高效的调度器
为什么要采样:
先给你一个最直白的比喻:模型前向计算(Forward)像是在考试时“算出每道题的分数(Logits)”,而采样(Sampling)则是根据这些分数“决定最终选哪个答案(Token)”。
如果去掉采样直接取最高分(贪心解码),模型就变成了“没有感情的复读机”。具体来说,采样这一步在 vLLM 推理链路中承载了三大核心使命:
1. 数学桥梁:将“分数”翻译成“汉字”
模型最后一层输出的 Logits 只是一堆浮点数(比如 -2.3, 5.1, 0.4...),范围不固定。采样第一步就是通过 Softmax 将这些分数归一化成概率分布(所有概率加起来等于 1)。只有完成了这一步转化,才能从这个分布中抽取出一个具体的 Token ID,进而解码成人类能读懂的文本。
2. 创造力调控(面试重点):平衡“确定”与“随机”
这是采样最核心的设计意图。通过调控 Temperature(温度系数)、Top-K 和 Top-P:
-
低温度(如 0.1):拉大高分和低分的差距,采样近乎贪心,输出严谨、死板,适合数学推理/代码生成。
-
高温度(如 0.9):拉平概率分布,低分词汇也有机会“中奖”,输出更具多样性和创造力,适合写诗/头脑风暴。
-
Top-K/P 截断:强行把累加概率超过 0.9 之外的“稀奇古怪”的词汇剔除掉,防止模型在重要场合胡言乱语。
3. 复杂约束的执行器(工程亮点)
在 vLLM 的采样器(Sampler)中,这一步不仅是“抽签”,还要强行植入各种业务硬约束:
-
禁止词(Bad Words):直接把对应 Token 的概率置为
-inf,物理上杜绝模型说出敏感词。 -
重复惩罚(Repetition Penalty):动态降低已出现 Token 的概率,强制模型换词,避免陷入“我我我...”的死循环。
-
语法/JSON 约束(结构化输出):强制只允许符合特定正则表达式的 Token 被采样,保证输出一定是合法的 JSON 格式。
为什么这一步在 vLLM 面试中特别值得聊?
如果在面试中只回答“选 Token”,那就亏了。你可以顺势带出 vLLM 的性能优化细节:
“面试官,这一步虽然看起来只是简单的数学运算,但在 vLLM V1 中,它是一个内存带宽敏感型操作。因为每一次 Decode 迭代(每生成 1 个 Token)都要执行一次采样。
vLLM 之所以将 Top-K、Top-P 和 Softmax 合并成一个 Fused CUDA Kernel,就是为了避免反复读写显存。如果把采样拆成多个独立算子调用,GPU 的 Kernel Launch 开销会极大拖慢 TPOT(Time Per Output Token)。所以,vLLM 把惩罚计算、概率归一化和截断全部融合在显存内部完成,用一次内核调用搞定所有逻辑,这是保证高并发推理吞吐的关键细节。”
1. 纠正:“筛选” vs “抽样”(概率游戏)
你说“筛选出最靠谱的那个”,这在工程上叫贪心解码(Greedy Decoding),只是采样的一种特例。
真正的采样(Sampling)不是“选第一名”,而是“按概率抽签”。
如果模型预测“我”是60%,“俺”是30%,“朕”是10%。采样不是永远选“我”,而是让“俺”有30%的概率被选中。采样的目的是引入随机性(多样性),防止模型变成只会说套话的机器人。如果永远选最靠谱的,写诗和故事时会极其死板。2. 核心问题:生成一个Token,采样一次!(绝不等到最后)
明确回答:Decode 每生成 1 个 Token,就立即采样 1 次。生成 100 个 Token,就采样 100 次。
绝对不可能等全部生成完再采样,原因有二:
原因一(自回归特性):大语言模型是自回归的。下一次预测必须依赖上一次输出的 Token。如果不采样出第 1 个 Token,模型根本不知道第 2 个 Token 的输入上下文是什么,后续计算根本无法进行。
原因二(早停机制):如果采样出来的 Token 恰好是
<EOS>(结束符),vLLM 会立刻终止该请求的 Decode 循环,释放显存。如果等全部生成完再采样,那遇到<EOS>时早就浪费了大量算力去生成无意义的后续内容。
3. 在 vLLM 中的实际执行流程(面试硬核细节)
在 vLLM V1 的
GPUModelRunner中,每 1 次 Forward(前向传播) 之后,紧跟 1 次 Sampler 调用。这是一个紧耦合的循环:Step 1:Forward(计算 Logits) → Sampler(采样出 Token_1) → 返回给前端
Step 2:把 Token_1 拼接到输入 → Forward(计算 Logits) → Sampler(采样出 Token_2) → 返回给前端
...循环往复,直到命中max_tokens或 EOS。这里有一个面试官极爱问的陷阱题:
“既然每次生成都要采样,那 Prefill(预填充)阶段需要采样吗?”
标准答案:不需要! Prefill 阶段只是把用户输入的 Prompt 一次性灌进去,计算出第一个 KV Cache 和第一个 Logits。Prefill 结束后才会进行第一次采样,产出第一个输出 Token。后续的每一次 Decode 迭代,才会严格遵循“先 Forward 计算,后采样”的节奏。
4. 进阶加分点(vLLM 的批处理采样)
如果此时你再补一句工程优化,面试官会眼前一亮:
“虽然每生成一个 Token 就采样一次,但 vLLM 为了压榨 GPU 性能,并不是单个单个地采样。
在 Continuous Batching 中,一个 Step 可能同时处理 32 个请求。vLLM 会把这 32 个请求的 Logits 拼成一个 大矩阵(Batch, Vocab),通过一个 Fused(融合)采样 Kernel,一次性同时算出这 32 个请求各自的下一个 Token。这既保证了‘每生成一步采样一次’的逻辑,又通过矩阵运算掩盖了采样的延迟开销。”总结一句话:采样的粒度是 “每个请求、每个生成步(Step)”,Prefill 结束后触发第一次,之后每一步紧跟 Forward 触发一次,直至结束。绝不等到全剧终。

浙公网安备 33010602011771号