vLLM 不是把外部模型包一层:它接管的是推理运行时
上一篇讲 vLLM 全链路时,我发现一个更基础的问题需要单独拆开:
vLLM 用其它模型时,到底是在调用外部模型自己的推理,还是自己加载权重做推理?
KV cache 到底在谁手里?
这篇只讲这个问题,不重复完整 EngineCore 链路。
源码版本是 vLLM v0.25.0,commit 702f4814fe54fabff350d43cb753ae3e47c0c276。
0. 一句话
vLLM 用其它模型,不是把请求转发给“那个模型自己的 generate()”。
更准确的说法是:
模型权重和部分模型结构可以来自外部生态;
但 serving-time 的推理循环、batching、KV cache、attention metadata、sampling 和 streaming 输出,由 vLLM 接管。
所以“vLLM 支持某个 HuggingFace 模型”不要理解成:
vLLM -> transformers.AutoModelForCausalLM.generate()
而要理解成:
vLLM -> 加载权重/构造 nn.Module -> 按 vLLM 的 Scheduler 和 KV cache 规则调用 forward
1. 最容易混淆的两个“外部”
这里有两个“外部”要分开:
外部模型资产:
模型权重、config、tokenizer、模型结构定义、remote code
外部推理运行时:
外部库自己的 generate loop、past_key_values 管理、batching、sampling
vLLM 可以使用前者,但主路径不会把后者原封不动拿来用。它可以加载 Qwen、Llama、Gemma、Mistral,也可以在某些情况下走 Transformers modeling backend;但请求进来以后,核心推理编排仍然是 vLLM 的 EngineCore、Scheduler、ModelRunner 和 OutputProcessor。
可以用这个图理解:
flowchart LR
A[Model repo: weights config tokenizer code] --> B[vLLM model loader]
B --> C[nn.Module inside vLLM worker]
D[vLLM Scheduler] --> E[KVCacheManager]
E --> F[vLLM KV cache tensors]
D --> G[GPUModelRunner]
G --> C
G --> F
C --> H[hidden states]
H --> I[vLLM sampler and output processor]
模型 repo 提供的是资产;vLLM worker 拥有运行时。
2. 权重是谁加载的
源码锚点是 GPUModelRunner.load_model():
这段代码里,vLLM 做的是:
model_loader = get_model_loader(self.vllm_config.load_config)
self.model = model_loader.load_model(vllm_config=..., model_config=...)
get_model_loader() 也很直接:
它根据 load_format 选一个 vLLM loader。如果格式不支持,直接报错。
所以第一层结论是:
权重进入 vLLM worker 后,成为 vLLM model runner 持有的 self.model。
这不是“vLLM 远程调用某个模型服务”,也不是“vLLM 调 transformers.generate”。它是在自己的 worker 里加载模型对象,然后后续由 vLLM 调用这个对象的 forward。
3. KV cache 是谁分配的
这里要分成物理显存和逻辑 slots 两层。
3.1 物理 KV cache tensors:worker 初始化
物理 KV cache 的初始化入口是:
这段代码会:
- 复制
kv_cache_config。 - 计算 block table 的最大长度。
- 初始化 attention backend。
- 创建
BlockTables。 - 解析 CUDA graph mode。
- 调
init_kv_cache(...)。 - 创建 KV connector。
再往下看:
_allocate_kv_cache() 里明确使用 torch.zeros(..., device=device) 分配 raw tensors。_reshape_kv_cache() 再按 attention backend 需要的形状,把 raw tensor 变成每层可用的 KV cache view。
所以物理层的结论是:
KV cache 的 GPU tensor 是 vLLM worker 初始化出来的。
外部模型不会自己新建一套 past_key_values,再塞回 vLLM。
3.2 逻辑 KV blocks:Scheduler / KVCacheManager 分配
请求运行时,不是每个请求自己申请一段连续显存,而是调度器给请求分配 block slots。
源码锚点是:
它的参数和注释说明了这一步在处理:
computed tokens
prefix-cache hit tokens
external computed tokens
new tokens to compute
lookahead tokens for speculative decoding
也就是说,一个请求能不能进入本轮执行,不只看 batch size,还要看:
有没有空闲 KV blocks
prefix cache 命中了多少
是否有外部 KV connector 的 tokens
lookahead/speculative tokens 需要多少
是否要保留 reserved blocks 和 watermark
所以逻辑层的结论是:
每个请求拿到的是 vLLM KVCacheManager 分配的 KV blocks。
模型 forward 只是在这些 slot 上写入/读取 K/V。
4. 模型 forward 是谁驱动的
源码锚点是:
这段代码先不是直接 self.model(...)。它先做一堆 vLLM runtime 工作:
- 更新 request state。
- 处理 finished/free/add/update requests。
- 根据
SchedulerOutput准备InputBatch。 prepare_attn(input_batch)得到 block tables 和 slot mappings。model_state.prepare_attn(...)构造 attention metadata。- 准备 LoRA、多模态 embeddings、pipeline parallel intermediate tensors。
- 进入
set_forward_context(...)。
最后才会根据 CUDA graph 模式选择:
cudagraph_manager.run_fullgraph(...)
cudagraph_manager.run_pw_graph(self.model, model_inputs)
self.model(**model_inputs)
这个顺序说明:模型 forward 是被 vLLM 的 batch、position、attention metadata、slot mapping、KV connector 和 CUDA graph context 包住执行的。
所以 forward 层的结论是:
self.model 负责按输入算 hidden states;
但输入怎么拼、KV slot 在哪、attention metadata 怎么传、这一步算哪些 token,由 vLLM 决定。
5. 那 Transformers backend 又是什么
Transformers backend 容易让人误会成“vLLM 退回 HuggingFace 推理”。源码里不是这个意思。
ModelConfig 里有两个关键函数:
它会根据模型是否 multimodal、MoE、runner/task 等,选择类似:
TransformersForCausalLM
TransformersMoEForCausalLM
TransformersMultiModalForCausalLM
TransformersEmbeddingModel
模型 registry 里还有:
这段会检查模型是否是 Transformers library 里注册的架构,或是否能从 auto_map / remote code 里解析,并检查 is_backend_compatible()。如果兼容,就返回 vLLM 的 Transformers backend class 名称。
注意这里的关键词是 backend class,而不是 generate()。
这意味着:
Transformers backend 是 vLLM 用来承接模型结构的一种适配层;
不是把请求交给 HuggingFace 的完整生成循环。
更白话一点:
vLLM 可以借 Transformers 的模型定义;
但车还是 vLLM 在开,油箱也是 vLLM 在管。
6. past_key_values 到哪里去了
如果你从 HuggingFace 视角理解生成,常见 mental model 是:
model(input_ids, past_key_values=...)
-> logits, new_past_key_values
vLLM 主路径不是这样组织的。它不会让每个请求拿着 Python tuple 形式的 past_key_values 进进出出。
vLLM 的 mental model 更像:
全局 KV cache tensors 已经在 worker 上分配好
每个请求只持有逻辑 block table
Scheduler 每步分配/回收 blocks
attention backend 根据 slot mapping 读写对应位置
所以你前面问的:
外部模型会把它自己内部的 KV cache 放到 vLLM 管理的 cache 内存么?
更准确的回答是:
不是“外部模型先生成自己的 KV cache,再放进 vLLM”。
而是“在 vLLM 推理路径里,attention 本来就被安排去读写 vLLM 的 KV cache”。
如果一个模型实现不能按 vLLM 的方式接入 attention、KV cache、metadata、sampling,它就不是直接可用的 vLLM serving 主路径。
7. 三种情况不要混
情况 A:vLLM native model
这是最直观的情况。vLLM 仓库里有模型实现,权重加载到 vLLM 的 self.model,attention/KV/sampling 由 vLLM 路径驱动。
模型结构:vLLM native
权重:来自模型仓库
推理循环:vLLM
KV cache:vLLM
情况 B:Transformers backend
模型结构更多借 Transformers 生态,但必须包装成 vLLM backend 兼容的类。
模型结构:Transformers backend 承接
权重:来自模型仓库
推理循环:vLLM
KV cache:vLLM
情况 C:真正的外部服务
如果你调用的是另一个推理服务的 HTTP API,那就不是这篇讲的 vLLM 内部模型执行。那种情况下 KV cache 在对方服务里,vLLM 也不会管理它。
模型结构:外部服务
权重:外部服务
推理循环:外部服务
KV cache:外部服务
不要把 B 和 C 混成一件事。
8. 例外和边界
这篇讲的是 generate 主路径。还有一些边界要单独记:
- Embedding / pooling 模型:不一定需要标准自回归 KV cache。
- Encoder-decoder 模型:除了 decoder KV cache,还有 encoder/cross-attention 相关状态。
- Multimodal 模型:可能有 encoder cache、multimodal processor cache。
- Mamba / hybrid / attention-free 模型:状态不一定是标准 attention K/V,但仍然要接入 vLLM 的 cache/state 管理。
- KV connector / disaggregated prefill / KV offloading:KV blocks 可以被转移、加载、复用,但它们仍然在 vLLM 的 KV block 语义里,不是 HF
past_key_valuestuple。
9. 最终心智模型
可以把 vLLM 分成两个世界:
模型资产世界:
weights
config
tokenizer
architecture code
推理运行时世界:
request scheduling
input batching
KV block allocation
attention metadata
CUDA graph
sampling
output streaming
vLLM 支持很多模型,主要是在扩展“模型资产世界”的入口;vLLM 的核心价值,在于它掌控“推理运行时世界”。
所以最短答案是:
vLLM 自己加载模型权重,自己驱动推理循环,自己管理 KV cache。
外部模型提供的是权重和可适配的 forward 能力,不是完整 serving runtime。
如果这个心智模型立住了,再看 EngineCore、Scheduler、ModelRunner、KVCacheManager 就不会乱:它们不是外部模型的包装壳,而是 vLLM 真正的推理系统。