执行摘要
- 一句话:重构 NPU PR CI 降低并发并快速失败
- 推荐动作:建议 CI 基础设施维护者精读,重点关注健康检查的根因失败过滤逻辑与 per-case 输出下沉设计,这两处体现了避免级联误杀和保证可观测性之间权衡;普通贡献者只需了解 NPU 套件命名规则即可。注意观察合并后 base-c 套件的实际稳定性,尤其是 GLM5 阈值下调是否有 flaky 依据。
功能与动机
PR body 明确指出两个动机:一是减少 pr-test-npu.yml 中的并发任务数,因为 excessive tasks 导致 K8s 调度阻塞,大量 queued tasks 无法运行;二是优化 pr-test-npu.yml 文件结构以提高可维护性。
实现拆解
- 统一测试套件命名:修改
test/run_suite.py 中 HWBackend.NPU 套件列表,将旧的 stage-a-unit-test-npu、stage-b-test-{1,2,4,8,16}-npu-a3 全部替换为 base-a-test-1-npu-a2、base-b-test-{1,2,4,8,16}-npu-a3,并新增 base-c-test-acc-{2,4,8,16}-npu-a3 与 base-c-test-perf-{2,4,8,16}-npu-a3 共 8 个准确率/性能套件。
- 合并同资源模型用例:把
test/registered/npu/ 下原先每个模型一个独立 job 的准确率与性能用例,按资源规格(2/4/8/16 卡 a3)归入上述 base-c 套件,并将各文件中的 register_npu_ci(...) 调用改为 register_npu_ci(est_time=3600, suite="base-c-test-xxx"),去掉 nightly=True 与 disabled 参数,从而大幅削减单次 PR 触发的并发 job 数。
- 封装可复用工作流:新增
.github/workflows/_npu-single-node-test-stage.yml(单节点准确率/性能测试模板)与 .github/workflows/_npu-pr-test-stage.yml(PR 测试阶段模板),通过 workflow_call 接收 runner、镜像、套件名、超时等输入;.github/workflows/pr-test-npu.yml 由约 400 行缩减到 170 行左右,各 job 改为 uses: 引用上述模板。
- 引入健康检查快速失败:在两个可复用工作流中增加
Check PR test health 步骤(actions/github-script@v8),检查同 commit 的 lint 状态与当前 run 内其他 job 的根因失败;只有当存在真实失败(已过滤级联失败和 h20 flaky job)时才 setFailed 提前终止,并支持 SKIP_PR_TEST_HEALTH_CHECK 与 bypass-fastfail label 逃生门。
- per-case 输出下沉到测试基类:在
python/sglang/test/ascend/e2e/test_npu_accuracy_utils.py 与 test_npu_performance_utils.py 中新增 _get_tc_name、_setup_per_case_output、_save_metrics_json、_backup_plog 四个类方法,将 workflow 中原有的固定路径输出、stdout 解析和 plog 备份动作改成按用例名落盘,保证套件合并后每个用例仍有独立可定位的产物。
关键文件:
python/sglang/test/ascend/e2e/test_npu_accuracy_utils.py(模块 测试基类;类别 test;类型 test-coverage;符号 _get_tc_name, _setup_per_case_output, _save_metrics_json, _backup_plog): 准确率测试基类新增 4 个类方法,把 per-case 输出目录、metrics 汇总、plog 备份从 workflow 下沉到测试框架,是套件合并后用例级可观测性的核心支撑。
python/sglang/test/ascend/e2e/test_npu_performance_utils.py(模块 测试基类;类别 test;类型 test-coverage;符号 _get_tc_name, _setup_per_case_output, _save_metrics_json, _backup_plog): 性能测试基类与准确率基类同步新增 4 个类方法,保证合并后的 perf 套件也能按用例名产出 metrics 与 plog。
.github/workflows/_npu-single-node-test-stage.yml(模块 CI 工作流;类别 infra;类型 infrastructure;符号 failedStep, rootCauseFailures, Check PR test health): 新增的单节点 E2E 复用工作流,承载准确率/性能套件的依赖安装、健康检查与测试执行,是降低并发和统一配置的核心载体。
.github/workflows/_npu-pr-test-stage.yml(模块 CI 工作流;类别 infra;类型 infrastructure;符号 failedStep, rootCauseFailures, Check PR test health): PR 测试阶段的复用工作流,抽取依赖安装、环境变量与健康检查逻辑,让 pr-test-npu.yml 主文件大幅缩减。
.github/workflows/pr-test-npu.yml(模块 CI 工作流;类别 infra;类型 infrastructure): 主工作流从约 400 行缩减到约 170 行,各 job 改为引用 _npu-pr-test-stage.yml 与 _npu-single-node-test-stage.yml,并统一按 base-a/b/c 命名。
test/run_suite.py(模块 套件注册;类别 test;类型 configuration): 更新 HWBackend.NPU 套件注册表,用 base-a/base-b/base-c 新命名替换旧的 stage-* 命名,并新增 acc/perf 两类共 8 个套件。
test/registered/npu/accuracy/glm5_top64_pruned/test_npu_glm5_top64_pruned_bf16_8p_gsm8k.py(模块 模型用例;类别 test;类型 test-coverage;符号 register_npu_ci): 用例从独立 nightly 注册改为并入 base-c-test-acc-16-npu-a3 套件,并将 accuracy 阈值从 0.50 下调到 0.48。
test/registered/npu/performance/qwen3_6_27b/test_npu_qwen3_6_27b_1p_in1024x1024_30_out1024_50ms.py(模块 模型用例;类别 test;类型 test-coverage;符号 register_npu_ci): 性能用例从 nightly 独立任务并入 base-c-test-perf-2-npu-a3 套件,注册参数简化为单行调用。
关键符号:_get_tc_name, _setup_per_case_output, _save_metrics_json, _backup_plog, rootCauseFailures
关键源码片段
.github/workflows/_npu-single-node-test-stage.yml
新增的单节点 E2E 复用工作流,承载准确率/性能套件的依赖安装、健康检查与测试执行,是降低并发和统一配置的核心载体。
// 健康检查:在正式执行测试前快速失败,节省 CI 资源。
// 跳过条件:显式 SKIP_PR_TEST_HEALTH_CHECK、定时任务(schedule)、
// 或 PR 携带 bypass-fastfail label。
if (process.env.SKIP_PR_TEST_HEALTH_CHECK === 'true') {
core.notice('[health-check] SKIP: SKIP_PR_TEST_HEALTH_CHECK=true');
return;
}
if (context.eventName === 'schedule') {
core.notice('[health-check] SKIP: scheduled run');
return;
}
// 通过 checks.listForRef 查询同一 commit 的 lint 状态
// (listJobsForWorkflowRun 只能看到当前 run 内的 job)。
const ref = context.payload.pull_request?.head?.sha || context.sha;
const { data } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: ref,
check_name: 'lint',
});
const lintRun = data.check_runs.find(cr => cr.app?.slug === 'github-actions');
if (lintRun?.status === 'completed' && lintRun?.conclusion === 'failure') {
core.setFailed('Fast-fail: lint check failed');
return;
}
// 根因失败判定:过滤掉级联失败(健康检查步骤本身失败)与
// 已知 flaky 的 h20 job,避免误伤其他 stage。
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
per_page: 100,
});
const rootCauseFailures = jobs.filter(j => {
if (j.status !== 'completed' || j.conclusion !== 'failure') return false;
// 按 job 名拆分出基础 key,兼容 inline 与 reusable 两种形式
const baseName = j.name.split(/[ /]/)[0];
if (baseName === 'base-c-test-8-gpu-h20') {
core.info(`[health-check] Filtered out h20 job: ${j.name}`);
return false;
}
// 如果失败步骤是健康检查本身,说明是级联失败
const failedStep = (j.steps || []).find(s => s.conclusion === 'failure');
if (failedStep && (failedStep.name.includes('check-pr-test-health') ||
failedStep.name.includes('Check PR test health'))) {
return false;
}
return true;
});
if (rootCauseFailures.length > 0) {
core.setFailed(`Fast-fail: skipping — root cause job(s): ` +
`${rootCauseFailures.map(j => j.name).join(', ')}`);
} else {
core.notice('[health-check] PASS: no root cause failures detected');
}
评论区精华
本 PR 没有有效的 review 讨论线程,comments_count 中仅有的 2 条均为 sglang-npu-bot 自动触发的 /tag-and-rerun-ci 命令;设计权衡主要通过代码内注释和 120 次 commit 的演进体现(例如健康检查中过滤 h20 与级联失败的注释说明)。
风险与影响
- 风险:
- 健康检查误判风险:
.github/workflows/_npu-single-node-test-stage.yml 与 _npu-pr-test-stage.yml 中的根因失败判定依赖 job 名称过滤(如忽略 base-c-test-8-gpu-h20)和失败步骤名称识别级联失败,一旦 job 命名规则变化或 check run 状态未同步,可能误杀正常测试或漏判真实失败。
- 套件合并增大失败定位成本:多个模型用例合并进同一 base-c 套件后,单个用例崩溃可能导致整个 job 失败并重跑整个套件;虽然
_save_metrics_json 提供了 per-case 产物,但 CI 层面仍需依赖 Python 日志区分具体失败用例。
- 测试严格性下降:
test/registered/npu/accuracy/glm5_top64_pruned/test_npu_glm5_top64_pruned_bf16_8p_gsm8k.py 中 accuracy 阈值从 0.50 下调到 0.48,可能削弱回归防线,需确认是否因 flaky 基线调整。
- 注册名变更范围大:60 个文件中的
register_npu_ci 套件名同步变更,若 run_suite.py 注册表与某个用例的 suite 名不一致,该用例会被静默跳过。
- 外部可移植性受限:工作流内硬编码了华为云镜像地址(
swr.cn-southwest-2.myhuaweicloud.com)与集群内缓存服务(cache-service.nginx-pypi-cache.svc.cluster.local),fork 仓库或非 NPU 集群无法直接复用。
- 影响:影响范围集中在 NPU 硬件上的 PR CI 流程:显著降低单次 PR 触发的并发 job 数,缓解 K8s 调度排队;通过健康检查快速失败减少无效 CI 消耗;对 NPU 相关开发者而言 PR 测试周期更短、失败反馈更早。对运行时、推理性能没有影响,但涉及 CI 基础设施的维护约定(套件命名、注册方式、输出目录),后续新增 NPU 用例需要遵循新的 base-c 套件分类。
- 风险标记:CI 快速失败可能误判, 套件合并增大失败定位成本, 阈值下调削弱回归防线, 大量注册名变更, 依赖内部镜像与缓存服务
关联脉络
- PR #34070 [CI] Trim redundant nightly test registrations: 同一时期对测试注册进行裁剪与合并,目标同为节省 CI 资源,反映测试套件治理的延续方向。
- PR #32505 [UT][NPU] add unit tests for ascend_torch_native_backend and mla_preprocess: NPU 测试体系扩展的一部分,与本 PR 共同完善 NPU CI 覆盖。
- PR #34041 Docker: install DeepEP from release wheels: 同为 CI 基础设施优化(构建与安装流程),与本 PR 的 workflow 调整处于同一演进线上。
参与讨论