Prhub

#27433 [NPU] Nightly CI refactor and enhancement

原始 PR 作者 hhhh1252023 合并时间 2026-06-26 08:52 文件变更 58 提交数 45 评论 21 代码增减 +10917 / -582

执行摘要

NPU 夜间 CI 指标仪表板和可重用工作流

现有的 NPU 夜间测试工作流只输出简单的通过/失败门控,没有每个测试结果的可视性。当失败发生时,必须手动检查各个作业日志以确定哪些模型或配置回归了。此外,测试指标值及其基线不以结构化方式捕获或比较,使得难以跟踪性能趋势。

该 PR 适合 NPU 相关开发者和 CI 运维人员精读。值得关注的设计决策包括:

  • 基于 stdout 模式匹配的轻量级指标收集方案,无需额外服务。
  • 可重用工作流模式(单节点、多节点)降低重复配置。
  • 优雅处理测试失败(即使失败也生成指标工件)确保仪表板完整性。
  • 自动网络接口选择逻辑,对多机部署有参考价值。
讨论亮点

Review 中主要讨论了以下问题:

  • 工作流文件位置:iforgetmyname 建议将可重用工作流移到 python/sglang/test/ascend,但作者指出 GitHub Actions 要求本地工作流引用必须在 .github/workflows 下,因此维持原位置。
  • 多节点镜像架构:iforgetmyname 指出多节点模板默认使用 linux-aarch64-a3-0 runner 但容器镜像是 x86,作者解释 CPU 控制节点是 x86 架构,因此使用 x86 镜像,但 runner 标签应改为 linux-amd64-cpu-8,后续提交中已修正。
  • 删除冗余文件:iforgetmyname 要求删除 run_aisbench.sh,作者确认删除。
  • 环境变量清理:iforgetmyname 询问是否应移除工作流中的某个环境变量,作者后续提交已调整。

实现拆解

  1. 重构 .github/workflows/nightly-test-npu.yml,将其拆分为可重用工作流编排:单节点测试、多节点 PD 分离和多节点混合部署。添加 check-all-jobs 门控,通过 GITHUB_STEP_SUMMARY 输出指标仪表板,包含每个测试的状态、测量值和基线对比。

  2. 新增 .github/workflows/nightly-test-npu-e2e-single-node.yml 可重用工作流,使用 || test_exit_code=$? 优雅处理测试失败,确保即使失败也生成 metrics.json 并上传为工件(if: always())。

  3. 新增 .github/workflows/nightly-test-npu-e2e-multi-node.yml 可重用工作流,通过 Kubernetes 编排多节点测试,支持 PD 分离和混合部署模式。

  4. 添加 Python 测试工具库:

    • test_npu_performance_utils.py:基类定义 assert_metrics(),通过 dump_metric() 输出吞吐量、TPOT、TTFT、E2E 延迟及其基线。
    • test_npu_accuracy_utils.py:基类定义 run_evalscope()assert_metrics(),转储准确率和基线。
    • gen_dataset_fixed_len.py:生成固定 token 长度数据集的工具,支持 GSM8K 和多模态数据。
    • run_npu_e2e_test.py:K8s 资源管理,包括创建 Pod、Job、ConfigMap、Role 等,支持 Jinja2 模板。
    • test_npu_multi_node_utils.py:多节点辅助函数,如自动检测网络接口、角色启动、环境变量设置。
  5. 新增多个具体测试用例(约 15 个文件),覆盖 Qwen3、DeepSeek、MiniMax、GLM5 等模型系列,验证不同精度、输入长度、前缀缓存和多节点配置下的性能和准确率。所有测试用例通过 register_npu_ci 注册到 CI 框架。

文件 模块 状态 重要度
.github/workflows/nightly-test-npu.yml CI 流程 modified 6.72
python/sglang/test/ascend/e2e/test_npu_performance_utils.py 性能测试工具 added 7.76
python/sglang/test/ascend/e2e/gen_dataset_fixed_len.py 数据集生成 added 7.48
python/sglang/test/ascend/e2e/test_npu_accuracy_utils.py 准确率测试工具 added 7.48
python/sglang/test/ascend/e2e/run_npu_e2e_test.py 测试编排 added 7.48
.github/workflows/nightly-test-npu-e2e-single-node.yml 单节点工作流 added 6.0
python/sglang/test/ascend/e2e/test_npu_multi_node_utils.py 多节点工具 added 7.49

关键符号

assert_metrics run_evalscope pad_to_target_tokens create_pod get_nic_name run_bench_serving

关键源码片段

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

数据集生成工具,用于创建固定 token 长度输入,支撑多种测试场景。

def pad_to_target_tokens(
    question,
    few_shot_pool_token_ids,
    tokenizer,
    target_tokens,
    test_template="Question: {question}\nLet's think step by step\nAnswer:\n",
):
    """Pad a question text to the target token length."""
    # 生成测试 prompt 并获取其 token ID
    test_prompt = test_template.format(question=question)
    test_token_ids = tokenizer.encode(test_prompt, add_special_tokens=False)
​
    # 计算还需要填充的 token 数
    remaining_tokens = target_tokens - len(test_token_ids)
    if remaining_tokens <= 0:
        # 如果已经超出目标长度,直接截断
        return tokenizer.decode(test_token_ids[:target_tokens], skip_special_tokens=True)
​
    # 随机打乱 few-shot pool 索引
    shuffled_ids = list(range(len(few_shot_pool_token_ids)))
    random.shuffle(shuffled_ids)
​
    # 从 pool 中抽取 token 填充到 prefix
    prefix_ids = []
    for idx in shuffled_ids:
        fs_ids = few_shot_pool_token_ids[idx]
        if len(prefix_ids) + len(fs_ids) <= remaining_tokens:
            prefix_ids.extend(fs_ids)
        else:
            # 如果超出剩余空间,只取部分
            partial_gap = remaining_tokens - len(prefix_ids)
            if partial_gap > 0:
                prefix_ids.extend(fs_ids[:partial_gap])
            break
​
    # 如果 pool 不够填充,重复第一个样本直到填满
    if len(prefix_ids) < remaining_tokens and few_shot_pool_token_ids:
        padding_source_ids = few_shot_pool_token_ids[shuffled_ids[0]]
        repeat_count = (remaining_tokens // len(padding_source_ids)) + 1
        padding_ids = (padding_source_ids * repeat_count)[:remaining_tokens - len(prefix_ids)]
        prefix_ids.extend(padding_ids)
​
    # 拼接 prefix 和测试 prompt,并解码
    full_ids = prefix_ids + test_token_ids
    return tokenizer.decode(full_ids[:target_tokens], skip_special_tokens=True)

评论区精华

工作流文件应放在何处 设计

iforgetmyname 建议将可重用工作流移到 python/sglang/test/ascend 目录,更靠近测试代码。

结论:作者指出 GitHub Actions 要求本地工作流引用必须位于 .github/workflows,因此维持原位。 · 已解决

多节点默认 runner 架构与容器镜像不匹配 正确性

iforgetmyname 发现默认 runner 为 linux-aarch64-a3-0 但容器镜像是 x86,询问原因。

结论:作者说明 CPU 控制节点是 x86 架构,后续将 runner 改为 linux-amd64-cpu-8。 · 已解决

删除未使用的脚本文件 other

iforgetmyname 要求删除 run_aisbench.sh。

结论:作者确认删除。 · 已解决

清除不必要的环境变量 question

iforgetmyname 询问是否应移除工作流中某个环境变量。

结论:作者在后续提交中已调整,推测已解决。 · 已解决

风险与影响

  • 配置风险:大量新增工作流和测试用例可能引入 YAML 或脚本错误,导致夜间测试不稳定。
  • K8s 依赖:多节点测试依赖外部 Kubernetes 集群,集群不可用或资源不足将导致测试失败。
  • 基线硬编码:基线值硬编码在测试文件中(如 tpot_baseline: 0.05),模型升级或环境变化后可能需手动更新,否则可能误报回归。
  • 网络接口检测get_nic_name() 基于 /proc/net/dev 接口流量选择,在某些虚拟化环境中可能选错,导致多机通信失败。
  • 数据集生成gen_dataset_fixed_len.py 的 token 填充逻辑假设 tokenizer 兼容,未测试所有模型 tokenizer。

影响范围:仅影响 NPU 夜间 CI 流程,不涉及 SRT 核心推理代码。

  • 对用户:无直接影响。
  • 对系统:夜间测试从简单 pass/fail 升级为结构化指标仪表板,显著提升可观察性。
  • 对团队:NPU 团队能更快定位性能回归和准确率退化;CI 运维人员需维护 K8s 集群和环境。
  • 影响程度:中等,因为涉及大量新配置和外部依赖。
K8s 外部依赖 基线硬编码 多节点配置复杂 大量新增配置可能不稳定

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论