Transformers 推理后端集成指南:实现一次,让 vLLM、SGLang 等任意推理引擎直接复用
本文面向需要在第三方推理引擎(如 vLLM、SGLang)中运行自定义模型的开发者。它系统讲解如何按照 Transformers 的"兼容后端"规范实现一个模型:通过 AttentionInterface 接入可插拔的注意力后端、在 PreTrainedConfig 中声明张量并行/流水线并行计划,以及多模态模型所需的 ProcessorMixin 附加契约。读完并对照仓库源码后,你可以让自己的模型不依赖引擎侧重新实现、被任意支持该接口的推理引擎直接加载与加速。
一、背景:为什么要把 Transformers 模型做成推理引擎的"后端"
Transformers 模型可以与 vLLM、SGLang 等推理引擎兼容。这样做的直接收益是:同一份 Transformers 模型实现可以"到处使用",无需为每个推理引擎从零重写一遍模型前向逻辑;对于引擎尚未原生实现的模型,只要它满足本文描述的注意力后端接口,也能被引擎直接调度。
该能力的技术核心只有一条:把模型的注意力层写成"可被外部替换/接管"的形式,并让配置声明出并行切分方案。Transformers 用三个具体机制承载这一契约:
AttentionInterface(及全局实例ALL_ATTENTION_FUNCTIONS)——注意力实现的注册与分发中心;PreTrainedModel._supports_attention_backend标志位——向外部引擎声明"本模型是后端兼容的";PreTrainedConfig.base_model_tp_plan/base_model_pp_plan——张量并行与流水线并行的声明式切分计划。
以下按"纯文本模型 → 多模态模型"两条主线展开。
二、模型实现:三步接入推理后端
2.1 第 1 步:满足标准模型贡献要求
模型首先必须符合 Transformers 的常规模型规范(见 新增模型指南 或 自定义模型贡献指南),其中有两条是"作为后端"的硬性前提:
- 模型目录中必须存在合法的
config.json; - 该
config.json中必须包含合法的auto_map字段,指向自定义模型类。
auto_map 决定了 Auto* 加载器如何找到模型代码;推理引擎在动态加载 Hub 模型时同样依赖这一字段定位模型类,缺失它会导致引擎无法把模型实例化为 Transformers 对象。
2.2 第 2 步:使用 AttentionInterface 定义注意力层
这是整个后端的"心脏"。规范要求:
- 在注意力层中通过
ALL_ATTENTION_FUNCTIONS动态取用注意力实现; - 从基础
MyModel类向各注意力层透传**kwargs(引擎侧注入的缓存、分页元数据等参数都经由此通道下发); - 在
PreTrainedModel子类上将_supports_attention_backend置为True。
文档给出的标准写法如下(modeling_my_model.py):
from transformers import PreTrainedModel
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
from torch import nn
class MyAttention(nn.Module):
def forward(self, hidden_states, **kwargs):
...
attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
attn_output, attn_weights = attention_interface(
self,
query_states,
key_states,
value_states,
**kwargs,
)
...
class MyModel(PreTrainedModel):
_supports_attention_backend = True
几个源码级要点,帮助理解这段代码为什么必须这样写:
-
AttentionInterface是一个可注册的字典式分发器。在 modeling_utils.py 中,AttentionInterface(GeneralInterface)维护了_global_mapping,当前内置了flash_attention_4/3/2、flex_attention、sdpa等实现,以及带paged|前缀的分页变体;ALL_ATTENTION_FUNCTIONS是所有共享模型的全局单例:# src/transformers/modeling_utils.py _global_mapping = { "flash_attention_4": flash_attention_forward, "flash_attention_3": flash_attention_forward, "flash_attention_2": flash_attention_forward, "flex_attention": flex_attention_forward, "sdpa": sdpa_attention_forward, "paged|flash_attention_4": paged_attention_forward, "paged|flash_attention_3": paged_attention_forward, "paged|flash_attention_2": paged_attention_forward, "paged|sdpa": sdpa_attention_paged_forward, "paged|eager": eager_paged_attention_forward, } # Global AttentionInterface shared by all models ALL_ATTENTION_FUNCTIONS: AttentionInterface = AttentionInterface()因此
ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]的取值完全由config._attn_implementation决定——引擎侧切换实现,只需要改这一个字符串,模型代码无需任何改动。get_interface还会对非法实现名严格校验并抛出KeyError,避免静默回退。 -
paged|前缀是分页注意力的显式声明。从源码结构看,AttentionInterface之所以为每种实现额外注册paged|xxx变体,正是因为推理引擎(以及仓库自身的连续批处理 continuous batching)需要把 KV 缓存以分页块(block)形式传入注意力内核。continuous_batching 入口 中的逻辑印证了这一约定:# src/transformers/generation/continuous_batching/continuous_api.py is_paged = "paged|" in target_implem ... if "paged|" not in target_implem: model.set_attn_implementation(f"paged|{target_implem}")也就是说,引擎/调度器通过把
_attn_implementation改写为paged|flash_attention_2这类字符串,让同一份模型代码走分页路径。这正是"模型作为后端"能在引擎中拿到分页 KV 缓存性能特性的底层机制。 -
_supports_attention_backend = True是面向外部引擎的公开契约。在PreTrainedModel中该标志默认关闭(_supports_attention_backend: bool = False,见 modeling_utils.py),并暴露为类方法供外部探测:# src/transformers/modeling_utils.py @classmethod def is_backend_compatible(cls): return cls._supports_attention_backend推理引擎在加载模型时会调用
is_backend_compatible()判断该 Transformers 模型是否"愿意"作为自己的后端;不置位该标志,模型即使实现了ALL_ATTENTION_FUNCTIONS调用也无法被识别为合规后端。
2.3 第 3 步:在配置中声明张量并行 / 流水线并行计划
可选但强烈建议的一步:在 PreTrainedConfig 子类中增加两个键,让模型天然支持多卡并行。
base_model_tp_plan:启用张量并行。它是"层全限定名模式 → 切分方式"的映射,当前仅支持"colwise"与"rowwise"两种策略(列切分/行切分,对应nn.Linear按输出维或输入维切分)。base_model_pp_plan:启用流水线并行。它把"直接子层名"映射到"字符串列表的元组":元组第一个元素是该层在 modeling 代码中的输入参数名,最后一个元素是该层输出的变量名。
configuration_my_model.py 示例:
from transformers import PreTrainedConfig
class MyConfig(PreTrainedConfig):
base_model_tp_plan = {
"layers.*.self_attn.k_proj": "colwise",
"layers.*.self_attn.v_proj": "colwise",
"layers.*.self_attn.o_proj": "rowwise",
"layers.*.mlp.gate_proj": "colwise",
"layers.*.mlp.up_proj": "colwise",
"layers.*.mlp.down_proj": "rowwise",
}
base_model_pp_plan = {
"embed_tokens": (["input_ids"], ["inputs_embeds"]),
"layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
"norm": (["hidden_states"], ["hidden_states"]),
}
从源码结构看,这两个属性在 configuration_utils.py 中定义为 ClassVar,默认为 None,与文档描述一一对应:
# src/transformers/configuration_utils.py
base_model_tp_plan: ClassVar[dict[str, Any] | None] = None
...
base_model_pp_plan: ClassVar[dict[str, Sequence[list[str]]] | None] = None
其 docstring(configuration_utils.py)明确说明:base_model_tp_plan 用于 model.tensor_parallel 时对子模块 FQN 应用张量并行计划;base_model_pp_plan 把子模块映射为流水线计划,使用户可把子模块放到合适的设备上。此外配置基类还内置了 base_model_fsdp_plan(FSDP2 分片策略)与 base_model_ep_plan 等占位,说明这套"计划字典"是统一的并行声明机制,TP/PP 只是其中两个成员——推理引擎读取这些计划即可完成切分,无需理解模型内部结构。
实践提示:TP 计划中
k_proj/v_proj用colwise、o_proj用rowwise的组合格式,正是"列切分投影 + 行切分输出"的经典张量并行配对;MLP 侧gate_proj/up_proj列切分、down_proj行切分同理。写错配对会导致切分后形状不匹配,建议在单卡下先用小配置验证forward输出形状一致。
三、多模态模型的附加契约
多模态(VLM)模型除了满足视觉语言模型贡献清单外,还必须实现以下三条,保证引擎侧能正确处理多模态输入。
3.1 声明占位 token:image_token / image_token_ids
ProcessorMixin 子类必须提供 self.image_token 与 self.image_token_ids 属性。该占位 token 的作用有二:
- 在输入 prompt 中标记图像位置;
- 在模型代码中用于把图像特征"散射(scatter)"到对应位置。
image_token_ids 在 ProcessorMixin 中是带缓存的属性,见 processing_utils.py:
# src/transformers/processing_utils.py
@property
def image_token_ids(self) -> list[int | None]:
if _image_token_ids := getattr(self, "_image_token_ids", None):
return _image_token_ids
...
3.2 实现 _get_num_multimodal_tokens 计算占位数量
该处理器方法必须计算"给定尺寸的多模态输入需要多少个占位 token",并返回一个 MultiModalData 对象。计数规则:
- 只有后续在 modeling 代码中真正被图像特征替换的 token 才计入;
- 出现在
<image>token 之间的行/列等"结构性"token(row/column tokens)不算图像占位。
MultiModalData 是一个 dataclass(processing_utils.py),用于统一承载各模态的 token 数量等元数据。文档示例(modeling_my_multimodal_model.py 中的处理器部分):
def _get_num_multimodal_tokens(self, image_sizes=None, **kwargs):
"""
Computes the number of placeholder tokens needed for multimodal inputs with the given sizes.
Args:
image_sizes (`list[list[int]]`, *optional*):
The input sizes formatted as (height, width) per each image.
Returns:
`MultiModalData`: A `MultiModalData` object holding number of tokens per each of the provided
input modalities, along with other useful data.
"""
vision_data = {}
if image_sizes is not None:
num_image_tokens = [256] * len(image_sizes) # 每张图固定 256 个占位 token
num_image_patches = [1] * len(image_sizes) # 不做 patch 切分,整图作为单张基础图像
vision_data.update({"num_image_tokens": num_image_tokens, "num_image_patches": num_image_patches})
return MultiModalData(**vision_data)
num_image_tokens 与 num_image_patches 的分离很关键:前者告诉引擎要预留多少占位位置,后者描述图像被切分成了几个基础块——引擎据此决定特征散射的粒度。
3.3 返回 mm_token_type_ids 标记每个位置的模态类型
处理器必须检查 return_mm_token_type_ids 的取值,并在为真时返回 mm_token_type_ids。该张量逐位置标注模态类型:
0:文本 token;1:图像占位 token;2:视频占位 token。
两条约束:
- 多模态 token 类型序列必须连续,同一类型的相邻 token 之间不能有断裂;
- 起始、结束、行、列等特殊 token 一律视为占位符处理。
文档示例(MyMultimodalProcessor.__call__ 片段):
class MyMultimodalProcessor(ProcessorMixin):
def __call__(self, images=None, text=None, **kwargs):
if return_mm_token_type_ids:
mm_token_type_ids = np.zeros_like(input_ids)
mm_token_type_ids[input_ids == self.image_token_id] = 1
text_inputs["mm_token_type_ids"] = mm_token_type_ids.tolist()
return BatchFeature(data={**text_inputs, **image_inputs}, tensor_type=return_tensors)
从源码结构看,ProcessorMixin 已内建 return_mm_token_type_ids 的解析与 mm_token_type_ids 的批量计算能力:processing_utils.py 中会按 image_token_ids / video_token_ids / audio_token_ids 把对应位置分别置为 1、2 等模态编号。自定义处理器复用该机制即可保证引擎拿到与文本/图像/视频对齐的模态类型张量。
四、自检清单:你的模型"作为后端"是否合格
按本文规范,上线前可对照下表逐项确认(均可在仓库中找到对应实现证据):
| 检查项 | 对应机制 | 仓库证据 |
|---|---|---|
有合法 config.json 且 auto_map 指向模型类 |
动态加载前提 | 新增模型指南、自定义模型指南 |
注意力层经 ALL_ATTENTION_FUNCTIONS 分发 |
可插拔注意力后端 | modeling_utils.py |
透传 **kwargs 到注意力层 |
引擎注入缓存/分页元数据 | 见 §2.2 示例 |
_supports_attention_backend = True |
is_backend_compatible() 对外契约 |
modeling_utils.py |
base_model_tp_plan(colwise/rowwise) |
张量并行切分 | configuration_utils.py |
base_model_pp_plan(输入/输出变量名) |
流水线并行切分 | configuration_utils.py |
多模态:image_token(s) + _get_num_multimodal_tokens + mm_token_type_ids |
多模态占位/散射契约 | processing_utils.py |
五、延伸阅读
- 注意力接口设计说明:
AttentionInterface与注意力注册的完整设计文档; - 多 GPU 推理性能指南:
base_model_tp_plan/base_model_pp_plan的并行用法; - vLLM 集成 与 SGLang 集成:两个典型引擎侧如何消费该后端契约;
- 源码入口:AttentionInterface 定义、is_backend_compatible、并行计划声明、连续批处理的 paged 前缀处理、多模态处理器契约。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python08
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00