[Docs] add Phase 1 and Phase 2 code walkthroughs
This commit is contained in:
parent
ca1f2f6337
commit
3964b3d210
@ -1,5 +1,9 @@
|
|||||||
# sskj — 多平台大模型推理性能基准测试项目
|
# 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)**
|
> **更新(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` 入口。
|
> 实现 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` 入口。
|
||||||
|
|||||||
593
docs/dsv4pro_pro6000d_2node_sglang/phase1_code.html
Normal file
593
docs/dsv4pro_pro6000d_2node_sglang/phase1_code.html
Normal file
@ -0,0 +1,593 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="color-scheme" content="light">
|
||||||
|
<title>Phase 1 Code:DSV4-Pro 双机 Pro6000D SGLang 快速性能地图</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--canvas: #eef3f4;
|
||||||
|
--paper: #ffffff;
|
||||||
|
--ink: #182126;
|
||||||
|
--muted: #5a6970;
|
||||||
|
--line: #d4dee1;
|
||||||
|
--navy: #17363d;
|
||||||
|
--teal: #087c72;
|
||||||
|
--teal-soft: #e8f5f3;
|
||||||
|
--amber: #a64c14;
|
||||||
|
--amber-soft: #fff1e7;
|
||||||
|
--code-bg: #17252b;
|
||||||
|
--code-ink: #eaf2f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; letter-spacing: 0; }
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ink);
|
||||||
|
background: var(--canvas);
|
||||||
|
font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.72;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
color: #f6fbfb;
|
||||||
|
background: var(--navy);
|
||||||
|
border-bottom: 5px solid #d2692b;
|
||||||
|
}
|
||||||
|
.header-inner, main { width: min(100% - 36px, 1120px); margin: 0 auto; }
|
||||||
|
.header-inner { padding: 34px 0 30px; }
|
||||||
|
.eyebrow { margin: 0 0 6px; color: #9edbd5; font-size: 13px; font-weight: 700; }
|
||||||
|
h1 { margin: 0; font-size: clamp(28px, 4vw, 42px); line-height: 1.25; }
|
||||||
|
.meta { margin-top: 15px; color: #d6e5e7; font-size: 14px; }
|
||||||
|
main {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 70px;
|
||||||
|
padding: 38px 48px 58px;
|
||||||
|
background: var(--paper);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 12px 30px rgba(27, 45, 51, 0.07);
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 46px 0 15px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
font-size: 25px;
|
||||||
|
line-height: 1.35;
|
||||||
|
border-bottom: 2px solid #adbbc0;
|
||||||
|
}
|
||||||
|
h2:first-of-type { margin-top: 18px; }
|
||||||
|
h3 { margin: 29px 0 10px; color: #21454d; font-size: 19px; }
|
||||||
|
h4 { margin: 22px 0 8px; font-size: 16px; }
|
||||||
|
p, ul, ol { margin-top: 0; margin-bottom: 16px; }
|
||||||
|
li + li { margin-top: 5px; }
|
||||||
|
a { color: var(--teal); text-underline-offset: 3px; }
|
||||||
|
code {
|
||||||
|
padding: 2px 5px;
|
||||||
|
color: #85380d;
|
||||||
|
background: var(--amber-soft);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: "SFMono-Regular", Consolas, monospace;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 14px 0 22px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
overflow: auto;
|
||||||
|
color: var(--code-ink);
|
||||||
|
background: var(--code-bg);
|
||||||
|
border-radius: 5px;
|
||||||
|
font: 13px/1.62 "SFMono-Regular", Consolas, monospace;
|
||||||
|
}
|
||||||
|
pre code { padding: 0; color: inherit; background: transparent; }
|
||||||
|
table { width: 100%; margin: 16px 0 26px; border-collapse: collapse; font-size: 14px; }
|
||||||
|
th, td {
|
||||||
|
padding: 9px 11px;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
th { color: #153b41; background: #eaf2f2; }
|
||||||
|
tbody tr:nth-child(even) { background: #fafcfc; }
|
||||||
|
.callout {
|
||||||
|
margin: 18px 0 26px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: var(--teal-soft);
|
||||||
|
border-left: 4px solid var(--teal);
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
margin: 18px 0 26px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: var(--amber-soft);
|
||||||
|
border-left: 4px solid var(--amber);
|
||||||
|
}
|
||||||
|
.toc {
|
||||||
|
columns: 2;
|
||||||
|
column-gap: 38px;
|
||||||
|
margin: 16px 0 24px;
|
||||||
|
padding-left: 22px;
|
||||||
|
}
|
||||||
|
.toc li { break-inside: avoid; }
|
||||||
|
.path { font-family: "SFMono-Regular", Consolas, monospace; font-size: 13px; }
|
||||||
|
.nowrap { white-space: nowrap; }
|
||||||
|
footer { margin-top: 48px; padding-top: 18px; color: var(--muted); border-top: 1px solid var(--line); }
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
main { padding: 28px 20px 42px; }
|
||||||
|
.toc { columns: 1; }
|
||||||
|
table { display: block; overflow-x: auto; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-inner">
|
||||||
|
<p class="eyebrow">Standalone Code Walkthrough / Phase 1</p>
|
||||||
|
<h1>DSV4-Pro 双机 Pro6000D SGLang 快速性能地图:代码详解</h1>
|
||||||
|
<div class="meta">
|
||||||
|
行号基线:<code>ca1f2f63375c</code>
|
||||||
|
生成时间:2026-07-31 13:04:21 CST
|
||||||
|
入口:<code>run_quick_map.sh</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="callout">
|
||||||
|
<strong>文档边界:</strong>这是一份独立代码档案,只解释 Phase 1 实现,不承担阶段结论展示。
|
||||||
|
下文的行号均绑定提交 <code>ca1f2f63375c</code>。代码变更后应先更新基线提交,再重新核对行号。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="read">1. 阅读方法</h2>
|
||||||
|
<ul class="toc">
|
||||||
|
<li><a href="#flow">总体控制流</a></li>
|
||||||
|
<li><a href="#files">文件职责</a></li>
|
||||||
|
<li><a href="#config">配置与场景</a></li>
|
||||||
|
<li><a href="#service">双机服务启动</a></li>
|
||||||
|
<li><a href="#bench">Benchmark 生成</a></li>
|
||||||
|
<li><a href="#mixed">混合 Prefill/Decode</a></li>
|
||||||
|
<li><a href="#results">指标解析与汇总</a></li>
|
||||||
|
<li><a href="#artifacts">结果目录与数据契约</a></li>
|
||||||
|
<li><a href="#index">函数行号索引</a></li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
行号写法例如
|
||||||
|
<code>run_quick_map.sh:L212-L268</code>。它表示该提交中,从第 212 行到第 268 行的完整函数段,
|
||||||
|
不是当前编辑器自动漂移后的行号。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="flow">2. 总体控制流</h2>
|
||||||
|
<pre><code>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</code></pre>
|
||||||
|
<p>
|
||||||
|
Shell 负责生命周期、远端执行、容器和失败策略;Python 负责结果读取、指标补算、聚合与报告。
|
||||||
|
这条分工是理解代码的第一把钥匙。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="files">3. 文件职责</h2>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>文件</th><th>行数</th><th>职责</th><th>主要输出</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="path">run_quick_map.sh</td><td>957</td>
|
||||||
|
<td>唯一入口,管理双机服务、固定场景、混合场景、失败恢复与清理。</td>
|
||||||
|
<td><code>run.log</code>、服务日志、每个 Case 的命令与原始结果。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="path">config.env</td><td>78</td>
|
||||||
|
<td>模型、节点、SGLang、NCCL/RDMA、benchmark、超时和路径配置。</td>
|
||||||
|
<td>被 Shell 直接 <code>source</code>,自身不产生输出。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="path">quick_map_scenarios.tsv</td><td>12</td>
|
||||||
|
<td>固定性能地图的声明式场景表,一行对应一个 Case。</td>
|
||||||
|
<td>输入给 <code>run_fixed_suite</code>。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="path">quick_map_results.py</td><td>716</td>
|
||||||
|
<td>校验 bench JSON、补算百分位、生成 meta/manifest、聚合重复实验。</td>
|
||||||
|
<td><code>summary.csv</code>、<code>summary.jsonl</code>、<code>aggregate.csv</code>、<code>report.md</code>。</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>3.1 文件之间如何调用</h3>
|
||||||
|
<pre><code>用户
|
||||||
|
└─ 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 解析和聚合,不启动模型</code></pre>
|
||||||
|
<p>
|
||||||
|
<code>run_quick_map.sh:L6-L16</code> 是关系的起点:先定位自身目录,再
|
||||||
|
<code>source config.env</code>,随后把结果工具固定为同目录下的
|
||||||
|
<code>quick_map_results.py</code>。Shell 与 Python 之间不是 import 关系,
|
||||||
|
而是 Shell 通过 Python CLI 子命令交换 JSON/CSV 文件。
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>上游文件</th><th>下游文件</th><th>连接点</th><th>传递内容</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>config.env</code></td><td><code>run_quick_map.sh</code></td>
|
||||||
|
<td><code>run_quick_map.sh:L8</code></td><td>Shell 变量,允许调用命令中的环境变量覆盖默认值。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>quick_map_scenarios.tsv</code></td><td><code>run_fixed_suite</code></td>
|
||||||
|
<td><code>run_quick_map.sh:L695-L729</code></td><td>Case ID、ISL、OSL、C、请求数规则和 Warm-up。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>run_quick_map.sh</code></td><td><code>quick_map_results.py</code></td>
|
||||||
|
<td><code>RESULT_TOOL</code>,<code>run_quick_map.sh:L13</code></td><td>命令行参数、bench JSON、meta 和 Manifest 路径。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>quick_map_results.py</code></td><td>结果目录</td>
|
||||||
|
<td><code>quick_map_results.py:L307-L601</code></td><td>结构化 Case、Run、汇总和报告。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>tests/test_quick_map_results.py</code></td><td><code>quick_map_results.py</code></td>
|
||||||
|
<td>Python 单元测试</td><td>用合成数据验证字段兼容、百分位和聚合。</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="config">4. 配置与场景</h2>
|
||||||
|
<h3>4.1 配置分区</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>代码范围</th><th>配置组</th><th>影响</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>config.env:L4-L6</code></td><td>实验与模型</td><td>实验名、模型名和两节点都能看到的模型路径。</td></tr>
|
||||||
|
<tr><td><code>config.env:L8-L18</code></td><td>节点与并行</td><td>Head/Worker 地址、TP16、EP2、双节点 rank。</td></tr>
|
||||||
|
<tr><td><code>config.env:L20-L22</code></td><td>镜像与缓存</td><td>SGLang 镜像、宿主机缓存目录和容器挂载。</td></tr>
|
||||||
|
<tr><td><code>config.env:L24-L35</code></td><td>NCCL/RDMA</td><td>限定 <code>eth0/eth3</code>、<code>mlx5_0/mlx5_3</code> 以及设备透传。</td></tr>
|
||||||
|
<tr><td><code>config.env:L37-L41</code></td><td>服务容量</td><td>显存比例、CUDA Graph Decode BS、活跃请求上限。</td></tr>
|
||||||
|
<tr><td><code>config.env:L43-L61</code></td><td>压测</td><td>随机数据生成、请求率、重复次数、混合注入和超时。</td></tr>
|
||||||
|
<tr><td><code>config.env:L67-L78</code></td><td>运行控制</td><td>相对路径、Case 过滤、Dry-run、断点续跑。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>4.2 具体值在哪里看</h3>
|
||||||
|
<p>
|
||||||
|
配置采用 <code>VAR="${VAR:-default}"</code>。含义是:启动命令已经提供
|
||||||
|
<code>VAR</code> 时使用外部值,否则使用 <code>config.env</code> 里的默认值。
|
||||||
|
所以应区分“代码默认值”和“某次 Run 的实际值”。
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>变量</th><th>当前默认值</th><th>默认值定义</th><th>传入服务</th><th>Run 后证据</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>MEM_FRACTION_STATIC</code></td><td><code>0.9</code></td>
|
||||||
|
<td><code>config.env:L38</code></td><td><code>run_quick_map.sh:L258</code> → <code>--mem-fraction-static</code></td>
|
||||||
|
<td><code>server/head_server_cmd.txt</code>;<code>run_manifest.json</code> 的 <code>mem_fraction_static</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>CUDA_GRAPH_MAX_BS_DECODE</code></td><td><code>64</code></td>
|
||||||
|
<td><code>config.env:L39</code></td><td><code>run_quick_map.sh:L259</code></td>
|
||||||
|
<td>服务命令;Manifest 的 <code>cuda_graph_max_bs_decode</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>MAX_RUNNING_REQUESTS</code></td><td><code>256</code></td>
|
||||||
|
<td><code>config.env:L40</code></td><td><code>run_quick_map.sh:L260</code></td>
|
||||||
|
<td>服务命令;Manifest 的 <code>max_running_requests</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>TP_SIZE / EP_SIZE / NNODES</code></td><td><code>16 / 2 / 2</code></td>
|
||||||
|
<td><code>config.env:L15-L17</code></td><td><code>run_quick_map.sh:L250-L253</code></td>
|
||||||
|
<td>服务命令;Manifest 的 <code>tp_size/ep_size/nnodes</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>NCCL_SOCKET_IFNAME</code></td><td><code>eth0</code></td>
|
||||||
|
<td><code>config.env:L26</code></td><td><code>run_quick_map.sh:L231</code></td>
|
||||||
|
<td>服务命令;Manifest 的同名小写字段;NCCL 服务日志</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>NCCL_IB_HCA</code></td><td><code>=mlx5_0:1,mlx5_3:1</code></td>
|
||||||
|
<td><code>config.env:L27</code></td><td><code>run_quick_map.sh:L232</code></td>
|
||||||
|
<td>服务命令;Manifest;两节点 NCCL 日志</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>以 <code>MEM_FRACTION_STATIC</code> 为例,三个查看层级是:</p>
|
||||||
|
<pre><code># 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"])'</code></pre>
|
||||||
|
<p>
|
||||||
|
第 3 层最可信,因为 <code>start_service_node</code> 在
|
||||||
|
<code>run_quick_map.sh:L269-L291</code> 先展开命令,再写入
|
||||||
|
<code><role>_server_cmd.txt</code>;<code>write_run_manifest</code>
|
||||||
|
在 <code>L641-L676</code> 另存一份结构化配置。二者不一致时,应以实际容器命令和服务日志继续核查。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>4.3 TSV 如何变成请求</h3>
|
||||||
|
<p>
|
||||||
|
<code>quick_map_scenarios.tsv:L1</code> 定义列:
|
||||||
|
<code>case_id, stage, isl, osl, concurrency, multiplier, minimum, warmup, note</code>。
|
||||||
|
<code>run_fixed_suite</code> 在 <code>run_quick_map.sh:L695-L736</code> 中逐行读取。
|
||||||
|
</p>
|
||||||
|
<pre><code>num_prompts = concurrency × multiplier
|
||||||
|
num_prompts = max(num_prompts, minimum)</code></pre>
|
||||||
|
<p>
|
||||||
|
计算位于 <code>run_quick_map.sh:L700-L718</code>。因此场景表不直接写死总请求数,
|
||||||
|
而是让总请求数随并发扩大,同时允许 <code>minimum</code> 给低并发 Case 提供最小样本量。
|
||||||
|
<code>CASE_IDS</code> 的过滤发生在 <code>L69-L84</code> 与 <code>L703-L705</code>。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>4.4 11 个固定场景</h3>
|
||||||
|
<p>
|
||||||
|
<code>quick_map_scenarios.tsv:L2-L12</code> 覆盖冷 Prefill、并发 Prefill、短 Decode、
|
||||||
|
长 Decode 与长上下文 Decode。长 Prefill 和长 Decode Case 默认不做额外 Warm-up,
|
||||||
|
避免昂贵预热和 Prefix Cache 污染;短 Decode Case保留一次 Warm-up。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="service">5. 双机服务启动</h2>
|
||||||
|
<h3>5.1 参数校验与 RDMA 门禁</h3>
|
||||||
|
<p>
|
||||||
|
<code>validate_network_config</code> 位于 <code>run_quick_map.sh:L85-L140</code>。
|
||||||
|
它不接受任意网卡,而是把计算网约束为 <code>eth0/eth3</code>,把 RDMA HCA 约束为
|
||||||
|
<code>mlx5_0/mlx5_3</code>。开启 RDMA 时,两条 rail 和必需设备路径都必须存在。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<code>preflight_rdma_devices_on_node</code> 在 <code>L141-L156</code> 逐节点检查
|
||||||
|
<code>/dev/infiniband/rdma_cm</code>、<code>uverbs0</code>、<code>uverbs3</code>。
|
||||||
|
这是宿主机设备存在性检查,不能证明 NCCL 最终真的用了 IB,所以后面还有日志门禁。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>5.2 Docker 与 SGLang 命令展开</h3>
|
||||||
|
<p>
|
||||||
|
<code>build_server_command</code> 位于 <code>run_quick_map.sh:L212-L268</code>。
|
||||||
|
关键部分如下:
|
||||||
|
</p>
|
||||||
|
<pre><code>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 ...</code></pre>
|
||||||
|
<ul>
|
||||||
|
<li><code>--network host</code> 让容器直接使用宿主机网络栈,避免额外端口映射。</li>
|
||||||
|
<li><code>--device</code> 把宿主机 RDMA 字符设备暴露给容器。只有环境变量而没有设备透传时,NCCL 仍可能找不到 IB。</li>
|
||||||
|
<li><code>--node-rank</code> 区分 Head 为 0、Worker 为 1;其余模型和并行参数保持一致。</li>
|
||||||
|
<li>完整展开命令会保存到结果目录,便于复现,而不是只留在终端历史中。</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>5.3 为什么 Worker 先启动</h3>
|
||||||
|
<p>
|
||||||
|
<code>start_service</code> 位于 <code>run_quick_map.sh:L334-L388</code>。
|
||||||
|
它先调用 Worker 的 <code>start_service_node</code>,再启动 Head,随后轮询 Head 的
|
||||||
|
<code>/health</code>。这样 Worker 已经等待分布式 rendezvous,Head 启动后两端更容易同步进入初始化。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
健康检查成功还不够。<code>verify_nccl_transport_node</code>
|
||||||
|
在 <code>L293-L325</code> 从服务日志拒绝 <code>NET/IB : No device found</code>,
|
||||||
|
并要求看到 <code>NET/IB</code> 及两条 HCA;<code>L326-L333</code> 对两节点都执行。
|
||||||
|
因而脚本采用 fail-closed:无法证明走 RDMA 就不开始 benchmark。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>5.4 停止与证据保存</h3>
|
||||||
|
<p>
|
||||||
|
<code>stop_service_node</code> 位于 <code>run_quick_map.sh:L389-L410</code>。
|
||||||
|
删除容器前先保存 <code>docker inspect</code> 和最终日志,再执行强制移除。
|
||||||
|
<code>cleanup</code> 在 <code>L849-L853</code> 配合 <code>trap</code>,保证异常退出也尝试清理两端服务。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="bench">6. Benchmark 请求生成与 Case 生命周期</h2>
|
||||||
|
<h3>6.1 命令生成</h3>
|
||||||
|
<p>
|
||||||
|
<code>prepare_bench_command</code> 位于 <code>run_quick_map.sh:L416-L464</code>。
|
||||||
|
它在 benchmark 客户端容器中运行 <code>python3 -m sglang.benchmark.serving</code>,
|
||||||
|
使用 <code>random</code> 数据集并显式传入 ISL、OSL、并发、请求数、请求率、Warm-up 与 Seed。
|
||||||
|
</p>
|
||||||
|
<pre><code>--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</code></pre>
|
||||||
|
<div class="warning">
|
||||||
|
<strong>OSL 语义:</strong>随机 benchmark 会把目标输出长度传给服务端,并使用忽略 EOS 的生成设置,
|
||||||
|
目标是生成足量 token。是否真正达到 OSL 仍以 <code>bench.json</code> 中的成功请求数和
|
||||||
|
<code>total_output_tokens</code> 为准,不能只看命令参数。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>6.2 单个 Case 的完整流程</h3>
|
||||||
|
<p><code>run_bench_case</code> 位于 <code>run_quick_map.sh:L545-L632</code>,顺序是:</p>
|
||||||
|
<ol>
|
||||||
|
<li>根据 suite、case、repetition 创建稳定结果目录。</li>
|
||||||
|
<li>若 <code>RESUME=1</code>,由 <code>case_already_completed</code> 检查 meta 和 bench 是否完整。</li>
|
||||||
|
<li>保存展开后的命令与 Case 元数据。</li>
|
||||||
|
<li>记录开始时间,使用 <code>timeout</code> 执行 benchmark。</li>
|
||||||
|
<li>调用 Python <code>check-bench</code> 校验 JSON,不把“进程退出码为 0”误当成有效结果。</li>
|
||||||
|
<li>失败时由 <code>detect_error_type</code> 区分超时、OOM、服务失活、传输错误和普通 benchmark 失败。</li>
|
||||||
|
<li>写入最终 <code>meta.json</code>,供后续汇总和 Phase 2 时间窗使用。</li>
|
||||||
|
</ol>
|
||||||
|
<p>
|
||||||
|
<code>case_already_completed</code> 在 <code>L511-L525</code> 同时要求 meta 状态为完成、
|
||||||
|
bench 文件存在且可解析。它避免只凭目录存在就跳过半成品。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="mixed">7. 混合 Prefill/Decode A/B</h2>
|
||||||
|
<h3>7.1 一次 repetition 的三个角色</h3>
|
||||||
|
<p><code>run_mixed_repetition</code> 位于 <code>run_quick_map.sh:L755-L832</code>:</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>角色</th><th>Shape</th><th>作用</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>control</td><td>1K → 1K,C=32</td><td>单独运行 Decode 背景,建立无注入基线。</td></tr>
|
||||||
|
<tr><td>decode_background</td><td>1K → 1K,C=32</td><td>混合组中的持续 Decode 请求流。</td></tr>
|
||||||
|
<tr><td>prefill_injection</td><td>128K → 1,C=1</td><td>在 Decode 正式测量期间注入一次长 Prefill。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>7.2 “背景”在代码里是什么</h3>
|
||||||
|
<p>
|
||||||
|
背景不是 SGLang 特殊模式。它只是 Shell 把一个正常 benchmark 放到后台进程运行:
|
||||||
|
<code>run_quick_map.sh:L783-L792</code> 的子 Shell 加 <code>&</code>。
|
||||||
|
<code>background_pid=$!</code> 保存该进程 PID,主脚本随后还能并行发起长 Prefill。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<code>wait_for_bench_main</code> 位于 <code>L738-L753</code>,轮询背景日志中的
|
||||||
|
<code>Starting main benchmark run</code>。看到它以后再等待配置的注入延迟,避免把 Warm-up 阶段误当正式混合阶段。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
注入前还会在 <code>L803-L816</code> 用 <code>kill -0</code> 检查背景进程是否仍存活。
|
||||||
|
若背景已经正常结束,Case 被重写为
|
||||||
|
<code>BACKGROUND_FINISHED_BEFORE_INJECTION</code>,防止生成一个实际上没有重叠的“混合成功”结果。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>7.3 A/B 对比来自哪里</h3>
|
||||||
|
<p>
|
||||||
|
Shell 只负责产生 control、background 和 injection 三份原始记录。
|
||||||
|
Python 在 <code>quick_map_results.py:L439-L580</code> 聚合同一 Case 的重复实验,
|
||||||
|
并在报告阶段计算 percentage change。主要观察 background 相对 control 的
|
||||||
|
Output TPS、TTFT P95、TPOT P95 与 E2E P95 变化。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="results">8. 指标解析与汇总</h2>
|
||||||
|
<h3>8.1 为什么需要 Python 补算</h3>
|
||||||
|
<p>
|
||||||
|
SGLang 版本变化可能导致字段名或原始明细形态不同。
|
||||||
|
<code>quick_map_results.py:L106-L125</code> 既支持单个 JSON 对象,也能从混合日志中寻找首个合法 JSON 行。
|
||||||
|
<code>L152-L157</code> 用多个候选字段名读取同一指标。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>8.2 延迟百分位</h3>
|
||||||
|
<p>
|
||||||
|
<code>latency_stats</code> 位于 <code>quick_map_results.py:L208-L233</code>。
|
||||||
|
优先读取 benchmark 已给出的 mean/P50/P95/P99;缺失时才从请求级数组补算:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>E2E:优先 <code>request_latencies</code>,否则用 TTFT 加该请求所有 ITL。</li>
|
||||||
|
<li>TTFT:来自 <code>ttfts</code>。</li>
|
||||||
|
<li>TPOT:优先 <code>tpots</code>,否则取每请求 ITL 平均值。</li>
|
||||||
|
<li>ITL:展开所有请求的逐 token 间隔。</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<code>percentile_ms</code> 在 <code>L128-L140</code> 使用线性插值,并把秒转换为毫秒。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>8.3 吞吐与完成状态</h3>
|
||||||
|
<p>
|
||||||
|
<code>compute_metrics</code> 位于 <code>quick_map_results.py:L236-L272</code>。
|
||||||
|
Total TPS 优先读取 benchmark 自带字段,缺失时才使用 Input TPS + Output TPS。
|
||||||
|
完成数优先读取 <code>completed</code> 或 <code>successful_requests</code>;
|
||||||
|
失败数缺失时才由尝试数减完成数。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>8.4 重复实验聚合</h3>
|
||||||
|
<p>
|
||||||
|
<code>aggregate_rows</code> 位于 <code>quick_map_results.py:L439-L481</code>。
|
||||||
|
它按 suite/case/role 聚合 repetition,输出均值、离散程度和成功状态。
|
||||||
|
<code>write_report</code> 在 <code>L493-L580</code> 生成面向人的 Markdown 报告,
|
||||||
|
<code>write_csv</code> 与 <code>summarize</code> 在 <code>L581-L601</code> 生成机器可读汇总。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="artifacts">9. 结果目录与数据契约</h2>
|
||||||
|
<pre><code>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</code></pre>
|
||||||
|
<p>
|
||||||
|
<code>meta.json</code> 的结构由 <code>quick_map_results.py:L307-L330</code> 写入,
|
||||||
|
包含 shape、并发、请求数、Warm-up、开始结束时间、退出码与错误分类。
|
||||||
|
<code>manifest.json</code> 由 <code>L333-L382</code> 维护,记录模型、镜像、并行参数、
|
||||||
|
NCCL/RDMA 参数和 Git 状态。两者共同保证结果可追溯。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="index">10. 函数行号索引</h2>
|
||||||
|
<h3>10.1 run_quick_map.sh</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>行号</th><th>函数</th><th>一句话职责</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>L27-L68</td><td><code>log</code> 到 <code>case_selected</code></td><td>日志、时间、命令打印、节点执行和 Case 过滤基础函数。</td></tr>
|
||||||
|
<tr><td>L69-L140</td><td><code>validate_case_filter</code> / <code>validate_network_config</code></td><td>运行前拒绝未知 Case 和非计算网配置。</td></tr>
|
||||||
|
<tr><td>L141-L211</td><td>RDMA、健康和客户端预检</td><td>检查设备、服务、GPU 占用和 benchmark 客户端。</td></tr>
|
||||||
|
<tr><td>L212-L268</td><td><code>build_server_command</code></td><td>构造每个节点的完整 Docker + SGLang 命令。</td></tr>
|
||||||
|
<tr><td>L269-L388</td><td>启动与 NCCL 验证</td><td>启动节点、等待健康、从日志证明 NET/IB 双 HCA。</td></tr>
|
||||||
|
<tr><td>L389-L415</td><td>停止服务</td><td>保存日志与 inspect 后删除两端容器。</td></tr>
|
||||||
|
<tr><td>L416-L510</td><td>bench 命令与 meta 参数</td><td>构造请求并准备结果元数据。</td></tr>
|
||||||
|
<tr><td>L511-L632</td><td>断点续跑、错误分类、单 Case</td><td>执行并验证一个 benchmark Case。</td></tr>
|
||||||
|
<tr><td>L633-L694</td><td>失败标记、Manifest、汇总、日志</td><td>Run 级元数据和结果收口。</td></tr>
|
||||||
|
<tr><td>L695-L737</td><td><code>run_fixed_suite</code></td><td>遍历 TSV 与 repetition。</td></tr>
|
||||||
|
<tr><td>L738-L847</td><td>混合 A/B</td><td>确保 Decode 与长 Prefill 在时间上真实重叠。</td></tr>
|
||||||
|
<tr><td>L849-L932</td><td>清理、独立 suite、all</td><td>管理完整生命周期与最终状态。</td></tr>
|
||||||
|
<tr><td>L933-L957</td><td><code>main</code></td><td>分发 <code>all/start/fixed/mixed/stop</code>。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>10.2 quick_map_results.py</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>行号</th><th>函数组</th><th>职责</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>L94-L125</td><td>JSON I/O</td><td>可靠读取原始 benchmark 输出。</td></tr>
|
||||||
|
<tr><td>L128-L207</td><td>百分位与请求级 fallback</td><td>从明细恢复 E2E、TTFT、TPOT、ITL。</td></tr>
|
||||||
|
<tr><td>L208-L272</td><td><code>latency_stats</code> / <code>compute_metrics</code></td><td>统一指标字段与单位。</td></tr>
|
||||||
|
<tr><td>L275-L306</td><td><code>parse_scenarios</code></td><td>校验 TSV schema、类型和重复 Case。</td></tr>
|
||||||
|
<tr><td>L307-L404</td><td>Case、Manifest、失败状态</td><td>维护机器可读运行状态。</td></tr>
|
||||||
|
<tr><td>L405-L492</td><td>行构造与聚合</td><td>把每次 repetition 合并为 Case 统计。</td></tr>
|
||||||
|
<tr><td>L493-L601</td><td>报告与汇总</td><td>输出 Markdown、CSV、JSONL。</td></tr>
|
||||||
|
<tr><td>L602-L716</td><td>CLI</td><td>定义 Shell 调用的子命令和参数。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
本文只描述提交 <code>ca1f2f63375c</code> 的实现。维护时应同时更新提交基线、行号索引与关键控制流,
|
||||||
|
不应只改文字结论。
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
586
docs/dsv4pro_pro6000d_2node_sglang/phase2_code.html
Normal file
586
docs/dsv4pro_pro6000d_2node_sglang/phase2_code.html
Normal file
@ -0,0 +1,586 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="color-scheme" content="light">
|
||||||
|
<title>Phase 2 Code:DSV4-Pro 双机 Pro6000D SGLang 硬件竞争归因</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--canvas: #eef3f4;
|
||||||
|
--paper: #ffffff;
|
||||||
|
--ink: #182126;
|
||||||
|
--muted: #5a6970;
|
||||||
|
--line: #d4dee1;
|
||||||
|
--navy: #17363d;
|
||||||
|
--teal: #087c72;
|
||||||
|
--teal-soft: #e8f5f3;
|
||||||
|
--amber: #a64c14;
|
||||||
|
--amber-soft: #fff1e7;
|
||||||
|
--code-bg: #17252b;
|
||||||
|
--code-ink: #eaf2f3;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; letter-spacing: 0; }
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ink);
|
||||||
|
background: var(--canvas);
|
||||||
|
font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.72;
|
||||||
|
}
|
||||||
|
header { color: #f6fbfb; background: var(--navy); border-bottom: 5px solid #d2692b; }
|
||||||
|
.header-inner, main { width: min(100% - 36px, 1120px); margin: 0 auto; }
|
||||||
|
.header-inner { padding: 34px 0 30px; }
|
||||||
|
.eyebrow { margin: 0 0 6px; color: #9edbd5; font-size: 13px; font-weight: 700; }
|
||||||
|
h1 { margin: 0; font-size: clamp(28px, 4vw, 42px); line-height: 1.25; }
|
||||||
|
.meta { margin-top: 15px; color: #d6e5e7; font-size: 14px; }
|
||||||
|
main {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 70px;
|
||||||
|
padding: 38px 48px 58px;
|
||||||
|
background: var(--paper);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 12px 30px rgba(27, 45, 51, 0.07);
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 46px 0 15px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
font-size: 25px;
|
||||||
|
line-height: 1.35;
|
||||||
|
border-bottom: 2px solid #adbbc0;
|
||||||
|
}
|
||||||
|
h2:first-of-type { margin-top: 18px; }
|
||||||
|
h3 { margin: 29px 0 10px; color: #21454d; font-size: 19px; }
|
||||||
|
h4 { margin: 22px 0 8px; font-size: 16px; }
|
||||||
|
p, ul, ol { margin-top: 0; margin-bottom: 16px; }
|
||||||
|
li + li { margin-top: 5px; }
|
||||||
|
a { color: var(--teal); text-underline-offset: 3px; }
|
||||||
|
code {
|
||||||
|
padding: 2px 5px;
|
||||||
|
color: #85380d;
|
||||||
|
background: var(--amber-soft);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: "SFMono-Regular", Consolas, monospace;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 14px 0 22px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
overflow: auto;
|
||||||
|
color: var(--code-ink);
|
||||||
|
background: var(--code-bg);
|
||||||
|
border-radius: 5px;
|
||||||
|
font: 13px/1.62 "SFMono-Regular", Consolas, monospace;
|
||||||
|
}
|
||||||
|
pre code { padding: 0; color: inherit; background: transparent; }
|
||||||
|
table { width: 100%; margin: 16px 0 26px; border-collapse: collapse; font-size: 14px; }
|
||||||
|
th, td {
|
||||||
|
padding: 9px 11px;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
th { color: #153b41; background: #eaf2f2; }
|
||||||
|
tbody tr:nth-child(even) { background: #fafcfc; }
|
||||||
|
.callout { margin: 18px 0 26px; padding: 14px 18px; background: var(--teal-soft); border-left: 4px solid var(--teal); }
|
||||||
|
.warning { margin: 18px 0 26px; padding: 14px 18px; background: var(--amber-soft); border-left: 4px solid var(--amber); }
|
||||||
|
.toc { columns: 2; column-gap: 38px; margin: 16px 0 24px; padding-left: 22px; }
|
||||||
|
.toc li { break-inside: avoid; }
|
||||||
|
.path { font-family: "SFMono-Regular", Consolas, monospace; font-size: 13px; }
|
||||||
|
footer { margin-top: 48px; padding-top: 18px; color: var(--muted); border-top: 1px solid var(--line); }
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
main { padding: 28px 20px 42px; }
|
||||||
|
.toc { columns: 1; }
|
||||||
|
table { display: block; overflow-x: auto; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-inner">
|
||||||
|
<p class="eyebrow">Standalone Code Walkthrough / Phase 2</p>
|
||||||
|
<h1>DSV4-Pro 双机 Pro6000D SGLang 硬件竞争归因:代码详解</h1>
|
||||||
|
<div class="meta">
|
||||||
|
行号基线:<code>ca1f2f63375c</code>
|
||||||
|
生成时间:2026-07-31 13:04:21 CST
|
||||||
|
入口:<code>run_hardware_contention_attribution.sh</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="callout">
|
||||||
|
<strong>文档边界:</strong>这是一份独立代码档案。它解释 Phase 2 如何复用 Phase 1 请求、
|
||||||
|
同步采集两节点监控,并按真实 Case 时间窗做硬件归因。所有行号绑定提交
|
||||||
|
<code>ca1f2f63375c</code>。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="read">1. 阅读导航</h2>
|
||||||
|
<ul class="toc">
|
||||||
|
<li><a href="#flow">总体控制流</a></li>
|
||||||
|
<li><a href="#files">文件职责</a></li>
|
||||||
|
<li><a href="#reuse">Phase 1 复用边界</a></li>
|
||||||
|
<li><a href="#preflight">预检与时钟</a></li>
|
||||||
|
<li><a href="#collectors">采集器实现</a></li>
|
||||||
|
<li><a href="#cases">Case 编排</a></li>
|
||||||
|
<li><a href="#alignment">时间窗对齐</a></li>
|
||||||
|
<li><a href="#outputs">结构化输出</a></li>
|
||||||
|
<li><a href="#index">函数行号索引</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="flow">2. 总体控制流</h2>
|
||||||
|
<pre><code>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</code></pre>
|
||||||
|
<p>
|
||||||
|
Phase 2 不复制模型部署和 benchmark 生成代码。它新增的是诊断编排层:
|
||||||
|
在同一个服务生命周期中,让两节点的 GPU、CPU、NUMA、网络和 RDMA 时间序列包围 benchmark,
|
||||||
|
最后按 Case 时间切片。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="files">3. 文件职责</h2>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>文件</th><th>行数</th><th>职责</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="path">run_hardware_contention_attribution.sh</td><td>689</td>
|
||||||
|
<td>唯一入口,调用 Phase 1 服务/请求,管理所有采集器、静态快照、Case marker 与清理。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="path">config.env</td><td>40</td>
|
||||||
|
<td>定义 Phase 1 相对路径、诊断 Case、采样间隔、采集命令和结果路径。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="path">hardware_contention_attribution.py</td><td>574</td>
|
||||||
|
<td>记录 Manifest/marker,汇总 GPU 与 RDMA,按 Case 时间窗切片并生成报告。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="path">tests/test_hardware_contention_attribution.py</td><td>197</td>
|
||||||
|
<td>验证 GPU/RDMA 汇总、时间窗切片和结果生成的纯 Python 逻辑。</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>3.1 Phase 2 与 Phase 1 的文件关系</h3>
|
||||||
|
<pre><code>用户
|
||||||
|
└─ 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 时间窗生成归因汇总</code></pre>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>上游</th><th>下游</th><th>代码连接点</th><th>关系</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Phase 2 <code>config.env</code></td><td>Phase 2 Shell</td>
|
||||||
|
<td><code>run_hardware_contention_attribution.sh:L8</code></td><td>提供诊断 Case、采样策略和 Phase 1 相对入口。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Phase 2 Shell</td><td>Phase 1 Shell</td>
|
||||||
|
<td><code>run_hardware_contention_attribution.sh:L195-L213</code></td><td>通过环境变量和 action 委托服务与请求。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Phase 1 <code>config.env</code></td><td>Phase 1 Shell</td>
|
||||||
|
<td><code>run_quick_map.sh:L8</code></td><td>提供实际模型服务参数,包括 <code>MEM_FRACTION_STATIC</code>。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Phase 1 Case 结果</td><td>Phase 2 Python</td>
|
||||||
|
<td><code>hardware_contention_attribution.py:L213-L260</code></td><td>提供 benchmark 指标和精确开始/结束时间。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Phase 2 Shell 采集器</td><td>Phase 2 Python</td>
|
||||||
|
<td><code>hardware_contention_attribution.py:L276-L321</code></td><td>提供两节点 GPU/RDMA 时间序列供 Case 切片。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Phase 2 单元测试</td><td>Phase 2 Python</td>
|
||||||
|
<td><code>tests/test_hardware_contention_attribution.py</code></td><td>用合成监控数据验证 delta、速率和时间窗。</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
这里有两套 <code>config.env</code>,职责不同。Phase 2 的配置控制“测哪些 Case、如何监控”;
|
||||||
|
Phase 1 的配置控制“模型如何部署、请求如何生成”。Phase 2 没有复制
|
||||||
|
<code>MEM_FRACTION_STATIC</code>,因此它最终仍从 Phase 1 的
|
||||||
|
<code>config.env:L38</code> 取得默认值。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>3.1 config.env 分区</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>范围</th><th>内容</th><th>说明</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>config.env:L3-L8</code></td><td>实验名与 Phase 1 入口</td><td>通过相对路径复用 Phase 1,不依赖启动命令当前目录。</td></tr>
|
||||||
|
<tr><td><code>config.env:L10-L16</code></td><td>节点、端口、容器名</td><td>用于健康检查、PID 映射和容器级监控。</td></tr>
|
||||||
|
<tr><td><code>config.env:L18-L20</code></td><td>诊断 Case</td><td>五个固定 Case,加一个混合 A/B 开关。</td></tr>
|
||||||
|
<tr><td><code>config.env:L22-L32</code></td><td>采集策略</td><td>1 秒采样、空闲基线、冷却、DCGM 字段、perf 事件、RDMA HCA 和时钟容差。</td></tr>
|
||||||
|
<tr><td><code>config.env:L34-L40</code></td><td>路径与运行模式</td><td>Repo/Result/Runtime、Dry-run 和是否容忍部分采集器失败。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="reuse">4. Phase 1 复用边界</h2>
|
||||||
|
<h3>4.1 委托函数</h3>
|
||||||
|
<p>
|
||||||
|
<code>run_phase1_action</code> 位于
|
||||||
|
<code>run_hardware_contention_attribution.sh:L195-L214</code>。它向 Phase 1 注入:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>相同 <code>RUN_ID</code> 体系下的独立 benchmark 子目录。</li>
|
||||||
|
<li>Phase 2 自己的 <code>service/</code> 证据目录。</li>
|
||||||
|
<li>指定 <code>CASE_IDS</code>,使 Phase 1 只跑诊断 Case。</li>
|
||||||
|
<li><code>CASE_COOLDOWN_S=0</code>,由 Phase 2 统一控制 Case 间隔。</li>
|
||||||
|
<li><code>DRY_RUN</code> 原样传递,确保 Dry-run 不会偷偷启动模型。</li>
|
||||||
|
</ul>
|
||||||
|
<pre><code>run_phase1_action start
|
||||||
|
run_phase1_action fixed # 通过 CASE_IDS 只跑一个 Case
|
||||||
|
run_phase1_action mixed
|
||||||
|
run_phase1_action stop</code></pre>
|
||||||
|
|
||||||
|
<h3>4.2 用户只运行哪个入口</h3>
|
||||||
|
<p>
|
||||||
|
正式执行只运行 Phase 2:
|
||||||
|
<code>bash run_hardware_contention_attribution.sh all</code>。
|
||||||
|
<code>start_service</code> 和 <code>stop_service</code>
|
||||||
|
位于 <code>L215-L229</code>,是编排器内部调用,不需要手工先执行 Phase 1。
|
||||||
|
</p>
|
||||||
|
<div class="warning">
|
||||||
|
<strong>设计不变量:</strong>Phase 2 不修改 Phase 1 的服务参数和请求口径。
|
||||||
|
如果模型启动或请求生成需要修复,应改 Phase 1;如果采集、时间对齐或归因需要修复,应改 Phase 2。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>4.3 Phase 2 中如何查服务参数的实际值</h3>
|
||||||
|
<p>
|
||||||
|
例如 <code>MEM_FRACTION_STATIC</code> 的完整传递链是:
|
||||||
|
</p>
|
||||||
|
<pre><code>调用命令环境(可选覆盖)
|
||||||
|
→ 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</code></pre>
|
||||||
|
<p>
|
||||||
|
Phase 2 的顶层 <code>manifest.json</code> 记录监控配置,不重复记录全部服务参数。
|
||||||
|
查“模型实际怎么起的”应看 <code>service/head_server_cmd.txt</code> 与
|
||||||
|
<code>service/worker_server_cmd.txt</code>;查结构化值应看任一 Phase 1 子 Run 的
|
||||||
|
<code>run_manifest.json</code>。
|
||||||
|
</p>
|
||||||
|
<pre><code># 查看 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"])'</code></pre>
|
||||||
|
<p>
|
||||||
|
如果这样启动:
|
||||||
|
<code>MEM_FRACTION_STATIC=0.85 bash run_hardware_contention_attribution.sh all</code>,
|
||||||
|
外部变量会由 Phase 2 进程继承给 Phase 1,Phase 1 的
|
||||||
|
<code>${MEM_FRACTION_STATIC:-0.9}</code> 会保留 <code>0.85</code>。
|
||||||
|
因此只看默认配置不足以证明某次实验用了什么,必须查看 Run 证据。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="preflight">5. 预检、Manifest 与时钟</h2>
|
||||||
|
<h3>5.1 配置和工具预检</h3>
|
||||||
|
<p>
|
||||||
|
<code>validate_config</code> 在
|
||||||
|
<code>run_hardware_contention_attribution.sh:L66-L106</code>
|
||||||
|
检查 Phase 1 入口、节点、Case 和关键数值。
|
||||||
|
<code>preflight_node_tools</code> 在 <code>L107-L134</code>
|
||||||
|
对两节点检查 Docker、NVIDIA、RDMA、sysstat、perf 与 NUMA 工具。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
采集器可用性不能在运行半小时后才发现。预检默认 fail-closed;
|
||||||
|
只有显式允许部分采集器缺失时,才降级继续。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>5.2 为什么检查两机时钟</h3>
|
||||||
|
<p>
|
||||||
|
<code>preflight_clock_sync</code> 位于 <code>L135-L151</code>。
|
||||||
|
Phase 2 用 wall clock 把 benchmark 的 <code>started_at/ended_at</code>
|
||||||
|
与两节点采样行对齐。如果两机时钟偏差超过配置容差,同一 Case 在 Worker 上会切到错误窗口。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>5.3 运行元数据</h3>
|
||||||
|
<p>
|
||||||
|
Shell 的 <code>write_manifest</code> 位于 <code>L152-L176</code>,
|
||||||
|
Python 的 <code>create_manifest</code> 位于
|
||||||
|
<code>hardware_contention_attribution.py:L367-L390</code>。
|
||||||
|
Manifest 记录 Git commit、是否 dirty、Phase 1 入口、节点、Case、采样周期、时钟容差和 Dry-run。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<code>mark_event</code> 位于 Shell <code>L177-L194</code>,
|
||||||
|
Python 的 <code>append_marker</code> 位于 <code>L338-L365</code>。
|
||||||
|
每个 idle/case/cooldown 边界写入纳秒级 wall time,作为人工审计时间线。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="collectors">6. 采集器实现</h2>
|
||||||
|
<h3>6.1 静态快照</h3>
|
||||||
|
<p>
|
||||||
|
<code>capture_command</code>、<code>static_snapshot_command</code> 和
|
||||||
|
<code>capture_static_snapshots</code> 位于 Shell <code>L230-L283</code>。
|
||||||
|
服务启动后和实验结束前分别采集:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>nvidia-smi</code> 与 GPU topology。</li>
|
||||||
|
<li><code>lscpu</code>、<code>numactl --hardware</code>、<code>numastat</code>。</li>
|
||||||
|
<li><code>ip</code>、<code>ethtool</code> 的 <code>eth0/eth3</code> 状态与计数器。</li>
|
||||||
|
<li><code>ibdev2netdev</code>、<code>ibstat</code>、<code>rdma</code> 设备信息。</li>
|
||||||
|
<li>容器列表与 inspect。</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
前后快照回答“实验是否改变了设备状态”;时间序列回答“Case 运行期间发生了什么”。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>6.2 通用采集器包装</h3>
|
||||||
|
<p>
|
||||||
|
<code>start_stream_collector</code> 位于 Shell
|
||||||
|
<code>L284-L318</code>。每个采集器都具备:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>保存完整命令到 <code>collector_commands/</code>。</li>
|
||||||
|
<li>用唯一 tag 标记远端进程,便于精准停止。</li>
|
||||||
|
<li>受 <code>COLLECTOR_TIMEOUT_S</code> 限制,避免永久悬挂。</li>
|
||||||
|
<li>保存 PID、日志与退出状态到 <code>collector_status.csv</code>。</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>6.3 GPU 采样</h3>
|
||||||
|
<p>
|
||||||
|
<code>gpu_sampler_command</code> 位于 Shell <code>L319-L332</code>,
|
||||||
|
每秒调用 <code>nvidia-smi --query-gpu</code>,采集利用率、显存利用率、显存占用、
|
||||||
|
功耗、温度、SM 时钟和显存时钟,并补上 <code>wall_time_ns,node,gpu</code>。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Python 的 <code>summarize_gpu_rows</code> 位于
|
||||||
|
<code>hardware_contention_attribution.py:L101-L135</code>。
|
||||||
|
它按 node + GPU 分组,对每个字段输出 mean、P95、max。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>6.4 RDMA 采样</h3>
|
||||||
|
<p>
|
||||||
|
Shell 的 <code>rdma_sampler_command</code> 与 <code>read_counter</code>
|
||||||
|
位于 <code>L333-L359</code>,读取 <code>mlx5_0/mlx5_3</code> 的端口发送/接收数据、
|
||||||
|
包、错误、丢弃与恢复计数器。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Python 的 <code>summarize_rdma_rows</code> 位于 <code>L148-L206</code>。
|
||||||
|
它按 node + HCA 排序,使用最后值减第一值,并注意 IB
|
||||||
|
<code>port_xmit_data/port_rcv_data</code> 的单位是 4 octets:
|
||||||
|
</p>
|
||||||
|
<pre><code>xmit_bytes = (last_xmit_data - first_xmit_data) × 4
|
||||||
|
xmit_gbps = xmit_bytes × 8 / duration_s / 1e9</code></pre>
|
||||||
|
<p>
|
||||||
|
同时保留错误计数器 delta,因此“带宽低”可以与“链路错误增加”分开判断。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>6.5 CPU、进程和 NUMA</h3>
|
||||||
|
<p>
|
||||||
|
<code>start_node_collectors</code> 位于 Shell <code>L405-L457</code>,
|
||||||
|
每个节点启动九类采集器:
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>采集器</th><th>回答的问题</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>nvidia-smi</code> / DCGM</td><td>GPU 是否算力、显存带宽、功耗或时钟受限。</td></tr>
|
||||||
|
<tr><td><code>mpstat</code></td><td>CPU 总体与逐核是否繁忙。</td></tr>
|
||||||
|
<tr><td><code>pidstat</code></td><td>SGLang/容器进程的 CPU、内存和上下文切换。</td></tr>
|
||||||
|
<tr><td><code>sar -n DEV,EDEV</code></td><td><code>eth0/eth3</code> 吞吐和错误。</td></tr>
|
||||||
|
<tr><td><code>perf stat</code></td><td>容器主进程的 CPU cycles、instructions、cache miss 等。</td></tr>
|
||||||
|
<tr><td><code>docker top</code></td><td>容器 PID 与宿主机 PID 映射。</td></tr>
|
||||||
|
<tr><td><code>numastat</code></td><td>进程内存是否跨 NUMA 节点访问。</td></tr>
|
||||||
|
<tr><td>RDMA counters</td><td>两条 HCA 的真实数据量和错误增量。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
<code>container_pid_preamble</code> 与 <code>numastat_command</code>
|
||||||
|
位于 <code>L372-L404</code>,先解析容器主 PID,再让 perf/numastat 对准实际服务进程。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>6.6 停止与残留清理</h3>
|
||||||
|
<p>
|
||||||
|
<code>check_collectors</code> 和 <code>stop_collectors</code>
|
||||||
|
位于 Shell <code>L469-L519</code>。
|
||||||
|
除了等待已知 PID,还会按唯一 tag 清理远端残留采集器。
|
||||||
|
<code>cleanup</code> 在 <code>L594-L602</code> 由 trap 调用,先停监控再停模型服务。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="cases">7. Case 编排</h2>
|
||||||
|
<h3>7.1 固定 Case</h3>
|
||||||
|
<p>
|
||||||
|
<code>run_fixed_case</code> 位于 Shell <code>L529-L556</code>。
|
||||||
|
它为 Case 生成独立子 Run ID,写 <code>case_start</code> marker,
|
||||||
|
委托 Phase 1 的 <code>fixed</code>,再检查该子 Run 的 <code>summary.csv</code>,
|
||||||
|
最后写 <code>case_end</code> marker。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
默认固定集合来自 <code>config.env:L18</code>,用于区分:
|
||||||
|
单请求长 Prefill、并发 Prefill、普通 Decode、持续长 Decode、长上下文 Decode。
|
||||||
|
Phase 2 不把所有 Phase 1 点再跑一遍,只保留对资源归因有辨识度的负载。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>7.2 混合 Case</h3>
|
||||||
|
<p>
|
||||||
|
<code>run_mixed_case</code> 位于 Shell <code>L557-L582</code>。
|
||||||
|
它复用 Phase 1 已经保证真实时间重叠的 mixed A/B,并把整段监控留在同一采集窗口内。
|
||||||
|
Phase 2 自己不重新实现后台请求与注入逻辑。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>7.3 run_all 的失败策略</h3>
|
||||||
|
<p>
|
||||||
|
<code>run_all</code> 位于 Shell <code>L604-L668</code>:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>服务健康时,即使一个 Case 失败,也继续后续诊断 Case,并累计 failures。</li>
|
||||||
|
<li>每个固定 Case 后检查 <code>/health</code>;服务失活则中止剩余 Case。</li>
|
||||||
|
<li>服务已失活时跳过 mixed,避免无意义错误。</li>
|
||||||
|
<li>不论成功失败,最终尝试 cooldown、停止采集、后快照、停止服务和汇总。</li>
|
||||||
|
<li>Run 状态区分 <code>COMPLETED</code>、<code>COMPLETED_WITH_FAILURES</code>、<code>DRY_RUN</code>。</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="alignment">8. 按真实 Case 时间窗归因</h2>
|
||||||
|
<h3>8.1 时间窗从哪里来</h3>
|
||||||
|
<p>
|
||||||
|
<code>load_case_windows</code> 位于
|
||||||
|
<code>hardware_contention_attribution.py:L233-L260</code>。
|
||||||
|
它读取 Phase 1 每个 <code>meta.json</code> 中的
|
||||||
|
<code>started_at</code> 与 <code>ended_at</code>,转换为纳秒时间戳。
|
||||||
|
这比用“Case marker 前后大概几秒”更精确,因为它对齐的是 benchmark 进程实际测量区间。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>8.2 如何切 GPU/RDMA 数据</h3>
|
||||||
|
<p>
|
||||||
|
<code>rows_in_window</code> 位于 <code>L263-L274</code>,
|
||||||
|
只保留 <code>started_ns ≤ wall_time_ns ≤ ended_ns</code> 的采样行。
|
||||||
|
<code>summarize_case_hardware</code> 位于 <code>L276-L319</code>,
|
||||||
|
对每个 Case、每个节点分别切 GPU 与 RDMA,再复用全局汇总函数。
|
||||||
|
</p>
|
||||||
|
<pre><code>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</code></pre>
|
||||||
|
<div class="warning">
|
||||||
|
1 秒采样意味着很短的请求可能只有少数样本。此时 P95 不稳定,应结合原始时间序列和 Case 持续时间,
|
||||||
|
不能把单个采样峰值解释为稳定瓶颈。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="outputs">9. 输出和归因边界</h2>
|
||||||
|
<pre><code>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</code></pre>
|
||||||
|
<p>
|
||||||
|
Python 的 <code>summarize</code> 位于
|
||||||
|
<code>hardware_contention_attribution.py:L405-L504</code>。
|
||||||
|
它汇总全局 GPU/RDMA、Phase 1 bench、Case 时间窗和 Case 级硬件数据,
|
||||||
|
同时统计采集器状态与文件大小。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
自动报告只整理证据,不自动宣布“瓶颈就是 GPU/NCCL/CPU”。
|
||||||
|
<code>L498-L499</code> 明确要求最终结论结合 markers、原始 DCGM/sysstat 和 SGLang 日志。
|
||||||
|
这是有意的保守边界,避免单个指标被机械误判。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="index">10. 函数行号索引</h2>
|
||||||
|
<h3>10.1 run_hardware_contention_attribution.sh</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>行号</th><th>函数组</th><th>职责</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>L25-L65</td><td>日志、远端执行、命令文件、结果日志</td><td>编排器基础设施。</td></tr>
|
||||||
|
<tr><td>L66-L151</td><td>配置、工具、时钟预检</td><td>正式启动前 fail-fast。</td></tr>
|
||||||
|
<tr><td>L152-L194</td><td>Manifest 与 marker</td><td>记录运行身份和事件边界。</td></tr>
|
||||||
|
<tr><td>L195-L229</td><td>Phase 1 委托与服务生命周期</td><td>复用服务和 benchmark,不复制实现。</td></tr>
|
||||||
|
<tr><td>L230-L283</td><td>静态快照</td><td>保存实验前后硬件、网络、RDMA 和容器状态。</td></tr>
|
||||||
|
<tr><td>L284-L318</td><td><code>start_stream_collector</code></td><td>统一包装远端长时间采集器。</td></tr>
|
||||||
|
<tr><td>L319-L404</td><td>GPU、RDMA、PID、NUMA 命令</td><td>生成各类采集命令。</td></tr>
|
||||||
|
<tr><td>L405-L468</td><td>启动所有采集器</td><td>两节点各启动九类监控。</td></tr>
|
||||||
|
<tr><td>L469-L519</td><td>检查与停止采集器</td><td>收集状态并清理残留。</td></tr>
|
||||||
|
<tr><td>L520-L582</td><td>sleep、固定 Case、混合 Case</td><td>诊断负载编排。</td></tr>
|
||||||
|
<tr><td>L583-L603</td><td>汇总、Manifest 完成、cleanup</td><td>结果收口与异常清理。</td></tr>
|
||||||
|
<tr><td>L604-L668</td><td><code>run_all</code></td><td>Phase 2 完整状态机。</td></tr>
|
||||||
|
<tr><td>L669-L689</td><td><code>main</code></td><td>分发 <code>all/summarize/stop</code>。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>10.2 hardware_contention_attribution.py</h3>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>行号</th><th>函数组</th><th>职责</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>L54-L100</td><td>时间、JSON、数字、CSV</td><td>结果工具基础函数。</td></tr>
|
||||||
|
<tr><td>L101-L141</td><td>GPU 汇总</td><td>按 node/GPU 输出 mean、P95、max。</td></tr>
|
||||||
|
<tr><td>L142-L212</td><td>RDMA 汇总</td><td>计数器 delta、字节换算、Gbps 和错误增量。</td></tr>
|
||||||
|
<tr><td>L213-L260</td><td>Bench 与 Case 时间窗读取</td><td>从 Phase 1 子 Run 建立诊断索引。</td></tr>
|
||||||
|
<tr><td>L263-L321</td><td>时间切片</td><td>按每个 Case 的真实运行窗口汇总 GPU/RDMA。</td></tr>
|
||||||
|
<tr><td>L322-L366</td><td>CSV 与 marker</td><td>输出结构化表和事件时间线。</td></tr>
|
||||||
|
<tr><td>L367-L404</td><td>Manifest 与 bench 校验</td><td>维护 Run 状态,确认子 Run 全部完成。</td></tr>
|
||||||
|
<tr><td>L405-L506</td><td><code>summarize</code></td><td>生成全部汇总表、summary JSON 和 report。</td></tr>
|
||||||
|
<tr><td>L507-L574</td><td>CLI</td><td>向 Shell 提供 marker/manifest/check/summarize 子命令。</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
本文只描述提交 <code>ca1f2f63375c</code> 的实现。后续增加 Nsight、SGLang profiler
|
||||||
|
或新的硬件计数器时,应先明确它属于 Phase 2 采集层还是后续深度剖析层,再更新本档案。
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user