Prhub

#33685 [NPU CI] Reorganize test output/log directory structure with workflow context

原始 PR 作者 pllimax 合并时间 2026-08-18 23:46 文件变更 139 提交数 38 评论 59 代码增减 +776 / -685

执行摘要

重构 NPU CI 日志输出目录,nightly 单节点改 suite 模式

PR body 明确指出动机:Restructure NPU CI output/log persistence so runs from different workflows are distinguishable by directory and re-runs never overwrite prior results, and migrate nightly single-node tests from a per-case matrix to per-suite jobs. 旧布局 {test_type}/{date}/{tc_name} 下,PR test、nightly、full test 等不同 workflow 的结果混在同一日期目录,难以追溯来源;同一 workflow 的 re-run 还会覆盖同名目录,导致历史结果丢失。

值得精读,特别是以下三个设计决策:1)run_start_metadata 单点记录运行元数据并用 fromJson 分发的模式,避免了多参数透传的冗余与不一致;2)partition 并行 + concurrency group 追加 partition id 的组合,既控制 job 时长又防止并行 job 互相取消;3)测试工具对 suite 级 METRICS_DATA_FILE 的回退兼容设计,让同一份工具代码同时服务 PR 与 nightly 两种布局。建议 CI 维护者关注路径解析启发式在后续布局演进时的脆弱性。

讨论亮点

review 主要由 cherryblo 主导,核心交锋如下:

  • 冗余人参删除:针对显式 branch_label / workflow_name / create_date / timestamp inputs,cherryblo 多次要求 "delete it"、"The newly added parameter has no practical usage scenarios, remove it.";最终收敛为 run_start_metadata 单一 JSON,简化模板输入契约。
  • 模板统一:cherryblo 要求 "Adopt the new single-node test template." 和 "Use the workflow file .github/workflows/_npu-pr-test-stage.yml.",促使 nightly 功能 job 最终复用 _npu-pr-test-stage.yml,单节点 job 复用 _npu-single-node-test-stage.yml
  • metrics upload 回退:cherryblo 要求 "Revert this change. Metrics upload related code will not be added for now.",最终删除 suite 级 Upload metrics 步骤,依赖 nightly 日志整体收集。
  • LOG_TEE_TARGET 与 tee 空参数:cherryblo 质疑 "What is the purpose of the LOG_TEE_TARGET variable? Is the usage of tee /tmp/test_output.log... correct?";后续 commit 用数组方式避免 PR 场景传空字符串给 tee(GNU tee 空参数会 exit 1 翻转管道退出码)。
  • RUN_LABEL / TIMESTAMP 用途质疑:cherryblo 问 "What are the purposes of these two input variables?" 和 "Do we require a timestamp to validate CI job reruns?";最终保留 RUN_LABEL 用于 pod 日志目录前缀,timestamp 字段被移除(唯一性由 run_id/run_attempt 保证)。
  • shell 严格模式与外部脚本冲突:commit 记录 "Fix set -u abort on Ascend env scripts",通过临时 set +u 包裹 source 外部 env 脚本解决。

实现拆解

  1. 运行元数据单点生成与传递:
    • .github/workflows/nightly-test-npu.ymlset-vars step 中计算 BRANCH_LABEL(优先取 github.event.pull_request.head.label,否则取 github.ref_name,再用 tr -cd 'a-zA-Z0-9._/:' + tr '/:' '--' 清洗成路径安全字符),并通过 python3 打包 run_start_metadata JSON(含 branch_label、workflow_name、create_time),create_timedate -u -d '+8 hours' +%Y%m%d-%H%M 生成(UTC+8 分钟精度),在 workflow 启动时记录一次,所有 job 共享同一前缀,跨午夜也不变。
    • 响应 review 中 cherryblo 反复提出的 "delete it",PR 侧显式的 branch_label / workflow_name / create_date / timestamp inputs 最终收敛为单一 run_start_metadata JSON 传递。
  2. 结构化目录落盘:
    • _npu-single-node-test-stage.ymlfromJson(inputs.run_start_metadata) 解析元数据,拼接 output/log 路径;github.run_id + github.run_attempt 保证重跑不覆盖;partition job 追加 -p{id} 后缀防止结果目录冲突。
    • concurrency group 追加 auto_partition_id,避免并行 partition 互相 cancel。
    • nightly-test-npu-e2e-multi-node.yml 同步适配同一套路径布局。
  3. nightly 单节点 suite 化:
    • 删除 nightly-test-npu-e2e-single-node.yml(-257 行),nightly-test-npu.yml 改为 nightly-perf/acc-{2,4,16}-npu-a3 等 6 个 suite jobs 加 a2 mix suite,acc-2 拆 4 个并行 partition 控制 5h 超时。
    • _npu-pr-test-stage.yml 新增 is_nightly_pipeline_job 输入:注入 --nightly --continue-on-error、预构建镜像 job 跳过依赖安装、动态补装 tabulate 等缺失依赖。
    • test/run_suite.py 注册 NIGHTLY_SUITES,并把 71 个仅 nightly 注册的用例迁到 full-{1,2,4,8,16}-npu-a3,使 nightly 与 PR 注册严格镜像。
  4. 测试工具与 k8s 模板适配:
    • test_npu_performance_utils.py / test_npu_accuracy_utils.py_setup_per_case_output 支持 suite 级 METRICS_DATA_FILE,per-case 结果落在 {suite}/{tc_name}_backup_plogplog_base 镜像 output 前缀;_save_metrics_json 写完后清理中间 JSONL。
    • run_npu_e2e_test.pygenerate_metrics_jsonoutput 后剩余段数解析 test_typerun_npu_e2e_test_case 从 metrics_data_file 推导 run_label 注入 k8s pod。
    • run_npu_testcase.shRUN_LABEL 构建 pod 内 log_path;k8s_multi_pd_separation.yaml.jinja2k8s_multi_pd_mix.yaml.jinja2k8s_single.yaml.jinja2 新增 RUN_LABEL / TROUBLE_SHOTTING env。
  5. 稳健性配套:
    • install_pkg 多源 pip 重试(pypi、清华镜像、集群内缓存),REQUIRED_PIP_PKGS 按需补装。
    • 显式 set -euo pipefail,对 Ascend 外部 env 脚本用 set +u 包裹避免未定义变量(如 ZSH_VERSION)中断脚本。
    • 修复 transformers 本地 wheel 拷贝时 glob 被引号包裹不展开的问题,以及 PR 场景下 GNU tee 收到空字符串参数导致 exit 1 的 bug。
    • 校准 est_time 阶梯(acc 归一 4800s、length 分级),deepseek_v4_flash 等长时 case 单独缩短到 1200s。
文件 模块 状态 重要度
.github/workflows/nightly-test-npu.yml 夜间流水线 modified 6.14
.github/workflows/_npu-single-node-test-stage.yml 单节点模板 modified 5.96
.github/workflows/nightly-test-npu-e2e-single-node.yml 旧单节点模板 removed 6.26
python/sglang/test/ascend/e2e/test_npu_performance_utils.py 性能工具 modified 5.68
python/sglang/test/ascend/e2e/test_npu_accuracy_utils.py 精度工具 modified 5.66
python/sglang/test/ascend/e2e/run_npu_e2e_test.py E2E 驱动 modified 5.6
.github/workflows/_npu-pr-test-stage.yml PR 测试模板 modified 4.84
.github/workflows/nightly-test-npu-e2e-multi-node.yml 多节点模板 modified 4.78
python/sglang/test/ascend/e2e/run_npu_testcase.sh 用例脚本 modified 4.67
test/run_suite.py 套件执行器 modified 4.47
python/sglang/test/ascend/e2e/k8s_multi_pd_separation.yaml.jinja2 K8s 模板 modified 4.33
test/registered/npu/performance/glm5_1/test_npu_glm5_1_w4a8_1p1d_32p_in64k_out1k_50ms.py 性能用例 modified 4.38

关键符号

_setup_per_case_output _save_metrics_json _backup_plog generate_metrics_json run_npu_e2e_test_case

关键源码片段

python/sglang/test/ascend/e2e/test_npu_performance_utils.py test-coverage

性能测试基类适配 suite 级输出目录:_setup_per_case_output 支持 METRICS_DATA_FILE 回退、plog 前缀镜像、JSONL 清理,是测试工具侧的关键改动。

@classmethod
def _setup_per_case_output(cls):
    """让每个用例在 suite 输出目录下拥有独立子目录。    当 workflow 把 METRICS_DATA_FILE 指向 suite 级目录(例如
    .../output/{branch_label}-{create_time}-{run_id}-{run_attempt}/
    {workflow_name}/{test_type}/{suite})时,把当前用例结果写到
    {suite}/{tc_name}/ 下;没有该环境变量时回退到旧 per-case 布局。
    """
    cls.tc_name = cls._get_tc_name()
    suite_output = os.environ.get("METRICS_DATA_FILE")
    if suite_output:
        # 在 suite 输出前缀下追加用例 id,保证结果按用例归位
        cls.metrics_data_file = os.path.join(suite_output, cls.tc_name)
        # 把 output 前缀镜像成 plog 前缀(去掉 test_type/suite 两段尾部)
        suite_plog = suite_output.replace("/output/", "/logs/plog/", 1)
        cls.plog_base = os.path.dirname(os.path.dirname(suite_plog))
    else:
        # 旧布局 {test_type}/{date}/{tc_name},等价于删除前 workflow 的行为
        current_date = datetime.now().strftime("%Y%m%d")
        test_type = getattr(cls, "test_type", "perf")
        base_output = f"/root/.cache/tests/output/{test_type}/{current_date}"
        cls.metrics_data_file = os.path.join(base_output, cls.tc_name)
        cls.plog_base = "/root/.cache/tests/logs/plog"
    os.makedirs(cls.metrics_data_file, exist_ok=True)
    # 让 evalscope / dump_metric 都写到 per-case 路径
    os.environ["METRICS_DATA_FILE"] = cls.metrics_data_file
    os.environ["SGLANG_TEST_METRICS_OUTPUT"] = os.path.join(
        cls.metrics_data_file, "metrics"
    )
    logger.info(
        "Per-case output: tc_name=%s metrics_data_file=%s",
        cls.tc_name,
        cls.metrics_data_file,
    )
python/sglang/test/ascend/e2e/run_npu_e2e_test.py test-coverage

多节点 e2e 驱动:generate_metrics_json 需按 PR/nightly 两种目录结构解析 test_type,run_npu_e2e_test_case 从输出路径推导 run_label 注入 k8s pod。

def generate_metrics_json(metrics_data_file, test_case, status):
    """把测试 stdout 里的 [METRIC] 行汇总成每用例 metrics.json。    nightly 目录比 PR 目录多出 {branch}-{date}-{run_id}-{run_attempt}/{workflow}
    两层前缀,因此 output 之后剩余段数不同,test_type 的偏移也不同。
    """
    log_file = os.path.join(metrics_data_file, "test_output.log")
    metrics, baselines = {}, {}
    if os.path.exists(log_file):
        with open(log_file, "r") as f:
            for line in f:
                m = re.match(r"\[METRIC\] (\S+)=(\S+)", line.strip())
                if m:
                    key, value = m.group(1), m.group(2)
                    try:
                        value = float(value)
                    except ValueError:
                        pass
                    # _baseline 后缀的指标单独归类,便于后续对比
                    if key.endswith("_baseline"):
                        baselines[key[:-9]] = value
                    else:
                        metrics[key] = value
​
    tc_name = test_case.rsplit("/", 1)[-1].rsplit(".", 1)[0]
    test_type = "unknown"
    # nightly: .../output/{branch}-{date}-{run_id}-{run_attempt}/{workflow}/{test_type}/...
    # PR: .../output/{test_type}/{date}/{tc_name}
    parts = metrics_data_file.split("/")
    for i, part in enumerate(parts):
        if part == "output":
            rest = len(parts) - (i + 1)
            # 剩余段数 >= 4 说明是 nightly 结构,test_type 在 output 后第 4 段
            if rest >= 4:
                test_type = parts[i + 3]
            elif rest >= 1:
                test_type = parts[i + 1]
            break
    # 其余逻辑不变:写 metrics.json 并同步一份到 /tmp/metrics.json

评论区精华

显式 branch_label/workflow_name/create_date/timestamp inputs 是否冗余 设计

cherryblo 在 nightly-test-npu.yml、pr-test-npu.yml、nightly-test-npu-e2e-single-node.yml 上多次要求删除显式输入:The newly added parameter has no practical usage scenarios, remove it;delete it;There is redundant definition for the inputs variable here。

结论:最终收敛为单一 run_start_metadata JSON(branch_label + workflow_name + create_time),在 workflow 启动时记录一次,经 needs 传递,删除了所有显式透传 inputs。 · 已解决

nightly job 应复用哪个测试模板 设计

cherryblo 建议:Adopt the new single-node test template;Use the workflow file .github/workflows/_npu-pr-test-stage.yml;Keep the test suite name consistent with A3。

结论:单节点与 a2 suite 复用 _npu-single-node-test-stage.yml,nightly-{1,2,4,8,16}-npu-a3 等功能 job 复用 _npu-pr-test-stage.yml;两者都通过 is_nightly_pipeline_job 注入 nightly 专属行为。 · 已解决

suite 级 metrics upload 是否引入 设计

cherryblo 明确回退:Revert this change. Metrics upload related code will not be added for now;Revert this change(针对 nightly-test-npu.yml 的 metrics 收集循环)。

结论:删除 suite 级 Upload metrics 步骤与 per-case log merge,nightly 日志整体收集已覆盖测试结果,避免重复造轮子。 · 已解决

LOG_TEE_TARGET 与 tee 空字符串参数 正确性

cherryblo 质疑:What is the purpose of the LOG_TEE_TARGET variable? Is the usage of tee /tmp/test_output.log ${LOG_TEE_TARGET} correct? 后续 commit 发现 PR 场景下为空字符串时 GNU tee 会 exit 1 并翻转管道退出码。

结论:用日志目标数组构建 tee 参数,PR 路径下不传空字符串;同时显式 set -euo pipefail 保证退出码反映 run_suite.py 真实结果。 · 已解决

RUN_LABEL / TIMESTAMP 环境变量的用途 question

cherryblo 在 k8s 模板与 run_npu_e2e_test.py 上提问:What are the purposes of these two input variables? Do we need this input parameter? Do we require a timestamp to validate CI job reruns?

结论:RUN_LABEL 保留,用于把 pod 内 log_path 与 CI 结构化目录前缀对齐;timestamp 字段最终被移除(最后一次重构 commit 明确:目录唯一性由 run_id/run_attempt 与 tc_name 保证)。 · 已解决

set -euo pipefail 与外部 Ascend env 脚本冲突 正确性

开启 shell 严格模式后,source 的 Ascend 外部 env 脚本引用未定义变量(如 ZSH_VERSION),直接中断整个 workflow 脚本。

结论:在 source cann / nnal / ascend-toolkit 等外部脚本前临时 set +u,source 完成后恢复 set -u,并加注释说明原因。 · 已解决

partition 并行与 concurrency group 冲突 设计

acc-2 拆成多个并行 partition 后,原 concurrency group 会把同一 suite 的不同 partition job 视为互相冲突而 cancel;cherryblo 要求解释该改动原因。

结论:concurrency group 追加 auto_partition_id,使并行 partition 可同时运行;partition job 在目录与 artifact 名上加 -p{id} 后缀避免碰撞。 · 已解决

BRANCH_LABEL 变量被重复赋值 style

cherryblo 指出:The variable ${BRANCH_LABEL} is being assigned values twice,并追加要求 Add explanatory comments。

结论:重构为单一赋值链(explicit input > PR head label > ref name),并补充脚本注入防护注释:PR 元数据经 env 变量传入避免 fork 分支名造成脚本注入。 · 已解决

风险与影响

  1. nightly-test-npu.yml 大规模重构(+289/-216):suite 定义、runner 对齐、needs 依赖任一配置错误都会导致 nightly 整体漏跑或误跑;历史上已出现 a2 依赖未安装、suite runner 与 PR job 不一致等问题,需关注 runner 标签(a3-800t-N vs a3-N)的匹配。
  2. run_npu_e2e_test.py 的路径解析依赖目录段数(rest >= 4 判定 nightly 布局):该启发式对后续目录结构调整非常敏感,布局再变会静默解析出错误的 test_type 或 run_label,且无显式告警。
  3. run_npu_testcase.shrun_label 默认值是 unknown:若 RUN_LABEL env 未通过 k8s 模板注入(例如新增的 pod 类型遗漏修改 jinja2),日志会全部归拢到 unknown 目录,掩盖可追溯性改进。
  4. run_suite.py 的 NIGHTLY_SUITES 与 71 个用例迁移:nightly 与 PR 注册要求严格镜像,注册遗漏会造成 nightly 覆盖偏差,且这种偏差无自动校验。
  5. set -euo pipefail 在全 shell 开启后,任何外部脚本或测试工具的未定义变量/非零退出都会立即失败;已通过 set +u 缓解 Ascend env 脚本问题,但风险面仍较大。
  6. acc-2 拆 4 partition 依赖 est_time 阶梯的准确性,划分不均可能导致某个 partition 超过 5h job 超时上限。

影响范围集中在 NPU CI 基础设施层:所有 NPU 单节点/多节点 nightly 与 PR 测试的 output、log、plog 目录结构全部变更,结果可追溯性显著提升(workflow + run_attempt 均可区分);nightly 单节点测试从大量 matrix job 收敛为少量 suite job,配合 partition 并行,单 job 时长更可控,GitHub Actions 页面不再被打爆。对使用者(模型开发者、CI 维护者)而言,_npu-single-node-test-stage.yml_npu-pr-test-stage.yml 的输入契约发生破坏性变化,任何外部复用这两个模板的 workflow 都需要同步更新;对普通用户无任何模型行为影响(PR body 明确声明 accuracy/speed 均不受影响)。影响等级中等偏高,但局限在 NPU CI 域。

CI 全链路重构 shell 严格模式风险 目录段数启发式解析 suite 注册映射易错 RUN_LABEL 注入依赖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论