2026-07-31 17:00:22 +08:00

598 lines
28 KiB
HTML
Raw Permalink 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 1 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; }
.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>
<p>
<a href="./推理优化计划.html">返回推理优化主计划</a> ·
<a href="./phase1_exp.html">打开 Phase 1 实验档案</a>
</p>
<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/&lt;RUN_ID&gt;/server/head_server_cmd.txt
python3 -c 'import json; print(json.load(open(
"results/&lt;RUN_ID&gt;/run_manifest.json"))["mem_fraction_static"])'</code></pre>
<p>
第 3 层最可信,因为 <code>start_service_node</code>
<code>run_quick_map.sh:L269-L291</code> 先展开命令,再写入
<code>&lt;role&gt;_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 已经等待分布式 rendezvousHead 启动后两端更容易同步进入初始化。
</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 → 1KC=32</td><td>单独运行 Decode 背景,建立无注入基线。</td></tr>
<tr><td>decode_background</td><td>1K → 1KC=32</td><td>混合组中的持续 Decode 请求流。</td></tr>
<tr><td>prefill_injection</td><td>128K → 1C=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>&amp;</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/&lt;RUN_ID&gt;/
run.log
manifest.json
server/
head_command.txt
worker_command.txt
*.log
*.inspect.json
cases/
&lt;case_id&gt;/rep&lt;N&gt;/
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>