Prhub

#52706 [Model] Add GraniteSWA and GraniteMoeSWA via existing Granite

原始 PR 作者 daviswer 合并时间 2026-08-19 11:32 文件变更 7 提交数 7 评论 24 代码增减 +186 / -207

执行摘要

新增 GraniteSWA/MoeSWA 模型支持

HF 在 5.15.1 发布 GraniteSWA 与 GraniteMoeSWA 后,vLLM 需要注册支持。原 PR #48270 用独立模型类实现,本 PR 改为复用现有实现,避免重复维护。PR body 特别强调 transformers 后端会静默丢弃 sink token、输出错误,只有 vLLM 原生实现才能保证正确性:「transformers backend does NOT handle these models correctly. Sink tokens get dropped silently and so the model gives wrong output」。

值得精读,尤其关注 granite_layer_attn_params 的分层配置解析设计,以及 hmellor 推动的 MoE 权重加载重构;讨论中提到的 per_layer_config API 可作为后续配置演进的参考。

讨论亮点

核心讨论集中在两个点:一是 MoE 权重加载方式——hmellor 指出 RoutedExperts 可以直接加载融合权重,无需在加载前手工拆分,并提交清理代码;二是每层 RoPE 参数的配置表示——hmellor 建议嵌套 rope_parameters,daviswer 以每层 RoPE 与 attention 类型相互独立为由选择独立列表,hmellor 最终认可并提示未来可参考 Nvidia 的 per_layer_config API。

实现拆解

  1. 统一分层参数解析:在 vllm/model_executor/models/granite.py 新增 granite_layer_attn_params(config, layer_idx),返回 (sliding_window, rope_theta, has_sink)。普通 Granite 配置不含 layer_types / layer_rope_theta 时回退到 full attention、全局 RoPE、无 sink,保证兼容。
  2. 注意力层按层构建GraniteAttention(granite.py)与 GraniteMoeAttention(granitemoe.py)的 __init__ 均改为接收完整 config,通过 extract_layer_index(prefix) 定位当前层。rope_theta == 0 时不建 rotary_emb(NoPE);has_sink 时创建 nn.Parameter 并用 sharded_weight_loader 按 TP 切分,最后传给 Attentionper_layer_sliding_windowsinks
  3. MoE 权重加载重构granitemoe.py 删除 fused_moe_make_expert_params_mapping 手写映射,改用 hf_to_vllm_mapper + AutoWeightsLoadergranitemoeshared.py 删除自定义 load_weights 中逐专家拆分权重的逻辑,直接复用 GraniteMoeModel.load_weights,该清理由 hmellor 提交并验证 PowerMoE-3bgranite-swash-3b-a600m 输出一致。
  4. 注册与配套vllm/model_executor/models/registry.pyGraniteSWAForCausalLM 指到 granite.GraniteForCausalLMGraniteMoeSWAForCausalLM 指到 granitemoeshared.GraniteMoeSharedForCausalLMtests/models/registry.py 新增 _HfExamplesInfo 并限定 transformers ≥ 5.15.1;tests/models/language/generation/test_granite.py 新增两个 SWA 检查点的 logprobs 一致性测试与两个 helper 单元测试;docs/models/supported_models.md 同步登记。
文件 模块 状态 重要度
vllm/model_executor/models/granite.py 模型实现 modified 7.77
vllm/model_executor/models/granitemoe.py 模型实现 modified 8.12
vllm/model_executor/models/granitemoeshared.py 模型实现 modified 7.34
vllm/model_executor/models/registry.py 模型注册 modified 5.1
tests/models/language/generation/test_granite.py 模型测试 modified 6.43
tests/models/registry.py 测试注册 modified 4.32
docs/models/supported_models.md 支持文档 modified 1.93

关键符号

granite_layer_attn_params GraniteAttention.__init__ GraniteMoeAttention.__init__ GraniteMoeModel._load_weights GraniteMoeSharedForCausalLM.load_weights test_granite_swa_features_are_off_without_swa_config test_granite_swa_features_resolve_per_layer

关键源码片段

vllm/model_executor/models/granite.py core-logic

新增 `granite_layer_attn_params` 统一解析每层 sliding window / RoPE / sink,并改造 `GraniteAttention`,是本 PR 的核心设计。

# vllm/model_executor/models/granite.py
def granite_layer_attn_params(
    config: PretrainedConfig, layer_idx: int
) -> tuple[int | None, float, bool]:
    """解析某一层的 sliding window、RoPE base 与 sink 使用情况。    普通 Granite 配置没有 SWA 字段,回退为 full attention、全局 RoPE base、
    无 sink。HF SWA 检查点使用 sinks 但没有专门标志,因此在存在 `layer_types`
    时假定启用,并允许 `attention_sinks` 字段覆盖该判断。    返回:
        滑动窗口大小(`None` 表示 full attention)、RoPE base theta(`0` 表示
        NoPE)、该层是否使用 attention sink。
    """
    layer_types = getattr(config, "layer_types", None)
    sliding_window = (
        config.sliding_window
        if layer_types is not None and layer_types[layer_idx] == "sliding_attention"
        else None
    )
​
    layer_rope_theta = getattr(config, "layer_rope_theta", None)
    rope_theta = (
        layer_rope_theta[layer_idx]
        if layer_rope_theta is not None
        else config.rope_parameters["rope_theta"]
    )
​
    # HF 的 SWA 检查点没有显式 sink 标志:只要配置里出现了 `layer_types`
    # 就假定需要 sink,`attention_sinks` 字段可以显式覆盖这个默认值。
    has_sink = getattr(config, "attention_sinks", layer_types is not None)
    return sliding_window, rope_theta, has_sink
vllm/model_executor/models/granitemoe.py refactor

`GraniteMoeAttention` 复用共享 helper,删除手工权重拆分(净删 150 行),是改动量最大的文件。

# vllm/model_executor/models/granitemoe.py(GraniteMoeAttention.__init__ 关键部分)
from .granite import granite_layer_attn_params
from .utils import extract_layer_index# ... 在完成 qkv_proj / o_proj 构建后 ...
sliding_window, rope_theta, has_sink = granite_layer_attn_params(
    config, extract_layer_index(prefix)
)# rope_theta 为 0 表示该层 NoPE,直接跳过 rotary_emb 构建;
# 否则用当前层的 theta 覆盖全局 rope_parameters。
self.use_rope = rope_theta != 0
if self.use_rope:
    self.rotary_emb = get_rope(
        self.head_dim,
        max_position=max_position,
        rope_parameters={**config.rope_parameters, "rope_theta": rope_theta},
        is_neox_style=True,
    )# 每个注意力头一个可学习 sink 参数,按 TP 维度切分加载。
if has_sink:
    self.sinks = nn.Parameter(torch.empty(self.num_heads), requires_grad=False)
    set_weight_attrs(self.sinks, {"weight_loader": sharded_weight_loader(0)})
else:
    self.sinks = None# 原生 vLLM Attention 同时接收 per_layer_sliding_window 与 sinks。
self.attn = Attention(
    self.num_heads,
    self.head_dim,
    self.scaling,
    num_kv_heads=self.num_kv_heads,
    cache_config=cache_config,
    quant_config=quant_config,
    per_layer_sliding_window=sliding_window,
    prefix=f"{prefix}.attn",
    sinks=self.sinks,
)

评论区精华

MoE 权重加载:拆分 vs 直接加载 设计

tdoublep 转达 hmellor 在 #48270 的旧评论,询问 `granitemoe.py` 中先拆分 MoE 权重再加载的逻辑是否仍然有问题;hmellor 确认 `RoutedExperts` 可以直接加载融合权重,随后提交清理。

结论:删除手工拆分逻辑,统一走 `hf_to_vllm_mapper` + `AutoWeightsLoader`,并用 `PowerMoE-3b` 与 `granite-swash-3b-a600m` 验证输出一致。 · 已解决

per-layer RoPE 参数表示:独立列表 vs 嵌套 rope_parameters 设计

hmellor 建议使用 Gemma 3 式嵌套 `rope_parameters[layer_type]`;daviswer 回应每层 RoPE 与 attention 类型是两个独立维度,故采用 Llama4 风格独立列表;hmellor 最终认可并提示未来可参考 Nvidia 的 `per_layer_config` API。

结论:维持独立列表方案,并记录 `per_layer_config` API 作为后续演进方向。 · 已解决

风险与影响

  • 依赖 transformers ≥ 5.15.1 才能正确解析 SWA 配置,旧版本下测试会跳过,但用户直接加载模型时依赖 HF config 解析,低版本可能读不出 layer_types / layer_rope_theta
  • MoE 权重加载从手写拆分改为 hf_to_vllm_mapper 映射,回归面较大;hmellor 已验证两个检查点,但旧版 HF MoE 层命名仍需覆盖。
  • 新功能(sink / per-layer sliding window)依赖 Attention 层参数支持,需关注各后端覆盖范围。
  • transformers 后端本身对这类模型输出错误(sink 静默丢弃),用户若用 transformers 后端对比可能得到错误结果,属上游问题。
  • 新功能默认关闭,现有 Granite 用户无行为变更,整体风险可控。

影响范围集中在 Granite 系列模型代码:granite.pygranitemoe.pygranitemoeshared.py 被重构,净删约 200 行;模型注册表与测试注册表同步扩展;新增两个受支持模型,对现有 Granite / GraniteMoeShared 用户无行为变化。团队后续新增同类模型变体时可参考此「复用现有实现 + 共享解析器」模式。

依赖 transformers 5.15.1 transformers 后端 sink 静默丢弃 权重加载重构回归风险 新功能默认关闭

关联 Issue

#48270 [Model] Add GraniteSWA and GraniteMoeSWA

完整报告

参与讨论