Prhub

#56332 [CI/Build] Add DSv4.1 auto-label rules and narrow DSv4

原始 PR 作者 jcotant-inferact 合并时间 2026-09-13 00:50 文件变更 2 提交数 1 评论 2 代码增减 +26 / -3

执行摘要

新增 DSv4.1 自动打标规则,并用负向断言收紧 DSv4

PR body 明确给出两个具体缺陷:其一,「DeepSeek-V4.1 had no label of its own, and the existing DSv4 patterns were claiming all of its traffic」——旧正则 deepseek[-\s]?v4 在 v4 之后没有任何边界约束,因此 DeepSeek-V4.1-Flash 被 DSv4 命中,#56228 这个纯 V4.1 PR 被错误标成 DSv4;其二是 issue labeler 的 DSv4 匹配器是子串 deepseek-v4 加关键字 DSv4,而 mergify 允许 [-\s]? 分隔符,两个打标器语义不一致,#56297(“DeepSeek V4.1 rejects Responses API text content types”)只拿到 deepseek 标签。作者进一步论证架构差异:V4.1 有独立模型树 vllm/models/deepseek_v4_1/、独立 tokenizer_mode(deepseek_v41)、独立的 parser、reasoning parser、tool parser 与 config 类,并新增 common/engram.py、nvidia/model_state.py、common/ops/indexer_k_store.py 等文件,同时不含 V4 的 mtp.py、cpu/、xpu/、fi_moe.py。因此需要把打标边界从“DeepSeek V4 系列”收窄为“DeepSeek V4”与“DeepSeek V4.1”两条互斥标签。

若你是维护者或 CI 负责人,值得精读 PR body 中“两个打标器如何保持互斥”的推理段落,尤其是用负向先行断言做版本边界、以及“Flash/Pro 不可按文件条件区分所以不拆标签”的取舍;普通工程师只需知道这是标签配置变更,无需逐行看 YAML。建议跟进三件事:一是合并后立刻确认 DSv4.1 标签存在并完成回填与 #56228 纠正;二是把作者验证过的 25 条标题案例固化成测试(作者已明确愿意提交),补上 check-label-rules 不覆盖的标题正则盲区;三是把 (?![._]?1) 这种一次性 lookahead 抽象成更通用的版本号边界策略,否则 V4.2 出现时会重演同一问题。

讨论亮点

本 PR 的 review 记录非常轻:claude[bot] 以「This pull request is from a fork — automated review is disabled.」表示 fork 来的 PR 不自动审查,njhill 未留文字直接 APPROVED,issue 侧只有 njhill 的 /ci run 与 Buildkite 机器人回复。真正的技术讨论发生在 PR body 的自证段落里,有三点值得记录:

  1. 为何 Flash 与 Pro 共用同一代标签:「Flash and Pro share one label per generation. They are the same code path in vLLM: one arch string, one config class, no variant branching anywhere in vllm/models/deepseek_v4/」——因此按变体拆标签不可能做出文件条件,只能靠标题路由,作者选择不做。
  2. 前置依赖被显式声明:「The DSv4.1 label must exist before this merges; a rule naming a nonexistent label does not create it.」这是一条容易被忽略的部署顺序约束。
  3. 测试守卫的悬置提议:「Happy to commit the title-regex cases as a test if reviewers want a permanent guard; there is no existing suite covering .github/ matcher behavior, and check-label-rules covers only the file conditions.」维护者未回应,PR 最终以单 commit 合并,标题正则仍无回归保护。

实现拆解

本 PR 是纯配置变更,没有源码、测试或部署产物改动,实现可拆为四步:

  1. 确立标签边界策略(.github/mergify.yml)。核心手法是用负向先行断言 (?![._]?1) 表示“v4 之后若紧跟 .1 或 _1 则不算 V4”,同时把分隔符从 [-\s]? 放宽为 [-\s_]? 以覆盖 deepseek_v4 写法。选择 lookahead 而不是精确枚举版本号,是为了让规则仍然匹配所有非 4.1 的 V4 标题(V4-Flash、V4-Pro 等)。

  2. 新增 label-dsv4-1 规则(.github/mergify.yml)。规则名用 label-dsv4-1,标签值是 DSv4.1;条件同样先排除 stale,再用 5 条 file 条件加 1 条 title 条件做“或”组合,并以 title-only 方式匹配标题,避免正文里的模型名带来噪音标签。文件条件的枚举依据是 4.1 源码的三种拼写:deepseek_v4_1(模型树)、deepseek_v41(大多数位置)、deepseekv41(tool parsers)。

条件 覆盖范围 要点
^vllm/models/deepseek_v4_1/ V4.1 模型树 与 DSv4 的 ^vllm/models/deepseek_v4/ 不重叠,后者要求 deepseek_v4 后紧跟 /
^vllm/(?:parser\|tokenizers\|reasoning\|transformers_utils/configs)/deepseek_v41.*\.py 解析器 / tokenizer / config 覆盖 deepseek_v41 拼写
^vllm/tool_parsers/deepseekv41.*\.py tool parsers 覆盖 deepseekv41 拼写
^rust/src/(?:chat\|parser)/src/.*deepseek_v41 Rust 前端 只覆盖 chat/parser 子目录
^tests/.*deepseek_?v41 测试 兼容带与不带下划线两种写法
  1. 统一两个打标器的语义(.github/workflows/issue_autolabel.yml)。把 DSv4 条目从 keywords: [{ term: "DSv4" }] + substrings: [{ term: "deepseek-v4" }] 改为单条 regexPatterns,正则文本与 mergify 完全一致,并用 JS 的 gi 标志替代 Python 端的 inline (?i);新增 "DSv4.1" 条目,正则同时接受 DSv4.1/DSV4_1 与 DeepSeek-V4.1/DeepSeek V4_1 等写法。两个条目的 searchIn 都保持 title,与 mergify 的标题匹配范围对齐。这样两个引擎在同一标题上给出相同结论,避免“同一 PR 在 mergify 与 issue labeler 下得到不同标签”的长期不一致。

  2. 验证、前置依赖与后续动作。验证侧使用 .venv/bin/pre-commit run --files .github/mergify.yml .github/workflows/issue_autolabel.yml 与 tools/pre_commit/check_label_rules.py(含 actionlint、check-label-rules),作者还额外注入“故意失效的 glob”确认 check-label-rules 不是空跑;仓库外另做 25 条真实 V4/V4.1 标题在 Python re 与 JS RegExp 双引擎对拍(25/25 全部通过)、文件条件与代码树核对(44/44 个 V4.1 文件,无过匹配、无向 DSv4 泄漏)、以及两个标签互斥性检查。本 PR 未新增任何测试文件;作者仅表示“愿意提交标题正则用例作为长期守卫”,但没有落地。前置依赖是 DSv4.1 标签必须先存在,规则本身不会创建标签;后续需要回填约 14 个 V4.1 PR 和 2 个 issue,并修正 #56228 的 DSv4 标签。

文件 模块 状态 重要度
.github/mergify.yml 标签自动化 modified 4.26
.github/workflows/issue_autolabel.yml 问题打标 modified 3.61

关键符号

label-dsv4 label-dsv4-1 DSv4 (issue labeler labelConfig 条目 ) DSv4.1 (issue labeler labelConfig 条目 )

关键源码片段

.github/workflows/issue_autolabel.yml configuration

issue labeler 侧配套:新增 "DSv4.1" 条目,并把 DSv4 从 keywords + substrings 子串匹配改成与 mergify 同源的正则,消除两个打标器之间的语义差(原先漏掉空格分隔标题)。

// .github/workflows/issue_autolabel.yml 中的 labelConfig(JS 匹配逻辑内嵌片段)
// 目标:issue labeler 与 mergify 使用同一套标题正则,两个打标器结论保持一致。
​
        deepseek: { keywords: [{ term: "DeepSeek", searchIn: "title" }], },
​
        // DSv4 与 DSv4.1 是两条独立架构线,负向断言保证两个标签不会同时命中。
        DSv4: {
          // 旧实现是 keywords: ["DSv4"] + substrings: ["deepseek-v4"]:
          // 子串匹配既漏掉 “DeepSeek V4.1” 这类空格分隔标题,也无法把 4.1 排除,
          // 换成与 mergify 相同的正则后,两边语义完全对齐。
          // 注意 YAML 双引号标量需要写成 \\b,交给 RegExp 构造的才是 \b;
          // 大小写不敏感由 gi 标志承担(mergify 侧用的是 inline (?i))。
          regexPatterns: [{ pattern: "(?:\\bDSv4\\b|deepseek[-\\s_]?v4)(?![._]?1)", flags: "gi", searchIn: "title" }],
        },
        "DSv4.1": {
          // 同时接受 DSv4.1 / DSV4_1 与 DeepSeek-V4.1 / DeepSeek V4_1 等写法,
          // searchIn 固定为 title:只看标题、不看正文,避免正文里的模型名带来噪音标签。
          regexPatterns: [{ pattern: "(?:\\bDSv4[._]?1\\b|deepseek[-\\s_]?v4[._]?1)", flags: "gi", searchIn: "title" }],
        },
​
        llama: { keywords: [{ term: "Llama", searchIn: "title" }], },

评论区精华

用负向先行断言做版本边界,并让两个打标器语义一致 设计

标题正则从 `(?:\bDSv4\b|deepseek[-\s]?v4)` 改为末尾加 `(?![._]?1)`,同时把分隔符扩展为 `[-\s_]`;issue labeler 侧则彻底放弃 keywords + substrings,改用与 mergify 同源的正则,原因是旧子串匹配漏掉空格分隔标题(#56297 只拿到 deepseek 标签)。

结论:方案被接受并合并;作者用 25 条真实标题在 Python re 与 JS RegExp 双引擎验证 25/25 通过,并确认没有标题会同时拿到两个标签。 · 已解决

Flash 与 Pro 是否要按变体拆分标签 设计

PR body 论证:Flash 与 Pro 在 vLLM 中是同一条代码路径(一个 arch 字符串、一个 config 类、deepseek_v4/ 内没有任何变体分支),V4-Flash 与 V4-Pro 的 eval 配置除模型名和 load timeout 外完全相同,「No file condition could distinguish them, so a per-variant label would only ever be title-routable」。

结论:决定每一代只用一个标签,不做变体级拆分;这是一条明确的“不过度设计”取舍。 · 已解决

标题正则用例是否固化为测试 测试

作者声明:「Happy to commit the title-regex cases as a test if reviewers want a permanent guard; there is no existing suite covering `.github/` matcher behavior, and `check-label-rules` covers only the file conditions.」维护者未回应。

结论:未落地;PR 以单 commit 合并,标题正则行为仍无自动化回归保护,属于可跟进的遗留项。 · 未解决

DSv4.1 标签的前置依赖与流量回填 other

作者强调「The `DSv4.1` label must exist before this merges; a rule naming a nonexistent label does not create it.」,并列出后续动作:回填约 14 个 V4.1 PR 与 2 个 issue,把 #56228 的 DSv4 改为 DSv4.1。

结论:作为上线顺序约束记录在 PR body 中,合并后的回填由作者后续处理。 · 待后续跟进

风险与影响

  1. 前置依赖风险:DSv4.1 标签必须已存在于仓库,否则新增的 label-dsv4-1 规则和 issue labeler 条目会指向不存在的标签(作者明确写了“a rule naming a nonexistent label does not create it”)。部署顺序搞错会让规则空转甚至报错,属于典型的配置上线顺序问题。
  2. 跨引擎正则漂移:同一份正则要同时被 mergify 的 Python re(inline (?i)、单引号/裸标量)和 GitHub Actions 内的 JS RegExp(gi 标志、YAML 双引号标量里必须写成 \\b)消费。本次已对齐,但转义层级与标志机制不同,后续任一侧单独修改就可能重新分叉,而 CI 里没有任何自动比对。
  3. 版本边界覆盖不全:(?![._]?1) 只能排除 v4 后紧跟 .1/_1 的写法,DeepSeek-V4-1(连字符分隔)仍会落到 DSv4,且该标题也不会命中 DSv4.1 的 [._]?1,属于“两边都不匹配/只匹配 DSv4”的盲区;未来出现 V4.2 时同样会被 DSv4 捕获,因为 lookahead 只针对“1”。
  4. 文件条件枚举式覆盖:5 条路径模式只覆盖 models、parser/tokenizers/reasoning/configs、tool_parsers、rust chat|parser、tests。新增目录(如 rust/src/server/、examples/、benchmark 或新子模块)下的 V4.1 改动不会被文件条件命中,只能依赖标题里带版本号,纯文档/示例类 PR 容易漏标。
  5. 缺少回归测试:check-label-rules 只校验文件条件,标题正则行为没有任何测试覆盖(作者已声明可补但未补),未来修 regex 时无守卫。
  6. 误标的下游传导:若 DSv4/DSv4.1 后续被用于 release notes 归类或选择性 CI 触发,错误分类会随之传导到版本说明和测试调度(当前证据只表明它用于检索与 triage,此项为条件性风险)。

对推理运行时与用户完全无影响:不涉及 engine、kernel、model 或 serving 代码,改动仅在 GitHub 自动化层。对维护者与团队的影响是直接且正向的:DSv4/DSv4.1 的 PR 与 issue 流量从此可分,triage、检索与后续 release notes 归类更准确;同时需要一次性投入回填成本——约 14 个已开 V4.1 PR 与 2 个 issue 要补标签,#56228 的 DSv4 标签要纠正。对 CI 系统的影响是标签规则即时生效于合并后的新 PR/issue,本 PR 自身在合并前仍按旧规则被打上 DSv4,属于预期行为。对 DSv4.1 这条模型线的长期影响在于可见性:独立标签让 4.1 的 kernel/模型演进(如 #56217 跟踪 issue)能在看板上被单独统计,而不是被 V4 的流量淹没。

依赖标签先存在 双引擎正则易漂移 版本边界只覆盖 .1/_1 文件条件枚举式覆盖 缺少测试守卫 标题 only 匹配

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论