执行摘要
- 一句话:NPU 夜间 CI 指标仪表板和可重用工作流
- 推荐动作:该 PR 适合 NPU 相关开发者和 CI 运维人员精读。值得关注的设计决策包括:
- 基于 stdout 模式匹配的轻量级指标收集方案,无需额外服务。
- 可重用工作流模式(单节点、多节点)降低重复配置。
- 优雅处理测试失败(即使失败也生成指标工件)确保仪表板完整性。
- 自动网络接口选择逻辑,对多机部署有参考价值。
功能与动机
现有的 NPU 夜间测试工作流只输出简单的通过/失败门控,没有每个测试结果的可视性。当失败发生时,必须手动检查各个作业日志以确定哪些模型或配置回归了。此外,测试指标值及其基线不以结构化方式捕获或比较,使得难以跟踪性能趋势。
实现拆解
-
重构 .github/workflows/nightly-test-npu.yml,将其拆分为可重用工作流编排:单节点测试、多节点 PD 分离和多节点混合部署。添加 check-all-jobs 门控,通过 GITHUB_STEP_SUMMARY 输出指标仪表板,包含每个测试的状态、测量值和基线对比。
-
新增 .github/workflows/nightly-test-npu-e2e-single-node.yml 可重用工作流,使用 || test_exit_code=$? 优雅处理测试失败,确保即使失败也生成 metrics.json 并上传为工件(if: always())。
-
新增 .github/workflows/nightly-test-npu-e2e-multi-node.yml 可重用工作流,通过 Kubernetes 编排多节点测试,支持 PD 分离和混合部署模式。
-
添加 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:多节点辅助函数,如自动检测网络接口、角色启动、环境变量设置。
-
新增多个具体测试用例(约 15 个文件),覆盖 Qwen3、DeepSeek、MiniMax、GLM5 等模型系列,验证不同精度、输入长度、前缀缓存和多节点配置下的性能和准确率。所有测试用例通过 register_npu_ci 注册到 CI 框架。
关键文件:
.github/workflows/nightly-test-npu.yml(模块 CI 流程;类别 infra;类型 ci-config): 主 CI 工作流,重构为门控仪表板,编排所有测试组,是本次变更的核心调度文件。
python/sglang/test/ascend/e2e/test_npu_performance_utils.py(模块 性能测试工具;类别 test;类型 test-coverage;符号 retry, get_cann_version, write_pkg_info_to_file, run_bench_serving): 性能测试基类,定义 assert_metrics 和指标转储方法,是所有性能测试的公共依赖。
python/sglang/test/ascend/e2e/gen_dataset_fixed_len.py(模块 数据集生成;类别 test;类型 test-coverage;符号 load_jsonl, save_jsonl, format_qa, pad_to_target_tokens): 数据集生成工具,用于创建固定 token 长度输入,支撑多种测试场景。
python/sglang/test/ascend/e2e/test_npu_accuracy_utils.py(模块 准确率测试工具;类别 test;类型 test-coverage;符号 run_evalscope, assert_metrics, TestNpuAccuracyTestCaseBase, setUpClass): 准确率测试基类,封装 evalscope 调用和断言逻辑。
python/sglang/test/ascend/e2e/run_npu_e2e_test.py(模块 测试编排;类别 test;类型 test-coverage;符号 get_unique_random_string, create_kube_yaml, create_pod, delete_pod): K8s 编排脚本,支持创建/删除 Pod、Job、ConfigMap 等,是多节点测试的关键组件。
.github/workflows/nightly-test-npu-e2e-single-node.yml(模块 单节点工作流;类别 infra;类型 ci-config): 单节点端到端可重用工作流,定义指标收集和工件上传流程。
python/sglang/test/ascend/e2e/test_npu_multi_node_utils.py(模块 多节点工具;类别 test;类型 test-coverage;符号 get_nic_name, get_host_name, get_host_ip, get_k8s_api): 多节点测试辅助函数,包括网络接口自动选择、角色启动、ConfigMap 查询等。
关键符号: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
数据集生成工具,用于创建固定 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)
评论区精华
Review 中主要讨论了以下问题:
风险与影响
- 风险:
- 配置风险:大量新增工作流和测试用例可能引入 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 外部依赖, 基线硬编码, 多节点配置复杂, 大量新增配置可能不稳定
关联脉络
参与讨论