ca1f2f63375c。代码变更后应先更新基线提交,再重新核对行号。
1. 阅读方法
行号写法例如
run_quick_map.sh:L212-L268。它表示该提交中,从第 212 行到第 268 行的完整函数段,
不是当前编辑器自动漂移后的行号。
2. 总体控制流
main "$@"
└─ ACTION=all → run_all
├─ 校验场景与客户端
├─ start_service
│ ├─ Worker 节点先启动
│ ├─ Head 节点后启动
│ ├─ 等待 /health
│ └─ 从两端日志验证 NET/IB + 两条 HCA
├─ run_fixed_suite
│ └─ TSV 每一行 → run_bench_case
├─ run_mixed_suite
│ └─ control → decode background + long prefill injection
├─ stop_service
├─ summarize_results
└─ complete_manifest
Shell 负责生命周期、远端执行、容器和失败策略;Python 负责结果读取、指标补算、聚合与报告。 这条分工是理解代码的第一把钥匙。
3. 文件职责
| 文件 | 行数 | 职责 | 主要输出 |
|---|---|---|---|
| run_quick_map.sh | 957 | 唯一入口,管理双机服务、固定场景、混合场景、失败恢复与清理。 | run.log、服务日志、每个 Case 的命令与原始结果。 |
| config.env | 78 | 模型、节点、SGLang、NCCL/RDMA、benchmark、超时和路径配置。 | 被 Shell 直接 source,自身不产生输出。 |
| quick_map_scenarios.tsv | 12 | 固定性能地图的声明式场景表,一行对应一个 Case。 | 输入给 run_fixed_suite。 |
| quick_map_results.py | 716 | 校验 bench JSON、补算百分位、生成 meta/manifest、聚合重复实验。 | summary.csv、summary.jsonl、aggregate.csv、report.md。 |
3.1 文件之间如何调用
用户
└─ bash run_quick_map.sh all
├─ source config.env
│ ├─ 给 Shell 提供模型、节点、服务、NCCL 和 benchmark 变量
│ └─ 计算 SCENARIO_FILE / RESULT_BASE / RUNTIME_BASE
├─ 读取 quick_map_scenarios.tsv
│ └─ 每一行变成一次 run_bench_case 调用
├─ 调用 quick_map_results.py
│ ├─ validate-scenarios:启动前校验 TSV
│ ├─ write-case / mark-case-failed:维护 Case 状态
│ ├─ write-manifest / complete-manifest:维护 Run 状态
│ ├─ check-bench:验证 bench.json
│ └─ summarize:生成 CSV、JSONL 和报告
└─ tests/test_quick_map_results.py
└─ 只测试 Python 解析和聚合,不启动模型
run_quick_map.sh:L6-L16 是关系的起点:先定位自身目录,再
source config.env,随后把结果工具固定为同目录下的
quick_map_results.py。Shell 与 Python 之间不是 import 关系,
而是 Shell 通过 Python CLI 子命令交换 JSON/CSV 文件。
| 上游文件 | 下游文件 | 连接点 | 传递内容 |
|---|---|---|---|
config.env | run_quick_map.sh |
run_quick_map.sh:L8 | Shell 变量,允许调用命令中的环境变量覆盖默认值。 |
quick_map_scenarios.tsv | run_fixed_suite |
run_quick_map.sh:L695-L729 | Case ID、ISL、OSL、C、请求数规则和 Warm-up。 |
run_quick_map.sh | quick_map_results.py |
RESULT_TOOL,run_quick_map.sh:L13 | 命令行参数、bench JSON、meta 和 Manifest 路径。 |
quick_map_results.py | 结果目录 | quick_map_results.py:L307-L601 | 结构化 Case、Run、汇总和报告。 |
tests/test_quick_map_results.py | quick_map_results.py |
Python 单元测试 | 用合成数据验证字段兼容、百分位和聚合。 |
4. 配置与场景
4.1 配置分区
| 代码范围 | 配置组 | 影响 |
|---|---|---|
config.env:L4-L6 | 实验与模型 | 实验名、模型名和两节点都能看到的模型路径。 |
config.env:L8-L18 | 节点与并行 | Head/Worker 地址、TP16、EP2、双节点 rank。 |
config.env:L20-L22 | 镜像与缓存 | SGLang 镜像、宿主机缓存目录和容器挂载。 |
config.env:L24-L35 | NCCL/RDMA | 限定 eth0/eth3、mlx5_0/mlx5_3 以及设备透传。 |
config.env:L37-L41 | 服务容量 | 显存比例、CUDA Graph Decode BS、活跃请求上限。 |
config.env:L43-L61 | 压测 | 随机数据生成、请求率、重复次数、混合注入和超时。 |
config.env:L67-L78 | 运行控制 | 相对路径、Case 过滤、Dry-run、断点续跑。 |
4.2 具体值在哪里看
配置采用 VAR="${VAR:-default}"。含义是:启动命令已经提供
VAR 时使用外部值,否则使用 config.env 里的默认值。
所以应区分“代码默认值”和“某次 Run 的实际值”。
| 变量 | 当前默认值 | 默认值定义 | 传入服务 | Run 后证据 |
|---|---|---|---|---|
MEM_FRACTION_STATIC | 0.9 |
config.env:L38 | run_quick_map.sh:L258 → --mem-fraction-static |
server/head_server_cmd.txt;run_manifest.json 的 mem_fraction_static |
CUDA_GRAPH_MAX_BS_DECODE | 64 |
config.env:L39 | run_quick_map.sh:L259 |
服务命令;Manifest 的 cuda_graph_max_bs_decode |
MAX_RUNNING_REQUESTS | 256 |
config.env:L40 | run_quick_map.sh:L260 |
服务命令;Manifest 的 max_running_requests |
TP_SIZE / EP_SIZE / NNODES | 16 / 2 / 2 |
config.env:L15-L17 | run_quick_map.sh:L250-L253 |
服务命令;Manifest 的 tp_size/ep_size/nnodes |
NCCL_SOCKET_IFNAME | eth0 |
config.env:L26 | run_quick_map.sh:L231 |
服务命令;Manifest 的同名小写字段;NCCL 服务日志 |
NCCL_IB_HCA | =mlx5_0:1,mlx5_3:1 |
config.env:L27 | run_quick_map.sh:L232 |
服务命令;Manifest;两节点 NCCL 日志 |
以 MEM_FRACTION_STATIC 为例,三个查看层级是:
# 1. 看仓库默认值
grep '^MEM_FRACTION_STATIC=' config.env
# 2. 看本次命令实际覆盖后的值
source ./config.env
printf '%s\n' "${MEM_FRACTION_STATIC}"
# 3. 看已经执行的 Run 最终用了什么
grep -- '--mem-fraction-static' results/<RUN_ID>/server/head_server_cmd.txt
python3 -c 'import json; print(json.load(open(
"results/<RUN_ID>/run_manifest.json"))["mem_fraction_static"])'
第 3 层最可信,因为 start_service_node 在
run_quick_map.sh:L269-L291 先展开命令,再写入
<role>_server_cmd.txt;write_run_manifest
在 L641-L676 另存一份结构化配置。二者不一致时,应以实际容器命令和服务日志继续核查。
4.3 TSV 如何变成请求
quick_map_scenarios.tsv:L1 定义列:
case_id, stage, isl, osl, concurrency, multiplier, minimum, warmup, note。
run_fixed_suite 在 run_quick_map.sh:L695-L736 中逐行读取。
num_prompts = concurrency × multiplier
num_prompts = max(num_prompts, minimum)
计算位于 run_quick_map.sh:L700-L718。因此场景表不直接写死总请求数,
而是让总请求数随并发扩大,同时允许 minimum 给低并发 Case 提供最小样本量。
CASE_IDS 的过滤发生在 L69-L84 与 L703-L705。
4.4 11 个固定场景
quick_map_scenarios.tsv:L2-L12 覆盖冷 Prefill、并发 Prefill、短 Decode、
长 Decode 与长上下文 Decode。长 Prefill 和长 Decode Case 默认不做额外 Warm-up,
避免昂贵预热和 Prefix Cache 污染;短 Decode Case保留一次 Warm-up。
5. 双机服务启动
5.1 参数校验与 RDMA 门禁
validate_network_config 位于 run_quick_map.sh:L85-L140。
它不接受任意网卡,而是把计算网约束为 eth0/eth3,把 RDMA HCA 约束为
mlx5_0/mlx5_3。开启 RDMA 时,两条 rail 和必需设备路径都必须存在。
preflight_rdma_devices_on_node 在 L141-L156 逐节点检查
/dev/infiniband/rdma_cm、uverbs0、uverbs3。
这是宿主机设备存在性检查,不能证明 NCCL 最终真的用了 IB,所以后面还有日志门禁。
5.2 Docker 与 SGLang 命令展开
build_server_command 位于 run_quick_map.sh:L212-L268。
关键部分如下:
docker run --rm --network host --ipc host --shm-size 20g
--device /dev/infiniband/rdma_cm
--device /dev/infiniband/uverbs0
--device /dev/infiniband/uverbs3
-e NCCL_SOCKET_IFNAME=eth0,eth3
-e NCCL_IB_HCA=mlx5_0,mlx5_3
-e NCCL_CROSS_NIC=...
IMAGE python3 -m sglang.launch_server
--model-path ...
--tp-size 16 --ep-size 2 --nnodes 2 --node-rank ...
--dist-init-addr HEAD_IP:DIST_PORT
--mem-fraction-static ...
--cuda-graph-max-bs-decode ...
--max-running-requests ...
--network host让容器直接使用宿主机网络栈,避免额外端口映射。--device把宿主机 RDMA 字符设备暴露给容器。只有环境变量而没有设备透传时,NCCL 仍可能找不到 IB。--node-rank区分 Head 为 0、Worker 为 1;其余模型和并行参数保持一致。- 完整展开命令会保存到结果目录,便于复现,而不是只留在终端历史中。
5.3 为什么 Worker 先启动
start_service 位于 run_quick_map.sh:L334-L388。
它先调用 Worker 的 start_service_node,再启动 Head,随后轮询 Head 的
/health。这样 Worker 已经等待分布式 rendezvous,Head 启动后两端更容易同步进入初始化。
健康检查成功还不够。verify_nccl_transport_node
在 L293-L325 从服务日志拒绝 NET/IB : No device found,
并要求看到 NET/IB 及两条 HCA;L326-L333 对两节点都执行。
因而脚本采用 fail-closed:无法证明走 RDMA 就不开始 benchmark。
5.4 停止与证据保存
stop_service_node 位于 run_quick_map.sh:L389-L410。
删除容器前先保存 docker inspect 和最终日志,再执行强制移除。
cleanup 在 L849-L853 配合 trap,保证异常退出也尝试清理两端服务。
6. Benchmark 请求生成与 Case 生命周期
6.1 命令生成
prepare_bench_command 位于 run_quick_map.sh:L416-L464。
它在 benchmark 客户端容器中运行 python3 -m sglang.benchmark.serving,
使用 random 数据集并显式传入 ISL、OSL、并发、请求数、请求率、Warm-up 与 Seed。
--dataset-name random
--random-input-len ISL
--random-output-len OSL
--num-prompts N
--max-concurrency C
--request-rate REQUEST_RATE
--warmup-requests W
--seed SEED
--output-file bench.json
--output-details
bench.json 中的成功请求数和
total_output_tokens 为准,不能只看命令参数。
6.2 单个 Case 的完整流程
run_bench_case 位于 run_quick_map.sh:L545-L632,顺序是:
- 根据 suite、case、repetition 创建稳定结果目录。
- 若
RESUME=1,由case_already_completed检查 meta 和 bench 是否完整。 - 保存展开后的命令与 Case 元数据。
- 记录开始时间,使用
timeout执行 benchmark。 - 调用 Python
check-bench校验 JSON,不把“进程退出码为 0”误当成有效结果。 - 失败时由
detect_error_type区分超时、OOM、服务失活、传输错误和普通 benchmark 失败。 - 写入最终
meta.json,供后续汇总和 Phase 2 时间窗使用。
case_already_completed 在 L511-L525 同时要求 meta 状态为完成、
bench 文件存在且可解析。它避免只凭目录存在就跳过半成品。
7. 混合 Prefill/Decode A/B
7.1 一次 repetition 的三个角色
run_mixed_repetition 位于 run_quick_map.sh:L755-L832:
| 角色 | Shape | 作用 |
|---|---|---|
| control | 1K → 1K,C=32 | 单独运行 Decode 背景,建立无注入基线。 |
| decode_background | 1K → 1K,C=32 | 混合组中的持续 Decode 请求流。 |
| prefill_injection | 128K → 1,C=1 | 在 Decode 正式测量期间注入一次长 Prefill。 |
7.2 “背景”在代码里是什么
背景不是 SGLang 特殊模式。它只是 Shell 把一个正常 benchmark 放到后台进程运行:
run_quick_map.sh:L783-L792 的子 Shell 加 &。
background_pid=$! 保存该进程 PID,主脚本随后还能并行发起长 Prefill。
wait_for_bench_main 位于 L738-L753,轮询背景日志中的
Starting main benchmark run。看到它以后再等待配置的注入延迟,避免把 Warm-up 阶段误当正式混合阶段。
注入前还会在 L803-L816 用 kill -0 检查背景进程是否仍存活。
若背景已经正常结束,Case 被重写为
BACKGROUND_FINISHED_BEFORE_INJECTION,防止生成一个实际上没有重叠的“混合成功”结果。
7.3 A/B 对比来自哪里
Shell 只负责产生 control、background 和 injection 三份原始记录。
Python 在 quick_map_results.py:L439-L580 聚合同一 Case 的重复实验,
并在报告阶段计算 percentage change。主要观察 background 相对 control 的
Output TPS、TTFT P95、TPOT P95 与 E2E P95 变化。
8. 指标解析与汇总
8.1 为什么需要 Python 补算
SGLang 版本变化可能导致字段名或原始明细形态不同。
quick_map_results.py:L106-L125 既支持单个 JSON 对象,也能从混合日志中寻找首个合法 JSON 行。
L152-L157 用多个候选字段名读取同一指标。
8.2 延迟百分位
latency_stats 位于 quick_map_results.py:L208-L233。
优先读取 benchmark 已给出的 mean/P50/P95/P99;缺失时才从请求级数组补算:
- E2E:优先
request_latencies,否则用 TTFT 加该请求所有 ITL。 - TTFT:来自
ttfts。 - TPOT:优先
tpots,否则取每请求 ITL 平均值。 - ITL:展开所有请求的逐 token 间隔。
percentile_ms 在 L128-L140 使用线性插值,并把秒转换为毫秒。
8.3 吞吐与完成状态
compute_metrics 位于 quick_map_results.py:L236-L272。
Total TPS 优先读取 benchmark 自带字段,缺失时才使用 Input TPS + Output TPS。
完成数优先读取 completed 或 successful_requests;
失败数缺失时才由尝试数减完成数。
8.4 重复实验聚合
aggregate_rows 位于 quick_map_results.py:L439-L481。
它按 suite/case/role 聚合 repetition,输出均值、离散程度和成功状态。
write_report 在 L493-L580 生成面向人的 Markdown 报告,
write_csv 与 summarize 在 L581-L601 生成机器可读汇总。
9. 结果目录与数据契约
results/<RUN_ID>/
run.log
manifest.json
server/
head_command.txt
worker_command.txt
*.log
*.inspect.json
cases/
<case_id>/rep<N>/
bench_cmd.txt
bench.log
bench.json
meta.json
summary.csv
summary.jsonl
aggregate.csv
report.md
meta.json 的结构由 quick_map_results.py:L307-L330 写入,
包含 shape、并发、请求数、Warm-up、开始结束时间、退出码与错误分类。
manifest.json 由 L333-L382 维护,记录模型、镜像、并行参数、
NCCL/RDMA 参数和 Git 状态。两者共同保证结果可追溯。
10. 函数行号索引
10.1 run_quick_map.sh
| 行号 | 函数 | 一句话职责 |
|---|---|---|
| L27-L68 | log 到 case_selected | 日志、时间、命令打印、节点执行和 Case 过滤基础函数。 |
| L69-L140 | validate_case_filter / validate_network_config | 运行前拒绝未知 Case 和非计算网配置。 |
| L141-L211 | RDMA、健康和客户端预检 | 检查设备、服务、GPU 占用和 benchmark 客户端。 |
| L212-L268 | build_server_command | 构造每个节点的完整 Docker + SGLang 命令。 |
| L269-L388 | 启动与 NCCL 验证 | 启动节点、等待健康、从日志证明 NET/IB 双 HCA。 |
| L389-L415 | 停止服务 | 保存日志与 inspect 后删除两端容器。 |
| L416-L510 | bench 命令与 meta 参数 | 构造请求并准备结果元数据。 |
| L511-L632 | 断点续跑、错误分类、单 Case | 执行并验证一个 benchmark Case。 |
| L633-L694 | 失败标记、Manifest、汇总、日志 | Run 级元数据和结果收口。 |
| L695-L737 | run_fixed_suite | 遍历 TSV 与 repetition。 |
| L738-L847 | 混合 A/B | 确保 Decode 与长 Prefill 在时间上真实重叠。 |
| L849-L932 | 清理、独立 suite、all | 管理完整生命周期与最终状态。 |
| L933-L957 | main | 分发 all/start/fixed/mixed/stop。 |
10.2 quick_map_results.py
| 行号 | 函数组 | 职责 |
|---|---|---|
| L94-L125 | JSON I/O | 可靠读取原始 benchmark 输出。 |
| L128-L207 | 百分位与请求级 fallback | 从明细恢复 E2E、TTFT、TPOT、ITL。 |
| L208-L272 | latency_stats / compute_metrics | 统一指标字段与单位。 |
| L275-L306 | parse_scenarios | 校验 TSV schema、类型和重复 Case。 |
| L307-L404 | Case、Manifest、失败状态 | 维护机器可读运行状态。 |
| L405-L492 | 行构造与聚合 | 把每次 repetition 合并为 Case 统计。 |
| L493-L601 | 报告与汇总 | 输出 Markdown、CSV、JSONL。 |
| L602-L716 | CLI | 定义 Shell 调用的子命令和参数。 |