From 3964b3d210e7d4002926fce4d03d466223e3ca5a Mon Sep 17 00:00:00 2001 From: Zhiyi Hong <2497491955@qq.com> Date: Fri, 31 Jul 2026 13:13:57 +0800 Subject: [PATCH] [Docs] add Phase 1 and Phase 2 code walkthroughs --- README.md | 4 + .../phase1_code.html | 593 ++++++++++++++++++ .../phase2_code.html | 586 +++++++++++++++++ 3 files changed, 1183 insertions(+) create mode 100644 docs/dsv4pro_pro6000d_2node_sglang/phase1_code.html create mode 100644 docs/dsv4pro_pro6000d_2node_sglang/phase2_code.html diff --git a/README.md b/README.md index 131abec..a0b47e7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # sskj — 多平台大模型推理性能基准测试项目 +> **更新(2026-07-31 13:11:40 CST)** +> +> 新增 Phase 1 与 Phase 2 的独立代码详解 HTML 档案,行号固定到提交 `ca1f2f63375c`。文档从唯一入口展开到配置来源、文件调用关系、双机服务与 RDMA 门禁、benchmark 请求生成、混合 Prefill/Decode 时序、两节点采集器、Case 时间窗切片和结构化结果,并为 `MEM_FRACTION_STATIC` 等关键变量记录“默认值定义 → Shell 传递 → 服务参数 → Run 证据”的完整追踪路径。代码档案保持独立,不加入主计划 HTML 或阶段介绍 HTML 的导航。 +> > **更新(2026-07-31 11:57:13 CST)** > > 实现 DeepSeek-V4-Pro 双机 Pro6000D SGLang TP16 的 Phase 2 硬件与资源竞争归因。新增唯一入口 `run_hardware_contention_attribution.sh`,内部复用 Phase 1 的双机服务与 benchmark,不要求用户手工启动 Phase 1;默认重放长/并发 Prefill、普通/持续/长上下文 Decode 和混合 Prefill/Decode A/B。Head 与 Worker 在同一诊断窗口采集 GPU、DCGM、CPU、进程、NUMA、`eth0/eth3` 和 `mlx5_0/mlx5_3` RDMA 数据,并保存 Case marker、完整命令、Manifest 和结构化汇总。正式执行只需运行 Phase 2 的 `all` 入口。 diff --git a/docs/dsv4pro_pro6000d_2node_sglang/phase1_code.html b/docs/dsv4pro_pro6000d_2node_sglang/phase1_code.html new file mode 100644 index 0000000..5f4e157 --- /dev/null +++ b/docs/dsv4pro_pro6000d_2node_sglang/phase1_code.html @@ -0,0 +1,593 @@ + + + + + + + Phase 1 Code:DSV4-Pro 双机 Pro6000D SGLang 快速性能地图 + + + +
+
+

Standalone Code Walkthrough / Phase 1

+

DSV4-Pro 双机 Pro6000D SGLang 快速性能地图:代码详解

+
+ 行号基线:ca1f2f63375c  + 生成时间:2026-07-31 13:04:21 CST  + 入口:run_quick_map.sh +
+
+
+ +
+
+ 文档边界:这是一份独立代码档案,只解释 Phase 1 实现,不承担阶段结论展示。 + 下文的行号均绑定提交 ca1f2f63375c。代码变更后应先更新基线提交,再重新核对行号。 +
+ +

1. 阅读方法

+ +

+ 行号写法例如 + run_quick_map.sh:L212-L268。它表示该提交中,从第 212 行到第 268 行的完整函数段, + 不是当前编辑器自动漂移后的行号。 +

+ +

2. 总体控制流

+
main "$@"
+  └─ ACTION=all → run_all
+       ├─ 校验场景与客户端
+       ├─ start_service
+       │    ├─ Worker 节点先启动
+       │    ├─ Head 节点后启动
+       │    ├─ 等待 /health
+       │    └─ 从两端日志验证 NET/IB + 两条 HCA
+       ├─ run_fixed_suite
+       │    └─ TSV 每一行 → run_bench_case
+       ├─ run_mixed_suite
+       │    └─ control → decode background + long prefill injection
+       ├─ stop_service
+       ├─ summarize_results
+       └─ complete_manifest
+

+ Shell 负责生命周期、远端执行、容器和失败策略;Python 负责结果读取、指标补算、聚合与报告。 + 这条分工是理解代码的第一把钥匙。 +

+ +

3. 文件职责

+ + + + + + + + + + + + + + + + + + + + + + + + +
文件行数职责主要输出
run_quick_map.sh957唯一入口,管理双机服务、固定场景、混合场景、失败恢复与清理。run.log、服务日志、每个 Case 的命令与原始结果。
config.env78模型、节点、SGLang、NCCL/RDMA、benchmark、超时和路径配置。被 Shell 直接 source,自身不产生输出。
quick_map_scenarios.tsv12固定性能地图的声明式场景表,一行对应一个 Case。输入给 run_fixed_suite
quick_map_results.py716校验 bench JSON、补算百分位、生成 meta/manifest、聚合重复实验。summary.csvsummary.jsonlaggregate.csvreport.md
+ +

3.1 文件之间如何调用

+
用户
+  └─ bash run_quick_map.sh all
+       ├─ source config.env
+       │    ├─ 给 Shell 提供模型、节点、服务、NCCL 和 benchmark 变量
+       │    └─ 计算 SCENARIO_FILE / RESULT_BASE / RUNTIME_BASE
+       ├─ 读取 quick_map_scenarios.tsv
+       │    └─ 每一行变成一次 run_bench_case 调用
+       ├─ 调用 quick_map_results.py
+       │    ├─ validate-scenarios:启动前校验 TSV
+       │    ├─ write-case / mark-case-failed:维护 Case 状态
+       │    ├─ write-manifest / complete-manifest:维护 Run 状态
+       │    ├─ check-bench:验证 bench.json
+       │    └─ summarize:生成 CSV、JSONL 和报告
+       └─ tests/test_quick_map_results.py
+            └─ 只测试 Python 解析和聚合,不启动模型
+

+ run_quick_map.sh:L6-L16 是关系的起点:先定位自身目录,再 + source config.env,随后把结果工具固定为同目录下的 + quick_map_results.py。Shell 与 Python 之间不是 import 关系, + 而是 Shell 通过 Python CLI 子命令交换 JSON/CSV 文件。 +

+ + + + + + + + + + + + + + + + + + + + + + + + +
上游文件下游文件连接点传递内容
config.envrun_quick_map.shrun_quick_map.sh:L8Shell 变量,允许调用命令中的环境变量覆盖默认值。
quick_map_scenarios.tsvrun_fixed_suiterun_quick_map.sh:L695-L729Case ID、ISL、OSL、C、请求数规则和 Warm-up。
run_quick_map.shquick_map_results.pyRESULT_TOOLrun_quick_map.sh:L13命令行参数、bench JSON、meta 和 Manifest 路径。
quick_map_results.py结果目录quick_map_results.py:L307-L601结构化 Case、Run、汇总和报告。
tests/test_quick_map_results.pyquick_map_results.pyPython 单元测试用合成数据验证字段兼容、百分位和聚合。
+ +

4. 配置与场景

+

4.1 配置分区

+ + + + + + + + + + + +
代码范围配置组影响
config.env:L4-L6实验与模型实验名、模型名和两节点都能看到的模型路径。
config.env:L8-L18节点与并行Head/Worker 地址、TP16、EP2、双节点 rank。
config.env:L20-L22镜像与缓存SGLang 镜像、宿主机缓存目录和容器挂载。
config.env:L24-L35NCCL/RDMA限定 eth0/eth3mlx5_0/mlx5_3 以及设备透传。
config.env:L37-L41服务容量显存比例、CUDA Graph Decode BS、活跃请求上限。
config.env:L43-L61压测随机数据生成、请求率、重复次数、混合注入和超时。
config.env:L67-L78运行控制相对路径、Case 过滤、Dry-run、断点续跑。
+ +

4.2 具体值在哪里看

+

+ 配置采用 VAR="${VAR:-default}"。含义是:启动命令已经提供 + VAR 时使用外部值,否则使用 config.env 里的默认值。 + 所以应区分“代码默认值”和“某次 Run 的实际值”。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
变量当前默认值默认值定义传入服务Run 后证据
MEM_FRACTION_STATIC0.9config.env:L38run_quick_map.sh:L258--mem-fraction-staticserver/head_server_cmd.txtrun_manifest.jsonmem_fraction_static
CUDA_GRAPH_MAX_BS_DECODE64config.env:L39run_quick_map.sh:L259服务命令;Manifest 的 cuda_graph_max_bs_decode
MAX_RUNNING_REQUESTS256config.env:L40run_quick_map.sh:L260服务命令;Manifest 的 max_running_requests
TP_SIZE / EP_SIZE / NNODES16 / 2 / 2config.env:L15-L17run_quick_map.sh:L250-L253服务命令;Manifest 的 tp_size/ep_size/nnodes
NCCL_SOCKET_IFNAMEeth0config.env:L26run_quick_map.sh:L231服务命令;Manifest 的同名小写字段;NCCL 服务日志
NCCL_IB_HCA=mlx5_0:1,mlx5_3:1config.env:L27run_quick_map.sh:L232服务命令;Manifest;两节点 NCCL 日志
+

MEM_FRACTION_STATIC 为例,三个查看层级是:

+
# 1. 看仓库默认值
+grep '^MEM_FRACTION_STATIC=' config.env
+
+# 2. 看本次命令实际覆盖后的值
+source ./config.env
+printf '%s\n' "${MEM_FRACTION_STATIC}"
+
+# 3. 看已经执行的 Run 最终用了什么
+grep -- '--mem-fraction-static' results/<RUN_ID>/server/head_server_cmd.txt
+python3 -c 'import json; print(json.load(open(
+  "results/<RUN_ID>/run_manifest.json"))["mem_fraction_static"])'
+

+ 第 3 层最可信,因为 start_service_node 在 + run_quick_map.sh:L269-L291 先展开命令,再写入 + <role>_server_cmd.txtwrite_run_manifest + 在 L641-L676 另存一份结构化配置。二者不一致时,应以实际容器命令和服务日志继续核查。 +

+ +

4.3 TSV 如何变成请求

+

+ quick_map_scenarios.tsv:L1 定义列: + case_id, stage, isl, osl, concurrency, multiplier, minimum, warmup, note。 + run_fixed_suiterun_quick_map.sh:L695-L736 中逐行读取。 +

+
num_prompts = concurrency × multiplier
+num_prompts = max(num_prompts, minimum)
+

+ 计算位于 run_quick_map.sh:L700-L718。因此场景表不直接写死总请求数, + 而是让总请求数随并发扩大,同时允许 minimum 给低并发 Case 提供最小样本量。 + CASE_IDS 的过滤发生在 L69-L84L703-L705。 +

+ +

4.4 11 个固定场景

+

+ quick_map_scenarios.tsv:L2-L12 覆盖冷 Prefill、并发 Prefill、短 Decode、 + 长 Decode 与长上下文 Decode。长 Prefill 和长 Decode Case 默认不做额外 Warm-up, + 避免昂贵预热和 Prefix Cache 污染;短 Decode Case保留一次 Warm-up。 +

+ +

5. 双机服务启动

+

5.1 参数校验与 RDMA 门禁

+

+ validate_network_config 位于 run_quick_map.sh:L85-L140。 + 它不接受任意网卡,而是把计算网约束为 eth0/eth3,把 RDMA HCA 约束为 + mlx5_0/mlx5_3。开启 RDMA 时,两条 rail 和必需设备路径都必须存在。 +

+

+ preflight_rdma_devices_on_nodeL141-L156 逐节点检查 + /dev/infiniband/rdma_cmuverbs0uverbs3。 + 这是宿主机设备存在性检查,不能证明 NCCL 最终真的用了 IB,所以后面还有日志门禁。 +

+ +

5.2 Docker 与 SGLang 命令展开

+

+ build_server_command 位于 run_quick_map.sh:L212-L268。 + 关键部分如下: +

+
docker run --rm --network host --ipc host --shm-size 20g
+  --device /dev/infiniband/rdma_cm
+  --device /dev/infiniband/uverbs0
+  --device /dev/infiniband/uverbs3
+  -e NCCL_SOCKET_IFNAME=eth0,eth3
+  -e NCCL_IB_HCA=mlx5_0,mlx5_3
+  -e NCCL_CROSS_NIC=...
+  IMAGE python3 -m sglang.launch_server
+    --model-path ...
+    --tp-size 16 --ep-size 2 --nnodes 2 --node-rank ...
+    --dist-init-addr HEAD_IP:DIST_PORT
+    --mem-fraction-static ...
+    --cuda-graph-max-bs-decode ...
+    --max-running-requests ...
+ + +

5.3 为什么 Worker 先启动

+

+ start_service 位于 run_quick_map.sh:L334-L388。 + 它先调用 Worker 的 start_service_node,再启动 Head,随后轮询 Head 的 + /health。这样 Worker 已经等待分布式 rendezvous,Head 启动后两端更容易同步进入初始化。 +

+

+ 健康检查成功还不够。verify_nccl_transport_node + 在 L293-L325 从服务日志拒绝 NET/IB : No device found, + 并要求看到 NET/IB 及两条 HCA;L326-L333 对两节点都执行。 + 因而脚本采用 fail-closed:无法证明走 RDMA 就不开始 benchmark。 +

+ +

5.4 停止与证据保存

+

+ stop_service_node 位于 run_quick_map.sh:L389-L410。 + 删除容器前先保存 docker inspect 和最终日志,再执行强制移除。 + cleanupL849-L853 配合 trap,保证异常退出也尝试清理两端服务。 +

+ +

6. Benchmark 请求生成与 Case 生命周期

+

6.1 命令生成

+

+ prepare_bench_command 位于 run_quick_map.sh:L416-L464。 + 它在 benchmark 客户端容器中运行 python3 -m sglang.benchmark.serving, + 使用 random 数据集并显式传入 ISL、OSL、并发、请求数、请求率、Warm-up 与 Seed。 +

+
--dataset-name random
+--random-input-len ISL
+--random-output-len OSL
+--num-prompts N
+--max-concurrency C
+--request-rate REQUEST_RATE
+--warmup-requests W
+--seed SEED
+--output-file bench.json
+--output-details
+
+ OSL 语义:随机 benchmark 会把目标输出长度传给服务端,并使用忽略 EOS 的生成设置, + 目标是生成足量 token。是否真正达到 OSL 仍以 bench.json 中的成功请求数和 + total_output_tokens 为准,不能只看命令参数。 +
+ +

6.2 单个 Case 的完整流程

+

run_bench_case 位于 run_quick_map.sh:L545-L632,顺序是:

+
    +
  1. 根据 suite、case、repetition 创建稳定结果目录。
  2. +
  3. RESUME=1,由 case_already_completed 检查 meta 和 bench 是否完整。
  4. +
  5. 保存展开后的命令与 Case 元数据。
  6. +
  7. 记录开始时间,使用 timeout 执行 benchmark。
  8. +
  9. 调用 Python check-bench 校验 JSON,不把“进程退出码为 0”误当成有效结果。
  10. +
  11. 失败时由 detect_error_type 区分超时、OOM、服务失活、传输错误和普通 benchmark 失败。
  12. +
  13. 写入最终 meta.json,供后续汇总和 Phase 2 时间窗使用。
  14. +
+

+ case_already_completedL511-L525 同时要求 meta 状态为完成、 + bench 文件存在且可解析。它避免只凭目录存在就跳过半成品。 +

+ +

7. 混合 Prefill/Decode A/B

+

7.1 一次 repetition 的三个角色

+

run_mixed_repetition 位于 run_quick_map.sh:L755-L832

+ + + + + + + +
角色Shape作用
control1K → 1K,C=32单独运行 Decode 背景,建立无注入基线。
decode_background1K → 1K,C=32混合组中的持续 Decode 请求流。
prefill_injection128K → 1,C=1在 Decode 正式测量期间注入一次长 Prefill。
+ +

7.2 “背景”在代码里是什么

+

+ 背景不是 SGLang 特殊模式。它只是 Shell 把一个正常 benchmark 放到后台进程运行: + run_quick_map.sh:L783-L792 的子 Shell 加 &。 + background_pid=$! 保存该进程 PID,主脚本随后还能并行发起长 Prefill。 +

+

+ wait_for_bench_main 位于 L738-L753,轮询背景日志中的 + Starting main benchmark run。看到它以后再等待配置的注入延迟,避免把 Warm-up 阶段误当正式混合阶段。 +

+

+ 注入前还会在 L803-L816kill -0 检查背景进程是否仍存活。 + 若背景已经正常结束,Case 被重写为 + BACKGROUND_FINISHED_BEFORE_INJECTION,防止生成一个实际上没有重叠的“混合成功”结果。 +

+ +

7.3 A/B 对比来自哪里

+

+ Shell 只负责产生 control、background 和 injection 三份原始记录。 + Python 在 quick_map_results.py:L439-L580 聚合同一 Case 的重复实验, + 并在报告阶段计算 percentage change。主要观察 background 相对 control 的 + Output TPS、TTFT P95、TPOT P95 与 E2E P95 变化。 +

+ +

8. 指标解析与汇总

+

8.1 为什么需要 Python 补算

+

+ SGLang 版本变化可能导致字段名或原始明细形态不同。 + quick_map_results.py:L106-L125 既支持单个 JSON 对象,也能从混合日志中寻找首个合法 JSON 行。 + L152-L157 用多个候选字段名读取同一指标。 +

+ +

8.2 延迟百分位

+

+ latency_stats 位于 quick_map_results.py:L208-L233。 + 优先读取 benchmark 已给出的 mean/P50/P95/P99;缺失时才从请求级数组补算: +

+ +

+ percentile_msL128-L140 使用线性插值,并把秒转换为毫秒。 +

+ +

8.3 吞吐与完成状态

+

+ compute_metrics 位于 quick_map_results.py:L236-L272。 + Total TPS 优先读取 benchmark 自带字段,缺失时才使用 Input TPS + Output TPS。 + 完成数优先读取 completedsuccessful_requests; + 失败数缺失时才由尝试数减完成数。 +

+ +

8.4 重复实验聚合

+

+ aggregate_rows 位于 quick_map_results.py:L439-L481。 + 它按 suite/case/role 聚合 repetition,输出均值、离散程度和成功状态。 + write_reportL493-L580 生成面向人的 Markdown 报告, + write_csvsummarizeL581-L601 生成机器可读汇总。 +

+ +

9. 结果目录与数据契约

+
results/<RUN_ID>/
+  run.log
+  manifest.json
+  server/
+    head_command.txt
+    worker_command.txt
+    *.log
+    *.inspect.json
+  cases/
+    <case_id>/rep<N>/
+      bench_cmd.txt
+      bench.log
+      bench.json
+      meta.json
+  summary.csv
+  summary.jsonl
+  aggregate.csv
+  report.md
+

+ meta.json 的结构由 quick_map_results.py:L307-L330 写入, + 包含 shape、并发、请求数、Warm-up、开始结束时间、退出码与错误分类。 + manifest.jsonL333-L382 维护,记录模型、镜像、并行参数、 + NCCL/RDMA 参数和 Git 状态。两者共同保证结果可追溯。 +

+ +

10. 函数行号索引

+

10.1 run_quick_map.sh

+ + + + + + + + + + + + + + + + + +
行号函数一句话职责
L27-L68logcase_selected日志、时间、命令打印、节点执行和 Case 过滤基础函数。
L69-L140validate_case_filter / validate_network_config运行前拒绝未知 Case 和非计算网配置。
L141-L211RDMA、健康和客户端预检检查设备、服务、GPU 占用和 benchmark 客户端。
L212-L268build_server_command构造每个节点的完整 Docker + SGLang 命令。
L269-L388启动与 NCCL 验证启动节点、等待健康、从日志证明 NET/IB 双 HCA。
L389-L415停止服务保存日志与 inspect 后删除两端容器。
L416-L510bench 命令与 meta 参数构造请求并准备结果元数据。
L511-L632断点续跑、错误分类、单 Case执行并验证一个 benchmark Case。
L633-L694失败标记、Manifest、汇总、日志Run 级元数据和结果收口。
L695-L737run_fixed_suite遍历 TSV 与 repetition。
L738-L847混合 A/B确保 Decode 与长 Prefill 在时间上真实重叠。
L849-L932清理、独立 suite、all管理完整生命周期与最终状态。
L933-L957main分发 all/start/fixed/mixed/stop
+ +

10.2 quick_map_results.py

+ + + + + + + + + + + + +
行号函数组职责
L94-L125JSON I/O可靠读取原始 benchmark 输出。
L128-L207百分位与请求级 fallback从明细恢复 E2E、TTFT、TPOT、ITL。
L208-L272latency_stats / compute_metrics统一指标字段与单位。
L275-L306parse_scenarios校验 TSV schema、类型和重复 Case。
L307-L404Case、Manifest、失败状态维护机器可读运行状态。
L405-L492行构造与聚合把每次 repetition 合并为 Case 统计。
L493-L601报告与汇总输出 Markdown、CSV、JSONL。
L602-L716CLI定义 Shell 调用的子命令和参数。
+ + +
+ + diff --git a/docs/dsv4pro_pro6000d_2node_sglang/phase2_code.html b/docs/dsv4pro_pro6000d_2node_sglang/phase2_code.html new file mode 100644 index 0000000..b1dd24a --- /dev/null +++ b/docs/dsv4pro_pro6000d_2node_sglang/phase2_code.html @@ -0,0 +1,586 @@ + + + + + + + Phase 2 Code:DSV4-Pro 双机 Pro6000D SGLang 硬件竞争归因 + + + +
+
+

Standalone Code Walkthrough / Phase 2

+

DSV4-Pro 双机 Pro6000D SGLang 硬件竞争归因:代码详解

+
+ 行号基线:ca1f2f63375c  + 生成时间:2026-07-31 13:04:21 CST  + 入口:run_hardware_contention_attribution.sh +
+
+
+ +
+
+ 文档边界:这是一份独立代码档案。它解释 Phase 2 如何复用 Phase 1 请求、 + 同步采集两节点监控,并按真实 Case 时间窗做硬件归因。所有行号绑定提交 + ca1f2f63375c。 +
+ +

1. 阅读导航

+ + +

2. 总体控制流

+
main "$@"
+  └─ ACTION=all → run_all
+       ├─ validate_config
+       ├─ preflight_node_tools + preflight_clock_sync
+       ├─ start_service
+       │    └─ 委托 Phase 1 的 start
+       ├─ capture_static_snapshots before
+       ├─ start_collectors
+       │    └─ Head 与 Worker 各启动 9 类采集器
+       ├─ idle baseline
+       ├─ 固定诊断 Case
+       │    └─ 每个 Case 委托 Phase 1 的 fixed
+       ├─ 混合 Prefill/Decode A/B
+       │    └─ 委托 Phase 1 的 mixed
+       ├─ cooldown
+       ├─ stop_collectors
+       ├─ capture_static_snapshots after
+       ├─ stop_service
+       ├─ summarize_results
+       │    └─ 按每个 Case 的 started_at/ended_at 切监控窗口
+       └─ finish_manifest
+

+ Phase 2 不复制模型部署和 benchmark 生成代码。它新增的是诊断编排层: + 在同一个服务生命周期中,让两节点的 GPU、CPU、NUMA、网络和 RDMA 时间序列包围 benchmark, + 最后按 Case 时间切片。 +

+ +

3. 文件职责

+ + + + + + + + + + + + + + + + + + + + +
文件行数职责
run_hardware_contention_attribution.sh689唯一入口,调用 Phase 1 服务/请求,管理所有采集器、静态快照、Case marker 与清理。
config.env40定义 Phase 1 相对路径、诊断 Case、采样间隔、采集命令和结果路径。
hardware_contention_attribution.py574记录 Manifest/marker,汇总 GPU 与 RDMA,按 Case 时间窗切片并生成报告。
tests/test_hardware_contention_attribution.py197验证 GPU/RDMA 汇总、时间窗切片和结果生成的纯 Python 逻辑。
+ +

3.1 Phase 2 与 Phase 1 的文件关系

+
用户
+  └─ bash Phase2/run_hardware_contention_attribution.sh all
+       ├─ source Phase2/config.env
+       │    └─ PHASE1_ENTRY 指向 Phase1/run_quick_map.sh
+       ├─ env ... bash "${PHASE1_ENTRY}" start/fixed/mixed/stop
+       │    └─ Phase1/run_quick_map.sh
+       │         ├─ source Phase1/config.env
+       │         ├─ 读取 Phase1/quick_map_scenarios.tsv
+       │         └─ 调用 Phase1/quick_map_results.py
+       ├─ 自己启动两节点硬件采集器
+       └─ 调用 Phase2/hardware_contention_attribution.py
+            ├─ 读取 Phase1 生成的 bench/*/cases/*/meta.json
+            ├─ 读取 Phase2 生成的 GPU/RDMA 时间序列
+            └─ 按 Case 时间窗生成归因汇总
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
上游下游代码连接点关系
Phase 2 config.envPhase 2 Shellrun_hardware_contention_attribution.sh:L8提供诊断 Case、采样策略和 Phase 1 相对入口。
Phase 2 ShellPhase 1 Shellrun_hardware_contention_attribution.sh:L195-L213通过环境变量和 action 委托服务与请求。
Phase 1 config.envPhase 1 Shellrun_quick_map.sh:L8提供实际模型服务参数,包括 MEM_FRACTION_STATIC
Phase 1 Case 结果Phase 2 Pythonhardware_contention_attribution.py:L213-L260提供 benchmark 指标和精确开始/结束时间。
Phase 2 Shell 采集器Phase 2 Pythonhardware_contention_attribution.py:L276-L321提供两节点 GPU/RDMA 时间序列供 Case 切片。
Phase 2 单元测试Phase 2 Pythontests/test_hardware_contention_attribution.py用合成监控数据验证 delta、速率和时间窗。
+

+ 这里有两套 config.env,职责不同。Phase 2 的配置控制“测哪些 Case、如何监控”; + Phase 1 的配置控制“模型如何部署、请求如何生成”。Phase 2 没有复制 + MEM_FRACTION_STATIC,因此它最终仍从 Phase 1 的 + config.env:L38 取得默认值。 +

+ +

3.1 config.env 分区

+ + + + + + + + + +
范围内容说明
config.env:L3-L8实验名与 Phase 1 入口通过相对路径复用 Phase 1,不依赖启动命令当前目录。
config.env:L10-L16节点、端口、容器名用于健康检查、PID 映射和容器级监控。
config.env:L18-L20诊断 Case五个固定 Case,加一个混合 A/B 开关。
config.env:L22-L32采集策略1 秒采样、空闲基线、冷却、DCGM 字段、perf 事件、RDMA HCA 和时钟容差。
config.env:L34-L40路径与运行模式Repo/Result/Runtime、Dry-run 和是否容忍部分采集器失败。
+ +

4. Phase 1 复用边界

+

4.1 委托函数

+

+ run_phase1_action 位于 + run_hardware_contention_attribution.sh:L195-L214。它向 Phase 1 注入: +

+ +
run_phase1_action start
+run_phase1_action fixed   # 通过 CASE_IDS 只跑一个 Case
+run_phase1_action mixed
+run_phase1_action stop
+ +

4.2 用户只运行哪个入口

+

+ 正式执行只运行 Phase 2: + bash run_hardware_contention_attribution.sh all。 + start_servicestop_service + 位于 L215-L229,是编排器内部调用,不需要手工先执行 Phase 1。 +

+
+ 设计不变量:Phase 2 不修改 Phase 1 的服务参数和请求口径。 + 如果模型启动或请求生成需要修复,应改 Phase 1;如果采集、时间对齐或归因需要修复,应改 Phase 2。 +
+ +

4.3 Phase 2 中如何查服务参数的实际值

+

+ 例如 MEM_FRACTION_STATIC 的完整传递链是: +

+
调用命令环境(可选覆盖)
+  → Phase1/config.env:L38,默认 0.9
+  → Phase1/run_quick_map.sh:L258
+  → SGLang --mem-fraction-static 0.9
+  → Phase2/results/<RUN_ID>/service/head_server_cmd.txt
+  → Phase2/results/<RUN_ID>/bench/<sub-run>/run_manifest.json
+

+ Phase 2 的顶层 manifest.json 记录监控配置,不重复记录全部服务参数。 + 查“模型实际怎么起的”应看 service/head_server_cmd.txt 与 + service/worker_server_cmd.txt;查结构化值应看任一 Phase 1 子 Run 的 + run_manifest.json。 +

+
# 查看 Phase 1 默认值
+grep '^MEM_FRACTION_STATIC=' \
+  ../dsv4pro_pro6000d_2node_sglang_tp16_quick_map/config.env
+
+# 查看某次 Phase 2 Run 的实际服务命令
+grep -- '--mem-fraction-static' \
+  results/<RUN_ID>/service/head_server_cmd.txt
+
+# 从 Phase 1 子 Run Manifest 读取结构化值
+python3 -c 'import glob,json; p=glob.glob(
+  "results/<RUN_ID>/bench/*/run_manifest.json")[0];
+print(json.load(open(p))["mem_fraction_static"])'
+

+ 如果这样启动: + MEM_FRACTION_STATIC=0.85 bash run_hardware_contention_attribution.sh all, + 外部变量会由 Phase 2 进程继承给 Phase 1,Phase 1 的 + ${MEM_FRACTION_STATIC:-0.9} 会保留 0.85。 + 因此只看默认配置不足以证明某次实验用了什么,必须查看 Run 证据。 +

+ +

5. 预检、Manifest 与时钟

+

5.1 配置和工具预检

+

+ validate_config 在 + run_hardware_contention_attribution.sh:L66-L106 + 检查 Phase 1 入口、节点、Case 和关键数值。 + preflight_node_toolsL107-L134 + 对两节点检查 Docker、NVIDIA、RDMA、sysstat、perf 与 NUMA 工具。 +

+

+ 采集器可用性不能在运行半小时后才发现。预检默认 fail-closed; + 只有显式允许部分采集器缺失时,才降级继续。 +

+ +

5.2 为什么检查两机时钟

+

+ preflight_clock_sync 位于 L135-L151。 + Phase 2 用 wall clock 把 benchmark 的 started_at/ended_at + 与两节点采样行对齐。如果两机时钟偏差超过配置容差,同一 Case 在 Worker 上会切到错误窗口。 +

+ +

5.3 运行元数据

+

+ Shell 的 write_manifest 位于 L152-L176, + Python 的 create_manifest 位于 + hardware_contention_attribution.py:L367-L390。 + Manifest 记录 Git commit、是否 dirty、Phase 1 入口、节点、Case、采样周期、时钟容差和 Dry-run。 +

+

+ mark_event 位于 Shell L177-L194, + Python 的 append_marker 位于 L338-L365。 + 每个 idle/case/cooldown 边界写入纳秒级 wall time,作为人工审计时间线。 +

+ +

6. 采集器实现

+

6.1 静态快照

+

+ capture_commandstatic_snapshot_command 和 + capture_static_snapshots 位于 Shell L230-L283。 + 服务启动后和实验结束前分别采集: +

+ +

+ 前后快照回答“实验是否改变了设备状态”;时间序列回答“Case 运行期间发生了什么”。 +

+ +

6.2 通用采集器包装

+

+ start_stream_collector 位于 Shell + L284-L318。每个采集器都具备: +

+
    +
  1. 保存完整命令到 collector_commands/
  2. +
  3. 用唯一 tag 标记远端进程,便于精准停止。
  4. +
  5. COLLECTOR_TIMEOUT_S 限制,避免永久悬挂。
  6. +
  7. 保存 PID、日志与退出状态到 collector_status.csv
  8. +
+ +

6.3 GPU 采样

+

+ gpu_sampler_command 位于 Shell L319-L332, + 每秒调用 nvidia-smi --query-gpu,采集利用率、显存利用率、显存占用、 + 功耗、温度、SM 时钟和显存时钟,并补上 wall_time_ns,node,gpu。 +

+

+ Python 的 summarize_gpu_rows 位于 + hardware_contention_attribution.py:L101-L135。 + 它按 node + GPU 分组,对每个字段输出 mean、P95、max。 +

+ +

6.4 RDMA 采样

+

+ Shell 的 rdma_sampler_commandread_counter + 位于 L333-L359,读取 mlx5_0/mlx5_3 的端口发送/接收数据、 + 包、错误、丢弃与恢复计数器。 +

+

+ Python 的 summarize_rdma_rows 位于 L148-L206。 + 它按 node + HCA 排序,使用最后值减第一值,并注意 IB + port_xmit_data/port_rcv_data 的单位是 4 octets: +

+
xmit_bytes = (last_xmit_data - first_xmit_data) × 4
+xmit_gbps = xmit_bytes × 8 / duration_s / 1e9
+

+ 同时保留错误计数器 delta,因此“带宽低”可以与“链路错误增加”分开判断。 +

+ +

6.5 CPU、进程和 NUMA

+

+ start_node_collectors 位于 Shell L405-L457, + 每个节点启动九类采集器: +

+ + + + + + + + + + + + +
采集器回答的问题
nvidia-smi / DCGMGPU 是否算力、显存带宽、功耗或时钟受限。
mpstatCPU 总体与逐核是否繁忙。
pidstatSGLang/容器进程的 CPU、内存和上下文切换。
sar -n DEV,EDEVeth0/eth3 吞吐和错误。
perf stat容器主进程的 CPU cycles、instructions、cache miss 等。
docker top容器 PID 与宿主机 PID 映射。
numastat进程内存是否跨 NUMA 节点访问。
RDMA counters两条 HCA 的真实数据量和错误增量。
+

+ container_pid_preamblenumastat_command + 位于 L372-L404,先解析容器主 PID,再让 perf/numastat 对准实际服务进程。 +

+ +

6.6 停止与残留清理

+

+ check_collectorsstop_collectors + 位于 Shell L469-L519。 + 除了等待已知 PID,还会按唯一 tag 清理远端残留采集器。 + cleanupL594-L602 由 trap 调用,先停监控再停模型服务。 +

+ +

7. Case 编排

+

7.1 固定 Case

+

+ run_fixed_case 位于 Shell L529-L556。 + 它为 Case 生成独立子 Run ID,写 case_start marker, + 委托 Phase 1 的 fixed,再检查该子 Run 的 summary.csv, + 最后写 case_end marker。 +

+

+ 默认固定集合来自 config.env:L18,用于区分: + 单请求长 Prefill、并发 Prefill、普通 Decode、持续长 Decode、长上下文 Decode。 + Phase 2 不把所有 Phase 1 点再跑一遍,只保留对资源归因有辨识度的负载。 +

+ +

7.2 混合 Case

+

+ run_mixed_case 位于 Shell L557-L582。 + 它复用 Phase 1 已经保证真实时间重叠的 mixed A/B,并把整段监控留在同一采集窗口内。 + Phase 2 自己不重新实现后台请求与注入逻辑。 +

+ +

7.3 run_all 的失败策略

+

+ run_all 位于 Shell L604-L668: +

+ + +

8. 按真实 Case 时间窗归因

+

8.1 时间窗从哪里来

+

+ load_case_windows 位于 + hardware_contention_attribution.py:L233-L260。 + 它读取 Phase 1 每个 meta.json 中的 + started_atended_at,转换为纳秒时间戳。 + 这比用“Case marker 前后大概几秒”更精确,因为它对齐的是 benchmark 进程实际测量区间。 +

+ +

8.2 如何切 GPU/RDMA 数据

+

+ rows_in_window 位于 L263-L274, + 只保留 started_ns ≤ wall_time_ns ≤ ended_ns 的采样行。 + summarize_case_hardware 位于 L276-L319, + 对每个 Case、每个节点分别切 GPU 与 RDMA,再复用全局汇总函数。 +

+
Case meta.started_at / ended_at
+          │
+          ├─ filter head/gpu_samples.csv
+          ├─ filter worker/gpu_samples.csv
+          ├─ filter head/rdma.csv
+          └─ filter worker/rdma.csv
+                 ↓
+     case_gpu_summary.csv / case_rdma_summary.csv
+
+ 1 秒采样意味着很短的请求可能只有少数样本。此时 P95 不稳定,应结合原始时间序列和 Case 持续时间, + 不能把单个采样峰值解释为稳定瓶颈。 +
+ +

9. 输出和归因边界

+
results/<RUN_ID>/
+  manifest.json
+  markers.csv
+  collector_status.csv
+  service/
+  commands/
+  bench/<phase1-sub-run>/
+  head/
+    gpu_samples.csv
+    rdma.csv
+    dcgm.log
+    mpstat.log
+    pidstat.log
+    sar_network.log
+    perf.log
+    docker_top.log
+    numastat.log
+    collector_commands/
+  worker/
+    ...同上...
+  gpu_summary.csv
+  rdma_summary.csv
+  bench_summary.csv
+  case_windows.csv
+  case_gpu_summary.csv
+  case_rdma_summary.csv
+  summary.json
+  report.md
+

+ Python 的 summarize 位于 + hardware_contention_attribution.py:L405-L504。 + 它汇总全局 GPU/RDMA、Phase 1 bench、Case 时间窗和 Case 级硬件数据, + 同时统计采集器状态与文件大小。 +

+

+ 自动报告只整理证据,不自动宣布“瓶颈就是 GPU/NCCL/CPU”。 + L498-L499 明确要求最终结论结合 markers、原始 DCGM/sysstat 和 SGLang 日志。 + 这是有意的保守边界,避免单个指标被机械误判。 +

+ +

10. 函数行号索引

+

10.1 run_hardware_contention_attribution.sh

+ + + + + + + + + + + + + + + + + +
行号函数组职责
L25-L65日志、远端执行、命令文件、结果日志编排器基础设施。
L66-L151配置、工具、时钟预检正式启动前 fail-fast。
L152-L194Manifest 与 marker记录运行身份和事件边界。
L195-L229Phase 1 委托与服务生命周期复用服务和 benchmark,不复制实现。
L230-L283静态快照保存实验前后硬件、网络、RDMA 和容器状态。
L284-L318start_stream_collector统一包装远端长时间采集器。
L319-L404GPU、RDMA、PID、NUMA 命令生成各类采集命令。
L405-L468启动所有采集器两节点各启动九类监控。
L469-L519检查与停止采集器收集状态并清理残留。
L520-L582sleep、固定 Case、混合 Case诊断负载编排。
L583-L603汇总、Manifest 完成、cleanup结果收口与异常清理。
L604-L668run_allPhase 2 完整状态机。
L669-L689main分发 all/summarize/stop
+ +

10.2 hardware_contention_attribution.py

+ + + + + + + + + + + + + +
行号函数组职责
L54-L100时间、JSON、数字、CSV结果工具基础函数。
L101-L141GPU 汇总按 node/GPU 输出 mean、P95、max。
L142-L212RDMA 汇总计数器 delta、字节换算、Gbps 和错误增量。
L213-L260Bench 与 Case 时间窗读取从 Phase 1 子 Run 建立诊断索引。
L263-L321时间切片按每个 Case 的真实运行窗口汇总 GPU/RDMA。
L322-L366CSV 与 marker输出结构化表和事件时间线。
L367-L404Manifest 与 bench 校验维护 Run 状态,确认子 Run 全部完成。
L405-L506summarize生成全部汇总表、summary JSON 和 report。
L507-L574CLI向 Shell 提供 marker/manifest/check/summarize 子命令。
+ + +
+ +