Prhub

#2499 feat(ci): add weekly full-suite cadence

原始 PR 作者 guapisolo 合并时间 2026-08-14 02:36 文件变更 16 提交数 3 评论 1 代码增减 +198 / -98

执行摘要

每周六新增全量 CI 节奏,GPU 矩阵限流至单 runner

PR body 明确指出:"Periodic full-suite coverage is needed for long and otherwise label-gated tests, but it must consume only a small slice of self-hosted GPU capacity instead of monopolizing the fleet." 即长耗时与 label-gated 测试缺少周期性全量覆盖窗口,而全量跑又必须限制在自托管 GPU 集群的小份额内,避免与日常 PR 队列争抢资源。

CI 工具链与基础设施负责人建议精读,特别是 tests/ci/ci_policy.py 中"每个精确 cron 显式映射独立 cadence"的设计原则和 RunPolicy 字段化改造,这两点对后续扩展 CI 节奏、避免新 cadence 静默继承 nightly 行为很有参考价值。普通产品开发者只需了解每周六存在全量 CI 运行即可,无需深入源码。

讨论亮点

唯一一条 inline 评论来自 github-advanced-security[bot] 的 CodeQL 扫描,针对 tests/ci/ci_utils.py 中 logger.info(f"[CI Gate][nightly] ... wrote baseline (trusted={result.trusted}, {len(values)} value(s))") 报出 Clear-text logging of sensitive information。从上下文看实际记录的是布尔值与整数,并非真实密钥,属于疑似误报;但作者在第三个 commit(2c18721)中主动将动态 gate 结果细节从日志中移除,改为静态字符串 [CI Gate][baseline] ...: wrote baseline,客观上消除了该告警。人工 reviewer yueming-yuan 直接 APPROVED,无其他设计争议。

实现拆解

  1. 策略层改造(tests/ci/ci_policy.py):新增 WEEKLY_CADENCE 常量,将 SCHEDULE_POLICIES 从单条 cron 0 15 * * * 拆分为 0 15 * * 0-5(nightly)与 0 15 * * 6(weekly)两条精确映射;RunPolicy 删除 is_nightly property,改为 admit_nightly_tests、bypass_fastfail、write_baseline 三个显式字段,其中 weekly 与 nightly 同步置 True;resolve_policy 分支顺序调整为 run-ci-all / weekly > nightly > run-ci-image,weekly 的 include 集合等于全部 KNOWN_LABELS(含 long、ft-long)。

  2. 运行层语义泛化(tests/ci/run_suite.py、tests/ci/ci_utils.py):filter_tests 的参数 nightly 更名为 admit_nightly_tests,run_a_suite 中 gate_nightly 改为 gate_write_baseline=policy.write_baseline,使 baseline 写入不再与 nightly 名称耦合;run_gate_hook 参数 nightly 更名为 write_baseline,日志由动态详情 (trusted=..., len(value(s))) 收敛为静态摘要 [CI Gate][baseline] ...: wrote baseline,持久化行为不变。

  3. 工作流层(.github/workflows/pr-test.yml、pr-test-rocm.yml):schedule 拆分为周日到周五 nightly + 周六 weekly 两条 cron;各 GPU stage 的 strategy 增加 max-parallel: ${{ needs.resolve-ci-policy.outputs.cadence == 'weekly' && 1 || N }},weekly 时每矩阵只占用 1 个 runner,其余节奏维持原并行度(2/2/3/2)。

  4. 测试与文档配套:test_ci_policy.py 新增 test_weekly_schedule_resolves_to_independent_full_policy;test_run_suite.py 新增 test_scheduled_runs_use_utc_1500、test_weekly_serializes_each_gpu_matrix、test_weekly_full_scope_admits_nightly_only_tests、test_weekly_policy_reaches_cuda_runner 等锁定测试;test_gate_integration.py 将 TestNightlyWrite 更名为 TestBaselineWrite 并全部改用 write_baseline 参数;docs/ci/00-stage.md、01-label.md、03-metric-history-gate.md 与 contributor-guide 同步描述新节奏。

文件 模块 状态 重要度
tests/ci/ci_policy.py 策略解析 modified 6.01
tests/ci/ci_utils.py 门禁钩子 modified 5.47
.github/workflows/pr-test.yml CI 编排 modified 5.09
tests/ci/run_suite.py 套件调度 modified 4.87
tests/ci/test/test_run_suite.py 套件测试 modified 6.92
tests/ci/test/test_gate_integration.py 门禁测试 modified 5.7
.github/workflows/pr-test-rocm.yml ROCm 编排 modified 3.77
tests/ci/test/test_ci_policy.py 策略测试 modified 5.34
tests/ci/ci_register.py CI 注册表 modified 3.43
docs/ci/00-stage.md CI 文档 modified 3.14

关键符号

resolve_policy resolve_workflow_inputs filter_tests run_a_suite run_gate_hook run_unittest_files

关键源码片段

tests/ci/ci_policy.py core-logic

CI 策略解析核心:新增 WEEKLY_CADENCE、拆分 SCHEDULE_POLICIES、RunPolicy 字段化(admit_nightly_tests / write_baseline),是本次变更的策略源头

# tests/ci/ci_policy.py —— CI 节奏与选择策略核心(PR#2499 变更后)REGULAR_CADENCE = "regular"
NIGHTLY_CADENCE = "nightly"
WEEKLY_CADENCE = "weekly" # 新增:周六全量节奏
CI_CADENCES = frozenset({REGULAR_CADENCE, NIGHTLY_CADENCE, WEEKLY_CADENCE})# 每个精确 cron 都必须显式映射到独立 cadence,
# 防止未来新增定时任务静默继承 nightly 行为。
SCHEDULE_POLICIES: dict[str, tuple[str, tuple[str, ...]]] = {
    "0 15 * * 0-5": (NIGHTLY_CADENCE, ()), # 周日到周五 15:00 UTC
    "0 15 * * 6": (WEEKLY_CADENCE, ()), # 周六 15:00 UTC 全量
}
​
​
@dataclass(frozen=True)
class RunPolicy:
    cadence: str
    include_labels: frozenset[str]
    admit_nightly_tests: bool # 是否放行 nightly-only 注册
    bypass_fastfail: bool # 失败后继续跑完剩余文件,还是立即停止
    write_baseline: bool # 是否把 gate 结果写入指标基线
​
​
def resolve_policy(cadence: str, raw_labels: set[str]) -> RunPolicy:
    # 先校验 cadence 合法性,并限制 nightly label 只能用于 nightly 节奏
    if cadence not in CI_CADENCES:
        raise ValueError(f"Unknown CI cadence {cadence!r}; expected one of {sorted(CI_CADENCES)}")
    if "nightly" in raw_labels and cadence != NIGHTLY_CADENCE:
        raise ValueError("The nightly workflow label requires cadence='nightly'")
​
    # 分支顺序即优先级:run-ci-all / weekly > nightly > run-ci-image
    requested = strip_run_ci_prefix(raw_labels) & set(KNOWN_LABELS)
    if "run-ci-all" in raw_labels or cadence == WEEKLY_CADENCE:
        scope = set(KNOWN_LABELS) # weekly 全量:包含 long / ft-long 等全部 label
    elif cadence == NIGHTLY_CADENCE:
        scope = set(KNOWN_LABELS) - {"long", "ft-long"}
    elif "run-ci-image" in raw_labels:
        scope = set(KNOWN_LABELS) - {"long", "ft-short", "ft-long"}
    else:
        scope = set()
​
    # 显式请求的 label 最后并入,保证显式指定优先于范围减法
    return RunPolicy(
        cadence=cadence,
        include_labels=frozenset(scope | requested),
        admit_nightly_tests=cadence in {NIGHTLY_CADENCE, WEEKLY_CADENCE},
        bypass_fastfail=cadence in {NIGHTLY_CADENCE, WEEKLY_CADENCE} or "bypass-fastfail" in raw_labels,
        write_baseline=cadence in {NIGHTLY_CADENCE, WEEKLY_CADENCE},
    )
tests/ci/ci_utils.py core-logic

gate hook 的 write_baseline 语义落地处,也是 CodeQL 日志告警所在文件;参数从 nightly 泛化为 write_baseline 后 nightly 与 weekly 均可持续化基线

def run_gate_hook(filename, merged_record_path, *, store, registry, write_baseline, provenance, now_iso=None):
    """根据策略决定是否把 gate 结果写入指标基线。    语义变化:原先只有 nightly 写 baseline,现在由 `write_baseline` 显式控制 ——
    nightly 与 weekly 都写,普通 PR 只输出 shadow 判定。任何异常都被吞掉,
    gate 永远不能改变测试的 pass/fail。
    """
    try:
        result = evaluate_gate(filename, merged_record_path, store, registry=registry)
​
        if write_baseline:
            if not result.metrics:
                # 没有声明 gate 的文件不写空 run 行
                logger.info(f"[CI Gate][baseline] {filename}: no gate declared; skipping write")
                return
            identity = RunIdentity(
                test_path=result.test_path, backend=result.backend, suite=result.suite
            )
            created_at = now_iso or datetime.datetime.now(datetime.timezone.utc).isoformat()
            # 相同坐标的 spec 只写一行,避免重复权重污染基线均值
            seen_coords = set()
            values = []
            for m in result.metrics:
                if m.current is None:
                    continue
                coord = (m.metric_key, m.steps_key, m.constraint_key, m.step)
                if coord in seen_coords:
                    continue
                seen_coords.add(coord)
                values.append(MetricSample(m.metric_key, m.steps_key, m.constraint_key, m.step, m.current))
            store.write_run(identity, provenance, created_at, trusted=result.trusted, values=values)
            # 日志保持静态摘要,不输出动态 gate 细节(回应 CodeQL 告警)
            logger.info(f"[CI Gate][baseline] {filename}: wrote baseline")
        else:
            # PR 运行:只写 shadow 判定到 step summary,绝不落库
            line = _shadow_verdict_line(filename, result)
            logger.info(line)
            detail = "\n".join(f"  - {m.metric_key} (step={m.step}): {m.reason}" for m in result.metrics)
            write_github_step_summary(line + ("\n" + detail if detail else "") + "\n")
    except Exception as e:
        logger.warning(f"[CI Gate] hook failed for {filename}: {type(e).__name__}: {e}")

评论区精华

ci_utils.py 日志是否泄露敏感信息 安全

github-advanced-security[bot] 的 CodeQL 扫描对 `logger.info(f"[CI Gate][nightly] ... wrote baseline (trusted={result.trusted}, {len(values)} value(s))")` 报出 Clear-text logging of sensitive information 告警。实际记录内容是布尔与整数,非真实密钥,疑似误报;但作者在 commit 2c18721 中主动将动态 gate 结果细节从日志移除,改为静态字符串 `[CI Gate][baseline] ...: wrote baseline`,并在 PR body 说明 logs record completion without dynamic result details。

结论:第三个 commit 已将动态详情移除,告警不再适用;baseline 持久化语义保持不变,仅收敛日志输出。 · 已解决

风险与影响

  1. 策略 API 破坏:RunPolicy.is_nightly 属性被移除,任何 tests/ci 之外的引用都会 ImportError/AttributeError;材料未显示其他引用点,建议合并前全仓搜索确认。
  2. GPU 资源叠加:weekly 虽将每个矩阵 max-parallel 限为 1,但独立 stage(如单例 2-GPU stage)仍可与矩阵 job 并发,PR body 也承认"Independent stages, including the singleton 2-GPU stage, may still overlap",极端情况下仍可能挤占 PR 队列。
  3. baseline 污染:weekly 与 nightly 都会写入指标 baseline,若全量跑期间存在 GPU 资源争抢导致性能/指标波动,可能写入 untrusted 行;现有 trusted 标记机制可兜底,但 baseline 刷新频率变化需要观察。
  4. cron 精确匹配:workflow 中两条 cron 字符串必须与 SCHEDULE_POLICIES 键严格一致,若 GitHub 端表达式被改写会直接 ValueError(有 test_every_configured_cron_has_an_explicit_python_policy 锁定,风险低)。

影响范围集中在 CI 基础设施域,不涉及产品运行时(miles/ 训练相关代码零改动)。对团队的价值在于:每周六获得一次覆盖 long/ft-long 等 label-gated 与 nightly-only 测试的全量回归窗口,弥补日常 PR/nightly 的覆盖盲区;对自托管 GPU 集群的占用被限制为每矩阵单 runner,降低对工作日 PR 队列的挤占。对 CI 工具链维护者而言,write_baseline 字段使 gate 写库语义与 cadence 解耦,后续新增节奏(如 biweekly)只需在 SCHEDULE_POLICIES 加一行并复用相同字段。文档(docs/ci/、contributor-guide)已同步,贡献者可预期周六存在全量运行。

策略 API 破坏(is_nightly 移除) weekly 独立 stage 仍可并发占用 GPU 双节奏写入 baseline 存在指标波动风险 cron 与策略键强一致依赖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论