vLLM 不是把外部模型包一层:它接管的是推理运行时

解释 vLLM 使用 Qwen、Llama、Gemma 或 Transformers backend 时,谁加载权重、谁驱动 forward、谁分配 KV cache,以及为什么这不是调用外部模型自己的 generate。

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 的初始化入口是:

这段代码会:

  1. 复制 kv_cache_config
  2. 计算 block table 的最大长度。
  3. 初始化 attention backend。
  4. 创建 BlockTables
  5. 解析 CUDA graph mode。
  6. init_kv_cache(...)
  7. 创建 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 工作:

  1. 更新 request state。
  2. 处理 finished/free/add/update requests。
  3. 根据 SchedulerOutput 准备 InputBatch
  4. prepare_attn(input_batch) 得到 block tables 和 slot mappings。
  5. model_state.prepare_attn(...) 构造 attention metadata。
  6. 准备 LoRA、多模态 embeddings、pipeline parallel intermediate tensors。
  7. 进入 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 主路径。还有一些边界要单独记:

  1. Embedding / pooling 模型:不一定需要标准自回归 KV cache。
  2. Encoder-decoder 模型:除了 decoder KV cache,还有 encoder/cross-attention 相关状态。
  3. Multimodal 模型:可能有 encoder cache、multimodal processor cache。
  4. Mamba / hybrid / attention-free 模型:状态不一定是标准 attention K/V,但仍然要接入 vLLM 的 cache/state 管理。
  5. KV connector / disaggregated prefill / KV offloading:KV blocks 可以被转移、加载、复用,但它们仍然在 vLLM 的 KV block 语义里,不是 HF past_key_values tuple。

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 真正的推理系统。