首页
/ Transformers 推理后端集成指南:实现一次,让 vLLM、SGLang 等任意推理引擎直接复用

Transformers 推理后端集成指南:实现一次,让 vLLM、SGLang 等任意推理引擎直接复用

2026-09-06 13:48:44作者:翟萌耘Ralph

本文面向需要在第三方推理引擎(如 vLLM、SGLang)中运行自定义模型的开发者。它系统讲解如何按照 Transformers 的"兼容后端"规范实现一个模型:通过 AttentionInterface 接入可插拔的注意力后端、在 PreTrainedConfig 中声明张量并行/流水线并行计划,以及多模态模型所需的 ProcessorMixin 附加契约。读完并对照仓库源码后,你可以让自己的模型不依赖引擎侧重新实现、被任意支持该接口的推理引擎直接加载与加速。

一、背景:为什么要把 Transformers 模型做成推理引擎的"后端"

Transformers 模型可以与 vLLMSGLang 等推理引擎兼容。这样做的直接收益是:同一份 Transformers 模型实现可以"到处使用",无需为每个推理引擎从零重写一遍模型前向逻辑;对于引擎尚未原生实现的模型,只要它满足本文描述的注意力后端接口,也能被引擎直接调度。

该能力的技术核心只有一条:把模型的注意力层写成"可被外部替换/接管"的形式,并让配置声明出并行切分方案。Transformers 用三个具体机制承载这一契约:

  1. AttentionInterface(及全局实例 ALL_ATTENTION_FUNCTIONS)——注意力实现的注册与分发中心;
  2. PreTrainedModel._supports_attention_backend 标志位——向外部引擎声明"本模型是后端兼容的";
  3. 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 定义注意力层

这是整个后端的"心脏"。规范要求:

  1. 在注意力层中通过 ALL_ATTENTION_FUNCTIONS 动态取用注意力实现;
  2. 从基础 MyModel 类向各注意力层透传 **kwargs(引擎侧注入的缓存、分页元数据等参数都经由此通道下发);
  3. 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/2flex_attentionsdpa 等实现,以及带 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_projcolwiseo_projrowwise 的组合格式,正是"列切分投影 + 行切分输出"的经典张量并行配对;MLP 侧 gate_proj/up_proj 列切分、down_proj 行切分同理。写错配对会导致切分后形状不匹配,建议在单卡下先用小配置验证 forward 输出形状一致。

三、多模态模型的附加契约

多模态(VLM)模型除了满足视觉语言模型贡献清单外,还必须实现以下三条,保证引擎侧能正确处理多模态输入。

3.1 声明占位 token:image_token / image_token_ids

ProcessorMixin 子类必须提供 self.image_tokenself.image_token_ids 属性。该占位 token 的作用有二:

  • 在输入 prompt 中标记图像位置;
  • 在模型代码中用于把图像特征"散射(scatter)"到对应位置。

image_token_idsProcessorMixin 中是带缓存的属性,见 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_tokensnum_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 把对应位置分别置为 12 等模态编号。自定义处理器复用该机制即可保证引擎拿到与文本/图像/视频对齐的模态类型张量。

四、自检清单:你的模型"作为后端"是否合格

按本文规范,上线前可对照下表逐项确认(均可在仓库中找到对应实现证据):

检查项 对应机制 仓库证据
有合法 config.jsonauto_map 指向模型类 动态加载前提 新增模型指南自定义模型指南
注意力层经 ALL_ATTENTION_FUNCTIONS 分发 可插拔注意力后端 modeling_utils.py
透传 **kwargs 到注意力层 引擎注入缓存/分页元数据 见 §2.2 示例
_supports_attention_backend = True is_backend_compatible() 对外契约 modeling_utils.py
base_model_tp_plancolwise/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

五、延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391