Prhub

#49957 Improve Transformers modelling backend `fx` tracer

原始 PR 作者 hmellor 合并时间 2026-07-27 19:51 文件变更 9 提交数 6 评论 0 代码增减 +307 / -128

执行摘要

改进 Transformers fx 追踪器,使用 meta tensor 推断形状

原有 fx tracer 遇到无法追踪的注意力调用会提前截断图,且形状推断依赖手写规则,容易出错。通过 meta tensor 执行和叶调用标记,可以追踪完整图,提高匹配准确性。

该 PR 是 Transformers 后端的核心改进,设计上采用 meta tensor 替换手写规则是一大亮点,建议关注 fx_utils.py 中的 _MetaProxy 实现和 fuser.py 中的多 fuser 错误处理逻辑。

讨论亮点

未产生讨论,已由 @Isotr0py 批准。主要关注点在于 meta tensor 引入的额外开销,但仅发生在追踪阶段,不影响推理性能。

实现拆解

  1. 重构 Proxy 类:用 _MetaProxy 替换 _SizedProxy,每个操作同时在 meta tensor 上执行,使 len、索引和 .shape 通过 PyTorch meta kernel 推断,消除了手写 _infer_len/_rank。
  2. 标记叶调用:新函数 is_leaf_call 和 _as_leaf_call 将无法追踪的注意力接口(如 vllm_attention_forward)标记为不透明叶调用,在节点 meta 中记录,避免截断图。
  3. 改进 fuser 缓存:get_fuser 的缓存键从仅按类改为按类加子模块名称元组,区分相同类的不同结构实例。
  4. 多 fuser 尝试:当匹配的 fuser 无法改写 forward 时,continue 到下一个 fuser 而非返回 None,增加融合成功率。
  5. 配置统一:所有 validate/fuse/update_attrs 方法改为接收 VllmConfig 而非多个单独配置,简化接口。
文件 模块 状态 重要度
vllm/model_executor/models/transformers/fx_utils.py fx 追踪器 modified 9.05
vllm/model_executor/models/transformers/fuser.py 融合检测 modified 7.34
vllm/model_executor/models/transformers/fusers/qkv.py QKV 融合 modified 7.22
vllm/model_executor/models/transformers/fusers/base.py 融合基类 modified 6.82

关键符号

is_leaf_call _reference_weight trace key get_fuser Fusers.__init__ _MetaProxy.__len__ _AllLeafTracer._infer_meta QKVFuser.match QKVFuser.validate RMSNormFuser.fuse GLUFuser.validate

关键源码片段

vllm/model_executor/models/transformers/fx_utils.py data-contract

核心文件,实现了基于 meta tensor 的追踪器和叶调用标记。

# 文件 : vllm/model_executor/models/transformers/fx_utils.py
# _MetaProxy: 携带 meta tensor 值的 Proxy,形状操作通过 meta kernel 推断
​
_UNKNOWN = object() # 无法推断的占位符,区别于 None
_MODULE_CALL = nn.Module.__call__ # 未 patch 前的原始 __call__
​
class _MetaProxy(fx.Proxy):
    """Proxy carrying the meta-tensor value of the traced expression."""
    meta: object = _UNKNOWN
​
    def __len__(self) -> int:
        if self.meta is not _UNKNOWN:
            return len(self.meta)
        return super().__len__() # fallback to default
​
    def __getattr__(self, k: str) -> "_MetaAttribute":
        return _MetaAttribute(self, k)
​
class _MetaAttribute(_MetaProxy, fx.proxy.Attribute):
    """Attribute proxy (e.g. x.shape) carrying meta value."""
    def __init__(self, root: fx.Proxy, attr: str):
        super().__init__(root, attr)
        root_meta = getattr(root, "meta", _UNKNOWN)
        if root_meta is not _UNKNOWN:
            with contextlib.suppress(Exception):
                self.meta = getattr(root_meta, attr)
​
class _AllLeafTracer(fx.Tracer):
    """Tracer that treats every submodule as a leaf."""
    varkw: str | None = None
​
    def is_leaf_module(self, m: nn.Module, module_qualified_name: str) -> bool:
        return True
​
    def proxy(self, node: fx.Node) -> fx.Proxy:
        return _MetaProxy(node, self)
​
    def create_proxy(self, kind, target, args, kwargs, *extra, **extra_kwargs):
        proxy = super().create_proxy(kind, target, args, kwargs, *extra, **extra_kwargs)
        if isinstance(proxy, _MetaProxy) and proxy.meta is _UNKNOWN:
            with contextlib.suppress(Exception):
                proxy.meta = self._infer_meta(kind, target, args, kwargs)
        return proxy
​
    def _infer_meta(self, kind: str, target: object, args: tuple, kwargs: dict):
        """Execute op on meta tensors to infer output value."""
        if kind == "placeholder":
            weight = _reference_weight(self.root)
            if str(target) == "hidden_states" and weight is not None:
                return torch.empty(1, 8, weight.shape[-1], dtype=weight.dtype, device="meta")
            return _UNKNOWN
        # other cases omitted for brevity
vllm/model_executor/models/transformers/fuser.py data-contract

修改了 get_fuser 的缓存键和错误处理逻辑。

# 文件 : vllm/model_executor/models/transformers/fuser.py
# 缓存键函数,考虑子模块名称
def key(module: nn.Module) -> tuple:
    """Cache key for `get_fuser`. Considers module type and its immediate children."""
    return (type(module), tuple(name for name, _ in module.named_children()))
​
@cached(cache={}, key=key)
def get_fuser(module: nn.Module) -> BaseFuser | None:
    """The fuser for module's class and shape (cached), or None if no match."""
    n_linear = sum(isinstance(c, nn.Linear) for c in module.children())
    is_leaf = next(module.children(), None) is None
    if n_linear < 2 and not is_leaf:
        return None
    if (graph := trace(module)) is None:
        return None
    for fuser_cls in (GLUFuser, QKVFuser, RMSNormFuser):
        if (fuser := fuser_cls.match(graph, module)) is not None:
            if isinstance(fuser, StackedFuser):
                try:
                    fuser.update_forward(module)
                except Exception as exc:
                    # 失败时继续尝试下一个 fuser
                    logger.debug(
                        "Attempted to fuse %s using %s but failed "
                        "to update its forward method: %s",
                        type(module), fuser_cls.__name__, exc
                    )
                    continue
            return fuser
    # ... norm warning logic omitted

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

meta tensor 执行在追踪时可能引入少量额外开销,但仅针对每个模块类一次,影响有限。叶调用标记依赖于节点 meta,若其他代码移除 meta 可能导致漏标记。配置统一为 VllmConfig 需确保所有调用方都正确传递。

对 Transformers 后端用户透明,内部融合逻辑更健壮。形状推断更准确,可能修复某些模型因截断图而无法融合的问题。

核心路径变更 meta tensor 运行时开销 配置接口变更

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论