Prhub

#46020 [Attention Backend] add HPC-Ops Attention backend

原始 PR 作者 thisjiang 合并时间 2026-06-30 18:17 文件变更 10 提交数 16 评论 23 代码增减 +978 / -14

执行摘要

新增 Tencent HPC-Ops 注意力后端,支持 FP8 融合算子

PR 提出将生产级高性能算子库 HPC-Ops 集成到 vLLM 的注意力后端中,旨在为 Hy3-FP8 等模型提供更高效的推理支持。PR 正文提到 'HPC-Ops is a production-grade, high-performance, and easy-to-use operator library for LLM inference',并说明当前后端仅支持 Hy3-FP8 模型,但提供了适配示例。

值得精读。该 PR 展示了如何将外部高性能算子库集成到 vLLM 的注意力后端框架中,包括自定义 op 注册、元数据构建、torch.compile 兼容性处理等模式。建议关注:HpcRopeNorm 的 CustomOp 设计、HpcAttnMetadata 与通用 metadata 的协作、以及 model_loader/utils.py 中的通用扩展点。

讨论亮点

Review 中聚焦于几个核心设计问题:

  • ivanium 指出在 hpc_attn.py 中 block_table 分割应使用 num_decode_reqs 而非硬编码索引,作者已采纳。
  • ivanium 建议在 attention.py 中采用更通用的 out_dtype 参数替代 HPC 专用条件分支,但最终采用了 output_dtype=self.dtype 的方案。zyongye 也支持此观点。
  • zyongye 指出需要在 HpcAttentionImpl 中设置 forward_includes_kv_cache_update=True 以避免重复 KV 缓存写入,作者已修复。
  • zyongye 质疑 HpcRopeNorm 被 torch.compile 封装的程度,作者说明这是为了兼容自定义硬件路径。
  • 在 meta.py 中 cos_sin_cache 的全局添加被作者承认冗余并删除。

实现拆解

  1. 新增 HPC 模块基础类(vllm/model_executor/layers/hpc/):创建 HpcModule 基类、HpcRopeNorm 自定义算子模块,以及 QkNormPolicy 枚举,实现融合 RoPE+QK-Norm+KV-Cache 写入+FP8 量化的核心逻辑。
  2. 注册新注意力后端(vllm/v1/attention/backends/hpc_attn.py):实现 HpcAttentionBackend、HpcAttnMetadata 及 HpcAttnMetadataBuilder,遵循 vLLM v1 注意力后端接口,处理 FP8 KV 缓存,支持 paged attention。
  3. 适配模型层(vllm/model_executor/models/hy_v3.py):修改 HYV3Attention 类,当检测到 HPC 后端启用时,使用 HpcRopeNorm 替代原有的 QK-Norm+RoPE 路径,并调整输出 dtype。
  4. 集成到框架基础结构:在注意力后端注册表(registry.py)添加 HPC_ATTN 枚举;在模型加载工具(model_loader/utils.py)中添加通用 HpcModule 的 process_weights_after_loading 调用;在注意力层(attention.py)中添加输出 dtype 特殊处理。
  5. 文档与配置:更新 docs/design/attention_backends.md 提及新后端;在 vllm/config/compilation.py 中调整支持。
文件 模块 状态 重要度
vllm/model_executor/layers/hpc/rope_norm.py 自定义算子 added 9.36
vllm/v1/attention/backends/hpc_attn.py 注意力后端 added 9.08
vllm/model_executor/layers/hpc/hpc_module.py 基础模块 added 8.04
vllm/model_executor/models/hy_v3.py 模型层 modified 7.19
vllm/model_executor/layers/hpc/__init__.py 包初始化 added 6.39
vllm/model_executor/model_loader/utils.py 模型加载 modified 6.12
vllm/model_executor/layers/attention/attention.py 注意力层 modified 5.44
vllm/v1/attention/backends/registry.py 注册表 modified 5.19
vllm/config/compilation.py 编译配置 modified 4.35
docs/design/attention_backends.md 文档 modified 1.18

关键符号

hpc_rope_norm_forward hpc_rope_norm_forward_fake HpcRopeNorm.forward HpcAttnMetadataBuilder.build HpcAttentionImpl.forward HpcAttentionBackend.get_supported_kernel_block_sizes HpcModule.process_weights_after_loading HYV3Attention.__init__ HYV3Attention.forward

关键源码片段

vllm/model_executor/layers/hpc/rope_norm.py core-logic

核心的自定义算子模块,实现融合 RoPE+QK-Norm+KV-Cache 写入 +FP8 量化逻辑,定义了 QkNormPolicy 枚举和 HpcRopeNorm 类。

# SPDX-License-Identifier: Apache-2.0
# vllm/model_executor/layers/hpc/rope_norm.py
"""HPC fused RoPE + QK-Norm + KV-Cache-Write + FP8 Q quant."""
from __future__ import annotations
from enum import IntEnum
from typing import Any
import torch
from vllm.model_executor.custom_op import CustomOp
from vllm.model_executor.layers.hpc.hpc_module import HpcModule# 全局字典存储所有 HpcRopeNorm 实例,用于在 forward 中按 layer_name 查找
_hpc_rope_norm_instances: dict[str, HpcRopeNorm] = {}class QkNormPolicy(IntEnum):
    """定义 QK Norm 与 RoPE 的执行顺序,枚举值直接传递给 HPC kernel ABI。"""
    NONE = 0 # 不使用 QK-Norm
    ROPE_THEN_NORM = 1 # 先 RoPE 后 Norm
    NORM_THEN_ROPE = 2 # 先 Norm 后 RoPE(如 HunYuan V3)def hpc_rope_norm_forward(qkv: torch.Tensor, output: torch.Tensor, layer_name: str):
    """自定义 op 的前向函数,完全 opaque 给 torch.compile。"""
    forward_context = get_forward_context()
    attn_metadata = forward_context.attn_metadata
    if isinstance(attn_metadata, dict):
        attn_metadata = attn_metadata[layer_name]
    if attn_metadata is None:
        output.zero_()
        return
    attn_layer = forward_context.no_compile_layers[layer_name]
    kv_cache = attn_layer.kv_cache # 5D 张量
    if kv_cache.numel() == 0:
        output.zero_()
        return
    rope_norm = _hpc_rope_norm_instances[layer_name]
    rope_norm._forward_impl(qkv, kv_cache, attn_metadata, attn_layer, output)def hpc_rope_norm_forward_fake(qkv, output, layer_name):
    """用于 torch.compile 追踪的假实现,仅标记 output 为 mutated。"""
    returndirect_register_custom_op(
    op_name="hpc_rope_norm_forward",
    op_func=hpc_rope_norm_forward,
    mutates_args=["output"],
    fake_impl=hpc_rope_norm_forward_fake,
)@CustomOp.register("hpc_rope_norm")
class HpcRopeNorm(CustomOp, HpcModule):
    """融合算子的 nn.Module 封装,支持 torch.compile eager/native 模式。"""
    def __init__(self, num_heads, num_kv_heads, head_dim, cos_sin_cache,
                 use_qk_norm, fallback_qnorm, fallback_knorm,
                 kv_cache_dtype, layer_name, qk_norm_policy=QkNormPolicy.ROPE_THEN_NORM):
        super().__init__()
        self.num_heads = num_heads
        self.num_kv_heads = num_kv_heads
        self.head_dim = head_dim
        self.use_qk_norm = use_qk_norm
        # ... 注册 norm weights 作为 nn.Parameter,cos_sin_cache 作为非持久 buffer
        self.register_buffer("cos_sin_cache", cos_sin_cache.float(), persistent=False)
        self.layer_name = layer_name
        _hpc_rope_norm_instances[layer_name] = self
​
    def process_weights_after_loading(self, model):
        # 从 fallback norm 模块中复制权重
        if self.fallback_qnorm is not None:
            self.q_weight.data.copy_(self.fallback_qnorm.weight.data)
        if self.fallback_knorm is not None:
            self.k_weight.data.copy_(self.fallback_knorm.weight.data)
​
    def forward(self, qkv, layer_name):
        # CustomOp 框架自动分发到 forward_cuda 或 forward_native
        return super().forward(qkv, layer_name)
vllm/v1/attention/backends/hpc_attn.py core-logic

HPC 注意力后端的入口,实现 HpcAttentionBackend、HpcAttnMetadata、HpcAttnMetadataBuilder,遵循 vLLM v1 后端接口。

# SPDX-License-Identifier: Apache-2.0
# vllm/v1/attention/backends/hpc_attn.py
"""HPC Attention Backend. Pure attention (prefill + decode), without RoPE or RMSNorm."""
from dataclasses import dataclass
from typing import ClassVar
import torch
from vllm.v1.attention.backend import (
    AttentionBackend, AttentionCGSupport, AttentionImpl,
    AttentionMetadata, AttentionMetadataBuilder, AttentionType,
    CommonAttentionMetadata, MultipleOf,
)
from vllm.v1.attention.backends.utils import (
    split_decodes_and_prefills, KVCacheLayoutType,
)
from vllm.v1.kv_cache_interface import AttentionSpec@dataclass
class HpcAttnMetadata(AttentionMetadata):
    """HPC attention kernel 所需元数据。"""
    num_actual_tokens: int
    num_decodes: int
    num_decode_tokens: int
    num_prefills: int
    num_prefill_tokens: int
    max_query_len: int
    slot_mapping: torch.Tensor
    seq_lens: torch.Tensor
    block_table_tensor: torch.Tensor
    qo_indptr: torch.Tensor | None = None
    # HPC RopeNorm 传递的额外字段
    hpc_kv_written: bool = False
    hpc_prefill_q_scale: torch.Tensor | None = None
    hpc_decode_q_scale: torch.Tensor | None = None
    hpc_split_k_flag: torch.Tensor | None = Noneclass HpcAttnMetadataBuilder(AttentionMetadataBuilder[HpcAttnMetadata]):
    _cudagraph_support = AttentionCGSupport.UNIFORM_SINGLE_TOKEN_DECODE
    reorder_batch_threshold: int = 1
​
    def __init__(self, kv_cache_spec, layer_names, vllm_config, device):
        super().__init__(kv_cache_spec, layer_names, vllm_config, device)
        self.num_qo_heads = self.model_config.get_num_attention_heads(...)
        self.num_kv_heads = kv_cache_spec.num_kv_heads
        self.head_dim = kv_cache_spec.head_size
        self.page_size = kv_cache_spec.block_size
        self.cache_dtype = self.cache_config.cache_dtype
        self.global_hyperparameters = infer_global_hyperparameters(
            get_per_layer_parameters(vllm_config, layer_names, HpcAttentionImpl)
        )
​
    def build(self, common_prefix_len, common_attn_metadata, fast_build=False) -> HpcAttnMetadata:
        # 从 CommonAttentionMetadata 填充 HpcAttnMetadata
        # 使用 split_decodes_and_prefills 分离 decode 和 prefill
        ...class HpcAttentionImpl(AttentionImpl[HpcAttnMetadata]):
    """HPC 注意力实现,分派到 HPC kernel。"""
    forward_includes_kv_cache_update: ClassVar[bool] = True # 避免 Attention 基类重复写入 KV cache
​
    def forward(self, query, key, value, attn_metadata, attn_spec):
        if attn_metadata is None or query.numel() == 0:
            return query
        # 从 metadata 获取 GPU 张量
        # 调用 HPC 封装的 prefill 和 decode kernel
        ...class HpcAttentionBackend(AttentionBackend[HpcAttnMetadata, HpcAttnMetadataBuilder]):
    @staticmethod
    def get_supported_kernel_block_sizes() -> tuple:
        return (64,)
​
    @staticmethod
    def get_metadata_cls() -> type:
        return HpcAttnMetadata
​
    @staticmethod
    def get_metadata_builder_cls() -> type:
        return HpcAttnMetadataBuilder
​
    @staticmethod
    def get_impl_cls() -> type:
        return HpcAttentionImpl

评论区精华

block_table 分割应使用 num_decode_reqs 而非硬编码索引 正确性

ivanium 指出在 hpc_attn.py 中 block_table_prefill 和 block_table_decode 的分割应使用 attn_metadata.num_decode_reqs 变量,避免硬编码。

结论:作者接受建议并修改 · 已解决

output_dtype 的 HPC 专用分支应使用更通用的方式 设计

ivanium 提议使用 output_dtype=self.dtype 替代 HPC 专用的条件分支,zyongye 表示支持,认为根本问题是 query 被量化传入。

结论:最终采用 output_dtype=self.dtype 方案 · 已解决

需在 HpcAttentionImpl 中设置 forward_includes_kv_cache_update=True 正确性

zyongye 指出 HpcRopeNorm 已经写入 KV cache,AttentionImpl 不应再次写入,需要设置 forward_includes_kv_cache_update=True。

结论:作者添加该属性 · 已解决

meta.py 中 cos_sin_cache 全局添加是否必要 other

ivanium 指出对 meta.py 的全局修改可能不必要,作者测试后确认无影响并移除。

结论:作者移除该修改 · 已解决

风险与影响

  1. 强依赖:后端依赖 hpc-ops 库,未安装时无法使用,但 PR 通过 importlib.util 条件导入避免崩溃。
  2. 配置限制:仅支持 --kv-cache-dtype fp8_e4m3 和 --block-size 64,错误配置可能导致运行时错误。
  3. 模型绑定:当前仅验证 Hy3-FP8 模型,其他模型需用户自行适配,且 PR 明确指出 HPC attention backend currently supports only the Hy3-FP8 model。
  4. 全局影响:对 model_loader/utils.py 的修改影响所有模型加载流程,可能对 DummyModelLoader 和 sleep/wake_up 路径产生副作用。
  5. 兼容性:attention.py 中 output_dtype 的 HPC 专用分支可能与其他 backends 产生冲突。

对用户:提供新的 attention backend 选项,仅对使用 Hy3-FP8 模型的用户有效,需明确设置 --attention_backend HPC_ATTN --kv-cache-dtype fp8_e4m3 --block-size 64
对系统:新增约 900 行核心代码,引入可选外部依赖 hpc-ops。
对团队:需要持续维护与 hpc-ops 库的接口兼容性,并跟进 vLLM 注意力后端接口演进。
影响程度:中等,但范围限定在特定模型/硬件组合。

依赖外部库 hpc-ops 仅支持 Hy3-FP8 模型 FP8 配置强制 全局加载路径修改 缺少测试覆盖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论