Prhub

#51415 [Fusion] Manual `ActivationQuantFusionPass` initial application

原始 PR 作者 mgoin 合并时间 2026-09-04 01:37 文件变更 10 提交数 10 评论 17 代码增减 +740 / -19

执行摘要

新增手动融合入口,将激活量化融合从编译器 Pass 迁移至模型代码显式调用。

根据 RFC #43224 和 Issue #43501,vLLM 计划将编译器阶段的激活量化融合(ActivationQuantFusionPass)逐步迁移为模型代码中的手动融合。手动融合能在更早阶段确定性地选择内核,避免编译器分析开销,并为特定硬件(如 Blackwell GPU 上的 NVFP4)提供更优的实现路径。本 PR 是该迁移计划的第一步,旨在验证手动融合接口的设计与可用性,并以 Llama 模型作为首个落地案例。

此 PR 是 vLLM 量化优化架构演进的重要一步,引入了一个设计良好、可扩展的手动融合框架。虽然当前只应用于 Llama 模型,但其接口设计为扩展到其他模型和量化方案奠定了坚实基础。代码实现清晰,测试覆盖全面(包括单元测试和端到端测试)。主要风险在于可能引入的 CI 失败(如 IMA 问题)和性能的精确量化。建议:

  1. 值得精读:对于关注量化优化、编译器融合或模型性能的工程师,本 PR 的实现细节(尤其是融合映射机制和接口设计)非常有参考价值。
  2. 重点关注:需要仔细审查与 CI IMA 问题相关的具体原因,确保稳定性。同时,关注后续的 TP2 一致性测试和 A/B 性能对比结果。
  3. 建议动作:建议在解决 CI 问题后合并,并跟踪其在更大规模模型和硬件上的表现,为后续扩展到其他模型(如 Mixtral)提供数据支持。
讨论亮点

在 Code Review 中,核心讨论集中在接口设计和扩展性上:

  • 接口设计决策:reviewer robertgshaw2-redhat 建议,是否应该将融合内核的选择逻辑封装到 LinearBase 的一个方法中,而不是直接传递整个 linear 对象。这能使接口更清晰。然而,作者 mgoin 坚持当前设计,理由是“根据量化方式不同,需要从线性层中提取不同的东西。我认为在 forward 函数上下文中直接传入 self.down_proj 更直观,因为它明确表示这个函数可能会影响 self.down_proj”。这反映了在模型代码清晰度与接口抽象之间的权衡。
  • 可扩展性探讨:robertgshaw2-redhat 进一步询问,是否应该为融合内核的选择机制(即 _FUSED_ACT_QUANT 映射表)构建一个预设的“预言机”(oracle),以便后续内核实现可以被灵活替换。mgoin 回应称“这是一个合理的推测,为未来的优化做准备”。这表明当前实现已经为后续扩展(如添加新的量化方案或激活类型)预留了空间。
  • CI 问题:Issue 评论中,njhill 反馈此 PR 引入了 CI 测试的内存访问越界(IMA)问题,并提供了失败构建的链接。这指出了手动融合可能引入的稳定性风险,需要在合并后持续关注。

实现拆解

  1. 核心接口定义:在 vllm/model_executor/layers/fusion/fused_act_quant.py 新增文件,定义了 maybe_fused_act_quant 函数。该函数接收激活函数实例 act_fn、输入张量 x 和下游线性层 linear。它通过查询 linear.input_quant_key 获取量化类型,然后在预定义的映射表 _FUSED_ACT_QUANT 中查找匹配的融合内核。若找到匹配,则调用对应的融合内核(如 _silu_and_mul_fp8_static)并返回 QuantizedActivation 对象;否则,回退到执行原始的 act_fn(x)。
  2. 融合内核实现:在 fused_act_quant.py 中实现了四个具体的融合内核函数:_silu_and_mul_fp8_static(FP8 静态逐张量量化)、_silu_and_mul_fp8_dynamic_block(FP8 动态分块量化基础函数)、_silu_and_mul_fp8_dynamic_128(FP8 动态分组,组大小128)和 _silu_and_mul_nvfp4_dynamic(NVFP4 动态量化)。这些函数分别调用对应的自定义 CUDA 内核(如 torch.ops._C.silu_and_mul_quant),并将结果包装为 QuantizedActivation 对象,传递给下游线性层。
  3. 内核映射表注册:_FUSED_ACT_QUANT 字典将 (激活类型, 量化键) 对映射到具体的融合内核函数。目前注册了 (SiluAndMul, kFp8StaticTensorSym)。对于 CUDA 平台,额外注册了 kFp8Dynamic128Sym 路径;对于支持的 GPU(SM100+),则注册了 kNvfp4Dynamic 路径。这种注册机制使得扩展新的量化方案只需增加一行映射。
  4. 下游内核适配:在 vllm/model_executor/kernels/linear/scaled_mm/pytorch.py 的 PerTensorTorchFP8ScaledMMLinearKernel 类中,新增了 input_quant_key 方法。当内核配置的激活量化键为 kFp8StaticTensorSym 时,该方法返回对应的量化键,从而向 maybe_fused_act_quant 表明该线性层支持手动融合。
  5. 模型代码集成:在 vllm/model_executor/models/llama.py 的 LlamaMLP.forward 方法中,将原本的 x = self.act_fn(x) 替换为 x = maybe_fused_act_quant(self.act_fn, x, self.down_proj)。这一改动将激活与后续线性层输入量化的融合决策交给了 maybe_fused_act_quant 函数,实现了在模型代码层面的手动融合。这是手动融合在模型中的首次应用。
  6. 测试配套:新增了 tests/compile/passes/test_silu_mul_quant_manual_fusion.py 测试文件,通过 MockLinearForFusion 模拟不同量化键的线性层,全面测试了 maybe_fused_act_quant 函数在 FP8 静态、FP8 动态128、NVFP4 动态以及回退路径下的行为与正确性。同时,在 tests/kernels/test_fused_quant_activation.py 中增加了针对 maybe_fused_act_quant 接口的单元测试。此外,修改了多个现有的端到端融合测试(如 test_tp2_async_tp.py, test_tp1_quant.py),禁用了已由手动融合接管的 act_quant_fusion 编译器 Pass 路径,并添加了针对 NVFP4 内核是否支持 input_quant_key 的检查逻辑。
文件 模块 状态 重要度
vllm/model_executor/layers/fusion/fused_act_quant.py 融合层 added 9.34
vllm/model_executor/models/llama.py 模型实现 modified 5.89
vllm/model_executor/kernels/linear/scaled_mm/pytorch.py 内核层 modified 6.83
tests/compile/passes/test_silu_mul_quant_manual_fusion.py 单元测试 added 8.05
tests/kernels/test_fused_quant_activation.py 内核测试 modified 7.34

关键符号

maybe_fused_act_quant _silu_and_mul_fp8_static _silu_and_mul_fp8_dynamic_block _silu_and_mul_fp8_dynamic_128 _silu_and_mul_nvfp4_dynamic input_quant_key

关键源码片段

vllm/model_executor/layers/fusion/fused_act_quant.py core-logic

核心新增文件,实现了手动激活量化融合的接口 `maybe_fused_act_quant` 和四个具体的融合内核,是本次架构变更的入口和核心逻辑所在。

# 手动融合入口函数:根据线性层声明的 input_quant_key,选择并执行匹配的融合内核,否则回退。
def maybe_fused_act_quant(
    act_fn: torch.nn.Module,
    x: torch.Tensor,
    linear: LinearBase,
) -> "torch.Tensor | QuantizedActivation":
    """Apply act_fn, fusing the downstream linear's input quant when possible.
​
    Returns a QuantizedActivation when a fused kernel matches
    (act_fn, linear.input_quant_key), else the plain activated tensor.
    """
    key = getattr(linear, "input_quant_key", None)
    if key is not None:
        producer = _FUSED_ACT_QUANT.get((type(act_fn), key))
        if producer is not None:
            return producer(x, linear)
    return act_fn(x)
​
​
# 融合内核映射表:( 激活模块类型 , 消费者量化键 ) -> 融合生产者函数。
# 镜像自 ActivationQuantFusionPass.FUSED_OPS;添加一行即可迁移一种方案。
_FUSED_ACT_QUANT: dict[tuple[type, QuantKey], Callable] = {
    (SiluAndMul, kFp8StaticTensorSym): _silu_and_mul_fp8_static,
}
​
# 为 CUDA 平台的动态分块量化添加条目
if current_platform.is_cuda_alike():
    _FUSED_ACT_QUANT[(SiluAndMul, kFp8Dynamic128Sym)] = _silu_and_mul_fp8_dynamic_128
​
# 如果支持 NVFP4(需要 SM100+),则添加条目
if current_platform.is_cuda() and hasattr(torch.ops._C, "silu_and_mul_nvfp4_quant"):
    _FUSED_ACT_QUANT[(SiluAndMul, kNvfp4Dynamic)] = _silu_and_mul_nvfp4_dynamic
vllm/model_executor/kernels/linear/scaled_mm/pytorch.py data-contract

下游内核适配,在 `PerTensorTorchFP8ScaledMMLinearKernel` 中新增 `input_quant_key` 方法,向手动融合机制暴露其支持的量化键,是融合路径能够触发的关键契约。

class PerTensorTorchFP8ScaledMMLinearKernel(TorchFP8ScaledMMLinearKernel):
    @classmethod
    def can_implement(cls, c: FP8ScaledMMLinearLayerConfig) -> tuple[bool, str | None]:
        # ... ( 省略原有逻辑 )
​
    # 新增方法:声明此内核支持的激活量化键,供手动融合机制查询。
    def input_quant_key(self) -> QuantKey | None:
        if self.config.activation_quant_key == kFp8StaticTensorSym:
            return kFp8StaticTensorSym
        return None
​
    def apply_scaled_mm(self, *, A, B, out_dtype, As, Bs, bias, output_shape) -> torch.Tensor:
        # ... ( 省略原有逻辑 )

评论区精华

手动融合接口设计:传递整个线性层对象 vs. 提取所需属性 设计

reviewer `robertgshaw2-redhat` 建议,在 `maybe_fused_act_quant` 接口中,与其传递整个 `linear` 对象,不如从 `LinearBase` 暴露一个专门的方法来提取融合所需的信息,以使接口更清晰。作者 `mgoin` 反对这一建议,认为根据不同的量化方式需要从线性层中提取不同的东西,直接传入 `self.down_proj` 更直观,因为它明确表示了该函数可能对 `down_proj` 产生影响。

结论:维持现有设计,直接传递 `linear` 对象。理由是模型代码的清晰度和对下游影响的明确性优先于接口的抽象性。 · 已解决

融合内核映射表的可扩展性与未来优化 设计

`robertgshaw2-redhat` 询问,是否应该为 `_FUSED_ACT_QUANT` 映射表的内核选择机制构建一个预设的“预言机”(oracle),以便后续内核实现可以被灵活替换。`mgoin` 回应称“这是一个合理的推测,为未来的优化做准备”。

结论:当前实现已具备良好的可扩展性(通过向字典添加一行映射即可)。讨论确认了这一设计方向,并为未来可能的“预言机”模式留下了空间。 · 已解决

风险与影响

  1. 功能正确性风险:maybe_fused_act_quant 的正确性依赖于下游线性层正确暴露 input_quant_key 属性。如果内核实现错误地返回了量化键,可能导致融合路径错误或数据类型不匹配。测试覆盖了主要路径,但对边缘情况(如不同 hidden_size、num_tokens 组合)的覆盖仍需通过更广泛的集成测试来验证。
  2. 兼容性风险:本 PR 修改了 Llama 模型的前向计算路径。虽然回退机制确保了非量化场景下的兼容性,但任何修改都可能影响模型在量化配置下的性能或正确性。尤其是手动融合与编译器 Pass 的互斥逻辑(fuse_act_quant=False)需要确保在所有配置下都能正确生效。
  3. 性能风险:手动融合在运行时增加了查找映射表的开销,但预期被内核启动优化的收益所抵消。然而,对于小批量推理,增加的 Python 层调用可能带来微小的延迟。性能影响需要通过 A/B 测试来量化,这也是 PR 中提到的后续工作。
  4. 可维护性风险:引入了新的融合路径和映射机制,增加了代码复杂度。未来需要为新的激活类型(如 ReLU)和量化方案(如 INT8)添加新的融合内核和映射条目,这要求开发者理解手动融合与编译器融合的交互关系。
  5. CI/稳定性风险:如 Issue 评论所示,此变更已引发 CI 测试失败(IMA),表明可能存在内存安全问题或与特定硬件/驱动版本的兼容性问题。这需要仔细排查具体原因,并在合并前解决。
  1. 对用户的影响:使用 Llama 模型并启用 FP8 或 NVFP4 量化的用户,在推理时将自动采用手动融合路径,可能获得更低的延迟和更高的吞吐量。对于使用其他模型或未启用量化的用户,行为无变化。
  2. 对系统的影响:引入了 maybe_fused_act_quant 作为新的激活量化融合入口点,改变了 vLLM 的优化路径选择逻辑。系统需要维护两条互补的融合路径(手动融合和编译器 Pass),增加了架构的复杂性。
  3. 对团队的影响:为量化内核和模型代码的开发者提供了新的手动融合范式。团队需要更新开发流程,在为新的量化方案添加支持时,同时考虑手动融合内核的实现和映射表的更新。同时,需要更谨慎地测试融合路径,确保正确性和性能。
模型核心路径变更 新机制引入 需要广泛集成测试 可能引发 CI 问题

关联 Issue

#43501 [Feature]: Porting `ActivationQuantFusionPass` to manual fusion

完整报告

参与讨论