Prhub

#50156 [Cohere] Misc changes to cohere model definitions

原始 PR 作者 kkt-cohere 合并时间 2026-08-18 12:59 文件变更 2 提交数 11 评论 6 代码增减 +87 / -26

执行摘要

Cohere 模型定义修复:RMSNorm、head_dim 与 SWA+1 语义对齐

PR body 明确表示这是 #42078 的后续,目的是对 Cohere 模型定义做若干杂项修正。提交记录显示最初动机是支持 Cohere Eagle 草稿模型将层拆到多个 KV cache group(基础 EagleProposer 假设单个 group 共享 block table / slot mapping),同时修复 CohereAttention.head_dim 未尊重 config.head_dim 的问题。最终在 MRv1 不受支持、MRv2 原生支持的背景下,只保留模型定义层面的修复。

值得精读,尤其是 SWA 窗口 +1 的语义对齐方式和 TP all-reduce 合并两个设计决策。它展示了如何在 review 压力下收敛 PR 范围、避免设计过度。建议阅读后关注后续 Cohere Eagle 推测解码相关 PR,并结合 single_type_kv_cache_manager.py 核对 +1 的传播路径;若需合入生产,应补充 RMSNorm 与 SWA+1 的回归测试。

讨论亮点

mgoin 在评论中指出:\"This is only relevant for MRv1. MRv2 natively supports draft layers spread across different KV groups. Could we then avoid adding this new proposer if we only support it on MRV2? I would prefer that if we could reuse the existing eagle structure instead. Otherwise, we could consider just adding more flexibility into the base eagle proposer if that would be sufficient\"。这一意见直接推动了提交历史中 \"Drop CohereEagleProposer now that MRv2 handles multi-group drafts\" 的决策,最终 PR 收敛为纯模型定义修复,并由 DarkLight1337 与 mgoin 批准合并。另有 mergify bot 的 pre-commit 失败提醒,作者随后修复了注释行长度。

实现拆解

  1. commandr.py 引入 RMSNorm 支持:新增 rms_norm_func 与 RMSNorm 类,并提供 select_norm_impl(config) 统一选择归一化层与 eps。所有使用 LayerNorm 的地方(CohereAttention 的 q/k norm、CohereDecoderLayer.input_layernorm、CohereForCausalLM.norm)都改为经由该选择函数,使新 Cohere 配置可声明 rms_norm_eps。
  2. CohereAttention 修复:将 layer_idx 提取提升到 init 顶层;head_dim 改为优先读取 config.head_dim,否则回退到 hidden_size / total_num_heads;滑动窗口在非 v1 且层类型为 sliding_attention 时取 config.sliding_window + 1,并附注释说明与 FlashAttention (value - 1, 0) 约定及 KV-cache 驱逐公式对齐。q/k norm 改用 select_norm_impl 返回的类。
  3. Decoder 层 TP 通信优化与 MLP 扩展:CohereMLP 新增 intermediate_size 与 reduce_results 参数,down_proj 透传 reduce_results;CohereDecoderLayer 记录 tp_size,在 forward 中先本地相加 attention 与 MLP 输出,再在 tp_size > 1 时做一次 tensor_model_parallel_all_reduce,将两次 all-reduce 合并为一次。
  4. cohere2_moe.py 同步 SWA+1:将 MoE 变体的 sliding_window 也改为 config.sliding_window + 1,与 commandr 保持一致,避免同一模型家族两种实现行为分裂。
  5. 范围收敛与测试配套:根据 review 意见移除了 CohereEagleProposer 及自定义权重加载逻辑,最终只保留模型定义修复;本 PR 未新增或修改任何测试文件,行为正确性依赖现有 Cohere 模型测试。
文件 模块 状态 重要度
vllm/model_executor/models/commandr.py 模型定义 modified 8.61
vllm/model_executor/models/cohere2_moe.py 模型定义 modified 4.7

关键符号

rms_norm_func RMSNorm select_norm_impl CohereAttention.__init__ CohereMLP.__init__ CohereDecoderLayer.forward

关键源码片段

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

核心模型定义文件,集中了 RMSNorm 支持、head_dim 修复、SWA+1 语义对齐、TP all-reduce 合并等主要变更,是所有 Cohere 模型执行路径的公共入口。

# 归一化实现选择:Cohere 新配置可能显式给出 rms_norm_eps,
# 此时应使用 RMSNorm;旧配置只有 layer_norm_eps,则回退到 LayerNorm。
def select_norm_impl(config: CohereConfig) -> tuple[type[nn.Module], float]:
    rms_eps = getattr(config, 'rms_norm_eps', None)
    if rms_eps is not None:
        return RMSNorm, rms_eps
    return LayerNorm, config.layer_norm_eps
​
​
@torch.compile(backend=current_platform.simple_compile_backend)
def rms_norm_func(hidden_states, weight, variance_epsilon):
    input_dtype = hidden_states.dtype
    hidden_states = hidden_states.to(torch.float32)
​
    # RMSNorm 只做方差归一化,不做均值中心化,
    # 与 LayerNorm 的统计口径不同,需按配置区分。
    variance = hidden_states.pow(2).mean(-1, keepdim=True)
    hidden_states = hidden_states * torch.rsqrt(variance + variance_epsilon)
​
    hidden_states = weight.to(torch.float32) * hidden_states
    return hidden_states.to(input_dtype)
​
​
class RMSNorm(nn.Module):
    def __init__(self, param_shape=None, eps=1e-6):
        super().__init__()
        self.weight = nn.Parameter(torch.ones(param_shape))
        self.variance_epsilon = eps
        set_weight_attrs(self.weight, {'weight_loader': row_parallel_weight_loader})
​
    def forward(self, hidden_states, residuals=None):
        hidden_states = rms_norm_func(hidden_states, self.weight, self.variance_epsilon)
        return hidden_states, residuals
        # Model v2 使用交错滑动窗口,v1 不使用。
        self.v1 = isinstance(config, CohereConfig)
​
        # Cohere SWA 层实际看到 [pos - sliding_window, pos],即 sliding_window + 1 个 token。
        # vLLM 的 FlashAttention 后端窗口参数语义为 (value - 1, 0),因此这里传 sliding_window + 1
        # 以匹配训练约定;该 +1 会同步传播到 KV-cache 驱逐公式
        # (single_type_kv_cache_manager.py),保证两侧一致。
        self.sliding_window = None
        if not self.v1 and config.layer_types[self.layer_idx] == 'sliding_attention':
            self.sliding_window = config.sliding_window + 1
    # Decoder 层 forward 的收尾部分:本地合并 attention 与 MLP 输出后再做一次 all-reduce。
    def forward(self, positions, hidden_states, residual=None):
        # ... 省略 attention / MLP 前向细节 ...
        hidden_states_attention = self.self_attn(positions, hidden_states)
        hidden_states_mlp = self.mlp(hidden_states)
​
        # 先在本地把 attention 与 MLP 输出相加,再统一做一次 all-reduce,
        # 将两次独立的 all-reduce 合并为一次,降低 TP 通信开销。
        parallel_block_output = hidden_states_attention + hidden_states_mlp
        if self.tp_size > 1:
            parallel_block_output = tensor_model_parallel_all_reduce(
                parallel_block_output
            )
        hidden_states = residual + parallel_block_output
        return hidden_states, residual
vllm/model_executor/models/cohere2_moe.py core-logic

MoE 变体同步滑动窗口 +1 修复,与 commandr 保持一致,避免同一模型家族行为分裂。

        self.sliding_window = None
        layer_types = getattr(config, 'layer_types', None)
        if (
            layer_types is not None
            and layer_types[self.layer_idx] == 'sliding_attention'
        ):
            # 与 commandr 保持一致:Cohere SWA 层实际覆盖 sliding_window + 1 个 token,
            # 这里加 1 以匹配 vLLM FlashAttention 的 (value - 1, 0) 窗口语义。
            self.sliding_window = config.sliding_window + 1

评论区精华

是否应保留 CohereEagleProposer 设计

mgoin 指出:该 proposer 只对 MRv1 有意义,MRv2 原生支持跨多个 KV cache group 的草稿层;希望复用现有 eagle 结构,或在基础 eagle proposer 上加灵活性。

结论:作者接受建议,在提交 'Drop CohereEagleProposer now that MRv2 handles multi-group drafts' 中移除了新增的 proposer,只保留模型定义修复。 · 已解决

滑动窗口 +1 语义 正确性

代码注释说明 Cohere SWA 层看到 sliding_window + 1 个 token,而 vLLM FlashAttention 后端使用 (value - 1, 0),因此传 sliding_window + 1 匹配训练约定,并传播到 KV-cache 驱逐公式。

结论:以注释形式固化该约定,commandr 与 cohere2_moe 两个模型文件保持一致。 · 已解决

pre-commit 失败修复 style

mergify bot 提示 pre-commit 检查失败,要求安装 pre-commit 并运行;作者随后修复了命令注释行长度,并包含 co-authored-by Cursor。

结论:修复后通过 CI,DarkLight1337 与 mgoin 均批准合并。 · 已解决

风险与影响

  1. 滑动窗口 +1 语义风险:commandr.py 与 cohere2_moe.py 同时把 sliding_window 改为 +1,这会直接改变 FlashAttention 的窗口覆盖范围,并进一步影响 KV-cache 驱逐公式(single_type_kv_cache_manager.py)。PR 注释声称已保持两侧一致,但仓库中未见到对应的驱逐公式改动,若实际运行路径不一致可能导致长上下文行为异常。
  2. RMSNorm 数值行为变化:select_norm_impl 在存在 rms_norm_eps 时切到 RMSNorm,而 RMSNorm 不做均值中心化,与 LayerNorm 数学定义不同。若已有 Cohere 权重实际按 LayerNorm 训练,切换后会改变前向数值,影响输出质量。
  3. head_dim 配置假设:CohereAttention 优先使用 config.head_dim,但 qkv_proj 的 shape 计算仍依赖 head_dim 与 num_heads 的乘积关系;若配置 head_dim 与 hidden_size / total_num_heads 不一致,可能触发 shape 不匹配错误。
  4. TP all-reduce 合并依赖:合并 all-reduce 依赖 RowParallelLinear 的 reduce_results=False 与手动 tensor_model_parallel_all_reduce,若未来接入量化或自定义层时未正确传递 reduce_results,可能导致 TP>1 时输出缺少跨卡归约。
  5. 缺少测试覆盖:本 PR 无任何测试文件变更,上述行为均依赖现有 Cohere 测试,边界场景(如旧配置无 rms_norm_eps、SWA 层位置变化、TP>1 合并路径)未被显式验证。

影响范围集中在 Cohere 模型家族:commandr.py 覆盖 Cohere v1/v2 及其变体,cohere2_moe.py 覆盖 MoE 版本(如 Command A)。用户在加载新的 Cohere 配置时可自动获得 RMSNorm 与自定义 head_dim 支持,SWA 窗口语义与训练约定对齐可避免长上下文下窗口偏差;TP>1 时 attention+MLP 的 all-reduce 合并可小幅降低通信开销。团队层面,这些修正为后续 Cohere Eagle 推测解码(MRv2 多 KV group 草稿)铺平了模型层基础,但需要配套测试补充。

缺少测试覆盖 滑动窗口语义变更影响 KV 驱逐 RMSNorm 行为变化可能影响数值 TP all-reduce 合并依赖 reduce_results 支持 head_dim 配置假设

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论