594 lines
28 KiB
HTML
594 lines
28 KiB
HTML
<!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>
|