Prhub

#28400 [Model] Laguna: support per-element output gating

原始 PR 作者 joerowell 合并时间 2026-06-18 08:09 文件变更 2 提交数 10 评论 11 代码增减 +44 / -17

执行摘要

Laguna 模型新增 per-element 输出门控

补齐SGLang与vLLM实现之间的差距,提供per-element门控这一缺失功能。PR body中提到:"There's some gaps between the vLLM implementation & the SGLang implementation... we've decided to augment the SGLang implementation with one of the major missing features: per-element gating."

本PR值得精读,尤其是如何通过配置规范化(将布尔值归一化为字符串)简化下游逻辑,以及通过参数验证提前捕获配置错误的设计模式。对于模型驱动的开发团队有参考价值。建议关注 review 中关于类型兼容性和测试取舍的讨论。

讨论亮点

gating类型兼容性(design):kpham-sgl 提问为何使用 bool | str 混合类型,joerowell 解释这是为了兼容遗留HF配置中 gating: True 的写法,新配置使用字符串。Jiminator 最终决定在配置加载时将 True 归一化为 "per-head",使得下游代码只处理字符串和假值。
门控关闭时不应构建g_proj(correctness):Jiminator 指出如果 gatingFalseNone,原始代码仍会构建 g_proj(但 gate_per_head=False 会导致走 per-element 分支),应该完全跳过门控层。作者采纳建议,改为 if self.gating: 条件构建,forward 中也对应检查。
测试文件移至nightly套件(testing):kpham-sgl 建议将单元测试移至 nightly,因为需要 GPU(rope kernel)无法在 CI 中运行。经讨论,最终删除测试文件,认为改动很小且已在 review 中覆盖正确性。

实现拆解

  1. python/sglang/srt/configs/laguna.pyLagunaConfig中添加gating: bool | str = True参数,并将gating=True归一化为"per-head",使得下游只处理字符串值,保持类型一致性。
  2. python/sglang/srt/models/laguna.pyLagunaAttention.__init__开头验证gating值的合法性(只允许True, False, None, "per-head", "per-element"),并设置self.gatingself.gate_per_head标志。
  3. 根据self.gating决定是否构建g_proj层:若启用门控,根据gate_per_head选择输出维度为total_num_headstotal_num_heads * head_dim;若禁用,将self.g_proj设为None,避免死层。
  4. forward中,仅在self.gating and self.g_proj is not None时计算门控,并根据self.gate_per_head决定是否对attn_output进行reshape(per-head需要reshape到(..., num_heads, head_dim)然后逐头相乘,per-element直接与gate逐元素相乘)。
  5. LagunaModel.load_weights中添加错误提示:若检查点包含.g_proj.权重但模型未构建g_proj,则抛出RuntimeError,帮助用户诊断配置不一致。
  6. 测试方面,曾添加一个单元测试文件test_laguna_gating.py用于验证g_proj维度分配,但因其依赖GPU(rope kernel构建),无法在CI中正常运行,最终被删除;改动本身的正确性依赖模型配置和人力review。
文件 模块 状态 重要度
python/sglang/srt/models/laguna.py 模型层 modified 7.16
python/sglang/srt/configs/laguna.py 模型配置 modified 5.06

关键符号

LagunaConfig.__init__ LagunaAttention.__init__ LagunaAttention.forward LagunaDecoderLayer.__init__

关键源码片段

python/sglang/srt/models/laguna.py core-logic

核心逻辑修改:重构门控层的构建和前向传播,支持 per-head 和 per-element 两种模式,并添加参数验证。

"""
LagunaAttention 构造函数中的门控验证与 g_proj 条件构建。
"""
def __init__(
    self,
    hidden_size: int,
    num_heads: int,
    num_kv_heads: int,
    head_dim: int,
    layer_id: int,
    rms_norm_eps: float,
    rope_theta: float,
    rope_scaling: Optional[Dict[str, Any]],
    partial_rotary_factor: float,
    max_position_embeddings: int,
    attention_bias: bool,
    sliding_window_size: int,
    layer_type: str,
    gating: bool | str = True, # 新增参数,支持布尔或字符串
    quant_config: Optional[QuantizationConfig] = None,
    prefix: str = "",
) -> None:
    super().__init__()
    # ... 其他初始化 ...
    # 验证 gating 值是否在允许集合中
    if gating not in (True, False, None, "per-head", "per-element"):
        raise ValueError(
            f"Unsupported gating value {gating!r}; expected one of "
            'True, False, None, "per-head", or "per-element".'
        )
    self.gating = bool(gating) # 布尔化用于快速判断是否启用门控
    self.gate_per_head = gating is True or gating == "per-head" # True = per-head 模式
​
    # ... attn_tp_rank, attn_tp_size ...
​
    # 根据门控模式决定 g_proj 输出维度
    if self.gating:
        g_proj_dim = (
            self.total_num_heads
            if self.gate_per_head
            else self.total_num_heads * self.head_dim # per-element 需要每个元素一个标量
        )
        self.g_proj = ColumnParallelLinear(
            hidden_size,
            g_proj_dim,
            bias=False,
            gather_output=False,
            quant_config=None,
            tp_rank=attn_tp_rank,
            tp_size=attn_tp_size,
            prefix=add_prefix("g_proj", prefix),
        )
    else:
        self.g_proj = None # 门控禁用时不构建死层
​
    # ... q_norm, k_norm, rotary_emb, attn ...
​
​
"""
forward 中的门控应用,根据模式选择不同乘法形状。
"""
def forward(self, positions, hidden_states, forward_batch):
    # ... qkv 计算 ...
    attn_output = self.attn(q, k, v, forward_batch)
​
    if self.gating and self.g_proj is not None:
        gate, _ = self.g_proj(hidden_states)
        gate = F.softplus(gate.float()).to(attn_output.dtype)
        if self.gate_per_head:
            # per-head: reshape 到 [batch*seq, num_heads, head_dim] 逐头相乘
            attn_output = attn_output.view(-1, self.num_heads, self.head_dim)
            attn_output = attn_output * gate.view(-1, self.num_heads, 1)
            attn_output = attn_output.reshape(-1, self.num_heads * self.head_dim)
        else:
            # per-element: 直接逐元素相乘,gate 形状为 [batch*seq, num_heads*head_dim]
            attn_output = attn_output * gate
​
    output, _ = self.o_proj(attn_output)
    return output
python/sglang/srt/configs/laguna.py configuration

配置入口:添加 gating 参数并归一化 True 到 per-head,为下游提供干净的接口。

def __init__(
    self,
    # ... 其他参数 ...
    attention_dropout: float = 0.0,
    gating: bool | str = True, # 新增:支持布尔或字符串,True 是遗留写法
    sliding_window: int = 512,
    # ... 更多参数 ...
) -> None:
    # ... 父类初始化 ...
    self.attention_dropout = attention_dropout
    # 归一化:将遗留的 True 转为其等价字符串 "per-head",保证下游类型一致
    self.gating = "per-head" if gating is True else gating
    self.sliding_window = sliding_window
    # ... 其他字段 ...

评论区精华

gating 类型兼容性讨论 设计

kpham-sgl 提问为何使用 bool|str 混合类型,joerowell 解释是为了兼容遗留 HF 配置中 gating: True 的写法,新配置使用字符串。Jiminator 提出归一化方案。

结论:Jiminator 决定在配置加载时将 True 归一化为 "per-head",使得下游代码只处理字符串和假值,保持类型一致。 · 已解决

门控关闭时不应构建 g_proj 正确性

Jiminator 指出当 gating 为 False/None 时,原代码 gate_per_head=False 会导致走 per-element 分支,构建了本应避免的 g_proj。建议用 if self.gating: 保护。

结论:作者采纳,改为条件构建 g_proj 和条件前向应用,完全禁用门控时不分配任何相关的层。 · 已解决

测试文件移除决策 测试

kpham-sgl 建议将单元测试移至 nightly 套件,因需要 GPU (rope kernel) 无法在 CI 中运行。经讨论,最终决定直接删除测试文件。

结论:删除测试文件 (commit abea34b),认为模型配置和门控改动简单,人力 review 即可保证正确性。 · 已解决

风险与影响

  1. 新增验证路径LagunaAttention.__init__ 会验证 gating 值不在允许集合时抛出 ValueError,如果 HF 未来引入其他值,可能导致加载失败。但这是预期的保护行为。
  2. g_proj 为 None 的前向保护forward 中已检查 self.gating and self.g_proj is not None,不会出现 None 解引用。
  3. 默认行为兼容性:默认 gating=True 被归一化为 "per-head",与之前行为一致,不会 Regression。
  4. 测试缺失:删除了单元测试,虽然改动逻辑简单,但可能遗漏未来重构导致的回归。不过模型配置和门控逻辑的变更可以通过模型加载和推理测试覆盖。
  5. load_weights 错误提示:当配置不匹配时抛出明确错误,减少用户排错时间。

仅影响 Laguna 模型的使用者。对于不启用门控或使用默认门控的用户,行为完全不变。对于需要 per-element 门控的用户(使用 "per-element" 配置),现在可以获得支持。改动影响范围小,团队可快速合并。

删除测试文件 新增参数验证路径

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论