执行摘要
- 一句话:引入 hw-agnostic 层解析与 VLLM_USE_HW_AGNOSTIC 开关
- 推荐动作:值得精读。这是 transformers 后端走向硬件无关模型定义的奠基 PR,重点学习三点:一是
_resolve 的“优先导入 + 异常回退”通用解析模式,避免逐层手动映射;二是 CustomOp.__new__ 在实例化期做 OOT 替换的设计,实现外部覆盖而调用方无感;三是 review 中关于动态创建类与 CUDA Graphs 兼容性的经验教训。建议结合 #45470 的 DSv4 迁移与后续 2/N 系列一起阅读,跟踪该机制如何从基础设施走向真实模型落地。
功能与动机
PR body 提出的目标是实现硬件无关的模型定义:让 tail 模型的建模代码驻留在 HF transformers 中,通过 --model-impl transformers 后端在 vLLM 内执行。现有后端靠 patch 方式把 HF 特定层替换为 vLLM 原生实现,而这些实现往往依赖特定硬件 kernel;本 PR 通过 VLLM_USE_HW_AGNOSTIC=1 让 hw-agnostic 层优先被导入、失败才回退默认 vLLM 层,从而把层实现与具体硬件 kernel 解耦。作者明确说明其实现基础来自 #45470 的 DSv4 迁移工作。
实现拆解
-
环境开关:在 vllm/envs.py 中新增 VLLM_USE_HW_AGNOSTIC 布尔环境变量(默认 False,解析 true/1),作为 transformers 后端层选择的全局开关。
-
注册与替换基础设施:新增 vllm/model_executor/hw_agnostic/custom_op.py,定义 op_registry(内置)与 op_registry_oot(OOT 覆盖)两个注册表,以及 CustomOp(算子级)与 PluggableLayer(模块组合级)两个基类。二者都在 __new__ 中按类名查询 OOT 注册表,实现实例化期的整体替换;maybe_get_oot_by_class 提供不经过 __new__ 的查询入口。register / register_oot 装饰器分别登记内置与外部覆盖类。
-
首批 hw-agnostic 实现:hw_agnostic/layers/activation.py 提供 SiluAndMul(SwiGLU 激活)及按 HF 激活名索引的 get_act_and_mul_fn;hw_agnostic/layers/layernorm.py 提供 RMSNorm(含 _rms_norm 与 _fused_add_rms_norm,均走 fp32 中间计算以保证跨后端数值一致)。
-
transformers 后端解析入口:新增 vllm/model_executor/models/transformers/layers.py,_resolve(module, name) 在开关开启时优先从 hw_agnostic.layers.<module> 导入符号,捕获 ImportError/AttributeError 后回退到 vllm.model_executor.layers;并在 import 期解析 RMSNorm、GemmaRMSNorm。get_act_and_mul_fn 按激活名逐名回退。同步改造 fusers/glu.py 与 fusers/rms_norm.py 的导入路径,使 GLU 融合与 RMSNorm 融合均经过新的层解析器。
-
测试配套:新增 tests/models/transformers/test_layer_registry.py,用 monkeypatch 注入伪 hw-agnostic 模块验证开关 0/1、缺失符号回退、未知激活名回退,并用随机初始化的 tiny Llama 以 VLLM_USE_HW_AGNOSTIC=0/1 分别端到端 serve,通过 apply_model 内省各层 provider,并用 greedy logprobs 对比校验输出一致。
关键文件:
vllm/model_executor/hw_agnostic/custom_op.py(模块 算子注册;类别 source;类型 core-logic;符号 maybe_get_oot_by_class, PluggableLayer, CustomOp, new): 整个 hw-agnostic 机制的核心:定义 op_registry / op_registry_oot 注册表、CustomOp / PluggableLayer 基类及 register / register_oot 装饰器,在 new 中实现按类名的 OOT 实例化替换,是所有后续 hw-agnostic 层的地基。
vllm/model_executor/models/transformers/layers.py(模块 层解析器;类别 source;类型 core-logic;符号 _resolve, get_act_and_mul_fn): transformers 后端的层解析入口:以 _resolve 实现 hw-agnostic 优先、默认回退的逐符号解析,并在模块导入期解析 RMSNorm / GemmaRMSNorm,是 fusers 改造与测试的锚点。
vllm/model_executor/hw_agnostic/layers/layernorm.py(模块 归一化层;类别 source;类型 core-logic;符号 RMSNorm, init, _rms_norm, _fused_add_rms_norm): 首个 hw-agnostic 层实例,展示 CustomOp 子类的写法:fp32 中间计算保证跨后端一致,并内置融合残差的 RMSNorm 路径。
vllm/model_executor/hw_agnostic/layers/activation.py(模块 激活函数;类别 source;类型 core-logic;符号 SiluAndMul, forward_native, get_act_and_mul_fn): 首个 hw-agnostic 激活实现:SiluAndMul(SwiGLU)与按 HF 激活名索引的注册表,演示激活名的逐名回退设计。
tests/models/transformers/test_layer_registry.py(模块 回归测试;类别 test;类型 test-coverage;符号 fake_hw_layernorm, test_falls_back_to_vllm_when_disabled, test_uses_hw_agnostic_when_enabled, test_falls_back_when_symbol_missing): 覆盖解析契约:开关 0/1、符号缺失回退、未知激活名回退,以及 tiny Llama 端到端两种模式的 logprobs 一致性验证。
vllm/envs.py(模块 环境配置;类别 source;类型 configuration): 新增全局开关 VLLM_USE_HW_AGNOSTIC,解析 true/1 为开启,是所有 hw-agnostic 行为的入口条件。
vllm/model_executor/models/transformers/fusers/glu.py(模块 GLU 融合;类别 source;类型 import-reroute): GLU 融合器改从新的 layers 解析入口获取 get_act_and_mul_fn,使激活能按名解析到 hw-agnostic 实现。
vllm/model_executor/models/transformers/fusers/rms_norm.py(模块 归一化融合;类别 source;类型 import-reroute): RMSNorm / GemmaRMSNorm 改从层解析器导入,使 TP 感知的归一化层能随开关切换到 hw-agnostic 实现。
关键符号:maybe_get_oot_by_class, PluggableLayer.new, PluggableLayer.register, PluggableLayer.register_oot, CustomOp.new, CustomOp.register, CustomOp.register_oot, CustomOp.forward_native, CustomOp.forward_oot, _resolve, get_act_and_mul_fn, RMSNorm.forward_native, SiluAndMul.forward_native
关键源码片段
vllm/model_executor/hw_agnostic/custom_op.py
整个 hw-agnostic 机制的核心:定义 op_registry / op_registry_oot 注册表、CustomOp / PluggableLayer 基类及 register / register_oot 装饰器,在 new 中实现按类名的 OOT 实例化替换,是所有后续 hw-agnostic 层的地基。
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""hw-agnostic 基础设施:CustomOp / PluggableLayer 的注册与 OOT 替换。
OOT(out-of-tree)注册表允许硬件厂商或后端以同名类覆盖 vLLM 默认实现;
实例化时通过 __new__ 按类名查找覆盖,调用方无需感知替换。
"""
import torch.nn as nn
# 两个注册表均按注册名索引到类:
# - op_registry : vLLM 内置(in-tree)算子 / 层
# - op_registry_oot : 外部覆盖(out-of-tree),优先于内置实现
op_registry: dict[str, type] = {}
op_registry_oot: dict[str, type] = {}
def maybe_get_oot_by_class(class_type: type) -> type:
"""按类名查询 OOT 覆盖;无覆盖时原样返回原类。"""
class_name = class_type.__name__
if class_name in op_registry_oot:
return op_registry_oot[class_name]
return class_type
class CustomOp(nn.Module):
"""自定义算子基类:在 __new__ 中做 OOT 替换分发。
OOT 覆盖发生在实例化期,子模块组合、参数初始化等完全由覆盖类接管;
forward 则在 forward_native(原生)与 forward_oot(插件覆盖)间选择。
"""
def __new__(cls, *args, **kwargs):
try:
op_name = cls.__name__ # 经 @CustomOp.register 注册后才有 name
except AttributeError:
raise TypeError(
f"Cannot instantiate '{cls.__name__}': its 'name' attribute "
f"was not set, possibly because it was not decorated with "
f"@CustomOp.register, or it's the CustomOp base class itself."
) from None
# 同名 OOT 算子存在时,改实例化 OOT 类,其余参数原样透传
if op_name not in op_registry_oot:
op_cls_to_instantiate = cls
else:
op_cls_to_instantiate = op_registry_oot[op_name]
logger.debug(
"Instantiating custom op: %s using %s",
op_name,
str(op_cls_to_instantiate),
)
return super().__new__(op_cls_to_instantiate)
def forward(self, *args, **kwargs):
return self._forward_method(*args, **kwargs)
def forward_native(self, *args, **kwargs):
"""PyTorch 原生实现;OOT 插件可覆盖 forward_oot 来替换它。"""
raise NotImplementedError
def forward_oot(self, *args, **kwargs):
# 默认回退到原生实现,即未覆盖时行为不变
return self.forward_native(*args, **kwargs)
@classmethod
def register(cls, name: str, dynamic_arg_dims=None):
"""注册内置算子:@CustomOp.register('rms_norm')"""
def decorator(op_cls):
assert name not in op_registry, f"Duplicate op name: {name}"
op_cls.name = name # __new__ 依赖该属性查出可覆盖名
op_cls._dynamic_arg_dims = dynamic_arg_dims
op_registry[name] = op_cls
return op_cls
return decorator
@classmethod
def register_oot(cls, _decorated_op_cls=None, name: str | None = None):
"""注册 OOT 覆盖,支持带括号/不带括号两种装饰器写法。"""
def decorator(op_cls):
reg_name = name if name is not None else cls.__name__
assert reg_name not in op_registry_oot, f"Duplicate op name: {reg_name}"
op_cls.name = reg_name
op_registry_oot[reg_name] = op_cls
return op_cls
if _decorated_op_cls is None:
return decorator # @CustomOp.register_oot() 或 (name=...)
if isinstance(_decorated_op_cls, type):
return decorator(_decorated_op_cls) # @CustomOp.register_oot
raise TypeError("Decorator can only be applied to classes.")
vllm/model_executor/models/transformers/layers.py
transformers 后端的层解析入口:以 _resolve 实现 hw-agnostic 优先、默认回退的逐符号解析,并在模块导入期解析 RMSNorm / GemmaRMSNorm,是 fusers 改造与测试的锚点。
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Transformers 后端的层解析入口。
当 VLLM_USE_HW_AGNOSTIC 开启时,层符号优先从 hw_agnostic.layers.<module>
导入;未移植的符号自动回退到 vllm.model_executor.layers。<module>。
每次解析都会记录实际来源,便于用户判断哪些层跑在 hw-agnostic 实现上。
"""
import importlib
import vllm.envs as envs
from vllm.logger import init_logger
logger = init_logger(__name__)
_HW_PKG = "vllm.model_executor.hw_agnostic.layers"
_VLLM_PKG = "vllm.model_executor.layers"
def _resolve(module: str, name: str):
"""从 hw-agnostic 模块取 name;未启用或符号缺失时回退到 vLLM。"""
if envs.VLLM_USE_HW_AGNOSTIC:
try:
obj = getattr(importlib.import_module(f"{_HW_PKG}.{module}"), name)
logger.info("Using hw-agnostic layer: %s", name)
return obj
except (ImportError, AttributeError):
# 模块未移植或符号不存在:按层回退,避免整体切换失败
logger.warning(
"hw-agnostic layer %s is not available; falling back to default",
name,
)
return getattr(importlib.import_module(f"{_VLLM_PKG}.{module}"), name)
# import 期解析:模型构建时直接引用这里的 RMSNorm / GemmaRMSNorm
RMSNorm = _resolve("layernorm", "RMSNorm")
GemmaRMSNorm = _resolve("layernorm", "GemmaRMSNorm")
def get_act_and_mul_fn(act_fn_name: str):
"""按激活名取 fused activation-and-mul 算子,逐名回退。
与 _resolve 不同的是,这里按名称参数化:某个激活没有 hw-agnostic
实现时单独回退,不影响其他激活的解析结果。
"""
if envs.VLLM_USE_HW_AGNOSTIC:
try:
from vllm.model_executor.hw_agnostic.layers.activation import (
get_act_and_mul_fn as hw_fn,
)
fn = hw_fn(act_fn_name)
logger.info_once("Using hw-agnostic activation: %s", act_fn_name)
return fn
except (ImportError, KeyError):
logger.warning_once(
"hw-agnostic activation %s is not available; falling back to vLLM",
act_fn_name,
)
from vllm.model_executor.layers.activation import get_act_and_mul_fn as vllm_fn
return vllm_fn(act_fn_name)
vllm/model_executor/hw_agnostic/layers/layernorm.py
首个 hw-agnostic 层实例,展示 CustomOp 子类的写法:fp32 中间计算保证跨后端一致,并内置融合残差的 RMSNorm 路径。
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""hw-agnostic RMSNorm:纯 PyTorch 实现,不依赖任何定制 kernel。"""
import torch
import torch.nn as nn
from torch import Tensor
from vllm.model_executor.hw_agnostic.custom_op import CustomOp
@CustomOp.register("rms_norm")
class RMSNorm(CustomOp):
"""``x -> w * x / sqrt(E[x^2] + eps)``;传入 residual 时融合
``residual += x`` 并返回 (normalized, residual)。"""
def __init__(self, hidden_size: int, eps: float = 1e-6,
var_hidden_size: int | None = None, has_weight: bool = True,
dtype: torch.dtype | None = None):
super().__init__()
self.hidden_size = hidden_size
self.variance_epsilon = eps
# 只有与 hidden_size 不同时才保留覆盖,减少分支
self.variance_size_override = (
None if var_hidden_size == hidden_size else var_hidden_size
)
weight_dtype = dtype or torch.get_default_dtype()
self.has_weight = has_weight
weight = torch.ones(hidden_size, dtype=weight_dtype)
if has_weight:
self.weight = nn.Parameter(weight) # 常规可训练权重
else:
# 无需权重时注册为 buffer,避免被当作可训参数
self.register_buffer("weight", weight, persistent=False)
def _rms_norm(self, x: Tensor, weight: Tensor | None, epsilon: float,
variance_size: int | None = None) -> Tensor:
"""在 fp32 上计算均方根归一化,保证各后端数值一致。"""
orig_dtype = x.dtype
x = x.to(torch.float32)
x_var = x if variance_size is None else x[..., :variance_size]
variance = x_var.pow(2).mean(dim=-1, keepdim=True)
x = x * torch.rsqrt(variance + epsilon)
if weight is not None:
x = x.to(weight.dtype) * weight # 权重按自身 dtype 乘回
return x.to(orig_dtype)
def _fused_add_rms_norm(self, x: Tensor, x_residual: Tensor,
weight: Tensor | None, epsilon: float,
variance_size: int | None = None):
"""融合残差加法与 RMSNorm,并回写更新后的残差。"""
orig_dtype = x.dtype
x = x.to(torch.float32)
x = x + x_residual.to(torch.float32)
x_residual = x.to(orig_dtype) # 返回原始精度,供后续层复用
x_var = x if variance_size is None else x[..., :variance_size]
variance = x_var.pow(2).mean(dim=-1, keepdim=True)
x = x * torch.rsqrt(variance + epsilon)
if weight is not None:
x = x.to(weight.dtype) * weight
return x.to(orig_dtype), x_residual
def forward_native(self, x, residual=None):
weight = self.weight if self.has_weight else None
epsilon = self.variance_epsilon
variance_size = self.variance_size_override
if residual is None:
return self._rms_norm(x, weight, epsilon, variance_size)
return self._fused_add_rms_norm(
x, residual, weight, epsilon, variance_size
)
评论区精华
Review 中核心交锋如下:
风险与影响
- 风险:
- 回退机制依赖异常捕获:
vllm/model_executor/models/transformers/layers.py 的 _resolve 仅捕获 ImportError/AttributeError,若 hw-agnostic 模块存在但实现有语义偏差,不会触发回退,可能静默产生输出差异;tiny Llama 测试覆盖了 logprobs 一致性,但覆盖面有限。
- import 顺序敏感:
RMSNorm = _resolve(...) 在模块导入期执行,VLLM_USE_HW_AGNOSTIC 必须在 import 前设置;多进程 worker 默认 fork 场景下存在 import 顺序风险(测试依赖 VLLM_WORKER_MULTIPROC_METHOD=spawn 规避)。
- 全局开关影响面:
VLLM_USE_HW_AGNOSTIC=1 会作用于该进程所有 transformers 后端模型,hw-agnostic RMSNorm 走 fp32 逐元素计算,性能与数值路径均与融合 kernel 不同。
- fuser 核心路径改造:
fusers/glu.py、fusers/rms_norm.py 的导入路径变更位于 transformers 后端图融合核心路径,回归可能导致 GLU 融合或残差归一化行为变化;现有测试未覆盖多卡与 cudagraph 组合。
__new__ 全局替换:custom_op.py 基于类名的全局 OOT 注册,若不同语义算子同名,可能互相覆盖;注册时机(import 顺序)影响替换是否生效。
- 影响:
- 用户侧:默认关闭,现有行为完全不变;开启后,transformers 后端模型的
RMSNorm、SiluAndMul 换用纯 PyTorch 实现,便于在缺少定制 kernel 的硬件上运行。
- 系统侧:确立了
hw_agnostic 包结构、CustomOp/PluggableLayer 的 OOT 注册模式与 _resolve 逐符号回退约定,为后续更多层“hw-agnostic 化”铺路,是系列 PR(1/N)的架构基础。
- 团队侧:形成“先试 hw-agnostic、失败回退默认、日志报告来源”的协作约定;hmellor 在批准时已提示,随层数增长可能需要重新审视
layers.py 的数据结构。
- 风险标记:环境开关全局生效, 回退依赖异常捕获, import 顺序敏感, fuser 核心路径改造, 实验性开关默认关闭
关联脉络
- PR #45470 DSv4 迁移工作(PR body 引用): PR body 明确说明:本 PR 的 hw-agnostic 层实现基础来自 #45470 的 DSv4 迁移工作,该工作将 DSv4 建模迁移到 HF transformers 后端,本 PR 在其之上叠加可移植层覆盖机制。
- PR #49577 [Feature] Mask Replay: 同为 transformers 后端 / v1 输出协议扩展线,且都改动 vllm/config 与 model_executor 相关契约层,说明该后端能力正在快速扩充,与本 PR 构成同一演进方向。
参与讨论