阶段状态:已完成。
正式 Run dsv4pro-phase1-full-20260730-220916 在双 Rail
NET/IB + GDRDMA 下完成 9 个固定点和 3 个混合 A/B 结果,
共 12/12 成功,用时 28 分 36 秒。补充 Run
dsv4pro-phase1-long-decode-20260730-234236 完成长输出与
长上下文 Decode 2/2。两个 Run 合计 11 个固定点和 3 个混合结果,
14/14 成功;服务、容器与 16 张 GPU 已清理。
1. 目标与边界
用数小时以内、可重复的小矩阵替代约一天以上的全量扫描,先回答 Prefill、Decode、长上下文和混合干扰各自是否存在明显异常,再决定后续 Timeline 和 Kernel Profiling 的捕获对象。该阶段不要求为了“跑满表格” 而浪费算力;一旦出现稳定、可复现且足以改变调查方向的异常,就可以提前结束。
- 只测试 SGLang,不测试 vLLM。
- 使用双机 16 卡完整实例,不做 PD 分离。
- 不启用 MTP、EAGLE、DSpark 或其他投机解码。
- 本轮不启用 Profiler;正文只记录最终有效 Run,失败尝试仅在末尾总结经验。
- 不修改或调用旧的全天全量 Benchmark 脚本。
2. 精简实现
实验代码位于:
/data/hzy/sskj/experiments/pro6000/
dsv4pro_pro6000d_2node_sglang_tp16_quick_map/
| 文件 | 职责 |
|---|---|
run_quick_map.sh |
唯一 Shell 入口:双机服务启停、固定矩阵、混合 A/B、错误处理与清理 |
config.env |
节点、模型、镜像、并行与容量参数 |
quick_map_scenarios.tsv |
十一个固定工作负载点 |
quick_map_results.py |
验证 Bench JSON,生成 CSV、JSONL 和 Markdown 汇总 |
tests/test_quick_map_results.py |
结果解析回归测试 |
单入口的操作面:
bash run_quick_map.sh all
# 仅排障时使用同一个入口
bash run_quick_map.sh start
bash run_quick_map.sh fixed
bash run_quick_map.sh mixed
bash run_quick_map.sh stop
3. 服务配置
| 配置项 | 当前值 | 说明 |
|---|---|---|
| 镜像 | lmsysorg/sglang:nightly-dev-cu13-20260720-b3570a45 |
沿用已验证可加载 DSV4-Pro 的版本 |
| 模型 | /data/hf_models/DeepSeek-V4-Pro |
两台节点均有本地权重 |
| 并行 | TP=16, EP=2, nnodes=2 |
每台 8 卡,共 16 Rank |
| 显存比例 | 0.9 |
保持已知基线,不在本阶段调参 |
| 活跃请求上限 | 256 |
覆盖本轮最大并发 64 |
| CUDA Graph Decode BS | 64 |
覆盖固定矩阵中的 Decode C64 |
| NCCL Socket 接口 | eth0 |
RDMA 失败回退时也承载跨机 Tensor,不只是 bootstrap |
| RoCE HCA | mlx5_0,mlx5_3 |
启动器只透传对应的 uverbs0/uverbs3 与 rdma_cm |
| 传输后端门禁 | REQUIRE_NCCL_IB=1 |
两端日志未证明 NET/IB + mlx5_0 + mlx5_3 时禁止开始 benchmark |
| 代码分支 | hzy |
从该维护分支向中央仓库 main 提交合并请求 |
4. 固定快速矩阵
| Case ID | ISL | OSL | C | 目的 |
|---|---|---|---|---|
short_prefill_latency_1k_c1 | 1K | 1 | 1 | 最小 TTFT |
mid_prefill_latency_32k_c1 | 32K | 1 | 1 | 中长 Prefill |
long_prefill_latency_128k_c1 | 128K | 1 | 1 | 长上下文 Prefill |
mid_prefill_throughput_32k_c16 | 32K | 1 | 16 | Prefill 输入吞吐 |
decode_latency_1k_to_1k_c1 | 1K | 1K | 1 | 单请求 TPOT |
decode_throughput_1k_to_1k_c16 | 1K | 1K | 16 | Decode 吞吐 |
decode_throughput_1k_to_1k_c32 | 1K | 1K | 32 | Decode 吞吐 |
decode_throughput_1k_to_1k_c64 | 1K | 1K | 64 | Decode 高并发 |
long_output_decode_1k_to_4k_c16 | 1K | 4K | 16 | 持续 Decode 与 KV 增长 |
long_context_decode_128k_to_1k_c1 | 128K | 1K | 1 | 长上下文上的 Decode 成本 |
balanced_32k_to_1k_c8 | 32K | 1K | 8 | 综合压力 |
快速 Run 使用一次重复和一波测量请求,即 num_prompts=C。
32K/128K Prefill 与 128K 长上下文 Decode 不做昂贵的同形状 Warm-up;
短 Prefill、普通 Decode 与 1K → 4K 长输出 Decode 使用一个 Warm-up,
并在正式计时前清空 Prefix Cache。固定矩阵不做 SLO 截断或自适应并发搜索。
5. SGLang Benchmark 与 Prefix Cache
5.1 random 如何生成 ISL
当前镜像的实现位于
/sgl-workspace/sglang/python/sglang/benchmark/datasets/random.py。
dataset-name=random 会读取 ShareGPT,打乱样本后取每条会话的首轮用户文本:
文本过长就截断,过短就重复其 token,直到达到目标 ISL。
random-range-ratio=1.0 使每条请求都使用精确的目标长度。
本机数据集共有 94,145 行,其中 92,886 行可用、71,904 个不同首轮文本,
因此不存在此前“两条数据只能形成两个并发请求”的问题。
random-ids 则直接构造随机整数 token id,不读取 ShareGPT。
当前源码同时警告这种方式可能触发 NaN,因此本阶段继续使用
random + 大规模 ShareGPT,并通过清缓存隔离不同测试点。
5.2 OSL 为什么能达到指定长度
SGLang 原生请求函数位于
/sgl-workspace/sglang/python/sglang/benchmark/serving.py。
它将目标 OSL 写入 max_new_tokens,并默认设置
ignore_eos=True。因此模型即使提前生成 EOS,也会继续生成到指定 OSL;
只有请求失败、超时或触及上下文限制时,实际输出才可能不足。
sampling_params = {
"max_new_tokens": request_func_input.output_len,
"ignore_eos": not args.disable_ignore_eos,
}
5.3 为什么 Warm-up 会污染 Prefix Cache
SGLang benchmark 的 Warm-up 直接复用 input_requests[0],
而正式测量随后仍会遍历包含该请求的完整列表。因此,只要服务启用了 Prefix Cache,
第一条正式请求就可能命中刚刚 Warm-up 的前缀。第一次 Run 的服务日志实际出现
#cached-token: 768,证明该污染在当前环境真实发生。
修复方式是在每个隔离测试点传入 --flush-cache。benchmark 会先完成
Warm-up,再调用服务端 /flush_cache,最后才启动计时。这样保留 Kernel
和执行路径预热,同时不把 Warm-up 的 KV 前缀带入测量。混合干扰中的长 Prefill
注入不会清缓存,避免在 Decode 背景运行时改变其服务状态;背景与注入使用不同随机种子。
5.4 如何单独测试 Prefix Caching
- 调用
/flush_cache,发送固定长 Prompt P,记录 Cold TTFT 和#cached-token。 - 不清缓存,原样重发 P,记录 Warm TTFT;预期 cached token 明显增加、TTFT 降低。
- 再次清缓存,发送同长度但内容不同的 Prompt Q,排除长度、JIT 和偶然波动造成的假提升。
三组请求保持 OSL、采样参数和并发一致,各重复至少 3 次。Prefix Cache 是生产优化能力, 不是“坏东西”;这里只是在无缓存性能基线中隔离它,后续会把缓存命中场景作为单独 A/B。
6. 混合干扰实现
这里的“背景”不是 SGLang 后台线程,而是先启动并持续运行的一批 Decode 基准流量。它既在实验期间占用 GPU,也是我们希望观察是否 变慢的对象。混合 A/B 的问题非常具体:同样一批 Decode 请求,在没有长 Prefill 干扰和有长 Prefill 干扰时,性能会相差多少?
| 组别 | 运行内容 | 作用 |
|---|---|---|
| A:Control | 仅运行 64 条 1K → 1K, C=32 Decode | 建立无干扰基线 |
| B:Treatment | 运行相同 Decode,并在正式测量开始 10 秒后注入一条 128K → 1 Prefill | 测量 Prefill 对 Decode 的干扰 |
- 先完成 A 组,仅运行 Decode,保存对照指标。
- 启动 B 组的 Decode 基准流量,并从日志确认它已进入正式测量,而不只是完成客户端初始化。
- 正式测量开始 10 秒后,并行提交一个
128K → 1长 Prefill。 - 等待两类请求都结束,分别保存 Decode 流量和长 Prefill 请求的结果。
- 用 A、B 两组 Decode 的 Output TPS、TTFT P95、TPOT P95 与 E2E P95 计算变化率;长 Prefill 自身的 TTFT 单独报告。
A:Decode ───────────────────────────────→ 结束
B:Decode ───────────────────────────────→ 结束
正式测量 + 10 秒
└─ 128K Prefill ─→ 结束
共同占用同一服务
(
run_bench_case ... 1024 1024 32 64
) &
background_pid=$!
# 实际代码先从 bench.log 确认正式测量已经开始。
sleep 10
run_bench_case ... 131072 1 1 1
wait "${background_pid}"
& 让 Decode benchmark 与后续 Prefill 并行;
$! 取得该 Decode benchmark 的进程号;
wait 等待它完成。总请求数 64、并发 32,表示最多同时有
32 条请求在途,通常形成约两波请求。如果 Decode 流量在注入前已经结束,
两类请求没有发生重叠,结果会被明确改写为
BACKGROUND_FINISHED_BEFORE_INJECTION,避免生成虚假的“混合成功”。
7. 结果与可追溯性
results/<RUN_ID>/
run_manifest.json
run.log
summary.csv
summary.jsonl
aggregate.csv
report.md
cases/<case_id>/rep1/
bench_cmd.txt
bench.jsonl
bench.log
meta.json
server/
head_server_cmd.txt
worker_server_cmd.txt
head_server.log
worker_server.log
汇总保留 Request/Input/Output/Total TPS,以及 E2E、TTFT、TPOT、ITL 的 Mean、P50、P95、P99。断点续跑前会重新解析原始 Bench JSON,不能只凭文件存在就跳过。
8. 已完成验证
| 检查 | 结果 | 证据 |
|---|---|---|
| Shell 语法 | 通过 | bash -n run_quick_map.sh |
| Python 单测 | 3/3 通过 | 场景唯一性、百分位回退、失败结果汇总 |
| 完整 Dry-run | 通过 | 服务、十一个固定点、混合 A/B、清理均展开成功 |
| 真实旧 Bench JSON 解析 | 通过 | 成功解析 P50/P95/P99 与吞吐字段 |
| 项目精简 | 通过 | 实验目录顶层仅保留一个 Shell 入口 |
| 双 Rail 传输门禁 | 通过 | Head 与 Worker 均识别 mlx5_0/mlx5_3,跨节点 Channel 使用 NET/IB/*/GDRDMA |
| 四点 Sanity | 4/4 通过 | 1K/32K Prefill 与 C1/C32 Decode 均恢复到合理量级 |
| 冷 Prefix 口径 | 通过 | 正式测量请求的 Head 日志显示 #cached-token: 0 |
| 完整真机 Run | 12/12 通过 | 固定矩阵 9/9,混合 A/B 3/3,运行期失败 0 |
| 长 Decode 补测 | 2/2 通过 | 1K → 4K C16 与 128K → 1K C1 均生成完整目标 OSL |
| 资源清理 | 通过 | 两节点相关容器与计算进程为 0,16 张 GPU 显存占用为 0 |
9. 最终真机结果
本节只使用正式成功 Run。Profiler 与投机解码均关闭,每个 Case 只做一次快速测量, 所以它适合决定下一步 Profile 对象,不作为需要统计置信度的最终容量认证。
9.1 执行摘要
| 项目 | 结果 | 证据 |
|---|---|---|
| Run ID | dsv4pro-phase1-full-20260730-220916 | COMPLETED |
| 运行时间 | 28 分 36 秒 | 22:09:47 至 22:38:22 CST |
| 网络路径 | 双 Rail NET/IB + GDRDMA | mlx5_0 与 mlx5_3 |
| 正式 Run 完整性 | 12/12 成功 | 固定点 9/9;混合 A/B 3/3 |
| 补充 Run | dsv4pro-phase1-long-decode-20260730-234236 | 固定点 2/2;约 12 分钟含服务启动与清理 |
| 阶段合计 | 14/14 成功 | 固定点 11/11;混合 A/B 3/3 |
9.2 Prefill
| 场景 | Input TPS | TTFT P95 | 观察 |
|---|---|---|---|
| 1K → 1,C=1 | 1,969.66 tok/s | 0.502 s | 短请求固定开销占比更高 |
| 32K → 1,C=1 | 2,652.76 tok/s | 12.335 s | 单请求吞吐进入稳定区间 |
| 128K → 1,C=1 | 2,710.16 tok/s | 48.344 s | 长 Prefill 代表点 |
| 32K → 1,C=16 | 3,112.77 tok/s | 162.087 s | 聚合吞吐仅比 C=1 高 17.3%,排队时延显著增加 |
9.3 Decode
| 1K → 1K | Output TPS | TTFT P95 | TPOT P95 | E2E P95 |
|---|---|---|---|---|
| C=1 | 31.41 tok/s | 0.363 s | 31.47 ms | 32.555 s |
| C=16 | 295.29 tok/s | 4.950 s | 50.02 ms | 55.444 s |
| C=32 | 461.68 tok/s | 8.022 s | 63.31 ms | 70.933 s |
| C=64 | 647.42 tok/s | 12.716 s | 93.44 ms | 101.163 s |
Decode 吞吐到 C=64 仍在上升,但增益递减且 TPOT 明显变差。综合场景
32K → 1K,C=8 的 Input/Output TPS 为
2,038.00 / 63.69,TTFT P95 为 82.084 s,
说明 Prefill 与 Decode 同时存在时干扰很强。
9.4 长 Decode 补测
| 场景 | Output TPS | TTFT P95 | TPOT P95 | E2E P95 |
|---|---|---|---|---|
| 1K → 4K,C=16 | 310.02 tok/s | 6.241 s | 50.33 ms | 211.364 s |
| 128K → 1K,C=1 | 12.43 tok/s | 49.326 s | 32.24 ms | 82.312 s |
1K → 4K,C=16 相比 1K → 1K,C=16,
Output TPS 增加 4.99%,TPOT P95 只增加 0.62%。较长 Decode 没有出现
稳态吞吐塌陷;吞吐略升是固定启动和 Prefill 成本被更多输出 token 摊薄。
128K → 1K,C=1 的 TTFT 只比 128K → 1
纯 Prefill 高 2.03%,而 TPOT P95 只比 1K → 1K,C=1
高 2.47%。因此这次长上下文请求的主要新增成本在 Prefill,而不是每个 Decode
token。表中的 12.43 Output TPS 是把 49 秒 Prefill 也计入总时长的端到端值,
不能把它误读为纯 Decode 速率。
9.5 混合 Prefill/Decode A/B
| 指标 | A:仅 Decode | B:注入 128K Prefill | 变化 |
|---|---|---|---|
| Output TPS | 455.68 tok/s | 345.95 tok/s | -24.08% |
| TTFT P95 | 9.443 s | 10.194 s | +7.96% |
| TPOT P95 | 65.88 ms | 109.73 ms | +66.55% |
| E2E P95 | 72.008 s | 117.630 s | +63.36% |
Phase 2 重放五个固定代表点:
128K → 1,C=1、32K → 1,C=16、
1K → 1K,C=32、1K → 4K,C=16、
128K → 1K,C=1,再执行有无 128K 注入的混合 A/B。
目标是区分计算、显存带宽、调度排队、跨机通信和节点不均衡。
完整产物: 报告、 逐点汇总、 聚合表、 运行清单; 长 Decode 补测的 报告、 逐点汇总 和 运行清单。
10. 经验教训
- 启动参数不等于实际传输路径;开始性能测试前必须由 NCCL 日志证明
NET/IB。 - Warm-up、固定随机种子和跨 Case Prefix Cache 会改变 TTFT,冷缓存与热缓存必须分开报告。
- 先跑四点 Sanity 再启动完整矩阵,可以在几分钟内验证环境、口径和数量级。
历史排查细节保存在 TTFT 脚本口径审计报告 与 TP16 网络路径审计报告, 不作为本阶段最终结果。
11. 实验复现命令
本节记录的是本阶段实际执行过的命令。长命令同时由程序原样保存到
results/<RUN_ID>/server/*_server_cmd.txt 和每个 Case 的
bench_cmd.txt;这些落盘文件是最终证据,正文中的换行仅用于阅读。
11.1 实际执行:完整 Phase 1
执行位置:174.1.51.5;脚本通过 SSH 启动 .7 Worker。
cd /data/hzy/sskj/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map
tmux new-session -d -s dsv4pro-phase1-full \
"RUN_ID=dsv4pro-phase1-full-20260730-220916 bash run_quick_map.sh all \
2>&1 | tee /data/hzy/dsv4pro_phase1_full_20260730-220916.log"
tmux attach -t dsv4pro-phase1-full
all 的真实顺序是:
Worker 启动 → Head 启动 → /health → NET/IB 门禁 → fixed → mixed → stop → summarize。
11.2 实际执行:Worker 服务
展开 174.1.51.7 的完整 docker run
docker run -d \
--name dsv4pro_pro6000d_2node_sglang_tp16_quick_map_worker \
--gpus all \
--network host \
--ipc host \
--shm-size 20g \
--ulimit memlock=-1 \
--ulimit stack=67108864 \
-v /data/hf_models/DeepSeek-V4-Pro:/data/hf_models/DeepSeek-V4-Pro:ro \
-v /data/hzy/sglang_cache/dsv4_pro_tp16:/root/.cache \
-e CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
-e PYTHONUNBUFFERED=1 \
-e HF_HUB_OFFLINE=1 \
-e TRANSFORMERS_OFFLINE=1 \
-e PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
-e NCCL_SOCKET_IFNAME=eth0 \
-e 'NCCL_IB_HCA==mlx5_0:1,mlx5_3:1' \
-e NCCL_CROSS_NIC=1 \
-e NCCL_DEBUG=INFO \
-e SGLANG_SHARED_EXPERT_TP1=1 \
--device /dev/infiniband/rdma_cm \
--device /dev/infiniband/uverbs0 \
--device /dev/infiniband/uverbs3 \
--entrypoint python3 \
lmsysorg/sglang:nightly-dev-cu13-20260720-b3570a45 \
-m sglang.launch_server \
--model-path /data/hf_models/DeepSeek-V4-Pro \
--tp-size 16 \
--ep-size 2 \
--nnodes 2 \
--node-rank 1 \
--dist-init-addr 10.101.0.11:20002 \
--trust-remote-code \
--host 0.0.0.0 \
--port 30002 \
--mem-fraction-static 0.9 \
--cuda-graph-max-bs-decode 64 \
--max-running-requests 256
11.3 实际执行:Head 服务
展开 174.1.51.5 的完整 docker run
docker run -d \
--name dsv4pro_pro6000d_2node_sglang_tp16_quick_map_head \
--gpus all \
--network host \
--ipc host \
--shm-size 20g \
--ulimit memlock=-1 \
--ulimit stack=67108864 \
-v /data/hf_models/DeepSeek-V4-Pro:/data/hf_models/DeepSeek-V4-Pro:ro \
-v /data/hzy/sglang_cache/dsv4_pro_tp16:/root/.cache \
-e CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
-e PYTHONUNBUFFERED=1 \
-e HF_HUB_OFFLINE=1 \
-e TRANSFORMERS_OFFLINE=1 \
-e PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
-e NCCL_SOCKET_IFNAME=eth0 \
-e 'NCCL_IB_HCA==mlx5_0:1,mlx5_3:1' \
-e NCCL_CROSS_NIC=1 \
-e NCCL_DEBUG=INFO \
-e SGLANG_SHARED_EXPERT_TP1=1 \
--device /dev/infiniband/rdma_cm \
--device /dev/infiniband/uverbs0 \
--device /dev/infiniband/uverbs3 \
--entrypoint python3 \
lmsysorg/sglang:nightly-dev-cu13-20260720-b3570a45 \
-m sglang.launch_server \
--model-path /data/hf_models/DeepSeek-V4-Pro \
--tp-size 16 \
--ep-size 2 \
--nnodes 2 \
--node-rank 0 \
--dist-init-addr 10.101.0.11:20002 \
--trust-remote-code \
--host 0.0.0.0 \
--port 30002 \
--mem-fraction-static 0.9 \
--cuda-graph-max-bs-decode 64 \
--max-running-requests 256
NCCL_IB_HCA==... 的两个等号不是笔误:第一个是环境变量赋值分隔符,
第二个是 NCCL HCA 列表的“精确匹配”前缀。
11.4 实际执行:代表 Benchmark
以下是正式 Run 的 128K → 1, C=1 冷 Prefix 命令:
展开完整 sglang.benchmark.serving 命令
timeout --signal=TERM --kill-after=30s 7200s \
docker run --rm \
--network host \
-v /data/hf_models/DeepSeek-V4-Pro:/data/hf_models/DeepSeek-V4-Pro:ro \
-v /data/yy/sskj/dataset/ShareGPT_V3_unfiltered_cleaned_split.json:/data/yy/sskj/dataset/ShareGPT_V3_unfiltered_cleaned_split.json:ro \
-v /data/hzy/sskj/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/results/dsv4pro-phase1-full-20260730-220916/cases/long_prefill_latency_128k_c1/rep1:/data/hzy/sskj/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/results/dsv4pro-phase1-full-20260730-220916/cases/long_prefill_latency_128k_c1/rep1 \
-e PYTHONUNBUFFERED=1 \
-e HF_HUB_OFFLINE=1 \
-e TRANSFORMERS_OFFLINE=1 \
--entrypoint python3 \
lmsysorg/sglang:nightly-dev-cu13-20260720-b3570a45 \
-m sglang.benchmark.serving \
--backend sglang \
--host 10.101.0.11 \
--port 30002 \
--dataset-name random \
--dataset-path /data/yy/sskj/dataset/ShareGPT_V3_unfiltered_cleaned_split.json \
--random-input-len 131072 \
--random-output-len 1 \
--random-range-ratio 1.0 \
--num-prompts 1 \
--max-concurrency 1 \
--request-rate 10000 \
--output-file /data/hzy/sskj/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/results/dsv4pro-phase1-full-20260730-220916/cases/long_prefill_latency_128k_c1/rep1/bench.jsonl \
--output-details \
--disable-tqdm \
--warmup-requests 0 \
--seed 42 \
--flush-cache
其余固定点使用同一命令模板,只替换 ISL、OSL、并发、请求数、Warm-up、Seed
和输出目录。每个点的最终展开命令保存在自己的 bench_cmd.txt。
混合 A/B 的并行启动顺序和两条请求命令见第 6 节及相应 Case 目录。
11.5 实际执行:长 Decode 补测
cd /data/hzy/sskj/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map
export CASE_IDS="long_output_decode_1k_to_4k_c16,long_context_decode_128k_to_1k_c1"
export RUN_ID="dsv4pro-phase1-long-decode-20260730-234236"
trap 'bash run_quick_map.sh stop' EXIT INT TERM
bash run_quick_map.sh start
bash run_quick_map.sh fixed
11.6 停止、清理与检查
cd /data/hzy/sskj/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map
bash run_quick_map.sh stop
curl -fsS http://10.101.0.11:30002/health || true
docker ps --filter name=dsv4pro_pro6000d_2node_sglang_tp16_quick_map
nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv
下一阶段: 打开 Phase 2 实验档案