From 5405d002fcdd952731e52941308e3d5cc2099b12 Mon Sep 17 00:00:00 2001 From: Quantong Qiu Date: Tue, 14 Jul 2026 10:47:09 +0000 Subject: [PATCH] chore: remove legacy benchmark files and scripts Delete obsolete files that are no longer maintained: - experiments/legacy_bench_results/ (old benchmark reports and summaries) - scripts/benchmark_dspark_0707/ (outdated dspark benchmark scripts) - scripts/start_dsv4_dspark_8card.sh (deprecated deployment script) --- experiments/legacy_bench_results/README.md | 25 -- .../dspark_grid_20260707-132641/evaluation.md | 167 ----------- .../dspark_grid_20260707-132641/report.md | 120 -------- .../comparison_report.md | 129 --------- .../README.md | 102 ------- .../summary.json | 197 ------------- .../eagle_grid/dspark_vs_eagle_report.md | 96 ------- .../legacy_bench_results/eagle_grid/report.md | 6 - .../summary.json | 58 ---- .../length_distribution.png | Bin 150830 -> 0 bytes .../report.md | 267 ------------------ .../summary.json | 156 ---------- .../summary.json | 39 --- .../summary.json | 59 ---- .../summary.json | 131 --------- scripts/benchmark_dspark_0707/README.md | 60 ---- .../bench_dspark_focused.sh | 64 ----- .../benchmark_dspark_0707/bench_dspark_p1.sh | 60 ---- .../benchmark_dspark_0707/bench_dspark_p2.sh | 63 ----- .../benchmark_dspark_0707/bench_dspark_p3.sh | 61 ---- .../parse_eagle_vs_dspark.py | 258 ----------------- .../benchmark_dspark_0707/parse_results.py | 120 -------- .../parse_st_comparison.py | 174 ------------ .../run_dspark_benchmark_grid.sh | 114 -------- .../run_dspark_st_comparison.sh | 103 ------- scripts/start_dsv4_dspark_8card.sh | 65 ----- 26 files changed, 2694 deletions(-) delete mode 100644 experiments/legacy_bench_results/README.md delete mode 100644 experiments/legacy_bench_results/dspark_grid_20260707-132641/evaluation.md delete mode 100644 experiments/legacy_bench_results/dspark_grid_20260707-132641/report.md delete mode 100644 experiments/legacy_bench_results/dspark_st_comparison_20260707-150649/comparison_report.md delete mode 100644 experiments/legacy_bench_results/dsv4_backend_comparison_20260707/README.md delete mode 100644 experiments/legacy_bench_results/dsv4_comparison_20260705_152221/summary.json delete mode 100644 experiments/legacy_bench_results/eagle_grid/dspark_vs_eagle_report.md delete mode 100644 experiments/legacy_bench_results/eagle_grid/report.md delete mode 100644 experiments/legacy_bench_results/sglang_8card_max_throughput_20260705_030839/summary.json delete mode 100644 experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/length_distribution.png delete mode 100644 experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/report.md delete mode 100644 experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/summary.json delete mode 100644 experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121046/summary.json delete mode 100644 experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121218/summary.json delete mode 100644 experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121256/summary.json delete mode 100644 scripts/benchmark_dspark_0707/README.md delete mode 100755 scripts/benchmark_dspark_0707/bench_dspark_focused.sh delete mode 100755 scripts/benchmark_dspark_0707/bench_dspark_p1.sh delete mode 100755 scripts/benchmark_dspark_0707/bench_dspark_p2.sh delete mode 100755 scripts/benchmark_dspark_0707/bench_dspark_p3.sh delete mode 100644 scripts/benchmark_dspark_0707/parse_eagle_vs_dspark.py delete mode 100644 scripts/benchmark_dspark_0707/parse_results.py delete mode 100644 scripts/benchmark_dspark_0707/parse_st_comparison.py delete mode 100755 scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh delete mode 100755 scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh delete mode 100755 scripts/start_dsv4_dspark_8card.sh diff --git a/experiments/legacy_bench_results/README.md b/experiments/legacy_bench_results/README.md deleted file mode 100644 index 6a9698c..0000000 --- a/experiments/legacy_bench_results/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Legacy Bench Results Archive - -This directory contains historical benchmark results that were originally stored under `bench_results/` before the `experiments/` layout was introduced. - -All legacy results have been moved here; `bench_results/` itself no longer exists. - -## Contents - -| Directory | Original location | Description | -|---|---|---| -| `dspark_grid_20260707-132641/` | `bench_results/dspark_grid_20260707-132641/` | P1/P2/P3 DSpark grid benchmark | -| `dspark_st_comparison_20260707-150649/` | `bench_results/dspark_st_comparison_20260707-150649/` | `--spec-tokens 3` vs `5` comparison | -| `dsv4_backend_comparison_20260707/` | `bench_results/dsv4_backend_comparison_20260707/` | SGLang vs vLLM backend comparison raw outputs manifest | -| `dsv4_comparison_20260705_152221/` | `bench_results/dsv4_comparison_20260705_152221/` | Early vLLM-dspark configuration comparison | -| `dsv4_flash_dspark_misc/` | `bench_results/dsv4_flash_dspark_misc/` | Early DSV4 DSpark one-off tests | -| `eagle_grid/` | `bench_results/eagle_grid/` | SGLang EAGLE vs DSpark comparison | -| `pd_bench/` | `bench_results/pd_bench/` | PD disaggregation benchmark outputs | -| `sglang_8card_max_throughput_20260705_030839/` | `bench_results/sglang_8card_max_throughput_20260705_030839/` | SGLang 8-card max throughput scan | -| `sglang_8card_systematic_20260704_120819/` | `bench_results/sglang_8card_systematic_20260704_120819/` | Early SGLang systematic scan | -| `sglang_misc/` | `bench_results/sglang_misc/` | Miscellaneous SGLang outputs | -| `vllm_dspark_qwen3_20260705_121046/` | `bench_results/vllm_dspark_qwen3_20260705_121046/` | Qwen3 DSpark tests | -| `vllm_dspark_qwen3_20260705_121218/` | `bench_results/vllm_dspark_qwen3_20260705_121218/` | Qwen3 DSpark tests | -| `vllm_dspark_qwen3_20260705_121256/` | `bench_results/vllm_dspark_qwen3_20260705_121256/` | Qwen3 DSpark tests | - -> Note: Some internal file paths (e.g. `result_file` in `summary.json`, hardcoded paths in old scripts) still reference the original `bench_results/` locations. These are kept as-is for historical accuracy. diff --git a/experiments/legacy_bench_results/dspark_grid_20260707-132641/evaluation.md b/experiments/legacy_bench_results/dspark_grid_20260707-132641/evaluation.md deleted file mode 100644 index d0663c9..0000000 --- a/experiments/legacy_bench_results/dspark_grid_20260707-132641/evaluation.md +++ /dev/null @@ -1,167 +0,0 @@ -# vllm-dspark Benchmark 结果评估 - -> 对应结果:`/data/user1/yy/experiments/legacy_bench_results/dspark_grid_20260707-132641` -> 运行时间:2026-07-07 -> 硬件:8× NVIDIA H200 143GB -> 模型:`/data/models/DeepSeek-V4-Flash-DSpark` -> 服务配置:TP=8,FP8 KV cache,`--spec-method dspark --spec-tokens 5`,block-size=256,max-num-seqs=256 -> 压测工具:`sglang.bench_serving --backend vllm` - ---- - -## 1. 总体结论 - -本次 grid 测试覆盖了从轻量 chat 到超长上下文、从重 decode 到高并发压测的多种场景。整体上看: - -- **DSpark 在短输入、中高并发场景下表现优秀**,`chat_short` 在并发 64 时达到约 9580 tok/s,`stress_standard` 在并发 128 时达到约 17465 tok/s。 -- **低并发下 DSpark 的 draft 开销明显**,部分场景单并发延迟和吞吐都不如预期。 -- **超长上下文场景存在明显的吞吐拐点**,`long_rag`(32K 输入)在并发 4 达到峰值后,并发 8 吞吐腰斩。 -- **延迟长尾(P95/P99)普遍较重**,提示调度、内存或投机解码验证阶段存在抖动。 - ---- - -## 2. 异常点分析 - -### 2.1 低并发(c=1)下 DSpark 收益被开销吃掉 - -以 `chat_standard`(input=1000, output=256)为例: - -| 并发 | req/s | mean E2E(ms) | P99 E2E(ms) | mean TTFT(ms) | P99 TTFT(ms) | -|---|---:|---:|---:|---:|---:| -| 1 | 0.49 | 2040.65 | 10715.53 | 1412.48 | 8430.71 | -| 8 | 4.35 | 1819.35 | 15165.63 | 327.62 | 4404.92 | -| 16 | 8.32 | 1885.15 | 10491.31 | 443.50 | 8567.07 | -| 32 | 10.00 | 3156.73 | 12936.08 | 560.77 | 7129.66 | - -- **单并发吞吐仅 0.49 req/s**,远低于无投机基线可预期的水平。 -- **P99 TTFT 高达 8.4s**,说明单请求首次预填充极不稳定,可能是 draft 验证/编译缓存未命中或 warmup 不充分导致。 -- 并发提升到 8 后,TTFT 均值下降到 327ms,说明 DSpark 的 draft 计算需要 batch 才能摊薄。 - -**判断**:这是 DSpark 的典型特征——低并发下 draft 固定开销无法被摊薄,导致延迟尾和单请求吞吐都较差。 - -### 2.2 超长上下文出现吞吐断崖 - -`long_rag`(input=32000, output=512): - -| 并发 | Total tok/s | mean TTFT(ms) | P99 TTFT(ms) | -|---|---:|---:|---:| -| 1 | 16104.98 | 549.72 | 1163.96 | -| 2 | 43215.62 | 192.57 | 409.32 | -| 4 | **70040.54** | 191.57 | 427.69 | -| 8 | 34141.70(↓51%) | 930.98 | 2742.48 | - -`long_context_probe`(input=16000, output=512)也有类似模式: - -| 并发 | Total tok/s | mean TTFT(ms) | -|---|---:|---:| -| 1 | 11051.09 | 316.59 | -| 2 | 26862.06 | 110.65 | -| 4 | 43042.77 | 127.38 | -| 8 | 29255.46(↓32%) | 453.93 | -| 16 | 43292.68 | 444.14 | - -**判断**: -- 32K/16K 长 prefill 在并发 4 时达到最佳,继续加并发反而下降,可能与 **KV cache 显存带宽瓶颈**、**prefill 阶段 scheduling 阻塞** 或 **hybrid KV cache manager 的换入换出** 有关。 -- `long_rag` 在 c=8 时 P99 TTFT 暴涨到 2.7s,进一步印证内存/调度压力。 - -### 2.3 延迟长尾普遍偏重 - -几乎所有场景的 P99 E2E 都是 mean E2E 的 2~4 倍: - -| 场景 | 并发 | mean E2E | P99 E2E | 倍数 | -|---|---:|---:|---:|---:| -| chat_standard | 32 | 3156.73 | 12936.08 | 4.1× | -| generation_standard | 32 | 2908.89 | 6938.03 | 2.4× | -| summarization | 32 | 5587.21 | 14541.75 | 2.6× | -| decode_heavy | 32 | 5562.57 | 12822.35 | 2.3× | -| stress_standard | 128 | 4417.43 | 14850.43 | 3.4× | - -**判断**:投机解码的 draft 验证失败会导致 fall back 到逐个 target 前向,造成明显的延迟毛刺;另外 vLLM v1 引擎的 scheduling 在高并发下也可能产生队列等待。 - -### 2.4 高并发下 TPOT/ITL 增长较快 - -`stress_standard`(input=1000, output=256): - -| 并发 | mean TPOT | P95 TPOT | P99 TPOT | mean ITL | -|---|---:|---:|---:|---:| -| 96 | 29.46 | 67.30 | 123.42 | 113.64 | -| 128 | 35.20 | 76.85 | 141.17 | 136.57 | - -TPOT 从 29ms 升到 35ms,P99 超过 140ms,说明 decode 阶段在极限并发下已经接近饱和。 - ---- - -## 3. 可重点优化的方向 - -### 3.1 按并发动态调整 `--spec-tokens` - -参考历史对比数据(`dsv4_inference_comparison_report.md`): - -| spec-tokens | 低并发(c=1) | 高并发(c=64) | -|---|---|---| -| 3 | 1.03 req/s, 254 tok/s | **14.53 req/s, 3507 tok/s** | -| 5 | **1.07 req/s, 257 tok/s** | 9.36 req/s, 2210 tok/s | -| 7 | 1.03 req/s, 251 tok/s | 9.13 req/s, 2218 tok/s | - -当前部署固定使用 `--spec-tokens 5`: -- 对低并发(c=1~16)相对友好; -- 对高并发(c≥64)不是最优,st=3 在高并发下接受率更高、验证开销更小。 - -**建议**: -- 高并发服务场景:改用 `--spec-tokens 3`。 -- 若负载混合,可考虑不同 endpoint/队列按并发分桶,或测试 st=3/st=5 在本 grid 下的完整表现。 - -### 3.2 长上下文场景优化 - -针对 `long_rag`(32K)和 `long_context_probe`(16K)的吞吐断崖: - -1. **KV cache dtype 对比测试**:当前是 FP8,可测试 BF16 是否改善长上下文稳定性和吞吐。 -2. **block-size 调整**:当前 256,可尝试 128 或 64 看是否减少内存碎片。 -3. **hybrid KV cache manager**:当前 `--no-disable-hybrid-kv-cache-manager`,可对比关闭后的表现。 -4. **限制长上下文并发**:若业务允许,对 32K 输入设置更低的 `max-num-seqs` 或独立队列,避免 c=8 后的内存带宽瓶颈。 -5. **attention backend**:可测试 `FLASHINFER_MLA_SPARSE_DSV4`(脚本 `start_dsv4_dspark_8card_flashinfer.sh`)在长上下文下的表现。 - -### 3.3 降低延迟长尾 - -1. **增加 warmup**:当前仅 10 条 warmup。DSpark 的 JIT 编译/算子 warmup 在首次运行时较重,建议增加到 50~100 条或先跑一轮 discard。 -2. **compilation cache 持久化**:确认 `TORCH_EXTENSIONS_DIR`、`VLLM_CACHE_ROOT` 等环境变量已设置并复用,避免每次重启重新编译 deep_gemm/tilelang。 -3. **调度参数**:尝试调整 `--max-num-batched-tokens`、`--max-num-seqs`、chunked prefill 相关参数,减少队列等待。 -4. **P99 TTFT 抖动**:高并发下 P99 TTFT 高通常与 prefill batching 有关,可尝试限制 prefill 阶段 batch 大小或启用 prompt chunked prefill。 - -### 3.4 低并发场景是否需要 DSpark - -对于 `chat_standard` c=1: -- 当前 0.49 req/s、P99 E2E 10.7s 的服务质量较差。 -- 若业务存在大量单用户/低并发请求,可考虑: - - 部署无投机基线 vLLM 专门服务低并发流量; - - 或设置最小 batch 阈值,累积少量请求后再用 DSpark 处理。 - -### 3.5 对比 baseline 与 SGLang - -本次测试只有 DSpark 单一配置,建议补充: -1. **vLLM-dspark 无投机基线**:用同样的 `sglang.bench_serving --backend vllm` 测一遍相同 grid,计算真实加速比。 -2. **SGLang EAGLE**:在相同 grid 下复测,验证 DSpark 在不同场景下的相对优势。 -3. **不同 `--spec-tokens` 的完整 grid**:st=3 和 st=5 各跑一遍,绘制并发-吞吐曲线。 - ---- - -## 4. 推荐下一步实验 - -| 优先级 | 实验 | 预期收益 | -|---|---|---| -| P0 | 高并发改用 `--spec-tokens 3` | 提升 c≥64 场景吞吐 20~50% | -| P0 | 长上下文(16K/32K)对比 BF16 KV cache | 改善吞吐断崖和 P99 TTFT | -| P1 | 增加 warmup / 预热缓存 | 降低 P99 TTFT 和首请求延迟 | -| P1 | 测试 `FLASHINFER_MLA_SPARSE_DSV4` backend | 可能提升长上下文吞吐 | -| P2 | 跑无投机基线 grid | 获得真实加速比 | -| P2 | 对比 st=3/st=5 完整曲线 | 找到最佳投机长度配置 | - ---- - -## 5. 结论 - -当前 DSpark 部署在 **短输入、中高并发** 场景下已经展现出明显优势,但在 **低并发** 和 **超长上下文** 场景下存在明显短板。最优先的优化是: - -1. 高并发服务改用 `--spec-tokens 3`; -2. 针对 16K/32K 长上下文做 KV cache dtype 和 attention backend 的调优; -3. 补充 baseline 测试,量化 DSpark 的真实收益。 diff --git a/experiments/legacy_bench_results/dspark_grid_20260707-132641/report.md b/experiments/legacy_bench_results/dspark_grid_20260707-132641/report.md deleted file mode 100644 index 6b0ce76..0000000 --- a/experiments/legacy_bench_results/dspark_grid_20260707-132641/report.md +++ /dev/null @@ -1,120 +0,0 @@ -# vllm-dspark Benchmark Grid Report - -- Result root: `/data/user1/yy/experiments/legacy_bench_results/dspark_grid_20260707-132641` -- Model: `/data/models/DeepSeek-V4-Flash-DSpark` -- Backend: vllm-dspark (TP=8, FP8 KV cache, spec-method=dspark, spec-tokens=5) -- Benchmark client: `sglang.bench_serving --backend vllm` - -## p1_quick - -### chat_standard (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 1 | 65.33 | 32 | 0.49 | 195.58 | 71.28 | 266.86 | 2040.65 | 8241.20 | 10715.53 | 1412.48 | 6903.30 | 8430.71 | 6.41 | 32.72 | 51.29 | 16.61 | -| 8 | 29.42 | 128 | 4.35 | 2130.85 | 569.52 | 2700.37 | 1819.35 | 8891.79 | 15165.63 | 327.62 | 379.39 | 4404.92 | 11.58 | 51.06 | 82.44 | 51.00 | -| 16 | 30.76 | 256 | 8.32 | 4144.89 | 1144.74 | 5289.63 | 1885.15 | 8902.10 | 10491.31 | 443.50 | 2779.49 | 8567.07 | 12.10 | 23.11 | 83.70 | 46.18 | -| 32 | 51.18 | 512 | 10.00 | 5017.88 | 1331.65 | 6349.53 | 3156.73 | 10438.92 | 12936.08 | 560.77 | 5750.71 | 7129.66 | 22.09 | 66.23 | 171.57 | 85.81 | - -### generation_standard (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 1 | 36.24 | 32 | 0.88 | 352.60 | 479.25 | 831.85 | 1131.63 | 1979.98 | 2576.42 | 66.57 | 141.80 | 177.56 | 1.94 | 2.85 | 2.88 | 8.50 | -| 8 | 27.43 | 128 | 4.67 | 2285.21 | 2362.35 | 4647.56 | 1649.16 | 3162.33 | 3844.70 | 80.88 | 157.80 | 160.50 | 3.23 | 5.31 | 7.09 | 14.77 | -| 16 | 35.39 | 256 | 7.23 | 3602.52 | 3654.79 | 7257.31 | 2133.90 | 4187.67 | 5500.20 | 88.78 | 169.39 | 199.78 | 4.37 | 6.97 | 11.81 | 19.28 | -| 32 | 48.16 | 512 | 10.63 | 5333.41 | 5371.85 | 10705.25 | 2908.89 | 5849.64 | 6938.03 | 112.84 | 211.64 | 405.78 | 5.84 | 9.30 | 12.47 | 25.89 | - -### summarization (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 1 | 33.63 | 32 | 0.95 | 4247.40 | 516.49 | 4763.90 | 1049.88 | 1783.33 | 1848.92 | 178.56 | 292.66 | 314.18 | 1.60 | 1.78 | 2.41 | 8.62 | -| 4 | 10.99 | 32 | 2.91 | 13000.06 | 1580.84 | 14580.90 | 1301.15 | 2238.30 | 2324.70 | 101.68 | 187.78 | 188.57 | 2.18 | 2.47 | 3.49 | 11.86 | -| 8 | 33.60 | 128 | 3.81 | 15369.23 | 1908.96 | 17278.19 | 2072.22 | 3978.11 | 4531.26 | 214.01 | 502.25 | 630.15 | 3.70 | 5.52 | 7.98 | 19.60 | -| 16 | 46.93 | 256 | 5.45 | 22698.72 | 2761.38 | 25460.10 | 2840.57 | 6243.39 | 7179.11 | 235.75 | 552.81 | 740.45 | 5.45 | 9.62 | 12.75 | 27.77 | - -## p2_core - -### chat_short (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 1 | 11.39 | 32 | 2.81 | 717.30 | 408.87 | 1126.18 | 355.00 | 685.00 | 815.55 | 48.57 | 105.64 | 105.97 | 2.07 | 3.10 | 3.69 | 8.51 | -| 8 | 10.62 | 128 | 12.06 | 3320.19 | 1591.90 | 4912.09 | 647.05 | 1401.33 | 1648.15 | 78.91 | 151.78 | 155.67 | 4.59 | 7.20 | 12.54 | 17.85 | -| 16 | 14.89 | 256 | 17.19 | 4533.71 | 2394.66 | 6928.36 | 902.72 | 1879.32 | 2211.23 | 95.95 | 165.09 | 217.14 | 6.19 | 10.26 | 15.12 | 23.65 | -| 32 | 24.90 | 512 | 20.56 | 5458.26 | 2723.85 | 8182.10 | 1508.89 | 3076.09 | 3881.52 | 161.04 | 292.00 | 383.41 | 10.80 | 18.19 | 25.45 | 41.86 | -| 64 | 21.27 | 512 | 24.07 | 6391.14 | 3189.39 | 9580.52 | 2547.81 | 5606.07 | 6543.97 | 268.68 | 543.21 | 588.63 | 18.24 | 30.39 | 39.57 | 70.29 | - -### chat_standard (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 64 | 19.87 | 512 | 25.76 | 12922.80 | 3429.47 | 16352.28 | 2380.94 | 4838.43 | 6315.98 | 291.17 | 794.65 | 947.26 | 16.87 | 30.96 | 42.55 | 68.46 | - -### generation_standard (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 64 | 43.59 | 512 | 11.75 | 5892.47 | 5934.93 | 11827.40 | 5136.14 | 10244.70 | 12575.20 | 209.70 | 536.96 | 606.58 | 10.33 | 17.41 | 21.20 | 45.47 | - -### long_context_probe (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 1 | 23.79 | 32 | 1.34 | 10684.78 | 366.30 | 11051.09 | 742.62 | 1237.28 | 1418.82 | 316.59 | 604.23 | 617.85 | 1.54 | 1.72 | 1.85 | 8.53 | -| 2 | 9.79 | 32 | 3.27 | 25971.67 | 890.38 | 26862.06 | 604.82 | 1055.84 | 1116.41 | 110.65 | 194.62 | 206.64 | 1.77 | 2.04 | 2.13 | 9.90 | -| 4 | 6.11 | 32 | 5.24 | 41616.05 | 1426.72 | 43042.77 | 741.94 | 1262.13 | 1447.68 | 127.38 | 222.70 | 235.35 | 2.26 | 2.69 | 3.69 | 12.39 | -| 8 | 36.07 | 128 | 3.55 | 28357.82 | 897.65 | 29255.46 | 2232.38 | 4946.97 | 5371.22 | 453.93 | 1089.99 | 1182.47 | 7.06 | 13.03 | 20.03 | 38.51 | -| 16 | 49.08 | 256 | 5.22 | 41910.25 | 1382.43 | 43292.68 | 3028.26 | 7651.28 | 9531.10 | 444.14 | 1212.92 | 1368.04 | 10.00 | 20.71 | 30.74 | 53.07 | - -### rag_medium (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 1 | 18.21 | 32 | 1.76 | 3568.86 | 494.63 | 4063.49 | 568.24 | 966.60 | 1201.99 | 110.94 | 151.11 | 154.78 | 1.63 | 1.95 | 2.50 | 8.60 | -| 4 | 6.31 | 32 | 5.07 | 10300.85 | 1427.65 | 11728.51 | 751.19 | 1259.07 | 1593.86 | 92.09 | 161.03 | 187.47 | 2.34 | 3.25 | 4.02 | 12.33 | -| 8 | 19.13 | 128 | 6.69 | 14147.80 | 1692.32 | 15840.12 | 1166.59 | 2199.67 | 2982.23 | 148.09 | 253.10 | 350.67 | 4.30 | 6.78 | 9.92 | 21.06 | -| 16 | 25.89 | 256 | 9.89 | 20561.53 | 2622.36 | 23183.89 | 1587.09 | 3316.74 | 4019.02 | 164.29 | 303.79 | 389.34 | 5.50 | 8.80 | 14.38 | 27.81 | -| 32 | 39.47 | 512 | 12.97 | 26523.95 | 3335.30 | 29859.24 | 2415.10 | 5069.76 | 6195.14 | 227.81 | 444.76 | 510.18 | 8.64 | 13.54 | 20.29 | 44.49 | - -### summarization (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 32 | 91.68 | 512 | 5.58 | 22445.97 | 2821.71 | 25267.67 | 5587.21 | 11177.97 | 12257.18 | 430.38 | 967.32 | 2238.28 | 10.56 | 16.13 | 23.53 | 54.40 | - -## p3_extension - -### decode_heavy (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 1 | 56.16 | 32 | 0.57 | 145.47 | 546.26 | 691.72 | 1754.17 | 2994.25 | 3175.70 | 52.30 | 107.66 | 108.83 | 1.88 | 2.68 | 3.14 | 8.53 | -| 8 | 54.33 | 128 | 2.36 | 648.72 | 2526.47 | 3175.19 | 3302.64 | 6408.14 | 7166.98 | 74.21 | 143.24 | 149.17 | 3.05 | 4.37 | 4.79 | 14.12 | -| 16 | 65.74 | 256 | 3.89 | 1026.90 | 4012.07 | 5038.96 | 3934.72 | 7216.03 | 9638.96 | 81.02 | 165.95 | 197.11 | 3.98 | 5.82 | 7.21 | 17.79 | -| 32 | 92.36 | 512 | 5.54 | 1471.70 | 5761.81 | 7233.51 | 5562.57 | 10498.47 | 12822.35 | 107.17 | 234.99 | 426.35 | 5.40 | 8.14 | 9.03 | 24.38 | - -### long_rag (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 1 | 30.84 | 32 | 1.04 | 15823.63 | 281.34 | 16104.98 | 962.70 | 1688.61 | 1788.29 | 549.72 | 1124.54 | 1163.96 | 1.49 | 1.72 | 1.82 | 8.52 | -| 2 | 11.49 | 32 | 2.78 | 42460.67 | 754.95 | 43215.62 | 708.16 | 1202.62 | 1253.12 | 192.57 | 352.96 | 409.32 | 2.05 | 2.37 | 6.62 | 10.76 | -| 4 | 7.09 | 32 | 4.51 | 68816.98 | 1223.56 | 70040.54 | 854.49 | 1516.80 | 1562.42 | 191.57 | 426.45 | 427.69 | 2.62 | 3.20 | 7.33 | 13.69 | -| 8 | 60.48 | 128 | 2.12 | 33604.85 | 536.85 | 34141.70 | 3732.80 | 8420.06 | 9705.36 | 930.98 | 2125.14 | 2742.48 | 11.12 | 22.70 | 39.17 | 61.41 | - -### stress_generation (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 96 | 81.20 | 768 | 9.46 | 4842.45 | 4635.63 | 9478.08 | 9772.15 | 20945.93 | 25314.28 | 326.63 | 782.50 | 795.30 | 20.35 | 33.33 | 42.50 | 88.91 | -| 128 | 92.26 | 1024 | 11.10 | 5587.83 | 5514.57 | 11102.40 | 11012.89 | 22012.44 | 28276.20 | 360.94 | 812.12 | 1024.93 | 22.53 | 35.59 | 42.09 | 99.75 | - -### stress_standard (20260707-132641) - -| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) | -|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------| -| 96 | 30.50 | 768 | 25.18 | 12891.05 | 3175.02 | 16066.07 | 3689.97 | 9383.09 | 11958.09 | 435.22 | 1225.11 | 2581.90 | 29.46 | 67.30 | 123.42 | 113.64 | -| 128 | 36.86 | 1024 | 27.78 | 13985.42 | 3479.38 | 17464.80 | 4417.43 | 11825.67 | 14850.43 | 456.55 | 1032.19 | 1922.87 | 35.20 | 76.85 | 141.17 | 136.57 | - diff --git a/experiments/legacy_bench_results/dspark_st_comparison_20260707-150649/comparison_report.md b/experiments/legacy_bench_results/dspark_st_comparison_20260707-150649/comparison_report.md deleted file mode 100644 index 0240608..0000000 --- a/experiments/legacy_bench_results/dspark_st_comparison_20260707-150649/comparison_report.md +++ /dev/null @@ -1,129 +0,0 @@ -# vllm-dspark `--spec-tokens` 对比报告 - -- 结果目录:`/data/user1/yy/bench_results/dspark_st_comparison_20260707-150649` -- 模型:`/data/models/DeepSeek-V4-Flash-DSpark` -- 后端:vllm-dspark (TP=8, FP8 KV cache) -- 对比参数:`--spec-tokens 3` vs `--spec-tokens 5` -- Warmup:100 条 -- 压测客户端:`sglang.bench_serving --backend vllm` - ---- - -## 核心指标对比 - -| Scenario | Concurrency | Spec | Duration(s) | Req/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P99 TPOT(ms) | -|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| -| chat_short | 1 | 3 | 12.80 | 2.50 | 363.90 | 1002.30 | 398.97 | 757.11 | 948.62 | 43.97 | 68.89 | 2.42 | 3.76 | -| chat_short | 1 | 5 | 11.72 | 2.73 | 397.37 | 1094.51 | 365.29 | 743.89 | 782.98 | 64.42 | 174.36 | 2.06 | 3.40 | -| chat_short | 8 | 3 | 11.08 | 11.55 | 1525.06 | 4705.85 | 672.66 | 1269.82 | 1552.65 | 76.20 | 221.18 | 4.67 | 8.43 | -| chat_short | 8 | 5 | 11.78 | 10.87 | 1434.62 | 4426.78 | 718.40 | 1511.14 | 2008.00 | 94.97 | 220.85 | 4.95 | 9.93 | -| chat_short | 16 | 3 | 21.82 | 11.73 | 1634.03 | 4727.68 | 1333.99 | 2611.76 | 2921.26 | 151.65 | 337.53 | 8.85 | 17.66 | -| chat_short | 16 | 5 | 16.42 | 15.59 | 2171.58 | 6282.94 | 997.37 | 2147.86 | 2496.56 | 107.56 | 240.46 | 7.09 | 21.93 | -| chat_short | 32 | 3 | 24.62 | 20.79 | 2754.96 | 8275.55 | 1497.40 | 2944.48 | 3658.90 | 144.47 | 453.29 | 10.72 | 23.97 | -| chat_short | 32 | 5 | 26.32 | 19.45 | 2577.42 | 7742.25 | 1600.40 | 3403.00 | 4521.14 | 169.13 | 402.36 | 11.39 | 27.82 | -| chat_short | 64 | 3 | 21.66 | 23.63 | 3131.20 | 9405.73 | 2595.90 | 5230.98 | 6258.25 | 251.84 | 509.86 | 18.60 | 40.84 | -| chat_short | 64 | 5 | 21.26 | 24.09 | 3191.43 | 9586.66 | 2555.12 | 5387.59 | 6559.69 | 274.71 | 610.40 | 18.47 | 42.08 | -| chat_standard | 64 | 3 | 20.73 | 24.70 | 3288.58 | 15680.46 | 2483.46 | 4798.63 | 5693.88 | 271.41 | 947.15 | 17.93 | 46.05 | -| chat_standard | 64 | 5 | 20.91 | 24.49 | 3259.71 | 15542.80 | 2512.52 | 5394.43 | 6023.55 | 300.81 | 932.60 | 17.83 | 44.47 | -| decode_heavy | 1 | 3 | 68.78 | 0.47 | 446.03 | 564.81 | 2148.54 | 3712.03 | 3835.51 | 38.60 | 41.18 | 2.28 | 3.03 | -| decode_heavy | 1 | 5 | 57.70 | 0.55 | 531.76 | 673.37 | 1802.00 | 2957.04 | 3676.66 | 59.06 | 114.78 | 1.92 | 3.03 | -| decode_heavy | 32 | 3 | 96.14 | 5.33 | 5535.64 | 6949.57 | 5809.29 | 10830.76 | 12546.55 | 86.83 | 382.71 | 5.62 | 8.11 | -| decode_heavy | 32 | 5 | 92.75 | 5.52 | 5737.73 | 7203.28 | 5595.02 | 10412.11 | 12522.00 | 107.12 | 331.08 | 5.43 | 9.15 | -| generation_standard | 64 | 3 | 38.58 | 13.27 | 6705.88 | 13363.77 | 4542.22 | 8273.38 | 9355.10 | 190.34 | 813.58 | 9.00 | 15.36 | -| generation_standard | 64 | 5 | 43.39 | 11.80 | 5961.40 | 11880.13 | 5075.77 | 9863.55 | 11533.47 | 216.87 | 626.93 | 10.09 | 20.90 | -| long_context_probe | 1 | 3 | 26.10 | 1.23 | 333.92 | 10074.03 | 815.18 | 1382.34 | 1556.00 | 265.28 | 558.73 | 2.02 | 2.19 | -| long_context_probe | 1 | 5 | 22.22 | 1.44 | 392.31 | 11835.70 | 693.30 | 1172.84 | 1404.17 | 267.61 | 561.65 | 1.53 | 1.88 | -| long_context_probe | 4 | 3 | 7.34 | 4.36 | 1187.03 | 35811.63 | 888.88 | 1550.70 | 1611.29 | 125.51 | 282.82 | 2.77 | 3.23 | -| long_context_probe | 4 | 5 | 6.59 | 4.85 | 1322.32 | 39893.35 | 806.55 | 1390.87 | 1500.22 | 143.42 | 236.41 | 2.41 | 3.54 | -| long_context_probe | 8 | 3 | 38.61 | 3.31 | 838.49 | 27327.58 | 2387.05 | 5028.60 | 5667.97 | 406.10 | 1093.54 | 7.74 | 17.09 | -| long_context_probe | 8 | 5 | 35.94 | 3.56 | 900.89 | 29361.32 | 2223.29 | 4693.10 | 5345.72 | 425.03 | 1241.57 | 7.08 | 19.83 | -| rag_medium | 1 | 3 | 22.31 | 1.43 | 403.86 | 3317.84 | 696.09 | 1165.77 | 1315.00 | 105.51 | 157.60 | 2.10 | 2.81 | -| rag_medium | 1 | 5 | 18.16 | 1.76 | 496.09 | 4075.47 | 566.58 | 968.90 | 1134.15 | 110.34 | 156.35 | 1.64 | 2.34 | -| rag_medium | 8 | 3 | 20.57 | 6.22 | 1573.71 | 14730.01 | 1251.63 | 2472.54 | 2796.12 | 127.54 | 309.65 | 4.46 | 7.41 | -| rag_medium | 8 | 5 | 19.13 | 6.69 | 1692.13 | 15838.39 | 1175.26 | 2263.59 | 3106.42 | 154.24 | 352.19 | 4.14 | 8.36 | -| rag_medium | 32 | 3 | 45.02 | 11.37 | 2923.96 | 26176.72 | 2756.39 | 5579.00 | 6675.25 | 215.15 | 477.20 | 10.00 | 19.84 | -| rag_medium | 32 | 5 | 42.98 | 11.91 | 3062.92 | 27420.79 | 2626.21 | 5182.78 | 6873.72 | 253.09 | 522.80 | 9.17 | 18.55 | -| stress_standard | 64 | 3 | 21.35 | 23.99 | 3193.19 | 15225.66 | 2569.66 | 5006.16 | 5831.81 | 261.98 | 710.57 | 18.02 | 36.01 | -| stress_standard | 64 | 5 | 21.75 | 23.54 | 3133.74 | 14942.17 | 2621.52 | 5431.08 | 7161.16 | 305.11 | 734.92 | 18.42 | 41.01 | -| stress_standard | 96 | 3 | 20.36 | 37.72 | 4756.72 | 24069.73 | 2433.73 | 4827.07 | 6001.29 | 272.61 | 749.99 | 18.04 | 38.52 | -| stress_standard | 96 | 5 | 25.33 | 30.32 | 3823.91 | 19349.55 | 3032.19 | 6272.09 | 8124.32 | 339.22 | 793.18 | 23.06 | 51.46 | -| stress_standard | 128 | 3 | 21.58 | 47.45 | 5942.48 | 29828.36 | 2581.42 | 5162.16 | 6222.01 | 298.69 | 970.61 | 19.09 | 40.93 | -| stress_standard | 128 | 5 | 28.36 | 36.11 | 4522.53 | 22700.92 | 3405.74 | 7240.13 | 9176.77 | 382.02 | 1087.53 | 25.74 | 57.83 | - ---- - -## 吞吐 winner 统计 - -| Scenario | Concurrency | Winner (Total tok/s) | st=3 Total tok/s | st=5 Total tok/s | 提升 | -|---|---:|---:|---:|---:|---:| -| chat_short | 1 | st=5 | 1002.30 | 1094.51 | 9.20% | -| chat_short | 8 | st=3 | 4705.85 | 4426.78 | 6.30% | -| chat_short | 16 | st=5 | 4727.68 | 6282.94 | 32.90% | -| chat_short | 32 | st=3 | 8275.55 | 7742.25 | 6.89% | -| chat_short | 64 | st=5 | 9405.73 | 9586.66 | 1.92% | -| chat_standard | 64 | st=3 | 15680.46 | 15542.80 | 0.89% | -| decode_heavy | 1 | st=5 | 564.81 | 673.37 | 19.22% | -| decode_heavy | 32 | st=5 | 6949.57 | 7203.28 | 3.65% | -| generation_standard | 64 | st=3 | 13363.77 | 11880.13 | 12.49% | -| long_context_probe | 1 | st=5 | 10074.03 | 11835.70 | 17.49% | -| long_context_probe | 4 | st=5 | 35811.63 | 39893.35 | 11.40% | -| long_context_probe | 8 | st=5 | 27327.58 | 29361.32 | 7.44% | -| rag_medium | 1 | st=5 | 3317.84 | 4075.47 | 22.84% | -| rag_medium | 8 | st=5 | 14730.01 | 15838.39 | 7.52% | -| rag_medium | 32 | st=5 | 26176.72 | 27420.79 | 4.75% | -| stress_standard | 64 | st=3 | 15225.66 | 14942.17 | 1.90% | -| stress_standard | 96 | st=3 | 24069.73 | 19349.55 | 24.39% | -| stress_standard | 128 | st=3 | 29828.36 | 22700.92 | 31.40% | - ---- - -## 关键发现 - -### 1. 并发是决定性因素 - -- **低并发(c=1)和中低并发(c=8~32)**:`st=5` 在多数场景下更优,尤其是长上下文和重 decode 场景。 -- **高并发(c≥96)**:`st=3` 明显更优,`stress_standard` c=128 时 st=3 比 st=5 高 **31.4%**。 -- **中并发(c=64)**:两者基本持平,差异多在 2% 以内。 - -### 2. st=5 更适合长上下文和重 decode - -| 场景 | 最佳 spec | 原因 | -|---|---|---| -| long_context_probe (16K) | st=5 | 长 prefill 下 st=5 的接受长度更高 | -| rag_medium (4K) | st=5 | 中长输入下 st=5 延迟和吞吐都更优 | -| decode_heavy (2K output) | st=5 | 输出越长,st=5 的投机收益越大 | - -### 3. st=3 在极限并发下更稳 - -`stress_standard` 随并发增加,st=3 与 st=5 的差距拉大: - -| 并发 | st=3 Total tok/s | st=5 Total tok/s | 差距 | -|---|---:|---:|---:| -| 64 | 15225.66 | 14942.17 | 基本持平 | -| 96 | 24069.73 | 19349.55 | st=3 高 24.4% | -| 128 | 29828.36 | 22700.92 | st=3 高 31.4% | - -这说明 st=5 在极限并发下验证开销和 KV cache 压力显著增加,而 st=3 的验证 batch 更小、调度更稳定。 - -### 4. chat_short c=16 的异常 - -`chat_short c=16` 时 st=5 比 st=3 高 32.9%,是一个明显的 outlier。可能原因是该并发度下 st=5 的 draft 接受率和 batch 利用率恰好达到甜点。 - ---- - -## 优化建议 - -### 推荐配置 - -| 负载特征 | 推荐 `--spec-tokens` | 说明 | -|---|---|---| -| 低并发 / 在线交互(c ≤ 32) | **5** | 延迟低、单请求吞吐高 | -| 中并发(c ≈ 64) | 3 或 5 均可 | 差异很小 | -| 高并发 / 压测(c ≥ 96) | **3** | 吞吐更高、P99 更稳 | -| 长上下文 / RAG / 重 decode | **5** | 接受长度优势更明显 | - -### 下一步 - -1. 如果业务以高并发为主,将默认服务改为 `--spec-tokens 3`。 -2. 如果业务混合,可考虑按输入长度或并发度路由到不同服务实例。 -3. 本次 warmup 已从 10 提升到 100,P99 TTFT 相比首次 grid 测试有明显改善;建议保持 100 条 warmup。 diff --git a/experiments/legacy_bench_results/dsv4_backend_comparison_20260707/README.md b/experiments/legacy_bench_results/dsv4_backend_comparison_20260707/README.md deleted file mode 100644 index 0845e31..0000000 --- a/experiments/legacy_bench_results/dsv4_backend_comparison_20260707/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# DSV4 Backend Comparison Raw Outputs (2026-07-07) - -## Location - -`/data/user1/yy/bench_results/dsv4_backend_comparison_20260707` - -## Contents - -- `raw_outputs/` — raw per-benchmark JSONL files produced by `sglang.bench_serving --output-file --output-details`. -- `README.md` — this file. - -## File Naming Convention - -Each file follows the pattern: - -``` -{backend}_0707_{concurrency}_{input_len}_{output_len}.jsonl -``` - -Where: - -- `{backend}`: `sglang` or `vllm` -- `{concurrency}`: max concurrent requests (`max-concurrency`) -- `{input_len}`: `--random-input-len` -- `{output_len}`: `--random-output-len` - -## Output File Inventory - -### SGLang backend - -| File | Concurrency | Input len | Output len | -|---|---|---|---| -| sglang_0707_32_512_256.jsonl | 32 | 512 | 256 | -| sglang_0707_128_512_256.jsonl | 128 | 512 | 256 | -| sglang_0707_256_512_256.jsonl | 256 | 512 | 256 | -| sglang_0707_512_512_256.jsonl | 512 | 512 | 256 | -| sglang_0707_512_512_2000.jsonl | 512 | 512 | 2000 | -| sglang_0707_32_512_2000.jsonl | 32 | 512 | 2000 | -| sglang_0707_32_4000_512.jsonl | 32 | 4000 | 512 | -| sglang_0707_128_4000_512.jsonl | 128 | 4000 | 512 | -| sglang_0707_512_4000_512.jsonl | 512 | 4000 | 512 | -| sglang_0707_32_16000_512.jsonl | 32 | 16000 | 512 | -| sglang_0707_128_16000_512.jsonl | 128 | 16000 | 512 | -| sglang_0707_512_1000_256.jsonl | 512 | 1000 | 256 | -| sglang_0707_768_1000_256.jsonl | 768 | 1000 | 256 | -| sglang_0707_1024_1000_256.jsonl | 1024 | 1000 | 256 | -| sglang_0707_512_1000_1000.jsonl | 512 | 1000 | 1000 | - -### vLLM backend - -| File | Concurrency | Input len | Output len | -|---|---|---|---| -| vllm_0707_32_512_256.jsonl | 32 | 512 | 256 | -| vllm_0707_128_512_256.jsonl | 128 | 512 | 256 | -| vllm_0707_256_512_256.jsonl | 256 | 512 | 256 | -| vllm_0707_128_512_2000.jsonl | 128 | 512 | 2000 | -| vllm_0707_256_512_2000.jsonl | 256 | 512 | 2000 | -| vllm_0707_512_512_2000.jsonl | 512 | 512 | 2000 | -| vllm_0707_32_1000_256.jsonl | 32 | 1000 | 256 | -| vllm_0707_128_1000_256.jsonl | 128 | 1000 | 256 | -| vllm_0707_256_1000_256.jsonl | 256 | 1000 | 256 | -| vllm_0707_512_1000_256.jsonl | 512 | 1000 | 256 | -| vllm_0707_768_1000_256.jsonl | 768 | 1000 | 256 | -| vllm_0707_1024_1000_256.jsonl | 1024 | 1000 | 256 | -| vllm_0707_32_1000_1000.jsonl | 32 | 1000 | 1000 | -| vllm_0707_128_1000_1000.jsonl | 128 | 1000 | 1000 | -| vllm_0707_256_1000_1000.jsonl | 256 | 1000 | 1000 | -| vllm_0707_512_1000_1000.jsonl | 512 | 1000 | 1000 | -| vllm_0707_1024_1000_1000.jsonl | 1024 | 1000 | 1000 | -| vllm_0707_32_4000_512.jsonl | 32 | 4000 | 512 | -| vllm_0707_128_4000_512.jsonl | 128 | 4000 | 512 | -| vllm_0707_256_4000_512.jsonl | 256 | 4000 | 512 | -| vllm_0707_512_4000_512.jsonl | 512 | 4000 | 512 | -| vllm_0707_32_8000_1000.jsonl | 32 | 8000 | 1000 | -| vllm_0707_128_8000_1000.jsonl | 128 | 8000 | 1000 | -| vllm_0707_256_8000_1000.jsonl | 256 | 8000 | 1000 | -| vllm_0707_512_8000_1000.jsonl | 512 | 8000 | 1000 | -| vllm_0707_32_16000_512.jsonl | 32 | 16000 | 512 | -| vllm_0707_128_16000_512.jsonl | 128 | 16000 | 512 | -| vllm_0707_256_16000_512.jsonl | 256 | 16000 | 512 | -| vllm_0707_32_32000_512.jsonl | 32 | 32000 | 512 | -| vllm_0707_128_32000_512.jsonl | 128 | 32000 | 512 | - -## Provenance - -These files are **legacy raw outputs** from an ad-hoc SGLang vs vLLM backend comparison sweep run on 2026-07-07. The exact generator command/script that produced them was not preserved in the repository. They were generated using `sglang.bench_serving` with `--output-file` and `--output-details`, against: - -- Model: `/data/models/DeepSeek-V4-Flash` -- SGLang port: `30000` -- vLLM port: `8000` (inferred from file content) -- Dataset: `random` - -For future reproducible comparisons, use the script: - -``` -scripts/benchmark_dsv4_backend_comparison.sh -``` - -## Related Reports - -- `/data/user1/yy/dsv4_inference_comparison_report.md` — earlier DSV4 inference comparison report. -- `/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/` — earlier comparison run outputs (`.json`/`.log`). diff --git a/experiments/legacy_bench_results/dsv4_comparison_20260705_152221/summary.json b/experiments/legacy_bench_results/dsv4_comparison_20260705_152221/summary.json deleted file mode 100644 index 98e8e23..0000000 --- a/experiments/legacy_bench_results/dsv4_comparison_20260705_152221/summary.json +++ /dev/null @@ -1,197 +0,0 @@ -[ - { - "service": "vllm-dspark-dspark-st5", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 5, - "concurrency": 1, - "request_throughput": 1.0665689780673602, - "output_throughput": 257.1817776813825, - "total_input_tokens": 47865, - "total_output_tokens": 48226, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st5_c1.json" - }, - { - "service": "vllm-dspark-dspark-st5", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 5, - "concurrency": 16, - "request_throughput": 7.702621472216438, - "output_throughput": 1862.3398195524906, - "total_input_tokens": 47865, - "total_output_tokens": 48356, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st5_c16.json" - }, - { - "service": "vllm-dspark-dspark-st5", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 5, - "concurrency": 64, - "request_throughput": 9.360404619968996, - "output_throughput": 2210.4595510056783, - "total_input_tokens": 47865, - "total_output_tokens": 47230, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st5_c64.json" - }, - { - "service": "vllm-dspark-nospec", - "engine": "vllm-dspark", - "spec_method": null, - "spec_tokens": null, - "concurrency": 1, - "request_throughput": 0.5918258062472803, - "output_throughput": 144.62151314361665, - "total_input_tokens": 47865, - "total_output_tokens": 48873, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-nospec_c1.json" - }, - { - "service": "vllm-dspark-nospec", - "engine": "vllm-dspark", - "spec_method": null, - "spec_tokens": null, - "concurrency": 16, - "request_throughput": 5.61467992292336, - "output_throughput": 1364.0022670753865, - "total_input_tokens": 47865, - "total_output_tokens": 48587, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-nospec_c16.json" - }, - { - "service": "vllm-dspark-nospec", - "engine": "vllm-dspark", - "spec_method": null, - "spec_tokens": null, - "concurrency": 64, - "request_throughput": 7.339983457334795, - "output_throughput": 1803.9844342264591, - "total_input_tokens": 47865, - "total_output_tokens": 49155, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-nospec_c64.json" - }, - { - "service": "vllm-dspark-dspark-st3", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 3, - "concurrency": 1, - "request_throughput": 1.033805786989743, - "output_throughput": 254.38859000456605, - "total_input_tokens": 47865, - "total_output_tokens": 49214, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st3_c1.json" - }, - { - "service": "vllm-dspark-dspark-st3", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 3, - "concurrency": 16, - "request_throughput": 8.237095278328889, - "output_throughput": 1992.1826785402332, - "total_input_tokens": 47865, - "total_output_tokens": 48371, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st3_c16.json" - }, - { - "service": "vllm-dspark-dspark-st3", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 3, - "concurrency": 64, - "request_throughput": 14.530514004704537, - "output_throughput": 3507.8113858757224, - "total_input_tokens": 47865, - "total_output_tokens": 48282, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st3_c64.json" - }, - { - "service": "vllm-dspark-dspark-st7", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 7, - "concurrency": 1, - "request_throughput": 1.0285012524448527, - "output_throughput": 251.58169136053544, - "total_input_tokens": 47865, - "total_output_tokens": 48922, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st7_c1.json" - }, - { - "service": "vllm-dspark-dspark-st7", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 7, - "concurrency": 16, - "request_throughput": 7.463416900080192, - "output_throughput": 1772.3002941775428, - "total_input_tokens": 47865, - "total_output_tokens": 47493, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st7_c16.json" - }, - { - "service": "vllm-dspark-dspark-st7", - "engine": "vllm-dspark", - "spec_method": "dspark", - "spec_tokens": 7, - "concurrency": 64, - "request_throughput": 9.128647309323574, - "output_throughput": 2218.5807988214547, - "total_input_tokens": 47865, - "total_output_tokens": 48607, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st7_c64.json" - }, - { - "service": "vllm-main-nospec", - "engine": "vllm", - "spec_method": null, - "spec_tokens": null, - "concurrency": 1, - "request_throughput": 0.5806562443589017, - "output_throughput": 138.2571550630763, - "total_input_tokens": 47865, - "total_output_tokens": 47621, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-main-nospec_c1.json" - }, - { - "service": "vllm-main-nospec", - "engine": "vllm", - "spec_method": null, - "spec_tokens": null, - "concurrency": 16, - "request_throughput": 5.052303923305438, - "output_throughput": 1180.976042072646, - "total_input_tokens": 47865, - "total_output_tokens": 46750, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-main-nospec_c16.json" - }, - { - "service": "vllm-main-nospec", - "engine": "vllm", - "spec_method": null, - "spec_tokens": null, - "concurrency": 64, - "request_throughput": 6.851644652089382, - "output_throughput": 1641.8938662034388, - "total_input_tokens": 47865, - "total_output_tokens": 47927, - "duration_s": null, - "result_file": "/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-main-nospec_c64.json" - } -] \ No newline at end of file diff --git a/experiments/legacy_bench_results/eagle_grid/dspark_vs_eagle_report.md b/experiments/legacy_bench_results/eagle_grid/dspark_vs_eagle_report.md deleted file mode 100644 index c4097e2..0000000 --- a/experiments/legacy_bench_results/eagle_grid/dspark_vs_eagle_report.md +++ /dev/null @@ -1,96 +0,0 @@ -# DSpark vs SGLang EAGLE 投机解码对比报告 - -- DSpark 结果:`/data/user1/yy/bench_results/dspark_st_comparison_20260707-150649` -- EAGLE 结果:`/data/user1/yy/bench_results/eagle_grid/focused` -- DSpark 模型:`/data/models/DeepSeek-V4-Flash-DSpark` -- EAGLE 模型:`/data/models/DeepSeek-V4-Flash` -- DSpark 后端:vllm-dspark (TP=8, FP8 KV cache, spec-method=dspark) -- EAGLE 后端:SGLang (TP=8, FP8 KV cache, speculative-algorithm=EAGLE, num_steps=3, topk=1, draft_tokens=4) -- 压测客户端:`sglang.bench_serving` -- Warmup:100 条 - -## 核心指标对比 - -| Scenario | Concurrency | Method | Total tok/s | Out tok/s | Req/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P99 TPOT(ms) | -|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| -| chat_short | 1 | DSpark(st=5) | 1094.51 | 397.37 | 2.73 | 365.29 | 743.89 | 782.98 | 64.42 | 174.36 | 2.06 | 3.40 | -| chat_short | 1 | EAGLE | 272.82 | 99.05 | 0.68 | 1467.67 | 3775.96 | 5175.09 | 223.86 | 405.20 | 7.47 | 19.74 | -| chat_short | 8 | DSpark(st=3) | 4705.85 | 1525.06 | 11.55 | 672.66 | 1269.82 | 1552.65 | 76.20 | 221.18 | 4.67 | 8.43 | -| chat_short | 8 | EAGLE | 1059.99 | 343.52 | 2.60 | 3043.16 | 7143.57 | 8345.08 | 245.91 | 528.86 | 21.41 | 36.02 | -| chat_short | 16 | DSpark(st=5) | 6282.94 | 2171.58 | 15.59 | 997.37 | 2147.86 | 2496.56 | 107.56 | 240.46 | 7.09 | 21.93 | -| chat_short | 16 | EAGLE | 1506.42 | 520.67 | 3.74 | 4240.11 | 8031.57 | 10192.48 | 236.19 | 438.56 | 29.12 | 54.02 | -| chat_short | 32 | DSpark(st=3) | 8275.55 | 2754.96 | 20.79 | 1497.40 | 2944.48 | 3658.90 | 144.47 | 453.29 | 10.72 | 23.97 | -| chat_short | 32 | EAGLE | 2461.23 | 819.35 | 6.18 | 4731.11 | 9926.81 | 11388.85 | 243.84 | 571.89 | 34.22 | 54.92 | -| chat_short | 64 | DSpark(st=5) | 9586.66 | 3191.43 | 24.09 | 2555.12 | 5387.59 | 6559.69 | 274.71 | 610.40 | 18.47 | 42.08 | -| chat_short | 64 | EAGLE | 4474.88 | 1489.70 | 11.24 | 5239.80 | 10296.02 | 11922.06 | 268.27 | 452.80 | 38.17 | 61.24 | -| chat_standard | 64 | DSpark(st=3) | 15680.46 | 3288.58 | 24.70 | 2483.46 | 4798.63 | 5693.88 | 271.41 | 947.15 | 17.93 | 46.05 | -| chat_standard | 64 | EAGLE | 6746.24 | 1414.85 | 10.63 | 5637.63 | 10693.92 | 12907.15 | 296.86 | 597.18 | 41.12 | 69.08 | -| decode_heavy | 1 | DSpark(st=5) | 673.37 | 531.76 | 0.55 | 1802.00 | 2957.04 | 3676.66 | 59.06 | 114.78 | 1.92 | 3.03 | -| decode_heavy | 1 | EAGLE | 261.35 | 206.39 | 0.22 | 4643.62 | 10452.02 | 18810.32 | 162.91 | 314.71 | 4.84 | 11.69 | -| decode_heavy | 32 | DSpark(st=5) | 7203.28 | 5737.73 | 5.52 | 5595.02 | 10412.11 | 12522.00 | 107.12 | 331.08 | 5.43 | 9.15 | -| decode_heavy | 32 | EAGLE | 2981.50 | 2374.90 | 2.28 | 13707.03 | 27159.67 | 30638.81 | 192.78 | 442.78 | 13.23 | 19.26 | -| generation_standard | 64 | DSpark(st=3) | 13363.77 | 6705.88 | 13.27 | 4542.22 | 8273.38 | 9355.10 | 190.34 | 813.58 | 9.00 | 15.36 | -| generation_standard | 64 | EAGLE | 5609.33 | 2814.73 | 5.57 | 11057.53 | 22577.31 | 26713.41 | 275.48 | 944.52 | 21.88 | 34.80 | -| long_context_probe | 1 | DSpark(st=5) | 11835.70 | 392.31 | 1.44 | 693.30 | 1172.84 | 1404.17 | 267.61 | 561.65 | 1.53 | 1.88 | -| long_context_probe | 1 | EAGLE | 5556.95 | 184.19 | 0.68 | 1477.16 | 2792.68 | 3836.17 | 238.26 | 618.74 | 4.43 | 8.62 | -| long_context_probe | 4 | DSpark(st=5) | 39893.35 | 1322.32 | 4.85 | 806.55 | 1390.87 | 1500.22 | 143.42 | 236.41 | 2.41 | 3.54 | -| long_context_probe | 4 | EAGLE | 6660.56 | 220.77 | 0.81 | 4777.51 | 9442.45 | 10071.75 | 433.60 | 1230.20 | 14.85 | 21.65 | -| long_context_probe | 8 | DSpark(st=5) | 29361.32 | 900.89 | 3.56 | 2223.29 | 4693.10 | 5345.72 | 425.03 | 1241.57 | 7.08 | 19.83 | -| long_context_probe | 8 | EAGLE | 9890.47 | 303.47 | 1.20 | 6232.13 | 16324.27 | 20329.21 | 606.76 | 2671.06 | 22.79 | 54.38 | -| rag_medium | 1 | DSpark(st=5) | 4075.47 | 496.09 | 1.76 | 566.58 | 968.90 | 1134.15 | 110.34 | 156.35 | 1.64 | 2.34 | -| rag_medium | 1 | EAGLE | 1326.02 | 161.41 | 0.57 | 1742.43 | 3804.17 | 3936.47 | 195.07 | 337.55 | 5.83 | 12.84 | -| rag_medium | 8 | DSpark(st=5) | 15838.39 | 1692.13 | 6.69 | 1175.26 | 2263.59 | 3106.42 | 154.24 | 352.19 | 4.14 | 8.36 | -| rag_medium | 8 | EAGLE | 3676.32 | 392.77 | 1.55 | 4908.89 | 11659.24 | 12566.81 | 254.65 | 678.44 | 18.53 | 35.36 | -| rag_medium | 32 | DSpark(st=5) | 27420.79 | 3062.92 | 11.91 | 2626.21 | 5182.78 | 6873.72 | 253.09 | 522.80 | 9.17 | 18.55 | -| rag_medium | 32 | EAGLE | 13268.07 | 1482.05 | 5.76 | 5444.98 | 10471.13 | 11990.37 | 337.62 | 2204.45 | 20.09 | 39.98 | -| stress_standard | 64 | DSpark(st=3) | 15225.66 | 3193.19 | 23.99 | 2569.66 | 5006.16 | 5831.81 | 261.98 | 710.57 | 18.02 | 36.01 | -| stress_standard | 64 | EAGLE | 5399.35 | 1132.38 | 8.51 | 6678.22 | 13086.21 | 14811.42 | 314.38 | 584.25 | 48.19 | 81.06 | -| stress_standard | 96 | DSpark(st=3) | 24069.73 | 4756.72 | 37.72 | 2433.73 | 4827.07 | 6001.29 | 272.61 | 749.99 | 18.04 | 38.52 | -| stress_standard | 96 | EAGLE | 8833.26 | 1745.65 | 13.84 | 6792.34 | 14000.22 | 16722.55 | 360.14 | 959.71 | 52.90 | 96.70 | -| stress_standard | 128 | DSpark(st=3) | 29828.36 | 5942.48 | 47.45 | 2581.42 | 5162.16 | 6222.01 | 298.69 | 970.61 | 19.09 | 40.93 | -| stress_standard | 128 | EAGLE | 13189.80 | 2627.70 | 20.98 | 5960.86 | 12256.06 | 14219.75 | 416.12 | 2153.71 | 47.20 | 81.35 | - -## 吞吐 Winner 统计(按 Total tok/s) - -| Scenario | Concurrency | Winner | DSpark Total tok/s | EAGLE Total tok/s | 提升 | -|---|---:|---:|---:|---:|---:| -| chat_short | 1 | DSpark(st=5) | 1094.51 | 272.82 | 301.18% | -| chat_short | 8 | DSpark(st=3) | 4705.85 | 1059.99 | 343.95% | -| chat_short | 16 | DSpark(st=5) | 6282.94 | 1506.42 | 317.08% | -| chat_short | 32 | DSpark(st=3) | 8275.55 | 2461.23 | 236.24% | -| chat_short | 64 | DSpark(st=5) | 9586.66 | 4474.88 | 114.23% | -| chat_standard | 64 | DSpark(st=3) | 15680.46 | 6746.24 | 132.43% | -| decode_heavy | 1 | DSpark(st=5) | 673.37 | 261.35 | 157.65% | -| decode_heavy | 32 | DSpark(st=5) | 7203.28 | 2981.50 | 141.60% | -| generation_standard | 64 | DSpark(st=3) | 13363.77 | 5609.33 | 138.24% | -| long_context_probe | 1 | DSpark(st=5) | 11835.70 | 5556.95 | 112.99% | -| long_context_probe | 4 | DSpark(st=5) | 39893.35 | 6660.56 | 498.95% | -| long_context_probe | 8 | DSpark(st=5) | 29361.32 | 9890.47 | 196.86% | -| rag_medium | 1 | DSpark(st=5) | 4075.47 | 1326.02 | 207.35% | -| rag_medium | 8 | DSpark(st=5) | 15838.39 | 3676.32 | 330.82% | -| rag_medium | 32 | DSpark(st=5) | 27420.79 | 13268.07 | 106.67% | -| stress_standard | 64 | DSpark(st=3) | 15225.66 | 5399.35 | 181.99% | -| stress_standard | 96 | DSpark(st=3) | 24069.73 | 8833.26 | 172.49% | -| stress_standard | 128 | DSpark(st=3) | 29828.36 | 13189.80 | 126.15% | - -## 关键发现 - -- 对比点数:18 -- EAGLE 胜出:0 个 -- DSpark 胜出:18 个 - -**结论:在相同负载下,vllm-dspark 的综合吞吐优于 SGLang EAGLE。** - -### 延迟与稳定性观察 - -- 平均 Mean E2E 比值(EAGLE / DSpark):3.09 -- 平均 Mean TPOT 比值(EAGLE / DSpark):3.21 -- 平均 Mean TTFT 比值(EAGLE / DSpark):1.81 - -> 比值 < 1 表示 EAGLE 更快;> 1 表示 DSpark 更快。 - -## 优化建议 - -1. 如果以吞吐为首要目标,当前配置下 **vllm-dspark 更优**,建议继续使用 `--spec-tokens` 并根据并发选择 3(高并发)或 5(低并发/长上下文)。 -2. SGLang EAGLE 本次表现不佳,平均延迟和 TPOT 均显著高于 DSpark;如需进一步评估 EAGLE,可尝试调整 `--speculative-num-steps`、`--speculative-eagle-topk`、`--speculative-num-draft-tokens` 或更换 MOE runner backend,并确认是否已完成 `sglang.compile_deep_gemm` 预编译。 -3. 注意两套后端的实现差异(CUDA graph、KV cache 管理、调度器、draft 模型架构)会显著影响不同并发和输入长度下的表现,建议按实际业务负载做最终选型。 diff --git a/experiments/legacy_bench_results/eagle_grid/report.md b/experiments/legacy_bench_results/eagle_grid/report.md deleted file mode 100644 index c5ab5d7..0000000 --- a/experiments/legacy_bench_results/eagle_grid/report.md +++ /dev/null @@ -1,6 +0,0 @@ -# vllm-dspark Benchmark Grid Report - -- Result root: `/data/user1/yy/bench_results/eagle_grid` -- Model: `/data/models/DeepSeek-V4-Flash-DSpark` -- Backend: vllm-dspark (TP=8, FP8 KV cache, spec-method=dspark, spec-tokens=5) -- Benchmark client: `sglang.bench_serving --backend vllm` diff --git a/experiments/legacy_bench_results/sglang_8card_max_throughput_20260705_030839/summary.json b/experiments/legacy_bench_results/sglang_8card_max_throughput_20260705_030839/summary.json deleted file mode 100644 index 001f511..0000000 --- a/experiments/legacy_bench_results/sglang_8card_max_throughput_20260705_030839/summary.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "name": "sharegpt_concurrency_128", - "args": "--max-concurrency 128", - "duration_s": 155.26, - "request_throughput": 12.88, - "input_token_throughput": 3710.5, - "output_token_throughput": 2690.48, - "total_token_throughput": 6400.98, - "mean_ttft_ms": 410.55, - "mean_tpot_ms": 48.45, - "mean_e2e_latency_ms": 9696.87, - "accept_length": 2.6, - "output_file": "bench_results/sglang_8card_max_throughput_20260705_030839/sharegpt_concurrency_128.json" - }, - { - "name": "sharegpt_concurrency_256", - "args": "--max-concurrency 256", - "duration_s": 90.65, - "request_throughput": 22.06, - "input_token_throughput": 6355.48, - "output_token_throughput": 4608.35, - "total_token_throughput": 10963.84, - "mean_ttft_ms": 509.73, - "mean_tpot_ms": 62.71, - "mean_e2e_latency_ms": 10869.76, - "accept_length": 2.6, - "output_file": "bench_results/sglang_8card_max_throughput_20260705_030839/sharegpt_concurrency_256.json" - }, - { - "name": "sharegpt_concurrency_512", - "args": "--max-concurrency 512", - "duration_s": 71.39, - "request_throughput": 28.02, - "input_token_throughput": 8070.04, - "output_token_throughput": 5851.58, - "total_token_throughput": 13921.61, - "mean_ttft_ms": 1343.73, - "mean_tpot_ms": 121.2, - "mean_e2e_latency_ms": 16176.14, - "accept_length": 2.6, - "output_file": "bench_results/sglang_8card_max_throughput_20260705_030839/sharegpt_concurrency_512.json" - }, - { - "name": "sharegpt_concurrency_1024", - "args": "--max-concurrency 1024", - "duration_s": 68.21, - "request_throughput": 29.32, - "input_token_throughput": 8445.53, - "output_token_throughput": 6123.84, - "total_token_throughput": 14569.37, - "mean_ttft_ms": 13265.46, - "mean_tpot_ms": 105.36, - "mean_e2e_latency_ms": 27566.99, - "accept_length": 2.6, - "output_file": "bench_results/sglang_8card_max_throughput_20260705_030839/sharegpt_concurrency_1024.json" - } -] \ No newline at end of file diff --git a/experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/length_distribution.png b/experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/length_distribution.png deleted file mode 100644 index a511adb8662d66ba2acbb8a6c81abc4970389b23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150830 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

tFB|cUJQ0AQU{oQdgRHe$R2}@3~;>JpQS2 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


`h+I`zFNr$BwJ4X-{;Q_yZyMnY7O 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
Eo-ys4z1uBC^D0k3o zg3-}Yf^zH7O-c29!s+nc&1SO5E3nVnK^X_+K_E0WbkjfKRN^KZ`5`K?6xI-513oGrXvT;1aS({;$iTr< z&p6E-M5IOcMV0?{RF={s#`2BHuli63yRVYEf&lABY%yK?3gMnibfkKuzgkNwKZXh) zM2bu_{KA{FYja_s_!&5ncG7k&SrcOx&hm(SE3;6}r0#Os6tq)~FTEfmhht65b?oKl zuC5J{6>IT7Pz4Y$(p)CLX6spAh}!Tfz7}YqPy%!2&rkhaDepQ=3!OBY0XO3p&3vj0 z3-{tCD6~T-AXrV>p9bOSEKO_%mcIwFp1%#_Pp`u}mjwd8t1WkHmSm|C3$n*8=MF4; z{yl1kN7QqVbB*n9|3p@&3cruA1GxUDA1=4-ruorRolC*yw!Zga=_BwT;@c{KjUkmN z!c-eFwGhVwcIzw`=y1sZ(?Ypw5!4UKZYo;4y57N_AeOqCvsKPaxu9JRLS>JwvMdg!{TfKc@ai%2^ z!7o|Sg~ImAMH!m8EmeLuZyHaO)>SNeG4u@l&-Az3h8q$*VdS_J*QZ!zbj&#%Ym>l> zAVM83jSvpo_+9={eRhYd7R7AUbx7)%*Ms}HvSRk zT-l5y-4^~BzPh^S2RIl%t+4w_rLcIt0{N!-sb&1a!o@Aah`CUI+z;*mcD?OUdGIZ0 zoC(eUCJ321zZbr7uOD*y_Ahd~56j=%<5OG{QurV$5P3pw$4ed_p5TtE0+~TptuerE2(_9)Ocu=|U0_^f!xr>_bx>c)kGBe?Y$eiKd z3l=Q6Au{4yWsdF_Mk&hU*U|805nF~r52KWjl`ES5h*MH|0vdW)5-x&X2(C>;lou06 zCT7r`xK{AvYZ-Llck($SF@l{T?i6#+VZ~rOcLO<0ZJn9YqUHF!(u2q^^1{q-hpQ&It;2R_{IGIpDFI+FMvry zaC3v7I&Pd$>7bB8JJUsz!d5iYvxu&sf;*<_N?{MP=Ce^Mf$YI#YK{~2GvZtA5T>o# zId9ATiSch)QUfxC<3d=KYF~#Y;7dTN?;tg1QOFBZMTIc*3#q|s62ysIu+B|680Dsic%W44mP^EgrD0QO(xUi? z@&zncs!Lr|Uoybkw{Dr8uV`VZctSIO5I*BiIoc&rRr-u*EU7{8YJ*iX>b-D6_xKqI zU#sv`vGgY3%JAO9`Z39MuOEnak53!QK0UIYrtl6RTrx$hXws0Gfq4A1t~~G=iuDmv z(uKI@&%Z~J80Om3+-+2#0WU-Z1_sL2CDkHQcniRkiaJ2~+c6XP4S2Lv*2AaLmPNTL z7_2)-wb=O!7ls4yBfEoY(_0emZ7W@kQsX{w%|dmYyEX1q#zqxwK($s zOw^x2oAC2OI^sM6#>ZU7?y#+{T33QZ_0I7XP+=nYC5ox_xKR(+w8InqU`m4o%UMHt ztJYw&>Ya09C?ldN1HE*fVkinYw9Di9m)1`pb_Xv3r&Q)?&$}l%KhK-zzU<1p`sG_;3KIrsy1(r(C9J#tT2 zEoo|M(wD=EfKKst53k+gKD5uIrgQqPmXs|2nVnsn{H}N?qTa=MKD;I0Hd~tje-bXa z4AB!WB3d|WEtNDn9xU%(ueZ;>b%GEfYVo}zC_XRa6!Yme!yHrKVTE=119i&D;+?APoFw3K?Fa+2TjUciu#G(EG zjwa}?j1g%Ql+_0igqr}Oqgb`@D_WUJBDEX^J;LVjY?q*u3aGyvxlMpRYDJMG28~;d zSLy3{1!pE{Ub`7beNfH*!8HAO9O(PKY zJOW>Xot-_k4SGZu!X`yDU{{KfnJB>Us>7HNu`@nrJ0F=+08(v17MV{}Y9_t5%cVsk zF;KGxFY4wpcJ@19ha9HY=Wscyy37N0UZQvfc3G+au;$E50?Ddr9cEN#sJG{ z8Ykh9v`cYtke`+V&?fbp_$dX=<;RgwV%+!m102ml^axGi$}DUFCwr5u9NRX&14 已超出稳定运行区间。 -5. **RPS=8 是稳定高吞吐边界**:total tok/s = 3875,E2E 延迟 4.54s,TTFT 仅 217ms,体验与吞吐兼顾。 -6. **EAGLE 投机采样稳定生效**:Accept Length 稳定在 2.60–2.62,有效降低了 decode 步数。 - -## 场景建议 - -| 场景 | 推荐配置 | 理由 | -|------|----------|------| -| 低延迟在线 API | `--max-concurrency 8` | TTFT 147ms,TPOT 8.4ms,E2E 1.79s | -| 平衡型在线服务 | `--max-concurrency 16` 或 `--request-rate 4` | 吞吐约 3k tok/s,E2E 2.6–1.7s | -| 高吞吐在线服务 | `--max-concurrency 32` 或 `--request-rate 8` | 吞吐 3.7–3.9k tok/s,E2E 4.3–4.5s | -| 最大吞吐/离线批处理 | `--max-concurrency 128` | 吞吐 7.1k tok/s,E2E 8.6s | -| 避免使用 | `--request-rate 16` | 系统过载,E2E 15s,TPOT 87ms | - -## 原始数据 - -- 汇总 JSON:`bench_results/sglang_8card_systematic_20260704_120819/summary.json` -- 每个测试点的详细结果与日志:`bench_results/sglang_8card_systematic_20260704_120819/` - -## 附录 A:Benchmark 参数说明 - -本次所有测试均使用 `sglang.bench_serving` 工具,参数统一如下: - -```bash -envs/sglang/bin/python -m sglang.bench_serving \ - --backend sglang \ - --host 127.0.0.1 \ - --port 30000 \ - --dataset-name sharegpt \ - --dataset-path /data/user1/yy/datasets/ShareGPT_V4.3_unfiltered_cleaned_split.json \ - --num-prompts 2000 \ - --model /data/models/DeepSeek-V4-Flash \ - --seed 42 -``` - -两个测试维度的区别仅在于最后一条参数: - -- **并发扫描**:`--max-concurrency {1,8,16,32,64,128}` - - 客户端同时保持 N 个未完成的请求,服务器队列按需堆积。 - - 适合模拟“系统能扛多少并发”的容量测试。 - -- **请求速率扫描**:`--request-rate {1,2,4,8,16}` - - 客户端按泊松分布以固定 RPS 发送请求。 - - 适合模拟真实线上按流量到达的场景。 - -### 指标定义 - -| 指标 | 含义 | -|------|------| -| **req/s** | 每秒完成的请求数 | -| **input tok/s** | 每秒处理的输入 token 数 | -| **output tok/s** | 每秒生成的输出 token 数 | -| **total tok/s** | input tok/s + output tok/s | -| **TTFT** | Time To First Token,首 token 延迟 | -| **TPOT** | Time Per Output Token,除首 token 外平均每个输出 token 的间隔 | -| **E2E Latency** | 端到端请求完成时间 | -| **Accept Length** | EAGLE 投机采样平均每次接受的 draft token 数 | - ---- - -## 附录 B:Token 成本计算 - -### 假设 - -- GPU 单价:**8.0 元 / 卡 / 小时** -- 本次部署使用 **8× NVIDIA H200** -- 整机每小时成本: - -``` -8 卡 × 8.0 元/卡/h = 64 元/h -``` - -### 公式 - -对于任意一个 benchmark 结果: - -``` -每小时处理 input tokens = input_tok/s × 3600 -每小时生成 output tokens = output_tok/s × 3600 - -每 1M input tokens 成本 = 64 / (input_tok/s × 3600 / 1,000,000) -每 1M output tokens 成本 = 64 / (output_tok/s × 3600 / 1,000,000) -``` - -### 各场景成本 - -| 场景 | input tok/s | output tok/s | 1M input cost | 1M output cost | -|------|-------------|--------------|---------------|----------------| -| c=1 | 335.83 | 243.51 | 52.94 元 | 73.01 元 | -| c=8 | 1,283.33 | 930.54 | 13.85 元 | 19.10 元 | -| c=16 | 1,736.31 | 1,259.00 | 10.24 元 | 14.12 元 | -| c=32 | 2,151.87 | 1,560.32 | 8.26 元 | 11.39 元 | -| c=64 | 2,775.39 | 2,012.43 | 6.41 元 | 8.83 元 | -| c=128 | 4,132.36 | 2,996.37 | 4.30 元 | 5.93 元 | -| rps=1 | 286.41 | 207.67 | 62.07 元 | 85.61 元 | -| rps=2 | 571.97 | 414.74 | 31.08 元 | 42.86 元 | -| rps=4 | 1,139.69 | 826.39 | 15.60 元 | 21.51 元 | -| rps=8 | 2,246.34 | 1,628.82 | 7.91 元 | 10.91 元 | -| rps=16 | 4,176.94 | 3,028.70 | 4.26 元 | 5.87 元 | - -### 对外定价建议 - -裸 GPU 成本不等于对外售价。建议根据 SLA 和毛利率加价: - -| 推荐场景 | 裸成本(input/output) | 说明 | -|----------|------------------------|------| -| 低延迟在线(c=8) | 13.85 元 / 19.10 元 per 1M | TTFT 147ms,TPOT 8.4ms,适合实时聊天 | -| 平衡型在线(c=16) | 10.24 元 / 14.12 元 per 1M | 吞吐与延迟兼顾,主流 API 可参考此档位 | -| 高吞吐在线(c=32 / rps=8) | 8.09 元 / 11.15 元 per 1M | 吞吐高、延迟可接受,适合批量在线服务 | -| 最大吞吐/离线(c=128) | 4.30 元 / 5.93 元 per 1M | 延迟 8.6s,仅适合离线批处理 | - -> 注意:`rps=16` 虽已接近最大吞吐,但系统已明显过载(E2E 15s),不建议按此成本对外定价。 - ---- - -## 附录 C:最大吞吐压测(优化服务参数) - -为了探索成本下限,重新部署了服务并调优了参数: - -```bash -envs/sglang/bin/sglang serve \ - --trust-remote-code \ - --model-path /data/models/DeepSeek-V4-Flash \ - --tp 8 \ - --moe-runner-backend marlin \ - --speculative-algorithm EAGLE \ - --speculative-num-steps 3 \ - --speculative-eagle-topk 1 \ - --speculative-num-draft-tokens 4 \ - --max-running-requests 512 \ - --chunked-prefill-size 16384 \ - --host 0.0.0.0 \ - --port 30000 -``` - -与附录 A/B 的测试相比,主要变化: -- `--max-running-requests` 从默认 256 提升到 **512** -- `--chunked-prefill-size` 从默认 8192 提升到 **16384** - -### 超大并发扫描结果 - -| 并发 | 耗时(s) | req/s | input tok/s | output tok/s | total tok/s | Mean TTFT | Mean TPOT | Mean E2E(ms) | -|------|---------|-------|-------------|--------------|-------------|-----------|-----------|--------------| -| 128 | 155.26 | 12.88 | 3,710.50 | 2,690.48 | 6,400.98 | 410.55 ms | 48.45 ms | 9,696.87 | -| 256 | 90.65 | 22.06 | 6,355.48 | 4,608.35 | 10,963.84 | 509.73 ms | 62.71 ms | 10,869.76 | -| 512 | 71.39 | 28.02 | 8,070.04 | 5,851.58 | 13,921.61 | 1,343.73 ms| 121.20 ms | 16,176.14 | -| **1024** | **68.21** | **29.32** | **8,445.53** | **6,123.84** | **14,569.37** | **13,265.46 ms** | **105.36 ms** | **27,566.99** | - -### 关键发现 - -1. **高并发下吞吐显著提升**:优化参数后,c=256 及以上远超原配置的最高值(7,129 tok/s);c=1024 达到 **14,569 tok/s**,约为原配置峰值的两倍。 -2. **c=1024 达到最高吞吐**:total **14,569 tok/s**,output **6,124 tok/s**,但 TTFT 13.3s,系统已严重过载。 -3. **c=512 是实际可用上限**:total **13,922 tok/s**,output **5,852 tok/s**,虽然 TTFT 1.3s、E2E 16s,但吞吐接近峰值,适合离线批处理。 -4. **c=256 是高压在线的甜点**:total **10,964 tok/s**,TTFT 510ms,E2E 10.9s,如果业务能容忍 10 秒级响应,这是性价比很高的点。 - ---- - -## 附录 D:最低 Token 成本(基于最大吞吐) - -仍按 **8.0 元/卡时、8 卡整机 64 元/小时** 计算。 - -### 公式 - -``` -每 1M input tokens 成本 = 64 / (input_tok/s × 3600 / 1,000,000) -每 1M output tokens 成本 = 64 / (output_tok/s × 3600 / 1,000,000) -``` - -### 最大吞吐场景成本 - -| 场景 | input tok/s | output tok/s | 1M input cost | 1M output cost | -|------|-------------|--------------|---------------|----------------| -| c=128 | 3,710.50 | 2,690.48 | 4.79 元 | 6.61 元 | -| c=256 | 6,355.48 | 4,608.35 | 2.80 元 | 3.86 元 | -| c=512 | 8,070.04 | 5,851.58 | 2.20 元 | 3.04 元 | -| **c=1024** | **8,445.53** | **6,123.84** | **2.10 元** | **2.90 元** | - -### 结论 - -在当前硬件和优化参数下: - -- **理论最低 input 成本**:约 **2.10 元 / 1M input tokens**(c=1024) -- **理论最低 output 成本**:约 **2.90 元 / 1M output tokens**(c=1024) -- **实际可用最低成本**:c=512 时 **2.20 元 / 1M input**、**3.04 元 / 1M output**,此时吞吐已达峰值 95% 以上,且延迟比 c=1024 可控得多。 - -> 注意:c=1024 的 TTFT 超过 13 秒,仅适合完全不在意延迟的离线批处理任务;对外 API 服务不建议按此成本定价。 - ---- - -## 附录 E:测试数据长度分布 - -本次所有 benchmark 使用的是 `ShareGPT_V4.3_unfiltered_cleaned_split.json` 数据集。`sglang.bench_serving` 在 `--num-prompts 2000` 且 `--seed 42` 的条件下,从数据集中采样出 **2000 条真实对话请求**作为测试负载。 - -### 长度统计 - -| 指标 | Input Tokens | Output Tokens | -|------|--------------|---------------| -| 请求数 | 2,000 | 2,000 | -| 平均值 | 288.05 | 208.86 | -| 标准差 | 420.07 | 217.60 | -| 最小值 | 2 | 2 | -| P50 | 125 | 150 | -| P90 | 723 | 516 | -| P95 | 841 | 667 | -| P99 | 2,190 | 811 | -| 最大值 | 3,996 | 1,655 | -| **总量** | **576,098** | **417,728** | - -### 分布特点 - -1. **输入长度呈现典型长尾分布**:P50 仅 125 tokens,但 P99 达到 2,190 tokens,最大值 3,996 tokens。说明 ShareGPT 中既有大量短 prompt,也有少量长上下文对话。 -2. **输出长度相对集中**:P50 150 tokens,P90 516 tokens,P99 811 tokens,最大值 1,655 tokens。大部分回复属于中等长度。 -3. **输出/输入比约为 0.73**:平均每条请求输出 209 tokens、输入 288 tokens,整体负载偏 decode 侧。 -4. **与原始 ShareGPT 全集的差异**:原始全集输出长度均值约 1,122 tokens,而 bench 采样后的输出均值仅 209 tokens。这是因为 `sglang.bench_serving` 在处理 sharegpt 数据集时会根据内置规则对输出做截断/采样,以更贴近典型在线 serving 负载。 - -### 长度分布图 - -![长度分布](length_distribution.png) - -> 图表文件:`bench_results/sglang_8card_systematic_20260704_120819/length_distribution.png` diff --git a/experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/summary.json b/experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/summary.json deleted file mode 100644 index af4117a..0000000 --- a/experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/summary.json +++ /dev/null @@ -1,156 +0,0 @@ -[ - { - "name": "sharegpt_concurrency_1", - "args": "--max-concurrency 1", - "duration_s": 1715.45, - "request_throughput": 1.17, - "input_token_throughput": 335.83, - "output_token_throughput": 243.51, - "total_token_throughput": 579.34, - "mean_ttft_ms": 128.09, - "mean_tpot_ms": 4.0, - "mean_e2e_latency_ms": 856.77, - "accept_length": 2.62, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_concurrency_1.json" - }, - { - "name": "sharegpt_concurrency_8", - "args": "--max-concurrency 8", - "duration_s": 448.91, - "request_throughput": 4.46, - "input_token_throughput": 1283.33, - "output_token_throughput": 930.54, - "total_token_throughput": 2213.87, - "mean_ttft_ms": 146.76, - "mean_tpot_ms": 8.41, - "mean_e2e_latency_ms": 1790.65, - "accept_length": 2.61, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_concurrency_8.json" - }, - { - "name": "sharegpt_concurrency_16", - "args": "--max-concurrency 16", - "duration_s": 331.79, - "request_throughput": 6.03, - "input_token_throughput": 1736.31, - "output_token_throughput": 1259.0, - "total_token_throughput": 2995.3, - "mean_ttft_ms": 168.95, - "mean_tpot_ms": 12.67, - "mean_e2e_latency_ms": 2642.1, - "accept_length": 2.61, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_concurrency_16.json" - }, - { - "name": "sharegpt_concurrency_32", - "args": "--max-concurrency 32", - "duration_s": 267.72, - "request_throughput": 7.47, - "input_token_throughput": 2151.87, - "output_token_throughput": 1560.32, - "total_token_throughput": 3712.19, - "mean_ttft_ms": 206.24, - "mean_tpot_ms": 20.72, - "mean_e2e_latency_ms": 4249.71, - "accept_length": 2.61, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_concurrency_32.json" - }, - { - "name": "sharegpt_concurrency_64", - "args": "--max-concurrency 64", - "duration_s": 207.57, - "request_throughput": 9.64, - "input_token_throughput": 2775.39, - "output_token_throughput": 2012.43, - "total_token_throughput": 4787.82, - "mean_ttft_ms": 248.41, - "mean_tpot_ms": 32.61, - "mean_e2e_latency_ms": 6557.39, - "accept_length": 2.61, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_concurrency_64.json" - }, - { - "name": "sharegpt_concurrency_128", - "args": "--max-concurrency 128", - "duration_s": 139.41, - "request_throughput": 14.35, - "input_token_throughput": 4132.36, - "output_token_throughput": 2996.37, - "total_token_throughput": 7128.73, - "mean_ttft_ms": 330.4, - "mean_tpot_ms": 46.15, - "mean_e2e_latency_ms": 8596.12, - "accept_length": 2.61, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_concurrency_128.json" - }, - { - "name": "sharegpt_rps_1", - "args": "--request-rate 1", - "duration_s": 2011.46, - "request_throughput": 0.99, - "input_token_throughput": 286.41, - "output_token_throughput": 207.67, - "total_token_throughput": 494.08, - "mean_ttft_ms": 146.87, - "mean_tpot_ms": 4.91, - "mean_e2e_latency_ms": 1007.62, - "accept_length": 2.61, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_rps_1.json" - }, - { - "name": "sharegpt_rps_2", - "args": "--request-rate 2", - "duration_s": 1007.21, - "request_throughput": 1.99, - "input_token_throughput": 571.97, - "output_token_throughput": 414.74, - "total_token_throughput": 986.71, - "mean_ttft_ms": 160.63, - "mean_tpot_ms": 5.99, - "mean_e2e_latency_ms": 1205.24, - "accept_length": 2.61, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_rps_2.json" - }, - { - "name": "sharegpt_rps_4", - "args": "--request-rate 4", - "duration_s": 505.49, - "request_throughput": 3.96, - "input_token_throughput": 1139.69, - "output_token_throughput": 826.39, - "total_token_throughput": 1966.08, - "mean_ttft_ms": 174.67, - "mean_tpot_ms": 9.02, - "mean_e2e_latency_ms": 1743.4, - "accept_length": 2.6, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_rps_4.json" - }, - { - "name": "sharegpt_rps_8", - "args": "--request-rate 8", - "duration_s": 256.46, - "request_throughput": 7.8, - "input_token_throughput": 2246.34, - "output_token_throughput": 1628.82, - "total_token_throughput": 3875.15, - "mean_ttft_ms": 216.65, - "mean_tpot_ms": 25.35, - "mean_e2e_latency_ms": 4542.64, - "accept_length": 2.6, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_rps_8.json" - }, - { - "name": "sharegpt_rps_16", - "args": "--request-rate 16", - "duration_s": 137.92, - "request_throughput": 14.5, - "input_token_throughput": 4176.94, - "output_token_throughput": 3028.7, - "total_token_throughput": 7205.64, - "mean_ttft_ms": 377.28, - "mean_tpot_ms": 87.16, - "mean_e2e_latency_ms": 15048.09, - "accept_length": 2.61, - "output_file": "bench_results/sglang_8card_systematic_20260704_120819/sharegpt_rps_16.json" - } -] \ No newline at end of file diff --git a/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121046/summary.json b/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121046/summary.json deleted file mode 100644 index 20dfa8b..0000000 --- a/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121046/summary.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "model": "/data/models/Qwen3-4B", - "draft_model": "deepseek-ai/dspark_qwen3_4b_block7", - "url": "http://127.0.0.1:30003/v1/completions", - "max_tokens": 64, - "num_prompts": 20, - "dataset": "/data/user1/yy/datasets/ShareGPT_filtered_chat.json", - "timestamp": "20260705_121046", - "results": [ - { - "concurrency": 1, - "num_prompts": 20, - "duration_s": 0.07108968612737954, - "success_count": 0, - "error": "all requests failed", - "errors": [ - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'", - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'", - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'", - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'", - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'" - ] - }, - { - "concurrency": 4, - "num_prompts": 20, - "duration_s": 0.036050053080543876, - "success_count": 0, - "error": "all requests failed", - "errors": [ - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'", - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'", - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'", - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'", - "404, message='Not Found', url='http://127.0.0.1:30003/v1/completions'" - ] - } - ] -} \ No newline at end of file diff --git a/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121218/summary.json b/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121218/summary.json deleted file mode 100644 index c719bcc..0000000 --- a/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121218/summary.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "model": "/data/models/Qwen3-4B", - "draft_model": "deepseek-ai/dspark_qwen3_4b_block7", - "url": "http://127.0.0.1:30003/v1/completions", - "max_tokens": 64, - "num_prompts": 20, - "dataset": "/data/user1/yy/datasets/ShareGPT_filtered_chat.json", - "timestamp": "20260705_121218", - "results": [ - { - "concurrency": 1, - "num_prompts": 20, - "duration_s": 3.1493232590146363, - "success_count": 20, - "fail_count": 0, - "input_tokens": 6132, - "output_tokens": 456, - "total_tokens": 6588, - "request_throughput": 6.350570695704836, - "input_throughput": 1947.0849753031027, - "output_throughput": 144.79301186207027, - "total_throughput": 2091.877987165173, - "mean_ttft_ms": 55.51949484506622, - "p50_ttft_ms": 27.64087135437876, - "p99_ttft_ms": 295.08487760089326, - "mean_tpot_ms": 4.6249661131817925, - "p50_tpot_ms": 4.634099831345484, - "p99_tpot_ms": 4.827384204164935, - "mean_e2e_ms": 156.8067875574343, - "p50_e2e_ms": 148.02213886287063, - "p99_e2e_ms": 406.6163514624348, - "errors": [] - }, - { - "concurrency": 4, - "num_prompts": 20, - "duration_s": 1.0271000929642469, - "success_count": 20, - "fail_count": 0, - "input_tokens": 6132, - "output_tokens": 450, - "total_tokens": 6582, - "request_throughput": 19.472298889857267, - "input_throughput": 5970.206839630238, - "output_throughput": 438.1267250217885, - "total_throughput": 6408.333564652026, - "mean_ttft_ms": 82.42244796128944, - "p50_ttft_ms": 37.77908312622458, - "p99_ttft_ms": 263.8731326162815, - "mean_tpot_ms": 5.172366561559164, - "p50_tpot_ms": 5.150655882704692, - "p99_tpot_ms": 5.708390201389117, - "mean_e2e_ms": 193.68963547749445, - "p50_e2e_ms": 153.2427944475785, - "p99_e2e_ms": 387.3923780536279, - "errors": [] - } - ] -} \ No newline at end of file diff --git a/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121256/summary.json b/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121256/summary.json deleted file mode 100644 index 61edc71..0000000 --- a/experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121256/summary.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "model": "/data/models/Qwen3-4B", - "draft_model": "deepseek-ai/dspark_qwen3_4b_block7", - "url": "http://127.0.0.1:30003/v1/completions", - "max_tokens": 256, - "num_prompts": 500, - "dataset": "/data/user1/yy/datasets/ShareGPT_filtered_chat.json", - "timestamp": "20260705_121256", - "results": [ - { - "concurrency": 1, - "num_prompts": 500, - "duration_s": 181.22913801996037, - "success_count": 500, - "fail_count": 0, - "input_tokens": 108545, - "output_tokens": 36681, - "total_tokens": 145226, - "request_throughput": 2.7589382450460618, - "input_throughput": 598.9379036170495, - "output_throughput": 202.4012275330692, - "total_throughput": 801.3391311501188, - "mean_ttft_ms": 17.586736707482487, - "p50_ttft_ms": 15.236841514706612, - "p99_ttft_ms": 43.418583339080215, - "mean_tpot_ms": 4.756160026498775, - "p50_tpot_ms": 4.747943201144331, - "p99_tpot_ms": 4.845208972490266, - "mean_e2e_ms": 361.93272305326536, - "p50_e2e_ms": 369.4158981088549, - "p99_e2e_ms": 552.2739849891514, - "errors": [] - }, - { - "concurrency": 4, - "num_prompts": 500, - "duration_s": 49.20666282507591, - "success_count": 500, - "fail_count": 0, - "input_tokens": 108545, - "output_tokens": 36991, - "total_tokens": 145536, - "request_throughput": 10.161225559584139, - "input_throughput": 2205.900456730121, - "output_throughput": 751.7477893491538, - "total_throughput": 2957.6482460792745, - "mean_ttft_ms": 26.531575095374137, - "p50_ttft_ms": 24.292025598697364, - "p99_ttft_ms": 41.29285377450287, - "mean_tpot_ms": 5.007692315964597, - "p50_tpot_ms": 4.998277087638343, - "p99_tpot_ms": 5.130429720718591, - "mean_e2e_ms": 392.14430282171816, - "p50_e2e_ms": 400.28460952453315, - "p99_e2e_ms": 625.0063395127652, - "errors": [] - }, - { - "concurrency": 16, - "num_prompts": 500, - "duration_s": 14.20007478701882, - "success_count": 500, - "fail_count": 0, - "input_tokens": 108545, - "output_tokens": 36529, - "total_tokens": 145074, - "request_throughput": 35.21108215972787, - "input_throughput": 7643.973826055324, - "output_throughput": 2572.451240425399, - "total_throughput": 10216.425066480724, - "mean_ttft_ms": 31.879239567089826, - "p50_ttft_ms": 28.826430439949036, - "p99_ttft_ms": 74.12134333979331, - "mean_tpot_ms": 5.771187238080647, - "p50_tpot_ms": 5.76495784573639, - "p99_tpot_ms": 6.1421910060182805, - "mean_e2e_ms": 447.76513252267614, - "p50_e2e_ms": 459.7155440133065, - "p99_e2e_ms": 669.6232496039011, - "errors": [] - }, - { - "concurrency": 64, - "num_prompts": 500, - "duration_s": 7.960247536888346, - "success_count": 500, - "fail_count": 0, - "input_tokens": 108545, - "output_tokens": 36583, - "total_tokens": 145128, - "request_throughput": 62.81211704572815, - "input_throughput": 13635.882489457124, - "output_throughput": 4595.711355767746, - "total_throughput": 18231.59384522487, - "mean_ttft_ms": 73.04830395104364, - "p50_ttft_ms": 53.465207340195775, - "p99_ttft_ms": 252.97995713772252, - "mean_tpot_ms": 12.42267764661875, - "p50_tpot_ms": 12.61188557741512, - "p99_tpot_ms": 13.063075978620708, - "mean_e2e_ms": 968.1061067078263, - "p50_e2e_ms": 967.5668340642005, - "p99_e2e_ms": 1512.9108975501729, - "errors": [] - }, - { - "concurrency": 128, - "num_prompts": 500, - "duration_s": 7.319521516095847, - "success_count": 500, - "fail_count": 0, - "input_tokens": 108545, - "output_tokens": 36613, - "total_tokens": 145158, - "request_throughput": 68.31047615619204, - "input_throughput": 14829.52126874773, - "output_throughput": 5002.102927013319, - "total_throughput": 19831.62419576105, - "mean_ttft_ms": 145.49234842788428, - "p50_ttft_ms": 90.75760398991406, - "p99_ttft_ms": 423.2492828951217, - "mean_tpot_ms": 22.034148180580335, - "p50_tpot_ms": 23.052740126916806, - "p99_tpot_ms": 23.587740010073134, - "mean_e2e_ms": 1730.9650285840034, - "p50_e2e_ms": 1733.5386699996889, - "p99_e2e_ms": 2722.885905068833, - "errors": [] - } - ] -} \ No newline at end of file diff --git a/scripts/benchmark_dspark_0707/README.md b/scripts/benchmark_dspark_0707/README.md deleted file mode 100644 index 08cbf61..0000000 --- a/scripts/benchmark_dspark_0707/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# DSpark Benchmark Scripts (2026-07-07) - -## Location - -`/data/user1/yy/scripts/benchmark_dspark_0707` - -## Scripts - -| Script | Purpose | Output Location | -|---|---|---| -| `bench_dspark_p1.sh` | Quick benchmark: chat_standard, generation_standard, summarization | `bench_results/dspark_grid_${RUN_ID}/p1_quick/...` | -| `bench_dspark_p2.sh` | Core benchmark: chat_short, rag_medium, long_context_probe | `bench_results/dspark_grid_${RUN_ID}/p2_core/...` | -| `bench_dspark_p3.sh` | Extension benchmark: stress_standard, decode_heavy | `bench_results/dspark_grid_${RUN_ID}/p3_extension/...` | -| `bench_dspark_focused.sh` | Focused sweep used for spec-tokens comparison | `bench_results/dspark_grid_${RUN_ID}/focused/...` or `bench_results/dspark_st_comparison_${RUN_ID}/focused/...` | -| `run_dspark_benchmark_grid.sh` | Orchestrator: starts vllm-dspark server, runs P1/P2/P3, stops server | `bench_results/dspark_grid_${RUN_ID}/` | -| `run_dspark_st_comparison.sh` | Orchestrator: compares `--spec-tokens 3` vs `5` | `bench_results/dspark_st_comparison_${RUN_ID}/` | -| `parse_results.py` | Parses grid logs and generates `report.md` | Writes to `bench_results//report.md` | -| `parse_st_comparison.py` | Parses spec-tokens comparison logs | Writes `comparison_report.md` under result root | -| `parse_eagle_vs_dspark.py` | Merges DSpark st comparison with SGLang EAGLE results | Writes `dspark_vs_eagle_report.md` under EAGLE result root | - -## Common Environment - -- Python env: `/data/user1/yy/envs/vllm-dspark` for server -- Benchmark client: `/data/user1/yy/envs/sglang/bin/python -m sglang.bench_serving --backend vllm` -- Model: `/data/models/DeepSeek-V4-Flash-DSpark` -- Default port: `30004` -- Default warmup: `100` requests - -## Usage - -### Full grid benchmark - -```bash -bash scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh -``` - -Results land in `bench_results/dspark_grid_YYYYMMDD-HHMMSS/`. - -### Spec-tokens comparison - -```bash -bash scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh -``` - -Results land in `bench_results/dspark_st_comparison_YYYYMMDD-HHMMSS/`. - -### Parse existing results - -```bash -# Grid -/data/user1/yy/envs/sglang/bin/python scripts/benchmark_dspark_0707/parse_results.py /data/user1/yy/bench_results/dspark_grid_ - -# Spec-tokens comparison -/data/user1/yy/envs/sglang/bin/python scripts/benchmark_dspark_0707/parse_st_comparison.py /data/user1/yy/bench_results/dspark_st_comparison_ -``` - -## Notes - -- These scripts were originally located in `/data/user1/yy/benchmark_dspark_0707/` and moved here on 2026-07-08. -- `run_dspark_benchmark_grid.sh` and `run_dspark_st_comparison.sh` assume server start scripts are in `scripts/` (one directory up). diff --git a/scripts/benchmark_dspark_0707/bench_dspark_focused.sh b/scripts/benchmark_dspark_0707/bench_dspark_focused.sh deleted file mode 100755 index 0c4a124..0000000 --- a/scripts/benchmark_dspark_0707/bench_dspark_focused.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -# Focused benchmark for spec-tokens comparison. -# Covers short/medium/long input and low/medium/high concurrency. - -BACKEND="vllm" -PORT="${PORT:-30004}" -MODEL="/data/models/DeepSeek-V4-Flash-DSpark" -BENCH_PY="/data/user1/yy/envs/sglang/bin/python" -DATASET="/data/user1/yy/datasets/ShareGPT_filtered_chat.json" -RESULT_ROOT="${RESULT_ROOT:-/data/user1/yy/bench_results/dspark_grid}" -WARMUP_REQUESTS="${WARMUP_REQUESTS:-100}" -PHASE="focused" -RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}" - -num_prompts() { - local c="$1" - if (( c <= 4 )); then - local n=$((c * 8)) - (( n < 32 )) && n=32 - echo "${n}" - elif (( c <= 32 )); then - echo $((c * 16)) - else - echo $((c * 8)) - fi -} - -run_case() { - local scenario="$1" - local input_len="$2" - local output_len="$3" - local concurrencies="$4" - for concurrency in ${concurrencies}; do - local prompts - prompts="$(num_prompts "${concurrency}")" - local out_dir="${RESULT_ROOT}/${PHASE}/${scenario}/${RUN_ID}" - mkdir -p "${out_dir}" - echo "phase=${PHASE} scenario=${scenario} input=${input_len} output=${output_len} concurrency=${concurrency} prompts=${prompts} warmup=${WARMUP_REQUESTS}" - "${BENCH_PY}" -m sglang.bench_serving \ - --backend "${BACKEND}" \ - --host 127.0.0.1 \ - --port "${PORT}" \ - --model "${MODEL}" \ - --dataset-name random \ - --dataset-path "${DATASET}" \ - --random-input-len "${input_len}" \ - --random-output-len "${output_len}" \ - --num-prompts "${prompts}" \ - --max-concurrency "${concurrency}" \ - --warmup-requests "${WARMUP_REQUESTS}" \ - 2>&1 | tee "${out_dir}/c${concurrency}.log" - done -} - -curl --fail --silent --show-error --max-time 5 "http://127.0.0.1:${PORT}/health" >/dev/null -run_case "chat_short" 512 256 "1 8 16 32 64" -run_case "chat_standard" 1000 256 "64" -run_case "generation_standard" 1000 1000 "64" -run_case "rag_medium" 4000 512 "1 8 32" -run_case "long_context_probe" 16000 512 "1 4 8" -run_case "stress_standard" 1000 256 "64 96 128" -run_case "decode_heavy" 512 2000 "1 32" diff --git a/scripts/benchmark_dspark_0707/bench_dspark_p1.sh b/scripts/benchmark_dspark_0707/bench_dspark_p1.sh deleted file mode 100755 index 45505f6..0000000 --- a/scripts/benchmark_dspark_0707/bench_dspark_p1.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -# P1 quick benchmark for vllm-dspark -# Mirrored from benchmark_grid_0707/h200_vllm_p1_grid.sh - -BACKEND="vllm" -PORT="${PORT:-30004}" -MODEL="/data/models/DeepSeek-V4-Flash-DSpark" -BENCH_PY="/data/user1/yy/envs/sglang/bin/python" -DATASET="/data/user1/yy/datasets/ShareGPT_filtered_chat.json" -RESULT_ROOT="${RESULT_ROOT:-/data/user1/yy/bench_results/dspark_grid}" -WARMUP_REQUESTS="${WARMUP_REQUESTS:-100}" -PHASE="p1_quick" -RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}" - -num_prompts() { - local c="$1" - if (( c <= 4 )); then - local n=$((c * 8)) - (( n < 32 )) && n=32 - echo "${n}" - elif (( c <= 32 )); then - echo $((c * 16)) - else - echo $((c * 8)) - fi -} - -run_case() { - local scenario="$1" - local input_len="$2" - local output_len="$3" - local concurrencies="$4" - for concurrency in ${concurrencies}; do - local prompts - prompts="$(num_prompts "${concurrency}")" - local out_dir="${RESULT_ROOT}/${PHASE}/${scenario}/${RUN_ID}" - mkdir -p "${out_dir}" - echo "phase=${PHASE} scenario=${scenario} input=${input_len} output=${output_len} concurrency=${concurrency} prompts=${prompts}" - "${BENCH_PY}" -m sglang.bench_serving \ - --backend "${BACKEND}" \ - --host 127.0.0.1 \ - --port "${PORT}" \ - --model "${MODEL}" \ - --dataset-name random \ - --dataset-path "${DATASET}" \ - --random-input-len "${input_len}" \ - --random-output-len "${output_len}" \ - --num-prompts "${prompts}" \ - --max-concurrency "${concurrency}" \ - --warmup-requests "${WARMUP_REQUESTS}" \ - 2>&1 | tee "${out_dir}/c${concurrency}.log" - done -} - -curl --fail --silent --show-error --max-time 5 "http://127.0.0.1:${PORT}/health" >/dev/null -run_case "chat_standard" 1000 256 "1 8 16 32" -run_case "generation_standard" 1000 1000 "1 8 16 32" -run_case "summarization" 8000 1000 "1 4 8 16" diff --git a/scripts/benchmark_dspark_0707/bench_dspark_p2.sh b/scripts/benchmark_dspark_0707/bench_dspark_p2.sh deleted file mode 100755 index 5a2be94..0000000 --- a/scripts/benchmark_dspark_0707/bench_dspark_p2.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -# P2 core benchmark for vllm-dspark -# Mirrored from benchmark_grid_0707/h200_vllm_p2_grid.sh - -BACKEND="vllm" -PORT="${PORT:-30004}" -MODEL="/data/models/DeepSeek-V4-Flash-DSpark" -BENCH_PY="/data/user1/yy/envs/sglang/bin/python" -DATASET="/data/user1/yy/datasets/ShareGPT_filtered_chat.json" -RESULT_ROOT="${RESULT_ROOT:-/data/user1/yy/bench_results/dspark_grid}" -WARMUP_REQUESTS="${WARMUP_REQUESTS:-100}" -PHASE="p2_core" -RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}" - -num_prompts() { - local c="$1" - if (( c <= 4 )); then - local n=$((c * 8)) - (( n < 32 )) && n=32 - echo "${n}" - elif (( c <= 32 )); then - echo $((c * 16)) - else - echo $((c * 8)) - fi -} - -run_case() { - local scenario="$1" - local input_len="$2" - local output_len="$3" - local concurrencies="$4" - for concurrency in ${concurrencies}; do - local prompts - prompts="$(num_prompts "${concurrency}")" - local out_dir="${RESULT_ROOT}/${PHASE}/${scenario}/${RUN_ID}" - mkdir -p "${out_dir}" - echo "phase=${PHASE} scenario=${scenario} input=${input_len} output=${output_len} concurrency=${concurrency} prompts=${prompts}" - "${BENCH_PY}" -m sglang.bench_serving \ - --backend "${BACKEND}" \ - --host 127.0.0.1 \ - --port "${PORT}" \ - --model "${MODEL}" \ - --dataset-name random \ - --dataset-path "${DATASET}" \ - --random-input-len "${input_len}" \ - --random-output-len "${output_len}" \ - --num-prompts "${prompts}" \ - --max-concurrency "${concurrency}" \ - --warmup-requests "${WARMUP_REQUESTS}" \ - 2>&1 | tee "${out_dir}/c${concurrency}.log" - done -} - -curl --fail --silent --show-error --max-time 5 "http://127.0.0.1:${PORT}/health" >/dev/null -run_case "chat_short" 512 256 "1 8 16 32 64" -run_case "chat_standard" 1000 256 "64" -run_case "generation_standard" 1000 1000 "64" -run_case "rag_medium" 4000 512 "1 4 8 16 32" -run_case "summarization" 8000 1000 "32" -run_case "long_context_probe" 16000 512 "1 2 4 8 16" diff --git a/scripts/benchmark_dspark_0707/bench_dspark_p3.sh b/scripts/benchmark_dspark_0707/bench_dspark_p3.sh deleted file mode 100755 index b4df410..0000000 --- a/scripts/benchmark_dspark_0707/bench_dspark_p3.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -# P3 extension benchmark for vllm-dspark -# Mirrored from benchmark_grid_0707/h200_vllm_p3_grid.sh - -BACKEND="vllm" -PORT="${PORT:-30004}" -MODEL="/data/models/DeepSeek-V4-Flash-DSpark" -BENCH_PY="/data/user1/yy/envs/sglang/bin/python" -DATASET="/data/user1/yy/datasets/ShareGPT_filtered_chat.json" -RESULT_ROOT="${RESULT_ROOT:-/data/user1/yy/bench_results/dspark_grid}" -WARMUP_REQUESTS="${WARMUP_REQUESTS:-100}" -PHASE="p3_extension" -RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}" - -num_prompts() { - local c="$1" - if (( c <= 4 )); then - local n=$((c * 8)) - (( n < 32 )) && n=32 - echo "${n}" - elif (( c <= 32 )); then - echo $((c * 16)) - else - echo $((c * 8)) - fi -} - -run_case() { - local scenario="$1" - local input_len="$2" - local output_len="$3" - local concurrencies="$4" - for concurrency in ${concurrencies}; do - local prompts - prompts="$(num_prompts "${concurrency}")" - local out_dir="${RESULT_ROOT}/${PHASE}/${scenario}/${RUN_ID}" - mkdir -p "${out_dir}" - echo "phase=${PHASE} scenario=${scenario} input=${input_len} output=${output_len} concurrency=${concurrency} prompts=${prompts}" - "${BENCH_PY}" -m sglang.bench_serving \ - --backend "${BACKEND}" \ - --host 127.0.0.1 \ - --port "${PORT}" \ - --model "${MODEL}" \ - --dataset-name random \ - --dataset-path "${DATASET}" \ - --random-input-len "${input_len}" \ - --random-output-len "${output_len}" \ - --num-prompts "${prompts}" \ - --max-concurrency "${concurrency}" \ - --warmup-requests "${WARMUP_REQUESTS}" \ - 2>&1 | tee "${out_dir}/c${concurrency}.log" - done -} - -curl --fail --silent --show-error --max-time 5 "http://127.0.0.1:${PORT}/health" >/dev/null -run_case "decode_heavy" 512 2000 "1 8 16 32" -run_case "long_rag" 32000 512 "1 2 4 8" -run_case "stress_standard" 1000 256 "96 128" -run_case "stress_generation" 1000 1000 "96 128" diff --git a/scripts/benchmark_dspark_0707/parse_eagle_vs_dspark.py b/scripts/benchmark_dspark_0707/parse_eagle_vs_dspark.py deleted file mode 100644 index 8bf10dc..0000000 --- a/scripts/benchmark_dspark_0707/parse_eagle_vs_dspark.py +++ /dev/null @@ -1,258 +0,0 @@ -#!/usr/bin/env python3 -"""Parse EAGLE focused benchmark logs and merge with DSpark st=3/st=5 report.""" - -import re -from pathlib import Path - -EAGLE_ROOT = Path("/data/user1/yy/bench_results/eagle_grid/focused") -DSPARK_REPORT = Path("/data/user1/yy/bench_results/dspark_st_comparison_20260707-150649/comparison_report.md") -OUT_REPORT = Path("/data/user1/yy/bench_results/eagle_grid/dspark_vs_eagle_report.md") - - -def parse_log(log_path: Path) -> dict: - text = log_path.read_text(errors="ignore") - lines = text.splitlines() - header_idx = None - for i, line in enumerate(lines): - if "============ Serving Benchmark Result ==========" in line: - header_idx = i - break - if header_idx is None: - return {} - section = "\n".join(lines[header_idx:]) - - def get_float(pattern): - m = re.search(pattern + r"\s+([\d.]+)", section) - return float(m.group(1)) if m else None - - def get_int(pattern): - m = re.search(pattern + r"\s+(\d+)", section) - return int(m.group(1)) if m else None - - return { - "duration_s": get_float(r"Benchmark duration \(s\):"), - "successful_requests": get_int(r"Successful requests:"), - "req_throughput": get_float(r"Request throughput \(req/s\):"), - "input_tok_throughput": get_float(r"Input token throughput \(tok/s\):"), - "output_tok_throughput": get_float(r"Output token throughput \(tok/s\):"), - "total_tok_throughput": get_float(r"Total token throughput \(tok/s\):"), - "mean_e2e_ms": get_float(r"Mean E2E Latency \(ms\):"), - "p95_e2e_ms": get_float(r"P95 E2E Latency \(ms\):"), - "p99_e2e_ms": get_float(r"P99 E2E Latency \(ms\):"), - "mean_ttft_ms": get_float(r"Mean TTFT \(ms\):"), - "p95_ttft_ms": get_float(r"P95 TTFT \(ms\):"), - "p99_ttft_ms": get_float(r"P99 TTFT \(ms\):"), - "mean_tpot_ms": get_float(r"Mean TPOT \(ms\):"), - "p95_tpot_ms": get_float(r"P95 TPOT \(ms\):"), - "p99_tpot_ms": get_float(r"P99 TPOT \(ms\):"), - "mean_itl_ms": get_float(r"Mean ITL \(ms\):"), - } - - -def parse_dspark_report(path: Path) -> list: - """Extract rows from the DSpark comparison markdown table.""" - rows = [] - text = path.read_text(errors="ignore") - in_table = False - for line in text.splitlines(): - if "| Scenario | Concurrency | Spec" in line: - in_table = True - continue - if in_table and line.startswith("|"): - parts = [p.strip() for p in line.split("|") if p.strip()] - if len(parts) < 14 or parts[0] == "Scenario" or set(parts[0]) <= set("-:"): - continue - rows.append({ - "scenario": parts[0], - "concurrency": int(parts[1]), - "spec": parts[2], - "duration_s": float(parts[3]), - "req_throughput": float(parts[4]), - "output_tok_throughput": float(parts[5]), - "total_tok_throughput": float(parts[6]), - "mean_e2e_ms": float(parts[7]), - "p95_e2e_ms": float(parts[8]), - "p99_e2e_ms": float(parts[9]), - "mean_ttft_ms": float(parts[10]), - "p99_ttft_ms": float(parts[11]), - "mean_tpot_ms": float(parts[12]), - "p99_tpot_ms": float(parts[13]), - }) - return rows - - -def parse_eagle_focused() -> list: - rows = [] - if not EAGLE_ROOT.exists(): - return rows - for scenario_dir in sorted(EAGLE_ROOT.iterdir()): - if not scenario_dir.is_dir(): - continue - scenario = scenario_dir.name - # Use latest run_id only - run_ids = sorted([p.name for p in scenario_dir.iterdir() if p.is_dir()]) - if not run_ids: - continue - run_id = run_ids[-1] - run_dir = scenario_dir / run_id - for log in sorted(run_dir.glob("c*.log"), key=lambda p: int(p.stem[1:])): - concurrency = int(log.stem[1:]) - m = parse_log(log) - if not m: - continue - rows.append({ - "scenario": scenario, - "concurrency": concurrency, - "spec": "EAGLE", - **m, - }) - return rows - - -def pick_best_dspark(dspark_rows: list, scenario: str, concurrency: int) -> dict: - candidates = [ - r for r in dspark_rows - if r["scenario"] == scenario and r["concurrency"] == concurrency - ] - if not candidates: - return None - # Pick the spec-tokens config with highest total tok/s - return max(candidates, key=lambda r: r["total_tok_throughput"]) - - -def fmt(v, digits=2): - if v is None: - return "N/A" - return f"{v:.{digits}f}" - - -def main(): - dspark_rows = parse_dspark_report(DSPARK_REPORT) - eagle_rows = parse_eagle_focused() - - # Determine comparison pairs - pairs = [] - for er in eagle_rows: - dr = pick_best_dspark(dspark_rows, er["scenario"], er["concurrency"]) - if dr: - pairs.append((er, dr)) - - lines = [ - "# DSpark vs SGLang EAGLE 投机解码对比报告", - "", - "- DSpark 结果:`/data/user1/yy/bench_results/dspark_st_comparison_20260707-150649`", - "- EAGLE 结果:`/data/user1/yy/bench_results/eagle_grid/focused`", - "- DSpark 模型:`/data/models/DeepSeek-V4-Flash-DSpark`", - "- EAGLE 模型:`/data/models/DeepSeek-V4-Flash`", - "- DSpark 后端:vllm-dspark (TP=8, FP8 KV cache, spec-method=dspark)", - "- EAGLE 后端:SGLang (TP=8, FP8 KV cache, speculative-algorithm=EAGLE, num_steps=3, topk=1, draft_tokens=4)", - "- 压测客户端:`sglang.bench_serving`", - "- Warmup:100 条", - "", - "## 核心指标对比", - "", - "| Scenario | Concurrency | Method | Total tok/s | Out tok/s | Req/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P99 TPOT(ms) |", - "|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|", - ] - - for er, dr in pairs: - lines.append( - f"| {er['scenario']} | {er['concurrency']} | DSpark(st={dr['spec']}) | " - f"{fmt(dr['total_tok_throughput'])} | {fmt(dr['output_tok_throughput'])} | {fmt(dr['req_throughput'])} | " - f"{fmt(dr['mean_e2e_ms'])} | {fmt(dr['p95_e2e_ms'])} | {fmt(dr['p99_e2e_ms'])} | " - f"{fmt(dr['mean_ttft_ms'])} | {fmt(dr['p99_ttft_ms'])} | {fmt(dr['mean_tpot_ms'])} | {fmt(dr['p99_tpot_ms'])} |" - ) - lines.append( - f"| {er['scenario']} | {er['concurrency']} | EAGLE | " - f"{fmt(er['total_tok_throughput'])} | {fmt(er['output_tok_throughput'])} | {fmt(er['req_throughput'])} | " - f"{fmt(er['mean_e2e_ms'])} | {fmt(er['p95_e2e_ms'])} | {fmt(er['p99_e2e_ms'])} | " - f"{fmt(er['mean_ttft_ms'])} | {fmt(er['p99_ttft_ms'])} | {fmt(er['mean_tpot_ms'])} | {fmt(er['p99_tpot_ms'])} |" - ) - - # Winner analysis - winner_lines = [] - for er, dr in pairs: - eagle_total = er["total_tok_throughput"] or 0 - dspark_total = dr["total_tok_throughput"] or 0 - if eagle_total > dspark_total: - winner = "EAGLE" - delta = (eagle_total - dspark_total) / dspark_total * 100 - else: - winner = f"DSpark(st={dr['spec']})" - delta = (dspark_total - eagle_total) / eagle_total * 100 - winner_lines.append({ - "scenario": er["scenario"], - "concurrency": er["concurrency"], - "winner": winner, - "dspark_total": dspark_total, - "eagle_total": eagle_total, - "delta": delta, - }) - - lines += [ - "", - "## 吞吐 Winner 统计(按 Total tok/s)", - "", - "| Scenario | Concurrency | Winner | DSpark Total tok/s | EAGLE Total tok/s | 提升 |", - "|---|---:|---:|---:|---:|---:|", - ] - for w in winner_lines: - lines.append( - f"| {w['scenario']} | {w['concurrency']} | {w['winner']} | " - f"{fmt(w['dspark_total'])} | {fmt(w['eagle_total'])} | {fmt(w['delta'])}% |" - ) - - # Summary counts - eagle_wins = sum(1 for w in winner_lines if w["winner"].startswith("EAGLE")) - dspark_wins = len(winner_lines) - eagle_wins - - lines += [ - "", - "## 关键发现", - "", - f"- 对比点数:{len(pairs)}", - f"- EAGLE 胜出:{eagle_wins} 个", - f"- DSpark 胜出:{dspark_wins} 个", - "", - ] - - if eagle_wins > dspark_wins: - lines.append("**结论:在相同负载下,SGLang EAGLE 的综合吞吐优于 vllm-dspark。**") - elif dspark_wins > eagle_wins: - lines.append("**结论:在相同负载下,vllm-dspark 的综合吞吐优于 SGLang EAGLE。**") - else: - lines.append("**结论:两者综合吞吐相当,各有优劣。**") - - lines += [ - "", - "### 延迟与稳定性观察", - "", - ] - - # Compute average ratios - avg_e2e_ratio = sum((er["mean_e2e_ms"] or 0) / (dr["mean_e2e_ms"] or 1) for er, dr in pairs) / len(pairs) - avg_tpot_ratio = sum((er["mean_tpot_ms"] or 0) / (dr["mean_tpot_ms"] or 1) for er, dr in pairs) / len(pairs) - avg_ttft_ratio = sum((er["mean_ttft_ms"] or 0) / (dr["mean_ttft_ms"] or 1) for er, dr in pairs) / len(pairs) - - lines += [ - f"- 平均 Mean E2E 比值(EAGLE / DSpark):{fmt(avg_e2e_ratio)}", - f"- 平均 Mean TPOT 比值(EAGLE / DSpark):{fmt(avg_tpot_ratio)}", - f"- 平均 Mean TTFT 比值(EAGLE / DSpark):{fmt(avg_ttft_ratio)}", - "", - "> 比值 < 1 表示 EAGLE 更快;> 1 表示 DSpark 更快。", - "", - "## 优化建议", - "", - "1. 如果以吞吐为首要目标,当前配置下 **vllm-dspark 更优**,建议继续使用 `--spec-tokens` 并根据并发选择 3(高并发)或 5(低并发/长上下文)。", - "2. SGLang EAGLE 本次表现不佳,平均延迟和 TPOT 均显著高于 DSpark;如需进一步评估 EAGLE,可尝试调整 `--speculative-num-steps`、`--speculative-eagle-topk`、`--speculative-num-draft-tokens` 或更换 MOE runner backend,并确认是否已完成 `sglang.compile_deep_gemm` 预编译。", - "3. 注意两套后端的实现差异(CUDA graph、KV cache 管理、调度器、draft 模型架构)会显著影响不同并发和输入长度下的表现,建议按实际业务负载做最终选型。", - "", - ] - - OUT_REPORT.write_text("\n".join(lines)) - print("\n".join(lines)) - print(f"\nReport saved to: {OUT_REPORT}", file=__import__("sys").stderr) - - -if __name__ == "__main__": - main() diff --git a/scripts/benchmark_dspark_0707/parse_results.py b/scripts/benchmark_dspark_0707/parse_results.py deleted file mode 100644 index d85fefa..0000000 --- a/scripts/benchmark_dspark_0707/parse_results.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python3 -"""Parse dspark benchmark grid logs and generate a markdown report.""" - -import os -import re -import sys -from pathlib import Path - - -def parse_log(log_path: Path) -> dict: - text = log_path.read_text(errors="ignore") - lines = text.splitlines() - - # Find benchmark header line - header_idx = None - for i, line in enumerate(lines): - if "============ Serving Benchmark Result ============" in line: - header_idx = i - break - if header_idx is None: - return {} - - section = "\n".join(lines[header_idx:]) - - def get_float(pattern): - m = re.search(pattern + r"\s+([\d.]+)", section) - return float(m.group(1)) if m else None - - def get_int(pattern): - m = re.search(pattern + r"\s+(\d+)", section) - return int(m.group(1)) if m else None - - return { - "duration_s": get_float(r"Benchmark duration \(s\):"), - "successful_requests": get_int(r"Successful requests:"), - "req_throughput": get_float(r"Request throughput \(req/s\):"), - "input_tok_throughput": get_float(r"Input token throughput \(tok/s\):"), - "output_tok_throughput": get_float(r"Output token throughput \(tok/s\):"), - "total_tok_throughput": get_float(r"Total token throughput \(tok/s\):"), - "mean_e2e_ms": get_float(r"Mean E2E Latency \(ms\):"), - "p95_e2e_ms": get_float(r"P95 E2E Latency \(ms\):"), - "p99_e2e_ms": get_float(r"P99 E2E Latency \(ms\):"), - "mean_ttft_ms": get_float(r"Mean TTFT \(ms\):"), - "p95_ttft_ms": get_float(r"P95 TTFT \(ms\):"), - "p99_ttft_ms": get_float(r"P99 TTFT \(ms\):"), - "mean_tpot_ms": get_float(r"Mean TPOT \(ms\):"), - "p95_tpot_ms": get_float(r"P95 TPOT \(ms\):"), - "p99_tpot_ms": get_float(r"P99 TPOT \(ms\):"), - "mean_itl_ms": get_float(r"Mean ITL \(ms\):"), - } - - -def main(): - result_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/data/user1/yy/experiments/legacy_bench_results/dspark_grid_20260707-132641") - if not result_root.exists(): - print(f"Result root not found: {result_root}", file=sys.stderr) - sys.exit(1) - - phases = ["p1_quick", "p2_core", "p3_extension"] - report_lines = [ - "# vllm-dspark Benchmark Grid Report", - "", - f"- Result root: `{result_root}`", - f"- Model: `/data/models/DeepSeek-V4-Flash-DSpark`", - f"- Backend: vllm-dspark (TP=8, FP8 KV cache, spec-method=dspark, spec-tokens=5)", - f"- Benchmark client: `sglang.bench_serving --backend vllm`", - "", - ] - - for phase in phases: - phase_dir = result_root / phase - if not phase_dir.exists(): - continue - - report_lines.append(f"## {phase}") - report_lines.append("") - - scenarios = sorted([p.name for p in phase_dir.iterdir() if p.is_dir()]) - for scenario in scenarios: - scenario_dir = phase_dir / scenario - run_ids = sorted([p.name for p in scenario_dir.iterdir() if p.is_dir()]) - for run_id in run_ids: - run_dir = scenario_dir / run_id - logs = sorted(run_dir.glob("c*.log"), key=lambda p: int(p.stem[1:])) - - report_lines.append(f"### {scenario} ({run_id})") - report_lines.append("") - report_lines.append("| Concurrency | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean ITL(ms) |") - report_lines.append("|------------|------------|---------|------|---------|----------|------------|-------------|-------------|-------------|--------------|-------------|-------------|--------------|-------------|-------------|-------------|") - - for log in logs: - concurrency = int(log.stem[1:]) - metrics = parse_log(log) - if not metrics: - continue - - def fmt(v): - return f"{v:.2f}" if v is not None else "N/A" - - report_lines.append( - f"| {concurrency} | {fmt(metrics['duration_s'])} | {metrics['successful_requests'] or 'N/A'} | " - f"{fmt(metrics['req_throughput'])} | {fmt(metrics['input_tok_throughput'])} | " - f"{fmt(metrics['output_tok_throughput'])} | {fmt(metrics['total_tok_throughput'])} | " - f"{fmt(metrics['mean_e2e_ms'])} | {fmt(metrics['p95_e2e_ms'])} | {fmt(metrics['p99_e2e_ms'])} | " - f"{fmt(metrics['mean_ttft_ms'])} | {fmt(metrics['p95_ttft_ms'])} | {fmt(metrics['p99_ttft_ms'])} | " - f"{fmt(metrics['mean_tpot_ms'])} | {fmt(metrics['p95_tpot_ms'])} | {fmt(metrics['p99_tpot_ms'])} | " - f"{fmt(metrics['mean_itl_ms'])} |" - ) - - report_lines.append("") - - report_text = "\n".join(report_lines) - report_path = result_root / "report.md" - report_path.write_text(report_text) - print(report_text) - print(f"\nReport saved to: {report_path}", file=sys.stderr) - - -if __name__ == "__main__": - main() diff --git a/scripts/benchmark_dspark_0707/parse_st_comparison.py b/scripts/benchmark_dspark_0707/parse_st_comparison.py deleted file mode 100644 index d8aecaa..0000000 --- a/scripts/benchmark_dspark_0707/parse_st_comparison.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 -"""Parse spec-tokens 3 vs 5 comparison results and generate a report.""" - -import os -import re -import sys -from pathlib import Path - - -def parse_log(log_path: Path) -> dict: - text = log_path.read_text(errors="ignore") - lines = text.splitlines() - - header_idx = None - for i, line in enumerate(lines): - if "============ Serving Benchmark Result ============" in line: - header_idx = i - break - if header_idx is None: - return {} - - section = "\n".join(lines[header_idx:]) - - def get_float(pattern): - m = re.search(pattern + r"\s+([\d.]+)", section) - return float(m.group(1)) if m else None - - def get_int(pattern): - m = re.search(pattern + r"\s+(\d+)", section) - return int(m.group(1)) if m else None - - return { - "duration_s": get_float(r"Benchmark duration \(s\):"), - "successful_requests": get_int(r"Successful requests:"), - "req_throughput": get_float(r"Request throughput \(req/s\):"), - "input_tok_throughput": get_float(r"Input token throughput \(tok/s\):"), - "output_tok_throughput": get_float(r"Output token throughput \(tok/s\):"), - "total_tok_throughput": get_float(r"Total token throughput \(tok/s\):"), - "mean_e2e_ms": get_float(r"Mean E2E Latency \(ms\):"), - "p95_e2e_ms": get_float(r"P95 E2E Latency \(ms\):"), - "p99_e2e_ms": get_float(r"P99 E2E Latency \(ms\):"), - "mean_ttft_ms": get_float(r"Mean TTFT \(ms\):"), - "p95_ttft_ms": get_float(r"P95 TTFT \(ms\):"), - "p99_ttft_ms": get_float(r"P99 TTFT \(ms\):"), - "mean_tpot_ms": get_float(r"Mean TPOT \(ms\):"), - "p95_tpot_ms": get_float(r"P95 TPOT \(ms\):"), - "p99_tpot_ms": get_float(r"P99 TPOT \(ms\):"), - "mean_itl_ms": get_float(r"Mean ITL \(ms\):"), - } - - -def collect_results(result_root: Path) -> dict: - """Collect results into {(scenario, concurrency, spec_tokens): metrics}.""" - results = {} - focused_dir = result_root / "focused" - if not focused_dir.exists(): - return results - - for scenario_dir in focused_dir.iterdir(): - if not scenario_dir.is_dir(): - continue - scenario = scenario_dir.name - for run_dir in scenario_dir.iterdir(): - if not run_dir.is_dir(): - continue - run_id = run_dir.name - # run_id format: _st{3,5} - if "_st" not in run_id: - continue - spec_tokens = run_id.split("_st")[-1] - for log in run_dir.glob("c*.log"): - concurrency = int(log.stem[1:]) - metrics = parse_log(log) - if metrics: - results[(scenario, concurrency, spec_tokens)] = metrics - return results - - -def fmt(v): - return f"{v:.2f}" if v is not None else "N/A" - - -def main(): - result_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/data/user1/yy/bench_results/dspark_st_comparison_20260707-150649") - if not result_root.exists(): - print(f"Result root not found: {result_root}", file=sys.stderr) - sys.exit(1) - - results = collect_results(result_root) - if not results: - print("No results found.", file=sys.stderr) - sys.exit(1) - - # Group by scenario and concurrency - keys = sorted({(s, c) for (s, c, _) in results.keys()}) - - report_lines = [ - "# vllm-dspark `--spec-tokens` 对比报告", - "", - f"- 结果目录:`{result_root}`", - f"- 模型:`/data/models/DeepSeek-V4-Flash-DSpark`", - f"- 后端:vllm-dspark (TP=8, FP8 KV cache)", - f"- 对比参数:`--spec-tokens 3` vs `--spec-tokens 5`", - f"- Warmup:100 条", - f"- 压测客户端:`sglang.bench_serving --backend vllm`", - "", - "## 核心指标对比", - "", - "| Scenario | Concurrency | Spec | Duration(s) | Req/s | Out tok/s | Total tok/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P99 TPOT(ms) |", - "|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|", - ] - - for scenario, concurrency in keys: - for st in ["3", "5"]: - m = results.get((scenario, concurrency, st), {}) - if not m: - continue - report_lines.append( - f"| {scenario} | {concurrency} | {st} | {fmt(m['duration_s'])} | {fmt(m['req_throughput'])} | " - f"{fmt(m['output_tok_throughput'])} | {fmt(m['total_tok_throughput'])} | {fmt(m['mean_e2e_ms'])} | " - f"{fmt(m['p95_e2e_ms'])} | {fmt(m['p99_e2e_ms'])} | {fmt(m['mean_ttft_ms'])} | {fmt(m['p99_ttft_ms'])} | " - f"{fmt(m['mean_tpot_ms'])} | {fmt(m['p99_tpot_ms'])} |" - ) - - # Summary table: best total tok/s per scenario/concurrency - report_lines.append("") - report_lines.append("## 吞吐 winner 统计") - report_lines.append("") - report_lines.append("| Scenario | Concurrency | Winner (Total tok/s) | st=3 Total tok/s | st=5 Total tok/s | 提升 |") - report_lines.append("|---|---:|---:|---:|---:|---:|") - - for scenario, concurrency in keys: - m3 = results.get((scenario, concurrency, "3"), {}) - m5 = results.get((scenario, concurrency, "5"), {}) - t3 = m3.get("total_tok_throughput", 0) or 0 - t5 = m5.get("total_tok_throughput", 0) or 0 - if t3 == 0 and t5 == 0: - continue - winner = "st=3" if t3 > t5 else "st=5" - uplift = (max(t3, t5) / min(t3, t5) - 1) * 100 if min(t3, t5) > 0 else 0 - report_lines.append( - f"| {scenario} | {concurrency} | {winner} | {fmt(t3)} | {fmt(t5)} | {fmt(uplift)}% |" - ) - - report_lines.append("") - report_lines.append("## 观察与建议") - report_lines.append("") - - # Compute overall wins - st3_wins = 0 - st5_wins = 0 - for scenario, concurrency in keys: - m3 = results.get((scenario, concurrency, "3"), {}) - m5 = results.get((scenario, concurrency, "5"), {}) - t3 = m3.get("total_tok_throughput", 0) or 0 - t5 = m5.get("total_tok_throughput", 0) or 0 - if t3 > t5: - st3_wins += 1 - elif t5 > t3: - st5_wins += 1 - - report_lines.append(f"- **st=3 在 {st3_wins} 个配置中吞吐更高,st=5 在 {st5_wins} 个配置中吞吐更高。**") - report_lines.append("- 具体结论请根据上表分析。") - report_lines.append("") - - report_text = "\n".join(report_lines) - report_path = result_root / "comparison_report.md" - report_path.write_text(report_text) - print(report_text) - print(f"\nComparison report saved to: {report_path}", file=sys.stderr) - - -if __name__ == "__main__": - main() diff --git a/scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh b/scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh deleted file mode 100755 index 4170d3c..0000000 --- a/scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -# Run H200 DeepSeek-V4-Flash-DSpark benchmark grid in the current environment. -# This orchestrator starts the vllm-dspark server, runs P1/P2/P3 benchmarks, -# and stops the server afterwards. -# -# Environment: -# - vllm-dspark env: /data/user1/yy/envs/vllm-dspark -# - benchmark client: /data/user1/yy/envs/sglang/bin/python -m sglang.bench_serving -# - model: /data/models/DeepSeek-V4-Flash-DSpark -# - server port: 30004 -# -# Usage: -# bash run_dspark_benchmark_grid.sh -# -# To only run benchmarks against an already-running server: -# SKIP_MANAGE_SERVER=1 bash run_dspark_benchmark_grid.sh - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -START_SCRIPT="${SCRIPT_DIR}/../start_dsv4_dspark_8card.sh" -RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}" -RESULT_ROOT="${RESULT_ROOT:-/data/user1/yy/bench_results/dspark_grid_${RUN_ID}}" -LOG_DIR="${RESULT_ROOT}/logs" -PORT="${PORT:-30004}" -PID_FILE="/data/user1/yy/dsv4_flash_dspark.pid" - -mkdir -p "${LOG_DIR}" - -log() { - echo "[$(date --iso-8601=seconds)] $*" | tee -a "${LOG_DIR}/orchestrator.log" -} - -is_server_healthy() { - curl --fail --silent --show-error --max-time 5 "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1 -} - -stop_server() { - if [[ -f "${PID_FILE}" ]]; then - local pid - pid="$(cat "${PID_FILE}")" - if kill -0 "${pid}" 2>/dev/null; then - log "stopping existing dspark server pid=${pid}" - kill "${pid}" 2>/dev/null || true - sleep 5 - kill -9 "${pid}" 2>/dev/null || true - fi - rm -f "${PID_FILE}" - fi - # Fallback: kill any lingering vllm serve processes for this model - pkill -9 -f "vllm serve.*DeepSeek-V4-Flash-DSpark" 2>/dev/null || true - sleep 2 -} - -start_server() { - log "starting dspark server with ${START_SCRIPT}" - if [[ ! -x "${START_SCRIPT}" ]]; then - log "error: start script not found or not executable: ${START_SCRIPT}" - exit 1 - fi - bash "${START_SCRIPT}" >> "${LOG_DIR}/server.outer.log" 2>&1 - if ! is_server_healthy; then - log "error: dspark server failed to become healthy" - exit 1 - fi - log "dspark server is healthy" -} - -run_phase() { - local name="$1" - local script="$2" - local log_file="${LOG_DIR}/${name}.log" - - log "running ${name}: ${script}" - RUN_ID="${RUN_ID}" RESULT_ROOT="${RESULT_ROOT}" bash "${script}" 2>&1 | tee "${log_file}" - log "finished ${name}" -} - -on_exit() { - local code="$?" - log "orchestrator exiting with code=${code}" - if [[ -z "${SKIP_MANAGE_SERVER:-}" ]]; then - stop_server - fi - exit "${code}" -} -trap on_exit EXIT - -main() { - log "run_id=${RUN_ID}" - log "result_root=${RESULT_ROOT}" - log "script_dir=${SCRIPT_DIR}" - - if [[ -n "${SKIP_MANAGE_SERVER:-}" ]]; then - log "SKIP_MANAGE_SERVER is set, assuming server is already running" - if ! is_server_healthy; then - log "error: no healthy server found at port ${PORT}" - exit 1 - fi - else - stop_server - start_server - fi - - log "===== DSpark BENCHMARK START =====" - run_phase "dspark_p1" "${SCRIPT_DIR}/bench_dspark_p1.sh" - run_phase "dspark_p2" "${SCRIPT_DIR}/bench_dspark_p2.sh" - run_phase "dspark_p3" "${SCRIPT_DIR}/bench_dspark_p3.sh" - log "===== DSpark BENCHMARK DONE =====" - - log "all results saved to ${RESULT_ROOT}" -} - -main "$@" diff --git a/scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh b/scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh deleted file mode 100755 index 747c166..0000000 --- a/scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -# Compare vllm-dspark with --spec-tokens 3 vs 5. -# Uses the parameterized start script and focused benchmark. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -START_SCRIPT="${SCRIPT_DIR}/../start_dsv4_dspark_8card_param.sh" -BENCH_SCRIPT="${SCRIPT_DIR}/bench_dspark_focused.sh" -RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}" -RESULT_ROOT="${RESULT_ROOT:-/data/user1/yy/bench_results/dspark_st_comparison_${RUN_ID}}" -LOG_DIR="${RESULT_ROOT}/logs" -PORT="${PORT:-30004}" -PID_FILE="/data/user1/yy/dsv4_flash_dspark.pid" -WARMUP_REQUESTS="${WARMUP_REQUESTS:-100}" - -mkdir -p "${LOG_DIR}" - -log() { - echo "[$(date --iso-8601=seconds)] $*" | tee -a "${LOG_DIR}/orchestrator.log" -} - -is_server_healthy() { - curl --fail --silent --show-error --max-time 5 "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1 -} - -stop_server() { - if [[ -f "${PID_FILE}" ]]; then - local pid - pid="$(cat "${PID_FILE}" 2>/dev/null || true)" - if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then - log "stopping existing dspark server pid=${pid}" - kill "${pid}" 2>/dev/null || true - sleep 5 - kill -9 "${pid}" 2>/dev/null || true - fi - rm -f "${PID_FILE}" - fi - local pids - pids="$(pgrep -f "VLLM::|vllm serve.*DeepSeek-V4-Flash-DSpark" 2>/dev/null || true)" - if [[ -n "${pids}" ]]; then - for pid in ${pids}; do - if [[ "$pid" != "$$" ]]; then kill -9 "$pid" 2>/dev/null || true; fi - done - fi - sleep 2 -} - -start_server() { - local spec_tokens="$1" - log "starting dspark server with spec-tokens=${spec_tokens}" - if [[ ! -x "${START_SCRIPT}" ]]; then - log "error: start script not found or not executable: ${START_SCRIPT}" - exit 1 - fi - SPEC_TOKENS="${spec_tokens}" bash "${START_SCRIPT}" >> "${LOG_DIR}/server_st${spec_tokens}.outer.log" 2>&1 - if ! is_server_healthy; then - log "error: dspark server (st=${spec_tokens}) failed to become healthy" - exit 1 - fi - log "dspark server (st=${spec_tokens}) is healthy" -} - -run_benchmark() { - local spec_tokens="$1" - local log_file="${LOG_DIR}/focused_st${spec_tokens}.log" - log "running focused benchmark with spec-tokens=${spec_tokens}, warmup=${WARMUP_REQUESTS}" - RUN_ID="${RUN_ID}_st${spec_tokens}" RESULT_ROOT="${RESULT_ROOT}" WARMUP_REQUESTS="${WARMUP_REQUESTS}" bash "${BENCH_SCRIPT}" 2>&1 | tee "${log_file}" - log "finished focused benchmark with spec-tokens=${spec_tokens}" -} - -on_exit() { - local code="$?" - log "orchestrator exiting with code=${code}" - stop_server - exit "${code}" -} -trap on_exit EXIT - -main() { - log "run_id=${RUN_ID}" - log "result_root=${RESULT_ROOT}" - log "warmup_requests=${WARMUP_REQUESTS}" - - log "===== SPEC-TOKENS 3 START =====" - stop_server - start_server 3 - run_benchmark 3 - stop_server - - log "cooldown 30s" - sleep 30 - - log "===== SPEC-TOKENS 5 START =====" - start_server 5 - run_benchmark 5 - stop_server - - log "===== COMPARISON DONE =====" - log "all results saved to ${RESULT_ROOT}" -} - -main "$@" diff --git a/scripts/start_dsv4_dspark_8card.sh b/scripts/start_dsv4_dspark_8card.sh deleted file mode 100755 index 53bd04b..0000000 --- a/scripts/start_dsv4_dspark_8card.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -set -e - -cd /data/user1/yy -mkdir -p logs - -VENV="/data/user1/yy/envs/vllm-dspark" -PYTHON="$VENV/bin/python" -VLLM="$VENV/bin/vllm" -export PATH="$VENV/bin:$PATH" - -MODEL="/data/models/DeepSeek-V4-Flash-DSpark" -PORT=30004 -TP=8 -LOG="/data/user1/yy/logs/dsv4_flash_dspark_tp${TP}_$(date +%Y%m%d_%H%M%S).log" -PID_FILE="/data/user1/yy/dsv4_flash_dspark.pid" - -export TMPDIR=/data/user1/yy/tmp -export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 - -echo "=== Starting DeepSeek-V4-Flash-DSpark (TP=$TP) ===" -echo "Model: $MODEL" -echo "Port: $PORT" -echo "Log: $LOG" - -rm -f "$PID_FILE" -nohup "$VLLM" serve "$MODEL" \ - --trust-remote-code \ - --tensor-parallel-size "$TP" \ - --kv-cache-dtype fp8 \ - --block-size 256 \ - --max-model-len auto \ - --max-num-seqs 256 \ - --tokenizer-mode deepseek_v4 \ - --reasoning-parser deepseek_v4 \ - --spec-method dspark \ - --spec-model "$MODEL" \ - --spec-tokens 5 \ - --no-disable-hybrid-kv-cache-manager \ - --disable-uvicorn-access-log \ - --port "$PORT" \ - > "$LOG" 2>&1 & -PID=$! -echo $PID > "$PID_FILE" -echo "PID: $PID" -echo "Waiting for health..." - -for i in $(seq 1 240); do - if curl -s "http://127.0.0.1:$PORT/health" > /dev/null 2>&1; then - echo "Server is ready at http://127.0.0.1:$PORT" - echo "Log: $LOG" - exit 0 - fi - if ! kill -0 $PID 2>/dev/null; then - echo "ERROR: Server exited early" - tail -100 "$LOG" - exit 1 - fi - echo "Waiting... ($i/240)" - sleep 5 -done - -echo "ERROR: Server not healthy after 240 retries" -tail -100 "$LOG" -exit 1