Implementation & Result Record

Phase 1:DeepSeek-V4-Pro 双机 Pro6000D SGLang 快速性能地图

节点:174.1.51.5 + 174.1.51.7 拓扑:SGLang TP16 / EP2 更新:2026-07-30 15:46 CST
返回推理优化主计划

阶段状态:提前结束,已进入硬件归因。 第一次真机 Run dsv4pro-pro6000d-2node-sglang-quick-20260730-140026 已验证双机服务可用,但因发现请求量和 Prefix Cache 口径问题而主动停止。 精简后的第二次 Run dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625 完成 3 个 Prefill 固定点后,发现输入吞吐稳定锁定在约 65 token/s。继续扫描 Decode 和混合流量不能解释该异常, 因此用户决定中止第 4 个 Case,直接进入 Phase 2。 Manifest 终态为 ABORTED_EARLY_FOR_PHASE2; 两节点容器和 16 张 GPU 已清理。

1. 目标与边界

用数小时以内、可重复的小矩阵替代约一天以上的全量扫描,先回答 Prefill、Decode、长上下文和混合干扰各自是否存在明显异常,再决定后续 Timeline 和 Kernel Profiling 的捕获对象。该阶段不要求为了“跑满表格” 而浪费算力;一旦出现稳定、可复现且足以改变调查方向的异常,就可以提前结束。

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 bootstrap eth1 普通 TCP 建连接口
RoCE HCA mlx5_0,mlx5_3 双 Rail 数据面,NCCL_CROSS_NIC=1
代码分支 hzy 从该维护分支向中央仓库 main 提交合并请求

4. 固定快速矩阵

Case ID ISL OSL C 目的
short_prefill_latency_1k_c11K11最小 TTFT
mid_prefill_latency_32k_c132K11中长 Prefill
long_prefill_latency_128k_c1128K11长上下文 Prefill
mid_prefill_throughput_32k_c1632K116Prefill 输入吞吐
decode_latency_1k_to_1k_c11K1K1单请求 TPOT
decode_throughput_1k_to_1k_c161K1K16Decode 吞吐
decode_throughput_1k_to_1k_c321K1K32Decode 吞吐
decode_throughput_1k_to_1k_c641K1K64Decode 高并发
balanced_32k_to_1k_c832K1K8综合压力

快速 Run 使用一次重复和一波测量请求,即 num_prompts=C。 32K/128K Prefill 不做昂贵的同形状 Warm-up;短 Prefill 与 Decode 使用一个 Warm-up,并在正式计时前清空 Prefix Cache。固定矩阵不做 SLO 截断或自适应并发搜索。

5. SGLang Benchmark 与 Prefix Cache

5.1 random 如何生成 ISL

当前镜像的实现位于 /sgl-workspace/sglang/python/sglang/benchmark/datasets/random.pydataset-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

  1. 调用 /flush_cache,发送固定长 Prompt P,记录 Cold TTFT 和 #cached-token
  2. 不清缓存,原样重发 P,记录 Warm TTFT;预期 cached token 明显增加、TTFT 降低。
  3. 再次清缓存,发送同长度但内容不同的 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 的干扰
  1. 先完成 A 组,仅运行 Decode,保存对照指标。
  2. 启动 B 组的 Decode 基准流量,并从日志确认它已进入正式测量,而不只是完成客户端初始化。
  3. 正式测量开始 10 秒后,并行提交一个 128K → 1 长 Prefill。
  4. 等待两类请求都结束,分别保存 Decode 流量和长 Prefill 请求的结果。
  5. 用 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 入口
双机容器启动通过第二次 Run 于 14:42:04 通过 Health Check,启动约 5 分 30 秒
Prefix Cache 隔离通过Warm-up 后 POST /flush_cache 返回 200,正式请求仍为 #cached-token: 0
中止清理通过头、Worker 节点均无相关容器和 Bench 进程,16 张 GPU 显存回到 0 MiB

9. 真机结果

最终采用 Run dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625。 以下三条均为一条请求、C=1OSL=1,且正式测量前 Prefix Cache 已清空。

Case ISL 输入 TPS TTFT E2E 状态
short_prefill_latency_1k_c11K64.44 tok/s15.88 s15.88 sCOMPLETED
mid_prefill_latency_32k_c132K64.96 tok/s504.44 s504.44 sCOMPLETED
long_prefill_latency_128k_c1128K65.20 tok/s2010.38 s2010.38 sCOMPLETED
mid_prefill_throughput_32k_c1632K × 16未形成最终结果未形成最终结果未形成最终结果ABORTED

9.1 可以下的结论

9.2 现在还不能下的结论

9.3 为什么提前结束

Phase 1 的目标是发现值得归因的关键异常,而不是机械完成九个格子。 三个独立长度已经给出同一个稳定信号;第 4 个并发 Prefill 在 922 秒后仍表现为 单序列 Chunk 推进。继续执行剩余矩阵预计还需数小时,却不能回答 “这 65 token/s 到底卡在哪里”。因此第 4 个 Case 被写入 EARLY_STOP_FOR_PHASE2,其余固定点和混合 A/B 保留为未执行。

9.4 为什么旧脚本的 TTFT 短很多

2026-07-30 对旧目录 /data/qqt/sskj/experiments/pro6000/dsv4_pro6000_sglang_tp16 做了逐项审计。结论是:旧结果与本轮冷 Prefill 不是同一缓存口径, 不是 quick-map 把相同请求跑慢了。

审计项旧脚本quick-map判断
服务端配置 同一镜像,TP16 / EP2,8K Chunk,FlashInfer MXFP4 MoE 相同 排除明显的启动参数回归
正式请求数 C=1 仍强制至少 10 条 延迟点只发 1 条 旧均值混合了多条请求的缓存状态
Warm-up 每个 Shape 固定 16 条同 Prompt Warm-up Warm-up 后清 Prefix Cache;32K/128K 延迟点不做额外 Warm-up 旧正式测量会继承 Warm-up 的 Prompt 前缀
Cache 清理 从不传 --flush-cache 正式测量前传 --flush-cache 旧脚本跨 Case、跨长度保留 Radix/Prefix Cache
Shape 顺序 固定 Seed=42,按 1K→4K→8K→16K→32K→64K→128K 递增 每个延迟点按冷缓存解释 旧请求会复用上一档相同 Prompt 的短前缀

旧时间线还有一条直接证据:17:40 的失败 Run 已完整执行过 1K / 128 / C=1,18:01 的正式 Run 没有重启服务便再次执行同一批 Seed=42 请求。因此旧文件中的 1K TTFT 约 0.455 秒,本身就是热缓存结果。

最小复现Mean TTFTP95 TTFT解释
1K→1,首次冷缓存16.04 s16.04 s清 Prefix Cache,1 条正式请求
1K→1,原样再次冷缓存15.90 s15.90 s再次清 Cache,排除一次性 JIT 主导
1K→128,冷缓存15.79 s15.79 s排除 OSL=1 特殊慢路径
旧命令语义重新复现14.60 s15.97 s10 条正式请求、16 条 Warm-up、不清 Cache
2026-07-28 旧产物0.455 s0.513 s服务已被前一次 Run 和后续递增长度预热

旧 32K 文件的第一条请求约 0.67 秒,其余 9 条平均约 35.51 秒; 旧 128K 文件的第一条约 0.96 秒,其余 9 条平均约 144.83 秒。 后 9 条也已经分别继承上一档 16K、64K 前缀。旧报告仍按完整 ISL 统计 Input TPS,因此会把只计算新增后缀的耗时除进完整 token 数,进一步放大吞吐。

审计结论:Phase 1 的约 65 token/s 是冷 Prefix Cache 的完整 Prompt 路径, 旧结果是热缓存/递增前缀路径。两者都可以测,但必须分成 Cold 与 Warm 两套实验, 不能放在同一列直接比较。审计原始产物保存在:

/data/hzy/dsv4_script_audit_20260730/

本地归档: TTFT 脚本口径审计报告 及同目录原始 JSON/log。

检查点 状态 结果或结论
服务健康通过端口 30002 已就绪,无 OOM、NCCL 或 Engine 异常
第一次固定点 Run主动停止发现 32K 单请求约需十余分钟;原协议的 4 次同形状请求会使整轮再次接近半天
精简后九个固定点3 完成 / 1 中止 / 5 未执行Prefill 异常信号已足够清晰,停止继续消耗算力
混合干扰 A/B未执行待 Prefill 根因明确后再决定是否重放
阶段耗时约 65 分钟14:36:26 启动,15:41:52 完成进程与容器清理
是否进入下一阶段用户决定立即进入 Phase 2 硬件指标归因

10. 结果位置

服务器原始结果:

/data/hzy/sskj/experiments/pro6000/
dsv4pro_pro6000d_2node_sglang_tp16_quick_map/results/
dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625/

本地已归档 report.mdaggregate.csv 和同目录下的 Manifest、Summary、主日志。

11. 运行命令

cd /data/hzy/sskj/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map

tmux new-session -d -s dsv4pro-pro6000d-2node-sglang-quick-map -c "$PWD"
tmux send-keys -t dsv4pro-pro6000d-2node-sglang-quick-map \
  'RUN_ID=dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625 bash run_quick_map.sh all' Enter

tmux attach -t dsv4pro-pro6000d-2node-sglang-quick-map

下一阶段: Phase 2:Prefill 硬件指标归因

返回推理优化主计划