587 lines
27 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 CodeDSV4-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/&lt;RUN_ID&gt;/service/head_server_cmd.txt
→ Phase2/results/&lt;RUN_ID&gt;/bench/&lt;sub-run&gt;/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/&lt;RUN_ID&gt;/service/head_server_cmd.txt
# 从 Phase 1 子 Run Manifest 读取结构化值
python3 -c 'import glob,json; p=glob.glob(
"results/&lt;RUN_ID&gt;/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 1Phase 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/&lt;RUN_ID&gt;/
manifest.json
markers.csv
collector_status.csv
service/
commands/
bench/&lt;phase1-sub-run&gt;/
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>