Standalone Code Walkthrough / Phase 1

DSV4-Pro 双机 Pro6000D SGLang 快速性能地图:代码详解

行号基线:ca1f2f63375c  生成时间:2026-07-31 13:04:21 CST  入口:run_quick_map.sh
文档边界:这是一份独立代码档案,只解释 Phase 1 实现,不承担阶段结论展示。 下文的行号均绑定提交 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.sh957 唯一入口,管理双机服务、固定场景、混合场景、失败恢复与清理。 run.log、服务日志、每个 Case 的命令与原始结果。
config.env78 模型、节点、SGLang、NCCL/RDMA、benchmark、超时和路径配置。 被 Shell 直接 source,自身不产生输出。
quick_map_scenarios.tsv12 固定性能地图的声明式场景表,一行对应一个 Case。 输入给 run_fixed_suite
quick_map_results.py716 校验 bench JSON、补算百分位、生成 meta/manifest、聚合重复实验。 summary.csvsummary.jsonlaggregate.csvreport.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.envrun_quick_map.sh run_quick_map.sh:L8Shell 变量,允许调用命令中的环境变量覆盖默认值。
quick_map_scenarios.tsvrun_fixed_suite run_quick_map.sh:L695-L729Case ID、ISL、OSL、C、请求数规则和 Warm-up。
run_quick_map.shquick_map_results.py RESULT_TOOLrun_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.pyquick_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-L35NCCL/RDMA限定 eth0/eth3mlx5_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_STATIC0.9 config.env:L38run_quick_map.sh:L258--mem-fraction-static server/head_server_cmd.txtrun_manifest.jsonmem_fraction_static
CUDA_GRAPH_MAX_BS_DECODE64 config.env:L39run_quick_map.sh:L259 服务命令;Manifest 的 cuda_graph_max_bs_decode
MAX_RUNNING_REQUESTS256 config.env:L40run_quick_map.sh:L260 服务命令;Manifest 的 max_running_requests
TP_SIZE / EP_SIZE / NNODES16 / 2 / 2 config.env:L15-L17run_quick_map.sh:L250-L253 服务命令;Manifest 的 tp_size/ep_size/nnodes
NCCL_SOCKET_IFNAMEeth0 config.env:L26run_quick_map.sh:L231 服务命令;Manifest 的同名小写字段;NCCL 服务日志
NCCL_IB_HCA=mlx5_0:1,mlx5_3:1 config.env:L27run_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_noderun_quick_map.sh:L269-L291 先展开命令,再写入 <role>_server_cmd.txtwrite_run_manifestL641-L676 另存一份结构化配置。二者不一致时,应以实际容器命令和服务日志继续核查。

4.3 TSV 如何变成请求

quick_map_scenarios.tsv:L1 定义列: case_id, stage, isl, osl, concurrency, multiplier, minimum, warmup, noterun_fixed_suiterun_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-L84L703-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_nodeL141-L156 逐节点检查 /dev/infiniband/rdma_cmuverbs0uverbs3。 这是宿主机设备存在性检查,不能证明 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 ...

5.3 为什么 Worker 先启动

start_service 位于 run_quick_map.sh:L334-L388。 它先调用 Worker 的 start_service_node,再启动 Head,随后轮询 Head 的 /health。这样 Worker 已经等待分布式 rendezvous,Head 启动后两端更容易同步进入初始化。

健康检查成功还不够。verify_nccl_transport_nodeL293-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 和最终日志,再执行强制移除。 cleanupL849-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
OSL 语义:随机 benchmark 会把目标输出长度传给服务端,并使用忽略 EOS 的生成设置, 目标是生成足量 token。是否真正达到 OSL 仍以 bench.json 中的成功请求数和 total_output_tokens 为准,不能只看命令参数。

6.2 单个 Case 的完整流程

run_bench_case 位于 run_quick_map.sh:L545-L632,顺序是:

  1. 根据 suite、case、repetition 创建稳定结果目录。
  2. RESUME=1,由 case_already_completed 检查 meta 和 bench 是否完整。
  3. 保存展开后的命令与 Case 元数据。
  4. 记录开始时间,使用 timeout 执行 benchmark。
  5. 调用 Python check-bench 校验 JSON,不把“进程退出码为 0”误当成有效结果。
  6. 失败时由 detect_error_type 区分超时、OOM、服务失活、传输错误和普通 benchmark 失败。
  7. 写入最终 meta.json,供后续汇总和 Phase 2 时间窗使用。

case_already_completedL511-L525 同时要求 meta 状态为完成、 bench 文件存在且可解析。它避免只凭目录存在就跳过半成品。

7. 混合 Prefill/Decode A/B

7.1 一次 repetition 的三个角色

run_mixed_repetition 位于 run_quick_map.sh:L755-L832

角色Shape作用
control1K → 1K,C=32单独运行 Decode 背景,建立无注入基线。
decode_background1K → 1K,C=32混合组中的持续 Decode 请求流。
prefill_injection128K → 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-L816kill -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;缺失时才从请求级数组补算:

percentile_msL128-L140 使用线性插值,并把秒转换为毫秒。

8.3 吞吐与完成状态

compute_metrics 位于 quick_map_results.py:L236-L272。 Total TPS 优先读取 benchmark 自带字段,缺失时才使用 Input TPS + Output TPS。 完成数优先读取 completedsuccessful_requests; 失败数缺失时才由尝试数减完成数。

8.4 重复实验聚合

aggregate_rows 位于 quick_map_results.py:L439-L481。 它按 suite/case/role 聚合 repetition,输出均值、离散程度和成功状态。 write_reportL493-L580 生成面向人的 Markdown 报告, write_csvsummarizeL581-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.jsonL333-L382 维护,记录模型、镜像、并行参数、 NCCL/RDMA 参数和 Git 状态。两者共同保证结果可追溯。

10. 函数行号索引

10.1 run_quick_map.sh

行号函数一句话职责
L27-L68logcase_selected日志、时间、命令打印、节点执行和 Case 过滤基础函数。
L69-L140validate_case_filter / validate_network_config运行前拒绝未知 Case 和非计算网配置。
L141-L211RDMA、健康和客户端预检检查设备、服务、GPU 占用和 benchmark 客户端。
L212-L268build_server_command构造每个节点的完整 Docker + SGLang 命令。
L269-L388启动与 NCCL 验证启动节点、等待健康、从日志证明 NET/IB 双 HCA。
L389-L415停止服务保存日志与 inspect 后删除两端容器。
L416-L510bench 命令与 meta 参数构造请求并准备结果元数据。
L511-L632断点续跑、错误分类、单 Case执行并验证一个 benchmark Case。
L633-L694失败标记、Manifest、汇总、日志Run 级元数据和结果收口。
L695-L737run_fixed_suite遍历 TSV 与 repetition。
L738-L847混合 A/B确保 Decode 与长 Prefill 在时间上真实重叠。
L849-L932清理、独立 suite、all管理完整生命周期与最终状态。
L933-L957main分发 all/start/fixed/mixed/stop

10.2 quick_map_results.py

行号函数组职责
L94-L125JSON I/O可靠读取原始 benchmark 输出。
L128-L207百分位与请求级 fallback从明细恢复 E2E、TTFT、TPOT、ITL。
L208-L272latency_stats / compute_metrics统一指标字段与单位。
L275-L306parse_scenarios校验 TSV schema、类型和重复 Case。
L307-L404Case、Manifest、失败状态维护机器可读运行状态。
L405-L492行构造与聚合把每次 repetition 合并为 Case 统计。
L493-L601报告与汇总输出 Markdown、CSV、JSONL。
L602-L716CLI定义 Shell 调用的子命令和参数。