commit eb431d392521f263183fefe46b8249360ddc0767 Author: Benchmark Bot Date: Wed Jul 8 02:17:13 2026 +0000 Initial commit: benchmark scripts and final reports diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92a7e21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# 环境/依赖/临时文件 +envs/ +deps/ +tmp/ +*.pid +__pycache__/ +*.pyc +*.pyo +*.egg-info/ + +# 大文件/压缩包 +*.zip +*.tar.gz +*.tar.bz2 + +# 中间日志 +logs/ +*.log + +# 数据集(可重新下载) +datasets/ + +# 原始请求级输出(方案 A:极简版,不存原始 jsonl) +bench_results/**/raw_outputs/ + +# 无关项目 +loomeval_yy/ diff --git a/BENCHMARK_WORKFLOW.md b/BENCHMARK_WORKFLOW.md new file mode 100644 index 0000000..c7e22df --- /dev/null +++ b/BENCHMARK_WORKFLOW.md @@ -0,0 +1,122 @@ +# Benchmark Workflow & Directory Conventions + +## Directory Layout + +``` +/data/user1/yy/ +├── scripts/ # all benchmark/orchestrator/utility scripts +│ ├── benchmark_dspark_0707/ # DSpark benchmark suite +│ ├── benchmark_dsv4_backend_comparison.sh +│ ├── start_dsv4_dspark_8card.sh +│ ├── start_sglang_dsv4_8card.sh +│ └── ... +├── bench_results/ # all benchmark outputs and reports +│ ├── dsv4_backend_comparison_20260707/ +│ │ ├── raw_outputs/ # JSONL raw outputs +│ │ ├── logs/ # per-run logs +│ │ └── README.md # output manifest + provenance +│ ├── dspark_grid_20260707-132641/ +│ ├── dspark_st_comparison_20260707-150649/ +│ ├── eagle_grid/ +│ └── ... +├── logs/ # server logs (stdout/stderr from start scripts) +├── datasets/ # benchmark datasets +└── envs/ # Python virtual environments +``` + +## Rules + +1. **Scripts live in `scripts/` only.** + - Group related scripts into subdirectories, e.g. `scripts/benchmark_dspark_0707/`. + - Each script group should have its own `README.md` listing scripts, purpose, and outputs. + +2. **Benchmark outputs live in `bench_results/` only.** + - Never leave `.jsonl`, `.json`, `.log`, or `.md` reports in the project root. + - Each benchmark run gets its own directory: `bench_results/_/`. + - Raw outputs go in `raw_outputs/`. + - Logs go in `logs/`. + - Reports (e.g. `report.md`, `comparison_report.md`) go in the run root. + +3. **Each `bench_results//` directory must contain a `README.md`.** + - What was benchmarked. + - Which script(s) produced the outputs. + - File naming convention. + - Inventory of outputs (table). + +4. **Scripts should default `RESULT_ROOT` to `bench_results/_${RUN_ID}`.** + - Allow override via `RESULT_ROOT` env var. + - Use `RUN_ID=$(date '+%Y%m%d-%H%M%S')` unless specified. + +5. **Server start scripts write to `logs/`.** + - `logs/_.log` + - Keep server logs separate from benchmark result logs. + +## Naming Conventions + +### Result directories + +``` +bench_results/_/ +``` + +Examples: + +- `bench_results/dspark_grid_20260707-132641/` +- `bench_results/dsv4_backend_comparison_20260707/` + +### Raw output files + +For detailed per-request JSONL outputs: + +``` +{backend}_{MMDD}_{concurrency}_{input_len}_{output_len}.jsonl +``` + +For summary JSON outputs from `sglang.bench_serving --output-file`: + +``` +{backend}_{scenario}_{params}.json +``` + +### Logs + +``` +logs/_YYYYMMDD_HHMMSS.log +logs/_orchestrator_YYYYMMDD_HHMMSS.log +``` + +## Quick Start + +### Run DSpark grid benchmark + +```bash +bash scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh +``` + +### Run DSpark spec-tokens comparison + +```bash +bash scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh +``` + +### Run SGLang vs vLLM backend comparison + +```bash +# Start SGLang on port 30000 and vLLM on port 8000, then: +bash scripts/benchmark_dsv4_backend_comparison.sh all +``` + +### Parse results + +```bash +/data/user1/yy/envs/sglang/bin/python scripts/benchmark_dspark_0707/parse_results.py \ + /data/user1/yy/bench_results/dspark_grid_ +``` + +## Checklist Before Committing / Archiving + +- [ ] No `.jsonl`, `.json`, `.log`, or `.md` files left in `/data/user1/yy/` root. +- [ ] All outputs moved to `bench_results/_/`. +- [ ] `bench_results//README.md` exists and documents provenance. +- [ ] Scripts moved to `scripts/` (or `scripts//`). +- [ ] Script path references updated after moving. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4c1366 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# DSV4 / DeepSeek-V4-Flash 推理测速项目 + +> 记录 vllm-dspark、SGLang、vLLM 等后端在 DeepSeek-V4-Flash 上的 benchmark 脚本与最终结果。 + +## 目录说明 + +| 目录/文件 | 说明 | +|---|---| +| `scripts/` | 所有 benchmark 脚本、服务启动脚本、结果解析脚本 | +| `bench_results/` | 各次实验的最终报告与 summary JSON | +| `BENCHMARK_WORKFLOW.md` | benchmark 目录与命名规范 | +| `scripts/SLO_STANDARDS.md` | 推理服务 SLO 标准(TTFT/TPOT) | + +> 注:`.gitignore` 已排除 `envs/`、`deps/`、`tmp/`、`datasets/`、`logs/`、`*.zip`、原始 `raw_outputs/*.jsonl` 等大文件/中间文件。 + +## 实验索引 + +| 实验 | 脚本 | 最终报告 | 说明 | +|---|---|---|---| +| DSpark grid benchmark | `scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh` | `bench_results/dspark_grid_20260707-132641/report.md` | P1/P2/P3 全量网格 | +| DSpark spec-tokens 对比 | `scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh` | `bench_results/dspark_st_comparison_20260707-150649/comparison_report.md` | `--spec-tokens 3` vs `5` | +| SGLang EAGLE vs DSpark | `scripts/start_sglang_dsv4_8card.sh` + 对比解析脚本 | `bench_results/eagle_grid/dspark_vs_eagle_report.md` | EAGLE 投机解码对比 | +| SGLang vs vLLM 后端对比(0707) | `scripts/benchmark_dsv4_backend_comparison.sh` | `bench_results/dsv4_backend_comparison_20260707/README.md` | 遗留 raw outputs 清单 | +| SGLang 8-card systematic | `scripts/run_sglang_benchmark.sh` | `bench_results/sglang_8card_systematic_20260704_120819/report.md` | 早期 SGLang 系统扫描 | +| SGLang 8-card max throughput | `scripts/run_sglang_max_throughput.sh` | `bench_results/sglang_8card_max_throughput_20260705_030839/summary.json` | 最大吞吐扫描 | +| vLLM-dspark Qwen3 | `scripts/bench_vllm_dspark_qwen3.py` | `bench_results/vllm_dspark_qwen3_20260705_121256/summary.json` | Qwen3 模型测速 | +| DSV4 后端对比(0705) | `scripts/bench_dsv4_comparison.py` | `bench_results/dsv4_comparison_20260705_152221/summary.json` | 早期 DSpark 配置对比 | + +## 快速复现 + +### DSpark grid + +```bash +bash scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh +``` + +### Spec-tokens 对比 + +```bash +bash scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh +``` + +### 解析已有结果 + +```bash +/data/user1/yy/envs/sglang/bin/python scripts/benchmark_dspark_0707/parse_results.py \ + /data/user1/yy/bench_results/dspark_grid_20260707-132641 +``` + +## SLO 参考 + +详见 `scripts/SLO_STANDARDS.md`。 + +主要关注指标: + +- **TTFT P95**:S2(DeepSeek-V4-Flash 所在层)目标 `< 3s` +- **TPOT**:S2 目标 `< 50ms` + +## 环境要求 + +- Python env:`/data/user1/yy/envs/vllm-dspark`(服务端)、`/data/user1/yy/envs/sglang`(压测客户端) +- 模型:`/data/models/DeepSeek-V4-Flash`、`/data/models/DeepSeek-V4-Flash-DSpark` +- 硬件:8× H200(当前配置) diff --git a/bench_results/dspark_grid_20260707-132641/evaluation.md b/bench_results/dspark_grid_20260707-132641/evaluation.md new file mode 100644 index 0000000..70d0cf7 --- /dev/null +++ b/bench_results/dspark_grid_20260707-132641/evaluation.md @@ -0,0 +1,167 @@ +# vllm-dspark Benchmark 结果评估 + +> 对应结果:`/data/user1/yy/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/bench_results/dspark_grid_20260707-132641/report.md b/bench_results/dspark_grid_20260707-132641/report.md new file mode 100644 index 0000000..6f8dc81 --- /dev/null +++ b/bench_results/dspark_grid_20260707-132641/report.md @@ -0,0 +1,120 @@ +# vllm-dspark Benchmark Grid Report + +- Result root: `/data/user1/yy/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/bench_results/dspark_st_comparison_20260707-150649/comparison_report.md b/bench_results/dspark_st_comparison_20260707-150649/comparison_report.md new file mode 100644 index 0000000..0240608 --- /dev/null +++ b/bench_results/dspark_st_comparison_20260707-150649/comparison_report.md @@ -0,0 +1,129 @@ +# 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/bench_results/dsv4_backend_comparison_20260707/README.md b/bench_results/dsv4_backend_comparison_20260707/README.md new file mode 100644 index 0000000..0845e31 --- /dev/null +++ b/bench_results/dsv4_backend_comparison_20260707/README.md @@ -0,0 +1,102 @@ +# 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/bench_results/dsv4_comparison_20260705_152221/summary.json b/bench_results/dsv4_comparison_20260705_152221/summary.json new file mode 100644 index 0000000..98e8e23 --- /dev/null +++ b/bench_results/dsv4_comparison_20260705_152221/summary.json @@ -0,0 +1,197 @@ +[ + { + "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/bench_results/eagle_grid/dspark_vs_eagle_report.md b/bench_results/eagle_grid/dspark_vs_eagle_report.md new file mode 100644 index 0000000..c4097e2 --- /dev/null +++ b/bench_results/eagle_grid/dspark_vs_eagle_report.md @@ -0,0 +1,96 @@ +# 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/bench_results/eagle_grid/report.md b/bench_results/eagle_grid/report.md new file mode 100644 index 0000000..c5ab5d7 --- /dev/null +++ b/bench_results/eagle_grid/report.md @@ -0,0 +1,6 @@ +# 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/bench_results/sglang_8card_max_throughput_20260705_030839/summary.json b/bench_results/sglang_8card_max_throughput_20260705_030839/summary.json new file mode 100644 index 0000000..001f511 --- /dev/null +++ b/bench_results/sglang_8card_max_throughput_20260705_030839/summary.json @@ -0,0 +1,58 @@ +[ + { + "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/bench_results/sglang_8card_systematic_20260704_120819/length_distribution.png b/bench_results/sglang_8card_systematic_20260704_120819/length_distribution.png new file mode 100644 index 0000000..a511adb Binary files /dev/null and b/bench_results/sglang_8card_systematic_20260704_120819/length_distribution.png differ diff --git a/bench_results/sglang_8card_systematic_20260704_120819/report.md b/bench_results/sglang_8card_systematic_20260704_120819/report.md new file mode 100644 index 0000000..ba05d12 --- /dev/null +++ b/bench_results/sglang_8card_systematic_20260704_120819/report.md @@ -0,0 +1,267 @@ +# SGLang 8-Card DeepSeek-V4-Flash 系统 Benchmark 报告 + +生成时间:2026-07-05 02:10:06 + +## 测试环境 + +- **模型**:DeepSeek-V4-Flash (`/data/models/DeepSeek-V4-Flash`) +- **推理框架**:SGLang +- **并行策略**:TP=8,8× NVIDIA H200 +- **MoE backend**:marlin +- **投机采样**:EAGLE(`--speculative-num-steps 3 --speculative-eagle-topk 1 --speculative-num-draft-tokens 4`) +- **服务端口**:30000 + +## 测试负载 + +- **数据集**:ShareGPT V4.3 unfiltered cleaned split +- **请求数**:每个测试点 2000 条真实对话请求 +- **测试维度**: + - 并发扫描:`--max-concurrency` = 1, 8, 16, 32, 64, 128 + - 请求速率扫描:`--request-rate` = 1, 2, 4, 8, 16 (Poisson-like) + +## Phase 1:并发扫描结果 + +| 并发 | 耗时(s) | req/s | input tok/s | output tok/s | total tok/s | Mean TTFT(ms) | Mean TPOT(ms) | Mean E2E(ms) | Accept Length | +|------|---------|-------|-------------|--------------|-------------|---------------|---------------|--------------|---------------| +| 1 | 1715.45 | 1.17 | 335.83 | 243.51 | 579.34 | 128.09 | 4.00 | 856.77 | 2.62 | +| 8 | 448.91 | 4.46 | 1283.33 | 930.54 | 2213.87 | 146.76 | 8.41 | 1790.65 | 2.61 | +| 16 | 331.79 | 6.03 | 1736.31 | 1259.00 | 2995.30 | 168.95 | 12.67 | 2642.10 | 2.61 | +| 32 | 267.72 | 7.47 | 2151.87 | 1560.32 | 3712.19 | 206.24 | 20.72 | 4249.71 | 2.61 | +| 64 | 207.57 | 9.64 | 2775.39 | 2012.43 | 4787.82 | 248.41 | 32.61 | 6557.39 | 2.61 | +| 128 | 139.41 | 14.35 | 4132.36 | 2996.37 | 7128.73 | 330.40 | 46.15 | 8596.12 | 2.61 | + +## Phase 2:请求速率扫描结果 + +| RPS | 耗时(s) | req/s | input tok/s | output tok/s | total tok/s | Mean TTFT(ms) | Mean TPOT(ms) | Mean E2E(ms) | Accept Length | +|-----|---------|-------|-------------|--------------|-------------|---------------|---------------|--------------|---------------| +| 1 | 2011.46 | 0.99 | 286.41 | 207.67 | 494.08 | 146.87 | 4.91 | 1007.62 | 2.61 | +| 2 | 1007.21 | 1.99 | 571.97 | 414.74 | 986.71 | 160.63 | 5.99 | 1205.24 | 2.61 | +| 4 | 505.49 | 3.96 | 1139.69 | 826.39 | 1966.08 | 174.67 | 9.02 | 1743.40 | 2.60 | +| 8 | 256.46 | 7.80 | 2246.34 | 1628.82 | 3875.15 | 216.65 | 25.35 | 4542.64 | 2.60 | +| 16 | 137.92 | 14.50 | 4176.94 | 3028.70 | 7205.64 | 377.28 | 87.16 | 15048.09 | 2.61 | + +## 关键发现 + +1. **吞吐随并发单调上升**:从 c=1 的 579 tok/s 提升到 c=128 的 7129 tok/s,说明 8×H200 在高压下仍能有效扩展。 +2. **c=32 是延迟与吞吐的拐点**:c=32 时 total tok/s 为 3712,E2E 延迟约 4.25s;c=64 时吞吐提升到 4788,但延迟增加到 6.56s。 +3. **c=128 达到最高吞吐**:total tok/s = 7129,output tok/s = 2996,但 E2E 延迟接近 8.6s,适合离线/批处理场景。 +4. **RPS=16 时系统过载**:实际 req/s 达到 14.5,TPOT 飙升到 87ms,E2E 延迟 15s,说明 RPS>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/bench_results/sglang_8card_systematic_20260704_120819/summary.json b/bench_results/sglang_8card_systematic_20260704_120819/summary.json new file mode 100644 index 0000000..af4117a --- /dev/null +++ b/bench_results/sglang_8card_systematic_20260704_120819/summary.json @@ -0,0 +1,156 @@ +[ + { + "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/bench_results/vllm_dspark_qwen3_20260705_121046/summary.json b/bench_results/vllm_dspark_qwen3_20260705_121046/summary.json new file mode 100644 index 0000000..20dfa8b --- /dev/null +++ b/bench_results/vllm_dspark_qwen3_20260705_121046/summary.json @@ -0,0 +1,39 @@ +{ + "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/bench_results/vllm_dspark_qwen3_20260705_121218/summary.json b/bench_results/vllm_dspark_qwen3_20260705_121218/summary.json new file mode 100644 index 0000000..c719bcc --- /dev/null +++ b/bench_results/vllm_dspark_qwen3_20260705_121218/summary.json @@ -0,0 +1,59 @@ +{ + "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/bench_results/vllm_dspark_qwen3_20260705_121256/summary.json b/bench_results/vllm_dspark_qwen3_20260705_121256/summary.json new file mode 100644 index 0000000..61edc71 --- /dev/null +++ b/bench_results/vllm_dspark_qwen3_20260705_121256/summary.json @@ -0,0 +1,131 @@ +{ + "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/cleanup_summary.md b/cleanup_summary.md new file mode 100644 index 0000000..18016ca --- /dev/null +++ b/cleanup_summary.md @@ -0,0 +1,110 @@ +# /data/user1/yy 文件夹整理记录 + +> 整理时间:2026-07-06 +> 说明:将脚本集中到 `scripts/`,uv 虚拟环境集中到 `envs/`,并保留兼容性软链接;删除过时的 log、pid、临时文件和冗余压缩包。 + +--- + +## 目录结构 + +``` +/data/user1/yy/ +├── bench_results/ +│ ├── dsv4_comparison_20260705_152221/ +│ ├── dsv4_flash_dspark_misc/ +│ ├── pd_bench/ +│ ├── sglang_8card_max_throughput_20260705_030839/ +│ ├── sglang_8card_systematic_20260704_120819/ +│ ├── sglang_misc/ +│ ├── vllm_dspark_qwen3_20260705_121046/ +│ ├── vllm_dspark_qwen3_20260705_121218/ +│ └── vllm_dspark_qwen3_20260705_121256/ +├── datasets/ +├── envs/ # uv 虚拟环境与缓存 +│ ├── sglang/ -> 原 /data/user1/yy/sglang +│ ├── spraseattn/ -> 原 /data/user1/yy/spraseattn +│ ├── uv_cache/ -> 原 /data/user1/yy/uv_cache +│ ├── vllm/ -> 原 /data/user1/yy/vllm +│ └── vllm-dspark/ -> 原 /data/user1/yy/vllm-dspark +├── loomeval_yy/ # 按用户要求保留 +├── scripts/ # 脚本集中存放 +│ ├── bench_dsv4_comparison.py +│ ├── bench_vllm_dspark_qwen3.py +│ ├── install_vllm_dspark.sh +│ ├── run_sglang_benchmark.sh +│ ├── run_sglang_max_throughput.sh +│ ├── start_dsv4_dspark_8card.sh +│ ├── start_dsv4_dspark_8card_bf16kv.sh +│ ├── start_dsv4_dspark_8card_flashinfer.sh +│ ├── start_pd_single_node.sh +│ ├── start_sglang_dsv4_8card.sh +│ ├── start_vllm_pd_single_node.sh +│ └── test_block_sparse_attn.py +├── tmp/ +├── vllm-main/ # vLLM 源码目录 +├── cleanup_summary.md +├── dspark_deepseekv4_fix_pr_prep.md +├── dsv4_inference_comparison_report.md +├── issue_47648_comment.json +└── issue_47648_comment.md +``` + +--- + +## 脚本说明 + +| 脚本 | 说明 | +|---|---| +| `scripts/bench_dsv4_comparison.py` | vLLM DeepSeek-V4 对比测试主控脚本 | +| `scripts/bench_vllm_dspark_qwen3.py` | Qwen3 DSpark 验证脚本 | +| `scripts/install_vllm_dspark.sh` | vllm-dspark 安装脚本 | +| `scripts/run_sglang_benchmark.sh` | SGLang 8卡系统测试脚本 | +| `scripts/run_sglang_max_throughput.sh` | SGLang 极限吞吐测试脚本 | +| `scripts/start_dsv4_dspark_8card.sh` | DSV4 DSpark 服务启动脚本 | +| `scripts/start_dsv4_dspark_8card_bf16kv.sh` | DSV4 DSpark bf16 KV 启动脚本 | +| `scripts/start_dsv4_dspark_8card_flashinfer.sh` | DSV4 DSpark FlashInfer 启动脚本 | +| `scripts/start_pd_single_node.sh` | PD 分离单节点启动脚本 | +| `scripts/start_sglang_dsv4_8card.sh` | SGLang DSV4 启动脚本 | +| `scripts/start_vllm_pd_single_node.sh` | vLLM PD 分离单节点启动脚本 | +| `scripts/test_block_sparse_attn.py` | block sparse attention 测试脚本 | + +--- + +## 删除的文件/目录 + +| 路径 | 原因 | +|---|---| +| `__pycache__/` | Python 字节码缓存,可重新生成 | +| `*.pid`(共 4 个) | 进程 ID 临时文件 | +| `logs/` 目录下所有文件 | 安装、部署、下载等历史调试日志 | +| `pd_logs/` 目录 | PD 分离测试日志 | +| `vllm_pd_logs/` 目录 | vLLM PD 分离测试日志 | +| `vllm-main.zip` | 与 `vllm-main/` 目录冗余,约 41MB | +| `tmp/` 目录下内容 | 临时文件 | +| 根目录下的 `.log` 文件 | 已归档或无需保留的日志 | + +--- + +## 归档到 `bench_results/` 的文件 + +| 原路径 | 目标路径 | 说明 | +|---|---|---| +| `bench_dsv4_comparison_master.log` | `bench_results/dsv4_comparison_20260705_152221/` | 本次对比测试主日志 | +| `sglang_8card.log` | `bench_results/sglang_misc/` | SGLang 8卡日志 | +| `sglang_0704_1_1048576_1.jsonl` | `bench_results/sglang_misc/` | SGLang 测试原始结果 | +| `sglang_8card_sharegpt_c*.json/log` | `bench_results/sglang_8card_systematic_20260704_120819/` | SGLang 系统测试散落结果 | +| `pd_bench_*.json` | `bench_results/pd_bench/` | PD 分离 benchmark 结果 | +| `dsv4_flash_dspark_c16_n50_*` | `bench_results/dsv4_flash_dspark_misc/` | DSV4 DSpark 早期单次测试 | + +--- + +## 路径更新说明 + +脚本和文档中的环境路径已统一更新为 `envs/` 下的新路径: + +- `/data/user1/yy/vllm-dspark/` → `/data/user1/yy/envs/vllm-dspark/` +- `/data/user1/yy/vllm/` → `/data/user1/yy/envs/vllm/` +- `/data/user1/yy/sglang/` → `/data/user1/yy/envs/sglang/` +- `/data/user1/yy/spraseattn/` → `/data/user1/yy/envs/spraseattn/` + +根目录不再保留兼容性软链接,所有环境入口统一通过 `envs/` 访问。 diff --git a/dspark_deepseekv4_fix_pr_prep.md b/dspark_deepseekv4_fix_pr_prep.md new file mode 100644 index 0000000..25c4071 --- /dev/null +++ b/dspark_deepseekv4_fix_pr_prep.md @@ -0,0 +1,286 @@ +# DeepSeek-V4-Flash-DSpark 修复记录(GitHub Issue #47648) + +> 本文件记录 vLLM DSpark 在 DeepSeek-V4-Flash 上的启动失败问题、根因、修复 diff 以及验证结果,便于后续直接用于提交 PR。 + +--- + +## 1. 问题概述 + +在 H200/SM90(以及同样走 NVIDIA 路径的 B200/SM120)上,使用 `--spec-method dspark` 部署 `DeepSeek-V4-Flash-DSpark` 时,模型初始化阶段会失败。主要表现为两类错误: + +1. **DSpark draft 权重加载路径错配**:draft 权重(checkpoint 中以 `mtp.{i}.*` 命名)没有被正确加载到模型中。 +2. **KV cache shape mismatch**:当启用 `fp8_ds_mla` layout 时,KV cache 的 per-token slot size 被错误计算为 512B,而实际需要 584B,导致 shape 断言失败。 + +关联 issue:[vllm-project/vllm#47648](https://github.com/vllm-project/vllm/issues/47648) + +--- + +## 2. 环境信息 + +- vLLM 版本:`0.23.1rc1.dev788+gfa4321de3`(vllm-dspark wheel) +- PyTorch:`2.11.0+cu129` +- GPU:8× NVIDIA H200(SM90),TP=8 +- 模型:`/data/models/DeepSeek-V4-Flash-DSpark` +- 启动命令: + +```bash +vllm serve /data/models/DeepSeek-V4-Flash-DSpark \ + --trust-remote-code \ + --tensor-parallel-size 8 \ + --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 /data/models/DeepSeek-V4-Flash-DSpark \ + --spec-tokens 5 \ + --no-disable-hybrid-kv-cache-manager \ + --disable-uvicorn-access-log \ + --port 30004 +``` + +--- + +## 3. 根因分析 + +### 3.1 根因一:DSpark draft 权重加载路径错配 + +#### 位置 + +`vllm/models/deepseek_v4/nvidia/dspark.py`,`_remap_dspark_name` 方法。 + +#### 问题描述 + +DeepSeek-V4-Flash-DSpark 的 draft 权重是嵌在目标模型 checkpoint 中的,命名格式为 `mtp.{i}.*`,例如: + +```text +mtp.0.self_attn.wq_b.weight +mtp.0.ffn.experts.0.w1.weight +mtp.1.self_attn.wq_b.weight +... +``` + +`_remap_dspark_name` 负责把这些 checkpoint key 映射到 DSpark draft 模型内部的真实参数名。原来的实现: + +```python +return f"model.layers.{self.num_hidden_layers + stage}.{rest}" +``` + +把 `mtp.0.*` 映射成了 `model.layers.{num_hidden_layers + 0}.*`(例如 `model.layers.64.*`)。 + +但实际上,DSpark draft 的 3 个 `DeepseekV4DecoderLayer` 是放在一个 `nn.ModuleList` 里的,PyTorch 真实注册的参数名只跟 `ModuleList` 的索引有关: + +```text +model.layers.0.* +model.layers.1.* +model.layers.2.* +``` + +`DeepseekV4DecoderLayer` 构造函数里传入的 `prefix=f"layers.{num_hidden_layers + i}"` 只是为了内部计算 `compress_ratio`(让 `layer_id >= num_hidden_layers` 时固定 `compress_ratio=1`),**不是真实的参数名前缀**。 + +因此,原来的映射导致所有 draft block 权重都找不到对应的模型参数,draft layer 实际上没有被正确加载。 + +#### 修复 + +把 `mtp.{i}` 的 block 权重映射到 `model.layers.{i}`: + +```python +def _remap_dspark_name(self, name: str) -> str | None: + """Map a checkpoint ``mtp.{i}.*`` name to this model's parameter path. + + Returns None for non-mtp weights (owned by the target model). + """ + m = re.match(r"mtp\.(\d+)\.(.*)", name) + if m is None: + return None + stage = int(m.group(1)) + rest = m.group(2) + # The confidence head is not wired into inference yet; drop its weights. + if rest.startswith("confidence_head."): + return None + # Head-stack params live at model level (mtp.last), context combiner at + # model level (mtp.0); everything else is a per-layer decoder block. + head_prefixes = ( + "norm.", + "hc_head_fn", + "hc_head_base", + "hc_head_scale", + "markov_head.", + ) + if rest.startswith(("main_proj.", "main_norm.")) or rest.startswith( + head_prefixes + ): + return f"model.{rest}" + # Draft layers live in a ModuleList, so their actual parameter names are + # model.layers.{stage}.* even though the prefix passed to the decoder + # layer is layers.{num_hidden_layers + stage} (for compress_ratio). + return f"model.layers.{stage}.{rest}" +``` + +### 3.2 根因二:DeepSeek-V4 KV cache shape mismatch + +#### 位置 + +`vllm/models/deepseek_v4/attention.py`,`DeepseekV4Attention.get_kv_cache_spec` 方法。 + +#### 问题描述 + +`DeepseekV4Attention.get_kv_cache_spec` 返回的 `MLAAttentionSpec` 没有设置 `kv_quant_mode`。在 `gpu_model_runner.py` 中,这个缺失导致 `cache_dtype_str` 被当成 `"auto"` 传给 `DeepseekV4FlashMLABackend.get_kv_cache_shape`,返回的 shape 是 `(num_blocks, block_size, 512)`。 + +但对于 `fp8_ds_mla` layout(UE8M0 block-scaled fp8,以 `uint8` 打包),每个 token 的 KV slot 实际大小是 584 字节,而不是 512。于是 KV cache 分配的空间不够,触发 shape mismatch / assert。 + +#### 修复 + +在 `MLAAttentionSpec` 中加上 `kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype)`: + +```python +def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec | None: + if ( + self.compress_ratio <= 1 + ): # SWA part. Allocated separately as DeepseekV4SWACache. + return None + # fp8_ds_mla is a UE8M0 block-scaled uint8 layout and needs 576B + # alignment; plain bf16 / per-tensor fp8 rows use natural element-size + # pages. + uses_fp8_ds_mla_layout = self.kv_cache_dtype == "fp8_ds_mla" + return MLAAttentionSpec( + block_size=vllm_config.cache_config.block_size, + num_kv_heads=1, + head_size=self.head_dim, + dtype=torch.uint8 if uses_fp8_ds_mla_layout else self.kv_cache_torch_dtype, + compress_ratio=self.compress_ratio, + cache_dtype_str=self.kv_cache_dtype, + alignment=576 if uses_fp8_ds_mla_layout else None, + model_version="deepseek_v4", + kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype), + ) +``` + +--- + +## 4. 修改文件清单 + +| 文件 | 修改内容 | +|---|---| +| `vllm/models/deepseek_v4/nvidia/dspark.py` | `_remap_dspark_name`:draft block 权重从 `model.layers.{num_hidden_layers + stage}` 改为 `model.layers.{stage}` | +| `vllm/models/deepseek_v4/attention.py` | `DeepseekV4Attention.get_kv_cache_spec`:在 `MLAAttentionSpec` 中补充 `kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype)` | + +> 如果上游 vllm-main 仓库与 vllm-dspark 安装包的文件结构一致,也需要同步修改 `vllm-main` 下对应路径的同名文件。 + +--- + +## 5. 完整 diff(面向 PR) + +```diff +--- a/vllm/models/deepseek_v4/nvidia/dspark.py ++++ b/vllm/models/deepseek_v4/nvidia/dspark.py +@@ -486,8 +486,8 @@ class DSparkDeepseekV4ForCausalLM(nn.Module): + if rest.startswith(("main_proj.", "main_norm.")) or rest.startswith( + head_prefixes + ): + return f"model.{rest}" +- # Draft layers live after the target layers in the decoder stack. +- return f"model.layers.{self.num_hidden_layers + stage}.{rest}" ++ # Draft layers live in a ModuleList, so their actual parameter names are ++ # model.layers.{stage}.* even though the prefix passed to the decoder ++ # layer is layers.{num_hidden_layers + stage} (for compress_ratio). ++ return f"model.layers.{stage}.{rest}" +``` + +```diff +--- a/vllm/models/deepseek_v4/attention.py ++++ b/vllm/models/deepseek_v4/attention.py +@@ -613,6 +613,7 @@ class DeepseekV4Attention(nn.Module, AttentionLayerBase, ABC): + cache_dtype_str=self.kv_cache_dtype, + alignment=576 if uses_fp8_ds_mla_layout else None, + model_version="deepseek_v4", ++ kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype), + ) +``` + +--- + +## 6. 验证结果 + +### 6.1 Qwen3 DSpark 通路验证 + +- 目标模型:`/data/models/Qwen3-4B` +- Draft 模型:`deepseek-ai/dspark_qwen3_4b_block7` +- 结果:服务启动成功,`/v1/completions` 返回结果正常,speculative decoding 工作。 + +### 6.2 DeepSeek-V4-Flash-DSpark 服务验证 + +修复后,使用第 2 节的命令可以成功启动服务,监听 `http://127.0.0.1:30004`。简单 completion 请求返回正常: + +```bash +curl -s -X POST http://127.0.0.1:30004/v1/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "/data/models/DeepSeek-V4-Flash-DSpark", + "prompt": "The capital of France is", + "max_tokens": 20, + "temperature": 0.0 + }' +# 返回: " Paris." +``` + +### 6.3 Serving benchmark + +使用 vLLM 内置 `vllm bench serve`: + +```bash +vllm bench serve \ + --host 127.0.0.1 --port 30004 \ + --backend openai \ + --dataset-name sharegpt \ + --dataset-path /data/user1/yy/datasets/ShareGPT_filtered_chat.json \ + --sharegpt-output-len 256 \ + --num-prompts 50 \ + --max-concurrency 16 \ + --endpoint /v1/completions \ + --model /data/models/DeepSeek-V4-Flash-DSpark \ + --seed 42 +``` + +结果: + +| 指标 | 数值 | +|---|---| +| Request throughput | **2.38 req/s** | +| Output token throughput | **610.47 tok/s** | +| Acceptance rate | **28.20%** | +| Mean acceptance length | **2.41** | +| Mean TTFT | 2162 ms | +| Mean TPOT | 16.85 ms | +| Total input tokens | 16381 | +| Total generated tokens | 12800 | + +服务器日志中的 SpecDecoding metrics 在稳态下 acceptance rate 落在 25%–36% 区间,与 DSpark 论文预期一致。 + +--- + +## 7. 影响范围 + +- **受影响**:所有使用 NVIDIA 路径运行 DeepSeek-V4-Flash-DSpark 的 GPU(包括 H200/SM90 的 FlashMLA 路径,以及 B200/SM120 的 FlashInfer SM120 路径)。 +- **不受影响**: + - Qwen3 DSpark(使用独立的 `qwen3_dspark.py` 实现,不共享 `_remap_dspark_name`)。 + - AMD/ROCm 和 XPU 平台(目前 DSpark 仅支持 NVIDIA)。 + +--- + +## 8. 后续 PR 待办 + +- [ ] 在 vllm-main 上同步应用以上两处修改。 +- [ ] 跑通 DeepSeek-V4-Flash-DSpark 的单元测试 / 冒烟测试。 +- [ ] 检查 `DeepseekV4IndexerCache.get_kv_cache_spec` 是否也需要补充 `kv_quant_mode`(当前未改动,因为未触发错误)。 +- [ ] 补充 DSpark draft 权重加载的回归测试(可选,但建议)。 +- [ ] 向 vllm-project/vllm 提交 PR,并在描述中引用 issue #47648。 + +--- + +## 9. 备注 + +- 本文件中的 token、路径、版本号均基于 2026-07-05 的实际运行环境。 +- GitHub issue 评论已发布:https://github.com/vllm-project/vllm/issues/47648#issuecomment-4886275279 diff --git a/dsv4_inference_comparison_report.md b/dsv4_inference_comparison_report.md new file mode 100644 index 0000000..ed006a5 --- /dev/null +++ b/dsv4_inference_comparison_report.md @@ -0,0 +1,351 @@ +# DeepSeek-V4 推理性能评估报告 + +## vLLM DSpark vs 其他投机解码方法 vs SGLang + +> 测试环境:8× NVIDIA H200 (143GB),CUDA 12.x,驱动 575.57.08 +> 测试时间:2026-07-05 +> 测试模型:`DeepSeek-V4-Flash` / `DeepSeek-V4-Flash-DSpark` +> 测试框架:vLLM 0.24.0、vLLM-dspark 0.23.1rc1.dev788、SGLang 0.5.14 + +--- + +## 1. 背景与目标 + +### 1.1 投机解码(Speculative Decoding)基本原理 + +大模型自回归生成的瓶颈在于:每生成一个 token 都要做一次完整的模型前向传播。投机解码的核心思想是: + +1. 用一个轻量的 **draft model**(或 draft head)一次性生成多个候选 token; +2. 用原始 **target model** 并行验证这些候选 token; +3. 接受与 target 模型一致的 token,从第一个不一致处重新生成。 + +理想情况下,如果 draft model 质量高,可以一次接受多个 token,从而把有效生成步长从 1 提升到 1+α(α 为平均接受长度),显著降低 latency、提升 throughput。 + +### 1.2 常见投机解码方法对比 + +| 方法 | 代表实现 | 优点 | 缺点 | +|---|---|---|---| +| **EAGLE / EAGLE-2** | vLLM EAGLE、SGLang EAGLE | 接受率高(40-70%),draft 模型小 | 需要单独训练 draft 模型,内存占用额外 | +| **MTP (Multi-Token Prediction)** | DeepSeek MTP | 与目标模型结构一致,可共享权重 | 通常只有 1-2 个 MTP head,加速比有限 | +| **DSpark** | vLLM-dspark | 半自回归 draft,并行生成整段,接受长度高 | draft 计算量较大,对 batching/scheduling 要求高 | +| **n-gram / prompt-lookup** | vLLM ngram | 无需额外模型 | 只适用于重复性文本,泛化差 | +| **Medusa** | Medusa | 多个解码头 | 需要训练,内存占用大 | + +### 1.3 DSpark 的特点 + +DSpark 是 DeepSeek 提出的块级半自回归投机解码方案,关键设计: + +- **块级 draft**:一次并行生成一个 block(多个 token),而不是逐 token; +- **非因果注意力**:draft block 内部允许未来 token 参与当前 token 计算; +- **Markov head**:在 block 内引入轻量序列依赖; +- **目标模型复用**:DSpark draft 权重与目标模型共存(`mtp.{i}.*`),可共享 embedding/head。 + +相比 EAGLE,DSpark 的 draft 更接近目标模型结构,因此接受率通常更高,但 draft 前向本身的计算量也更大。DSpark 的优势在 **高并发、长输出** 场景下更明显;在低并发或短输出场景,draft 开销可能抵消收益。 + +### 1.4 评估目标 + +本报告系统评估: + +1. **vLLM + DSpark** 在 DeepSeek-V4-Flash 上的推理优势; +2. 与 **vLLM 无投机解码** 基线的对比; +3. 与 **SGLang + EAGLE** 的对比; +4. 不同参数(`--spec-tokens`、并发度、输出长度)下的最优配置; +5. vLLM 与 SGLang 两个引擎部署 DSV4 的差异。 + +--- + +## 2. 测试环境 + +### 2.1 硬件 + +| 项目 | 配置 | +|---|---| +| GPU | 8× NVIDIA H200 143GB | +| 互联 | NVLink + NVSwitch | +| 驱动 | 575.57.08 | +| CUDA | 12.x | + +### 2.2 软件版本 + +| 引擎 | 版本 | 路径 | +|---|---|---| +| SGLang | 0.5.14 | `/data/user1/yy/envs/sglang` | +| vLLM | 0.24.0 | `/data/user1/yy/envs/vllm` | +| vLLM-dspark | 0.23.1rc1.dev788+gfa4321de3 | `/data/user1/yy/envs/vllm-dspark` | + +### 2.3 模型 + +| 模型 | 路径 | 说明 | +|---|---|---| +| DeepSeek-V4-Flash | `/data/models/DeepSeek-V4-Flash` | 标准目标模型 | +| DeepSeek-V4-Flash-DSpark | `/data/models/DeepSeek-V4-Flash-DSpark` | 内含 DSpark draft 权重 | + +### 2.4 数据集 + +- ShareGPT V4.3 unfiltered cleaned split:`/data/user1/yy/datasets/ShareGPT_V4.3_unfiltered_cleaned_split.json` + +### 2.5 测试参数 + +- Tensor Parallel:8 +- KV cache dtype:fp8 +- Block size:256 +- Max model len:auto +- Max num seqs:256 +- 输出长度:256(固定) +- Prompt 数量:200 +- 并发度:1, 16, 64 + +--- + +## 3. 测试方法 + +### 3.1 服务启动 + +各引擎服务启动命令如下: + +**vLLM-dspark + DSpark** +```bash +vllm serve /data/models/DeepSeek-V4-Flash-DSpark \ + --trust-remote-code --tensor-parallel-size 8 \ + --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 /data/models/DeepSeek-V4-Flash-DSpark \ + --spec-tokens 5 \ + --no-disable-hybrid-kv-cache-manager --disable-uvicorn-access-log \ + --port 30004 +``` + +**vLLM-dspark 无投机解码** +```bash +vllm serve /data/models/DeepSeek-V4-Flash-DSpark \ + --trust-remote-code --tensor-parallel-size 8 \ + --kv-cache-dtype fp8 --block-size 256 --max-model-len auto \ + --max-num-seqs 256 --tokenizer-mode deepseek_v4 \ + --reasoning-parser deepseek_v4 \ + --no-disable-hybrid-kv-cache-manager --disable-uvicorn-access-log \ + --port 30004 +``` + +**vLLM 0.24.0 无投机解码** +```bash +vllm serve /data/models/DeepSeek-V4-Flash \ + --trust-remote-code --tensor-parallel-size 8 \ + --kv-cache-dtype fp8 --block-size 256 --max-model-len auto \ + --max-num-seqs 256 --tokenizer-mode deepseek_v4 \ + --reasoning-parser deepseek_v4 \ + --disable-uvicorn-access-log --port 30005 +``` + +**SGLang + EAGLE** +```bash +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 \ + --host 0.0.0.0 --port 30000 +``` + +### 3.2 Benchmark 命令 + +使用各引擎自带的 serving benchmark: + +**vLLM / vLLM-dspark** +```bash +vllm bench serve --host 127.0.0.1 --port --backend openai \ + --dataset-name sharegpt --dataset-path \ + --sharegpt-output-len 256 --num-prompts 200 \ + --max-concurrency --endpoint /v1/completions \ + --model --seed 42 --save-result --result-dir +``` + +**SGLang** +```bash +python -m sglang.bench_serving --backend sglang --host 127.0.0.1 --port 30000 \ + --dataset-name sharegpt --dataset-path \ + --num-prompts 2000 --sharegpt-output-len 256 \ + --max-concurrency --model --seed 42 --output-file +``` + +> 注:SGLang 历史测试使用 2000 prompts,vLLM-dspark / vLLM 本次测试使用 200 prompts,对比时主要关注相对趋势而非绝对数值。 + +--- + +## 4. 测试结果 + +### 4.1 总体对比(请求吞吐 req/s) + +| 配置 | 引擎 | 投机方法 | 并发=1 | 并发=16 | 并发=64 | 并发=128 | 并发=256 | +|---|---|---:|---:|---:|---:|---:|---:| +| SGLang + EAGLE | sglang | EAGLE (4 draft) | 1.17 | 6.03 | 9.64 | 14.35 | 22.06 | +| vLLM-dspark + DSpark (st=3) | vllm-dspark | DSpark | 1.03 | 8.24 | **14.53** | - | - | +| vLLM-dspark + DSpark (st=5) | vllm-dspark | DSpark | 1.07 | 7.70 | 9.36 | - | - | +| vLLM-dspark + DSpark (st=7) | vllm-dspark | DSpark | 1.03 | 7.46 | 9.13 | - | - | +| vLLM-dspark 无投机 | vllm-dspark | 无 | 0.59 | 5.61 | 7.34 | - | - | +| vLLM 0.24.0 无投机 | vllm | 无 | 0.58 | 5.05 | 6.85 | - | - | + +### 4.2 总体对比(输出 token 吞吐 tok/s) + +| 配置 | 并发=1 | 并发=16 | 并发=64 | 并发=128 | 并发=256 | +|---|---:|---:|---:|---:|---:| +| SGLang + EAGLE | 243.51 | 1259.0 | 2012.43 | 2996.37 | 4608.35 | +| vLLM-dspark + DSpark (st=3) | 254.39 | 1992.18 | **3507.81** | - | - | +| vLLM-dspark + DSpark (st=5) | 257.18 | 1862.34 | 2210.46 | - | - | +| vLLM-dspark + DSpark (st=7) | 251.58 | 1772.30 | 2218.58 | - | - | +| vLLM-dspark 无投机 | 144.62 | 1364.00 | 1803.98 | - | - | +| vLLM 0.24.0 无投机 | 138.26 | 1180.98 | 1641.89 | - | - | + +### 4.3 vLLM-dspark 不同 `--spec-tokens` 对比 + +| spec-tokens | 并发 | req/s | out tok/s | 平均接受长度 | 接受率 | mean TTFT (ms) | mean TPOT (ms) | mean ITL (ms) | +|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| 3 | 1 | 1.03 | 254.39 | 2.22 | 40.56% | 78.07 | 3.64 | 8.00 | +| 3 | 16 | 8.24 | 1992.18 | 2.19 | 39.66% | 90.90 | 7.63 | 16.23 | +| 3 | 64 | **14.53** | **3507.81** | 2.16 | 38.71% | 355.29 | 15.84 | 33.13 | +| 5 | 1 | 1.07 | 257.18 | 2.44 | 28.71% | 88.43 | 3.53 | 8.56 | +| 5 | 16 | 7.70 | 1862.34 | 2.39 | 27.87% | 92.23 | 8.19 | 18.90 | +| 5 | 64 | 9.36 | 2210.46 | 2.40 | 28.07% | 453.06 | 26.19 | 60.10 | +| 7 | 1 | 1.03 | 251.58 | 2.43 | 20.45% | 73.16 | 3.70 | 8.92 | +| 7 | 16 | 7.46 | 1772.30 | 2.41 | 20.10% | 106.43 | 8.92 | 20.07 | +| 7 | 64 | 9.13 | 2218.58 | 2.42 | 20.25% | 351.01 | 26.25 | 61.33 | + +> 数据来源:`/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/vllm-dspark-dspark-st{3,5,7}_c{1,16,64}.json` + +### 4.4 延迟指标对比 + +| 配置 | 并发 | mean TTFT (ms) | mean TPOT (ms) | mean ITL (ms) | +|---|---:|---:|---:|---:| +| vLLM-dspark + DSpark (st=3) | 16 | 90.90 | 7.63 | 16.23 | +| vLLM-dspark + DSpark (st=5) | 16 | 92.23 | 8.19 | 18.90 | +| vLLM-dspark + DSpark (st=7) | 16 | 106.43 | 8.92 | 20.07 | +| vLLM-dspark 无投机 | 16 | 113.49 | 11.06 | 11.02 | +| vLLM 0.24.0 无投机 | 16 | 332.87 | 11.73 | 11.74 | +| SGLang + EAGLE | 16 | 168.95 | 12.67 | - | + +### 4.5 投机解码 vs 无投机解码加速比 + +以 vLLM-dspark 无投机为基线: + +| 配置 | 并发=1 | 并发=16 | 并发=64 | +|---|---:|---:|---:| +| DSpark st=3 | 1.75× | 1.47× | **1.98×** | +| DSpark st=5 | 1.80× | 1.37× | 1.28× | +| DSpark st=7 | 1.74× | 1.33× | 1.24× | + +以 vLLM 0.24.0 无投机为基线: + +| 配置 | 并发=1 | 并发=16 | 并发=64 | +|---|---:|---:|---:| +| DSpark st=3 | 1.78× | 1.63× | 2.12× | +| DSpark st=5 | 1.84× | 1.52× | 1.37× | +| DSpark st=7 | 1.77× | 1.48× | 1.33× | + +--- + +## 5. 结果分析 + +### 5.1 DSpark 的优势场景 + +- **高并发**:当 batch size 足够大时,DSpark 的 draft 计算可以被充分并行化,接受率带来的收益超过 draft 开销。`--spec-tokens 3` 在并发 64 时达到 14.53 req/s,几乎是 vLLM-dspark 无投机的 2 倍; +- **长输出**:输出 token 越多,投机解码节省的 target 前向次数越多; +- **低延迟与高吞吐兼得**:在并发 16 时,DSpark st=3 的 mean TTFT 仅 90.90 ms,mean TPOT 7.63 ms,均优于无投机基线和 SGLang EAGLE。 + +### 5.2 DSpark 的劣势场景 + +- **单请求**:虽然单请求也有 1.7-1.8× 加速,但加速比主要来自接受率;draft 模型的固定开销仍然存在; +- **`--spec-tokens` 过大**:st=5 和 st=7 在高并发(64)下性能反而下降。原因是 draft tokens 数量增加后,验证阶段的计算量和 KV cache 压力增大,而接受率并未提升(st=7 接受率仅 20%); +- **TTFT 抖动**:高并发下 DSpark 的 P99 TTFT 明显升高(st=5 c=64 时 P99 TTFT 达 5381 ms),说明调度/内存压力较大。 + +### 5.3 不同 `--spec-tokens` 的选择 + +| spec-tokens | 最佳并发 | 表现 | +|---|---|---| +| 3 | 高并发(64+) | 接受率最高(~40%),吞吐最高,验证开销最小 | +| 5 | 中低并发(1-16) | 接受长度略高(2.4),单请求吞吐最优 | +| 7 | 不推荐 | 接受率下降(~20%),验证开销大,高并发下性能倒退 | + +### 5.4 与 EAGLE 的对比 + +- **接受率**:EAGLE 接受长度稳定在 2.61,DSpark st=3 接受长度 2.16-2.22,但 DSpark 接受率(token 级别)达 38-40%,说明每段 draft 的质量并不低; +- **吞吐**:vLLM-dspark + DSpark st=3 在并发 64 时(14.53 req/s)已接近 SGLang EAGLE 在并发 128 时(14.35 req/s)。SGLang 在更高并发(256-512)下仍能通过扩大 batch 提升吞吐,但这是以极高的 TTFT 为代价的; +- **延迟**:vLLM-dspark DSpark 在中低并发下的 TTFT/TPOT 明显优于 SGLang EAGLE; +- **实现成熟度**:SGLang 的 EAGLE 实现更成熟稳定;vLLM-dspark 的 DSpark 仍在快速迭代,部分参数组合(如 st=5/7 @ c=64)出现性能倒退。 + +### 5.5 vLLM-dspark vs vLLM 0.24.0 + +- 在完全无投机解码的情况下,vLLM-dspark(0.23.1rc)比 vLLM 0.24.0 更快: + - 并发 1:0.59 vs 0.58 req/s(接近) + - 并发 16:5.61 vs 5.05 req/s(+11%) + - 并发 64:7.34 vs 6.85 req/s(+7%) +- 这说明 vLLM-dspark 分支对 DeepSeek-V4 的 MLA / MoE / sparse attention 路径有更针对性的优化。 + +### 5.6 vLLM vs SGLang 引擎差异 + +- **启动与兼容性**:vLLM-dspark 对 DSV4 支持更直接,SGLang 对 EAGLE 支持更成熟; +- **调度策略**:SGLang 的 radix attention / chunked prefill 在长上下文场景有优势; +- **性能天花板**:SGLang EAGLE 在极高并发(512+)下吞吐更高,但延迟失控;vLLM-dspark DSpark 在中高并发(16-64)下延迟更优; +- **数据可比性**:SGLang 历史测试使用 2000 prompts,vLLM 本次仅 200 prompts,绝对数值需谨慎对比。 + +--- + +## 6. 最优参数建议 + +### 6.1 低延迟场景(单用户 / 低并发) + +- 推荐:**DSpark `--spec-tokens 5`**; +- 原因:单请求下 st=5 的 req/s 最高(1.07),TPOT/ITL 与 st=3/7 接近,接受长度最长(2.44)。 + +### 6.2 高吞吐场景(高并发 / 在线服务) + +- 推荐:**DSpark `--spec-tokens 3`**,`--max-num-seqs 256`,`--max-concurrency 64-128`; +- 原因:st=3 在高并发下接受率最高、验证开销最小,实测吞吐最高(14.53 req/s,3507 tok/s)。 + +### 6.3 长输出场景 + +- 推荐:开启 DSpark,`--spec-tokens 3-5`; +- 原因:输出越长,每次投机成功节省的 target 前向越多。st=3 更适合高并发,st=5 更适合中低并发。 + +### 6.4 短输出场景 + +- 推荐:**关闭投机解码** 或使用 `--spec-tokens 3`; +- 原因:短输出下投机收益有限,且 st=5/7 的验证开销可能抵消收益。 + +### 6.5 避免的参数组合 + +- **避免 `--spec-tokens 7` @ 高并发**:接受率仅 20%,高并发下吞吐不如 st=3/5; +- **避免 `--spec-tokens 5` @ 极高并发**:P99 TTFT 大幅升高,服务质量下降。 + +--- + +## 7. 结论 + +1. **vLLM-dspark + DSpark 在 DeepSeek-V4-Flash 上显著优于无投机解码基线**,最佳配置(st=3, c=64)比 vLLM-dspark 无投机快 **1.98×**,比 vLLM 0.24.0 无投机快 **2.12×**; +2. **DSpark 的最佳 `--spec-tokens` 取决于并发度**: + - 低并发(1-16):`--spec-tokens 5` 综合最优; + - 高并发(64+):`--spec-tokens 3` 综合最优; +3. **与 SGLang EAGLE 相比**,vLLM-dspark DSpark 在中高并发下延迟更优,吞吐接近;SGLang 在极限并发下吞吐更高但延迟失控; +4. **vLLM-dspark 分支对 DSV4 的优化效果明显**,即使无投机解码也比 vLLM 0.24.0 主分支更快; +5. **DSpark 目前仍不够稳定**,部分参数组合(st=5/7 @ c=64)出现性能倒退和高 TTFT 抖动,生产部署前需要针对实际负载调参。 + +--- + +## 8. 附录 + +### 8.1 原始数据文件 + +- vLLM-dspark / vLLM 测试结果:`/data/user1/yy/bench_results/dsv4_comparison_20260705_152221/` +- SGLang 历史结果: + - `/data/user1/yy/bench_results/sglang_8card_systematic_20260704_120819/` + - `/data/user1/yy/bench_results/sglang_8card_max_throughput_20260705_030839/` + +### 8.2 测试脚本 + +- 主控脚本:`/data/user1/yy/bench_dsv4_comparison.py` +- 指标提取脚本:`/tmp/extract_dsv4_metrics.py` + +### 8.3 已知限制 + +- vLLM-dspark 的 DSpark 实现较新,部分参数组合可能不稳定; +- 本次测试未覆盖 PD 分离、前缀缓存、不同输出长度等高级特性; +- SGLang 与 vLLM 的 prompt 数量不一致(2000 vs 200),绝对数值对比仅供参考; +- 不同数据集和 prompt 长度分布会影响结果。 diff --git a/scripts/SLO_STANDARDS.md b/scripts/SLO_STANDARDS.md new file mode 100644 index 0000000..ad24540 --- /dev/null +++ b/scripts/SLO_STANDARDS.md @@ -0,0 +1,33 @@ +# 推理服务 SLO 标准 + +> 记录各模型层级(Tier)的延迟目标。TTFT 以 **P95** 为主要关注指标。 + +## 模型层级定义 + +| 层级 | 名称 | 参数规模 | 典型模型 | 应用场景 | 核心目标 | +|---|---|---|---|---|---| +| S0 | 轻量层 | < 30B | Llama 3.1 8B Instruct | 高并发轻推理 | 高并发轻推理 | +| S1 | 实时交互层 | [30B, 100B) | Llama 3.3 70B Instruct | Chat / Copilot / Agent | 极低延迟 | +| S2 | 均衡服务层 | [100B, 500B) | DeepSeek V4 Flash、MiniMax M2.7 | 企业 API | 性价比平衡 | +| S3 | 深度推理层 | [500B, 1T) | DeepSeek V3.2 | 高复杂推理 | 稳定吞吐 | +| S4 | 超级推理层 | ≥ 1T | DS V4 Pro | 深度思考 | 智能优先 | + +## TTFT SLO(P95 为主要关注指标) + +| 指标 | S0 | S1 | S2 | S3 | S4 | +|---|---|---|---|---|---| +| P50 | < 500ms | < 800ms | < 1.5s | < 2s | < 1.5s | +| P95 | < 0.4s | < 1s | < 3s | < 5s | < 3s | + +## TPOT SLO + +| 指标 | S0 | S1 | S2 | S3 | S4 | +|---|---|---|---|---|---| +| TPOT | < 20ms | < 40ms | < 50ms | < 50ms | < 50ms | + +## 说明 + +- **TTFT**(Time To First Token):从请求到达服务端到首个 token 返回的延迟。 +- **TPOT**(Time Per Output Token):除首 token 外,平均每输出一个 token 的耗时。 +- 当前重点关注 **TTFT P95**,即 95% 请求的 TTFT 应满足对应层级的阈值。 +- _benchmark 输出应同时给出 P50 / P90 / P95 / P99 TTFT,但评估以 P95 为准。_ diff --git a/scripts/analyze_dspark_nsys.py b/scripts/analyze_dspark_nsys.py new file mode 100644 index 0000000..bd8ffb9 --- /dev/null +++ b/scripts/analyze_dspark_nsys.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Analyze nsys traces for DSpark profiling. + +Usage: + python scripts/analyze_dspark_nsys.py +""" + +import json +import subprocess +import sys +from pathlib import Path + + +def run_nsys_report(rep_file: Path, report_name: str) -> str: + cmd = [ + "nsys", "stats", + "--report", report_name, + "--format", "json", + str(rep_file), + ] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print(f"nsys {report_name} failed: {result.stderr}") + return "" + return result.stdout + + +def summarize_cuda_sum(rep_file: Path) -> list[dict]: + """Return top CUDA kernels by total time.""" + text = run_nsys_report(rep_file, "cuda_kernel_sum") + if not text: + return [] + # nsys stats --format json outputs JSON after some header lines. + # Find the first '[' character. + start = text.find("[") + if start < 0: + return [] + data = json.loads(text[start:]) + # Sort by total time descending. + rows = data[1:] if len(data) > 1 else data + rows_sorted = sorted(rows, key=lambda x: x.get("Total Time (ns)", 0), reverse=True) + return rows_sorted[:50] + + +def summarize_nvtx_sum(rep_file: Path) -> list[dict]: + text = run_nsys_report(rep_file, "nvtx_sum") + start = text.find("[") + if start < 0: + return [] + data = json.loads(text[start:]) + rows = data[1:] if len(data) > 1 else data + rows_sorted = sorted(rows, key=lambda x: x.get("Total Time (ns)", 0), reverse=True) + return rows_sorted[:30] + + +def main(): + if len(sys.argv) < 2: + print("Usage: analyze_dspark_nsys.py ") + sys.exit(1) + rep_file = Path(sys.argv[1]) + if not rep_file.exists(): + print(f"File not found: {rep_file}") + sys.exit(1) + + print(f"Analyzing {rep_file}...\n") + + cuda_kernels = summarize_cuda_sum(rep_file) + print("=== Top CUDA Kernels by Total Time ===") + for row in cuda_kernels[:20]: + name = row.get("Name", "N/A") + total_ns = row.get("Total Time (ns)", 0) + count = row.get("Instances", 0) + avg_ns = row.get("Avg (ns)", 0) + print(f" {total_ns/1e6:8.2f} ms {count:6d} calls avg={avg_ns/1e6:6.3f} ms {name[:80]}") + + nvtx = summarize_nvtx_sum(rep_file) + if nvtx: + print("\n=== Top NVTX Ranges ===") + for row in nvtx[:20]: + name = row.get("Range", "N/A") + total_ns = row.get("Total Time (ns)", 0) + count = row.get("Instances", 0) + print(f" {total_ns/1e6:8.2f} ms {count:6d} calls {name[:80]}") + + +if __name__ == "__main__": + main() diff --git a/scripts/bench_dsv4_comparison.py b/scripts/bench_dsv4_comparison.py new file mode 100644 index 0000000..e9acf5d --- /dev/null +++ b/scripts/bench_dsv4_comparison.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +"""Benchmark orchestration for DeepSeek-V4 inference comparison. + +Compares: +- vllm-dspark + DeepSeek-V4-Flash-DSpark + DSpark (various spec-tokens) +- vllm-dspark + DeepSeek-V4-Flash-DSpark without spec decode +- vllm (0.24.0) + DeepSeek-V4-Flash without spec decode +- sglang + DeepSeek-V4-Flash + EAGLE (reuse existing results) +""" + +import json +import os +import subprocess +import sys +import time +import urllib.request +from datetime import datetime +from pathlib import Path + +ROOT = Path("/data/user1/yy") +RESULT_DIR = ROOT / "bench_results" / f"dsv4_comparison_{datetime.now().strftime('%Y%m%d_%H%M%S')}" +RESULT_DIR.mkdir(parents=True, exist_ok=True) + +DATASET = "/data/user1/yy/datasets/ShareGPT_V4.3_unfiltered_cleaned_split.json" +NUM_PROMPTS = 200 +SEED = 42 +OUTPUT_LEN = 256 +HOST = "127.0.0.1" + +# Service configs to benchmark +SERVICES = [ + { + "name": "vllm-dspark-dspark-st5", + "engine": "vllm-dspark", + "model": "/data/models/DeepSeek-V4-Flash-DSpark", + "port": 30004, + "spec_method": "dspark", + "spec_tokens": 5, + "cmd": [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "serve", + "/data/models/DeepSeek-V4-Flash-DSpark", + "--trust-remote-code", + "--tensor-parallel-size", "8", + "--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", "/data/models/DeepSeek-V4-Flash-DSpark", + "--spec-tokens", "5", + "--no-disable-hybrid-kv-cache-manager", + "--disable-uvicorn-access-log", + "--port", "30004", + ], + "env": {"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7", "TMPDIR": str(ROOT / "tmp")}, + "bench_backend": "openai", + }, + { + "name": "vllm-dspark-nospec", + "engine": "vllm-dspark", + "model": "/data/models/DeepSeek-V4-Flash-DSpark", + "port": 30004, + "spec_method": None, + "cmd": [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "serve", + "/data/models/DeepSeek-V4-Flash-DSpark", + "--trust-remote-code", + "--tensor-parallel-size", "8", + "--kv-cache-dtype", "fp8", + "--block-size", "256", + "--max-model-len", "auto", + "--max-num-seqs", "256", + "--tokenizer-mode", "deepseek_v4", + "--reasoning-parser", "deepseek_v4", + "--no-disable-hybrid-kv-cache-manager", + "--disable-uvicorn-access-log", + "--port", "30004", + ], + "env": {"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7", "TMPDIR": str(ROOT / "tmp")}, + "bench_backend": "openai", + }, + { + "name": "vllm-dspark-dspark-st3", + "engine": "vllm-dspark", + "model": "/data/models/DeepSeek-V4-Flash-DSpark", + "port": 30004, + "spec_method": "dspark", + "spec_tokens": 3, + "cmd": [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "serve", + "/data/models/DeepSeek-V4-Flash-DSpark", + "--trust-remote-code", + "--tensor-parallel-size", "8", + "--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", "/data/models/DeepSeek-V4-Flash-DSpark", + "--spec-tokens", "3", + "--no-disable-hybrid-kv-cache-manager", + "--disable-uvicorn-access-log", + "--port", "30004", + ], + "env": {"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7", "TMPDIR": str(ROOT / "tmp")}, + "bench_backend": "openai", + }, + { + "name": "vllm-dspark-dspark-st7", + "engine": "vllm-dspark", + "model": "/data/models/DeepSeek-V4-Flash-DSpark", + "port": 30004, + "spec_method": "dspark", + "spec_tokens": 7, + "cmd": [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "serve", + "/data/models/DeepSeek-V4-Flash-DSpark", + "--trust-remote-code", + "--tensor-parallel-size", "8", + "--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", "/data/models/DeepSeek-V4-Flash-DSpark", + "--spec-tokens", "7", + "--no-disable-hybrid-kv-cache-manager", + "--disable-uvicorn-access-log", + "--port", "30004", + ], + "env": {"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7", "TMPDIR": str(ROOT / "tmp")}, + "bench_backend": "openai", + }, + { + "name": "vllm-main-nospec", + "engine": "vllm", + "model": "/data/models/DeepSeek-V4-Flash", + "port": 30005, + "spec_method": None, + "cmd": [ + "/data/user1/yy/envs/vllm/bin/vllm", "serve", + "/data/models/DeepSeek-V4-Flash", + "--trust-remote-code", + "--tensor-parallel-size", "8", + "--kv-cache-dtype", "fp8", + "--block-size", "256", + "--max-model-len", "auto", + "--max-num-seqs", "256", + "--tokenizer-mode", "deepseek_v4", + "--reasoning-parser", "deepseek_v4", + "--disable-uvicorn-access-log", + "--port", "30005", + ], + "env": {"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7", "TMPDIR": str(ROOT / "tmp")}, + "bench_backend": "openai", + }, +] + +CONCURRENCIES = [1, 16, 64] + + +def log(msg): + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}", flush=True) + + +def wait_for_health(port, timeout=300): + url = f"http://{HOST}:{port}/health" + start = time.time() + while time.time() - start < timeout: + try: + with urllib.request.urlopen(url, timeout=5) as resp: + if resp.status == 200: + return True + except Exception: + pass + time.sleep(2) + return False + + +def start_service(service): + log(f"Starting {service['name']} on port {service['port']}...") + env = os.environ.copy() + env.update(service["env"]) + log_file = RESULT_DIR / f"{service['name']}_service.log" + proc = subprocess.Popen( + service["cmd"], + stdout=open(log_file, "w"), + stderr=subprocess.STDOUT, + env=env, + ) + if not wait_for_health(service["port"]): + log(f"ERROR: {service['name']} failed to start") + proc.terminate() + return None + log(f"{service['name']} is ready") + return proc + + +def stop_service(proc, name): + if proc is None: + return + log(f"Stopping {name} (pid {proc.pid})...") + proc.terminate() + try: + proc.wait(timeout=60) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait() + log(f"{name} stopped") + + +def run_benchmark(service, concurrency): + name = service["name"] + port = service["port"] + backend = service["bench_backend"] + result_file = RESULT_DIR / f"{name}_c{concurrency}.json" + log_file = RESULT_DIR / f"{name}_c{concurrency}.log" + + if service["engine"] == "vllm-dspark": + bench_cmd = [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "bench", "serve", + "--host", HOST, + "--port", str(port), + "--backend", backend, + "--dataset-name", "sharegpt", + "--dataset-path", DATASET, + "--sharegpt-output-len", str(OUTPUT_LEN), + "--num-prompts", str(NUM_PROMPTS), + "--max-concurrency", str(concurrency), + "--endpoint", "/v1/completions", + "--model", service["model"], + "--seed", str(SEED), + "--save-result", + "--result-dir", str(RESULT_DIR), + "--result-filename", result_file.name, + ] + else: + bench_cmd = [ + "/data/user1/yy/envs/vllm/bin/vllm", "bench", "serve", + "--host", HOST, + "--port", str(port), + "--backend", backend, + "--dataset-name", "sharegpt", + "--dataset-path", DATASET, + "--sharegpt-output-len", str(OUTPUT_LEN), + "--num-prompts", str(NUM_PROMPTS), + "--max-concurrency", str(concurrency), + "--endpoint", "/v1/completions", + "--model", service["model"], + "--seed", str(SEED), + "--save-result", + "--result-dir", str(RESULT_DIR), + "--result-filename", result_file.name, + ] + + log(f"Running benchmark {name} concurrency={concurrency}...") + start = time.time() + with open(log_file, "w") as f: + proc = subprocess.Popen(bench_cmd, stdout=f, stderr=subprocess.STDOUT) + proc.wait() + duration = time.time() - start + log(f"Benchmark {name} c={concurrency} finished in {duration:.1f}s, exit={proc.returncode}") + + if result_file.exists(): + with open(result_file) as f: + data = json.load(f) + return { + "service": name, + "engine": service["engine"], + "spec_method": service.get("spec_method"), + "spec_tokens": service.get("spec_tokens"), + "concurrency": concurrency, + "request_throughput": data.get("request_throughput"), + "output_throughput": data.get("output_throughput"), + "total_input_tokens": data.get("total_input_tokens"), + "total_output_tokens": data.get("total_output_tokens"), + "duration_s": data.get("duration_s"), + "result_file": str(result_file), + } + else: + log(f"WARNING: result file {result_file} not found") + return { + "service": name, + "engine": service["engine"], + "spec_method": service.get("spec_method"), + "spec_tokens": service.get("spec_tokens"), + "concurrency": concurrency, + "error": "result file missing", + "log_file": str(log_file), + } + + +def main(): + summary = [] + for service in SERVICES: + proc = start_service(service) + if proc is None: + continue + try: + for concurrency in CONCURRENCIES: + result = run_benchmark(service, concurrency) + summary.append(result) + # Save incremental summary + with open(RESULT_DIR / "summary.json", "w") as f: + json.dump(summary, f, indent=2) + finally: + stop_service(proc, service["name"]) + # Small gap between services + time.sleep(10) + + log(f"All benchmarks complete. Results in {RESULT_DIR}") + with open(RESULT_DIR / "summary.json", "w") as f: + json.dump(summary, f, indent=2) + + +if __name__ == "__main__": + main() diff --git a/scripts/bench_vllm_dspark_qwen3.py b/scripts/bench_vllm_dspark_qwen3.py new file mode 100644 index 0000000..ff427d9 --- /dev/null +++ b/scripts/bench_vllm_dspark_qwen3.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +"""Throughput benchmark for vLLM DSpark service (Qwen3-4B + dspark_qwen3_4b_block7).""" + +import argparse +import asyncio +import json +import random +import time +from dataclasses import dataclass, field +from datetime import datetime +from typing import List + +import aiohttp +import numpy as np +from transformers import AutoTokenizer + + +@dataclass +class RequestResult: + prompt_len: int = 0 + output_len: int = 0 + ttft_ms: float = 0.0 + tpot_ms: float = 0.0 + e2e_ms: float = 0.0 + success: bool = False + error: str = "" + + +async def async_request( + session: aiohttp.ClientSession, + url: str, + model_name: str, + prompt: str, + max_tokens: int, + prompt_len: int, + result: RequestResult, +) -> None: + payload = { + "model": model_name, + "prompt": prompt, + "max_tokens": max_tokens, + "temperature": 0.0, + "stream": True, + "stream_options": {"include_usage": True}, + } + result.prompt_len = prompt_len + start = time.perf_counter() + first_token_time = None + token_times: List[float] = [] + output_text = "" + try: + async with session.post(url, json=payload) as resp: + resp.raise_for_status() + async for line in resp.content: + line = line.decode("utf-8").strip() + if not line or line.startswith(":"): + continue + if line.startswith("data: "): + data = line[len("data: "):] + if data == "[DONE]": + break + chunk = json.loads(data) + choices = chunk.get("choices", []) + if choices: + delta = choices[0].get("text", "") + if delta: + now = time.perf_counter() + token_times.append(now) + output_text += delta + if first_token_time is None: + first_token_time = now + end = time.perf_counter() + result.e2e_ms = (end - start) * 1000.0 + if first_token_time is not None: + result.ttft_ms = (first_token_time - start) * 1000.0 + if len(token_times) >= 2: + # TPOT: average time between consecutive tokens + intervals = [ + token_times[i] - token_times[i - 1] + for i in range(1, len(token_times)) + ] + result.tpot_ms = sum(intervals) / len(intervals) * 1000.0 + result.output_len = len(token_times) + result.success = True + except Exception as e: + result.e2e_ms = (time.perf_counter() - start) * 1000.0 + result.error = str(e) + result.success = False + + +async def run_concurrency_benchmark( + url: str, + model_name: str, + prompts: List[str], + prompt_lens: List[int], + max_tokens: int, + concurrency: int, + num_prompts: int, + seed: int, +) -> dict: + rng = random.Random(seed) + indices = [rng.randrange(len(prompts)) for _ in range(num_prompts)] + + semaphore = asyncio.Semaphore(concurrency) + results: List[RequestResult] = [RequestResult() for _ in range(num_prompts)] + + async def bounded_request(idx: int, i: int): + async with semaphore: + async with aiohttp.ClientSession() as session: + await async_request( + session, + url, + model_name, + prompts[idx], + max_tokens, + prompt_lens[idx], + results[i], + ) + + start = time.perf_counter() + await asyncio.gather(*[bounded_request(idx, i) for i, idx in enumerate(indices)]) + duration = time.perf_counter() - start + + successes = [r for r in results if r.success] + failures = [r for r in results if not r.success] + if not successes: + return { + "concurrency": concurrency, + "num_prompts": num_prompts, + "duration_s": duration, + "success_count": 0, + "error": "all requests failed", + "errors": [r.error for r in failures[:5]], + } + + total_in_tokens = sum(r.prompt_len for r in successes) + total_out_tokens = sum(r.output_len for r in successes) + total_tokens = total_in_tokens + total_out_tokens + + ttfts = [r.ttft_ms for r in successes] + tpots = [r.tpot_ms for r in successes] + e2es = [r.e2e_ms for r in successes] + + return { + "concurrency": concurrency, + "num_prompts": num_prompts, + "duration_s": duration, + "success_count": len(successes), + "fail_count": len(failures), + "input_tokens": total_in_tokens, + "output_tokens": total_out_tokens, + "total_tokens": total_tokens, + "request_throughput": len(successes) / duration, + "input_throughput": total_in_tokens / duration, + "output_throughput": total_out_tokens / duration, + "total_throughput": total_tokens / duration, + "mean_ttft_ms": float(np.mean(ttfts)), + "p50_ttft_ms": float(np.percentile(ttfts, 50)), + "p99_ttft_ms": float(np.percentile(ttfts, 99)), + "mean_tpot_ms": float(np.mean(tpots)), + "p50_tpot_ms": float(np.percentile(tpots, 50)), + "p99_tpot_ms": float(np.percentile(tpots, 99)), + "mean_e2e_ms": float(np.mean(e2es)), + "p50_e2e_ms": float(np.percentile(e2es, 50)), + "p99_e2e_ms": float(np.percentile(e2es, 99)), + "errors": [r.error for r in failures[:5]], + } + + +def load_sharegpt_prompts(path: str, tokenizer, max_samples: int = 2000): + with open(path, "r") as f: + data = json.load(f) + prompts = [] + lens = [] + for item in data: + conv = item.get("conversations", []) + for turn in conv: + if turn.get("from") == "human": + prompt = turn.get("value", "") + if prompt: + prompts.append(prompt) + lens.append(len(tokenizer.encode(prompt))) + break + if len(prompts) >= max_samples: + break + return prompts, lens + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=30003) + parser.add_argument("--model", default="/data/models/Qwen3-4B") + parser.add_argument("--dataset", default="/data/user1/yy/datasets/ShareGPT_filtered_chat.json") + parser.add_argument("--tokenizer", default="/data/models/Qwen3-4B") + parser.add_argument("--max-tokens", type=int, default=256) + parser.add_argument("--num-prompts", type=int, default=500) + parser.add_argument("--concurrency", type=int, nargs="+", default=[1, 4, 16, 64, 128]) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--output-dir", default="/data/user1/yy/bench_results") + args = parser.parse_args() + + url = f"http://{args.host}:{args.port}/v1/completions" + print(f"Loading tokenizer from {args.tokenizer} ...") + tokenizer = AutoTokenizer.from_pretrained(args.tokenizer) + print(f"Loading dataset from {args.dataset} ...") + prompts, prompt_lens = load_sharegpt_prompts(args.dataset, tokenizer) + print(f"Loaded {len(prompts)} prompts, prompt lens: min={min(prompt_lens)}, max={max(prompt_lens)}, mean={sum(prompt_lens)/len(prompt_lens):.1f}") + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + result_dir = f"{args.output_dir}/vllm_dspark_qwen3_{timestamp}" + import os + os.makedirs(result_dir, exist_ok=True) + summary_file = f"{result_dir}/summary.json" + + summary = { + "model": args.model, + "draft_model": "deepseek-ai/dspark_qwen3_4b_block7", + "url": url, + "max_tokens": args.max_tokens, + "num_prompts": args.num_prompts, + "dataset": args.dataset, + "timestamp": timestamp, + "results": [], + } + + print("\nStarting benchmark sweeps...") + print(f"{'Concurrency':>12} {'Req/s':>10} {'In tok/s':>12} {'Out tok/s':>12} {'Total tok/s':>13} {'TTFT(ms)':>10} {'TPOT(ms)':>10} {'E2E(ms)':>10}") + print("-" * 100) + + for c in args.concurrency: + print(f"Running concurrency={c} ...", flush=True) + result = asyncio.run( + run_concurrency_benchmark( + url, + args.model, + prompts, + prompt_lens, + args.max_tokens, + c, + args.num_prompts, + args.seed, + ) + ) + summary["results"].append(result) + with open(summary_file, "w") as f: + json.dump(summary, f, indent=2) + if result.get("success_count", 0) == 0: + print(f"{c:>12} FAILED: {result.get('error', 'unknown')}") + continue + print( + f"{c:>12} " + f"{result['request_throughput']:>10.2f} " + f"{result['input_throughput']:>12.2f} " + f"{result['output_throughput']:>12.2f} " + f"{result['total_throughput']:>13.2f} " + f"{result['mean_ttft_ms']:>10.1f} " + f"{result['mean_tpot_ms']:>10.1f} " + f"{result['mean_e2e_ms']:>10.1f}" + ) + + print(f"\nSummary saved to {summary_file}") + + +if __name__ == "__main__": + main() diff --git a/scripts/benchmark_dspark_0707/README.md b/scripts/benchmark_dspark_0707/README.md new file mode 100644 index 0000000..08cbf61 --- /dev/null +++ b/scripts/benchmark_dspark_0707/README.md @@ -0,0 +1,60 @@ +# 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 new file mode 100755 index 0000000..0c4a124 --- /dev/null +++ b/scripts/benchmark_dspark_0707/bench_dspark_focused.sh @@ -0,0 +1,64 @@ +#!/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 new file mode 100755 index 0000000..45505f6 --- /dev/null +++ b/scripts/benchmark_dspark_0707/bench_dspark_p1.sh @@ -0,0 +1,60 @@ +#!/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 new file mode 100755 index 0000000..5a2be94 --- /dev/null +++ b/scripts/benchmark_dspark_0707/bench_dspark_p2.sh @@ -0,0 +1,63 @@ +#!/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 new file mode 100755 index 0000000..b4df410 --- /dev/null +++ b/scripts/benchmark_dspark_0707/bench_dspark_p3.sh @@ -0,0 +1,61 @@ +#!/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 new file mode 100644 index 0000000..8bf10dc --- /dev/null +++ b/scripts/benchmark_dspark_0707/parse_eagle_vs_dspark.py @@ -0,0 +1,258 @@ +#!/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 new file mode 100644 index 0000000..1d177cd --- /dev/null +++ b/scripts/benchmark_dspark_0707/parse_results.py @@ -0,0 +1,120 @@ +#!/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/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 new file mode 100644 index 0000000..d8aecaa --- /dev/null +++ b/scripts/benchmark_dspark_0707/parse_st_comparison.py @@ -0,0 +1,174 @@ +#!/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 new file mode 100755 index 0000000..4170d3c --- /dev/null +++ b/scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh @@ -0,0 +1,114 @@ +#!/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 new file mode 100755 index 0000000..747c166 --- /dev/null +++ b/scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh @@ -0,0 +1,103 @@ +#!/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/benchmark_dsv4_backend_comparison.sh b/scripts/benchmark_dsv4_backend_comparison.sh new file mode 100644 index 0000000..5e7561e --- /dev/null +++ b/scripts/benchmark_dsv4_backend_comparison.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# Reproducible SGLang vs vLLM backend comparison for DeepSeek-V4-Flash. +# Produces JSONL raw outputs compatible with the legacy 2026-07-07 sweep. +# +# Usage: +# bash scripts/benchmark_dsv4_backend_comparison.sh [vllm|sglang|all] +# +# Output: +# bench_results/dsv4_backend_comparison_${RUN_ID}/raw_outputs/ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +BACKEND_FILTER="${1:-all}" +RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}" +RESULT_ROOT="${RESULT_ROOT:-${ROOT_DIR}/bench_results/dsv4_backend_comparison_${RUN_ID}}" +RAW_DIR="${RESULT_ROOT}/raw_outputs" +LOG_DIR="${RESULT_ROOT}/logs" + +mkdir -p "${RAW_DIR}" "${LOG_DIR}" + +BENCH_PY="${ROOT_DIR}/envs/sglang/bin/python" +DATASET="${ROOT_DIR}/datasets/ShareGPT_filtered_chat.json" +MODEL="/data/models/DeepSeek-V4-Flash" +SERVED_MODEL_NAME="deepseek-v4-flash" +NUM_PROMPTS=512 +WARMUP=100 + +log() { + echo "[$(date --iso-8601=seconds)] $*" | tee -a "${LOG_DIR}/orchestrator.log" +} + +run_case() { + local backend="$1" + local port="$2" + local concurrency="$3" + local input_len="$4" + local output_len="$5" + local output_file="${RAW_DIR}/${backend}_$(date '+%m%d')_${concurrency}_${input_len}_${output_len}.jsonl" + local detail_log="${LOG_DIR}/${backend}_c${concurrency}_i${input_len}_o${output_len}.log" + + log "running ${backend}: concurrency=${concurrency} input=${input_len} output=${output_len} -> ${output_file}" + + "${BENCH_PY}" -m sglang.bench_serving \ + --backend "${backend}" \ + --host 127.0.0.1 \ + --port "${port}" \ + --model "${MODEL}" \ + --served-model-name "${SERVED_MODEL_NAME}" \ + --dataset-name random \ + --dataset-path "${DATASET}" \ + --random-input-len "${input_len}" \ + --random-output-len "${output_len}" \ + --num-prompts "${NUM_PROMPTS}" \ + --max-concurrency "${concurrency}" \ + --warmup-requests "${WARMUP}" \ + --output-file "${output_file}" \ + --output-details \ + > "${detail_log}" 2>&1 + + log "finished ${backend} c=${concurrency} i=${input_len} o=${output_len}" +} + +main() { + log "run_id=${RUN_ID}" + log "result_root=${RESULT_ROOT}" + log "backend_filter=${BACKEND_FILTER}" + + # This script assumes a healthy server is already running on the target port. + if [[ "${BACKEND_FILTER}" == "all" || "${BACKEND_FILTER}" == "sglang" ]]; then + log "===== SGLang sweep (port 30000) =====" + run_case sglang 30000 32 512 256 + run_case sglang 30000 128 512 256 + run_case sglang 30000 256 512 256 + run_case sglang 30000 512 512 256 + run_case sglang 30000 512 512 2000 + run_case sglang 30000 32 4000 512 + run_case sglang 30000 128 4000 512 + run_case sglang 30000 512 4000 512 + run_case sglang 30000 32 16000 512 + run_case sglang 30000 128 16000 512 + run_case sglang 30000 512 1000 256 + run_case sglang 30000 768 1000 256 + run_case sglang 30000 1024 1000 256 + run_case sglang 30000 512 1000 1000 + fi + + if [[ "${BACKEND_FILTER}" == "all" || "${BACKEND_FILTER}" == "vllm" ]]; then + log "===== vLLM sweep (port 8000) =====" + run_case vllm 8000 32 512 256 + run_case vllm 8000 128 512 256 + run_case vllm 8000 256 512 256 + run_case vllm 8000 128 512 2000 + run_case vllm 8000 256 512 2000 + run_case vllm 8000 512 512 2000 + run_case vllm 8000 32 1000 256 + run_case vllm 8000 128 1000 256 + run_case vllm 8000 256 1000 256 + run_case vllm 8000 512 1000 256 + run_case vllm 8000 768 1000 256 + run_case vllm 8000 1024 1000 256 + run_case vllm 8000 32 1000 1000 + run_case vllm 8000 128 1000 1000 + run_case vllm 8000 256 1000 1000 + run_case vllm 8000 512 1000 1000 + run_case vllm 8000 1024 1000 1000 + run_case vllm 8000 32 4000 512 + run_case vllm 8000 128 4000 512 + run_case vllm 8000 256 4000 512 + run_case vllm 8000 512 4000 512 + run_case vllm 8000 32 8000 1000 + run_case vllm 8000 128 8000 1000 + run_case vllm 8000 256 8000 1000 + run_case vllm 8000 512 8000 1000 + run_case vllm 8000 32 16000 512 + run_case vllm 8000 128 16000 512 + run_case vllm 8000 256 16000 512 + run_case vllm 8000 32 32000 512 + run_case vllm 8000 128 32000 512 + fi + + log "===== Done =====" + log "all outputs saved to ${RAW_DIR}" +} + +main "$@" diff --git a/scripts/install_vllm_dspark.sh b/scripts/install_vllm_dspark.sh new file mode 100755 index 0000000..d1595f4 --- /dev/null +++ b/scripts/install_vllm_dspark.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +export TMPDIR=/data/tmp +export TEMP=/data/tmp +export TMP=/data/tmp +export PIP_CACHE_DIR=/data/tmp/pip-cache +export SETUPTOOLS_SCM_PRETEND_VERSION=0.23.1.dev788 +mkdir -p $PIP_CACHE_DIR + +cd /data/user1/yy/vllm-main +PYTHON=/data/user1/yy/envs/vllm-dspark/bin/python +PIP=/data/user1/yy/envs/vllm-dspark/bin/pip + +echo "Installing torch 2.11.0 with CUDA 12.9..." +$PIP install torch==2.11.0 --index-url https://download.pytorch.org/whl/cu129 --cache-dir $PIP_CACHE_DIR + +echo "Installing numpy and build dependencies..." +$PIP install numpy cmake ninja packaging "setuptools>=77.0.3,<81.0.0" setuptools-scm setuptools-rust wheel jinja2 --cache-dir $PIP_CACHE_DIR + +echo "Installing vllm from source (this will compile CUDA kernels, may take 30-60 min)..." +$PIP install -e . --no-build-isolation --cache-dir $PIP_CACHE_DIR + +echo "Verifying installation..." +$PYTHON -c "import vllm; print('vllm version:', vllm.__version__)" + +echo "Installation complete." diff --git a/scripts/profile_dspark.py b/scripts/profile_dspark.py new file mode 100755 index 0000000..ce40591 --- /dev/null +++ b/scripts/profile_dspark.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +"""Profile DSpark inference with Nsight Systems and/or PyTorch profiler. + +Usage: + python scripts/profile_dspark.py --config dspark-st3 --concurrency 64 --tool nsys + python scripts/profile_dspark.py --config nospec --concurrency 64 --tool nsys +""" + +import argparse +import json +import os +import subprocess +import sys +import time +import urllib.request +from datetime import datetime +from pathlib import Path + +ROOT = Path("/data/user1/yy") +RESULT_DIR = ROOT / "bench_results" / f"dspark_profile_{datetime.now().strftime('%Y%m%d_%H%M%S')}" +RESULT_DIR.mkdir(parents=True, exist_ok=True) + +DATASET = "/data/user1/yy/datasets/ShareGPT_V4.3_unfiltered_cleaned_split.json" +NUM_PROMPTS = 200 +SEED = 42 +OUTPUT_LEN = 256 +HOST = "127.0.0.1" + +CONFIGS = { + "dspark-st3": { + "model": "/data/models/DeepSeek-V4-Flash-DSpark", + "port": 30004, + "spec_tokens": 3, + "cmd": [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "serve", + "/data/models/DeepSeek-V4-Flash-DSpark", + "--trust-remote-code", + "--tensor-parallel-size", "8", + "--kv-cache-dtype", "fp8", + "--block-size", "256", + "--max-model-len", "auto", + "--max-num-seqs", "256", + "--gpu-memory-utilization", "0.75", + "--tokenizer-mode", "deepseek_v4", + "--reasoning-parser", "deepseek_v4", + "--spec-method", "dspark", + "--spec-model", "/data/models/DeepSeek-V4-Flash-DSpark", + "--spec-tokens", "3", + "--no-disable-hybrid-kv-cache-manager", + "--disable-uvicorn-access-log", + "--port", "30004", + ], + }, + "dspark-st5": { + "model": "/data/models/DeepSeek-V4-Flash-DSpark", + "port": 30004, + "spec_tokens": 5, + "cmd": [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "serve", + "/data/models/DeepSeek-V4-Flash-DSpark", + "--trust-remote-code", + "--tensor-parallel-size", "8", + "--kv-cache-dtype", "fp8", + "--block-size", "256", + "--max-model-len", "auto", + "--max-num-seqs", "256", + "--gpu-memory-utilization", "0.75", + "--tokenizer-mode", "deepseek_v4", + "--reasoning-parser", "deepseek_v4", + "--spec-method", "dspark", + "--spec-model", "/data/models/DeepSeek-V4-Flash-DSpark", + "--spec-tokens", "5", + "--no-disable-hybrid-kv-cache-manager", + "--disable-uvicorn-access-log", + "--port", "30004", + ], + }, + "nospec": { + "model": "/data/models/DeepSeek-V4-Flash-DSpark", + "port": 30004, + "cmd": [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "serve", + "/data/models/DeepSeek-V4-Flash-DSpark", + "--trust-remote-code", + "--tensor-parallel-size", "8", + "--kv-cache-dtype", "fp8", + "--block-size", "256", + "--max-model-len", "auto", + "--max-num-seqs", "256", + "--gpu-memory-utilization", "0.75", + "--tokenizer-mode", "deepseek_v4", + "--reasoning-parser", "deepseek_v4", + "--no-disable-hybrid-kv-cache-manager", + "--disable-uvicorn-access-log", + "--port", "30004", + ], + }, +} + + +def log(msg): + print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}", flush=True) + + +def wait_for_health(port, timeout=300): + url = f"http://{HOST}:{port}/health" + start = time.time() + while time.time() - start < timeout: + try: + with urllib.request.urlopen(url, timeout=5) as resp: + if resp.status == 200: + return True + except Exception: + pass + time.sleep(2) + return False + + +def run_warmup(port, model, n=20): + """Run a few warmup requests to stabilize CUDA graphs.""" + log(f"Running {n} warmup requests...") + for i in range(n): + try: + req = urllib.request.Request( + f"http://{HOST}:{port}/v1/completions", + data=json.dumps({ + "model": model, + "prompt": "Hello, how are you?", + "max_tokens": 32, + "temperature": 0, + "seed": SEED, + }).encode(), + headers={"Content-Type": "application/json"}, + method="POST", + ) + with urllib.request.urlopen(req, timeout=60) as resp: + resp.read() + except Exception as e: + log(f"Warmup request {i} failed: {e}") + + +def run_benchmark(port, model, concurrency, result_dir): + result_file = result_dir / f"profile_c{concurrency}.json" + log_file = result_dir / f"profile_c{concurrency}.log" + bench_cmd = [ + "/data/user1/yy/envs/vllm-dspark/bin/vllm", "bench", "serve", + "--host", HOST, + "--port", str(port), + "--backend", "openai", + "--dataset-name", "sharegpt", + "--dataset-path", DATASET, + "--sharegpt-output-len", str(OUTPUT_LEN), + "--num-prompts", str(NUM_PROMPTS), + "--max-concurrency", str(concurrency), + "--endpoint", "/v1/completions", + "--model", model, + "--seed", str(SEED), + "--save-result", + "--result-dir", str(result_dir), + "--result-filename", result_file.name, + ] + log(f"Running benchmark c={concurrency}...") + start = time.time() + with open(log_file, "w") as f: + proc = subprocess.Popen(bench_cmd, stdout=f, stderr=subprocess.STDOUT) + proc.wait() + duration = time.time() - start + log(f"Benchmark c={concurrency} finished in {duration:.1f}s, exit={proc.returncode}") + return result_file + + +def start_service(config, tool, result_dir): + name = config + cfg = CONFIGS[config] + port = cfg["port"] + base_cmd = cfg["cmd"] + + if tool == "nsys": + nsys_output = result_dir / f"{name}_nsys" + cmd = [ + "nsys", "profile", + "--sample=none", + "--cpuctxsw=none", + "--trace=cuda,nvtx,osrt", + "--output", str(nsys_output), + "--force-overwrite", "true", + "--delay", "30", + "--duration", "15", + ] + base_cmd + else: + cmd = base_cmd + + log(f"Starting service for {name} (tool={tool}) on port {port}...") + log_file = result_dir / f"{name}_service.log" + env = os.environ.copy() + env["CUDA_VISIBLE_DEVICES"] = "0,1,2,3,4,5,6,7" + env["TMPDIR"] = str(ROOT / "tmp") + + proc = subprocess.Popen( + cmd, + stdout=open(log_file, "w"), + stderr=subprocess.STDOUT, + env=env, + ) + if not wait_for_health(port): + log(f"ERROR: {name} failed to start") + proc.terminate() + return None + log(f"{name} is ready") + return proc + + +def stop_service(proc, name): + if proc is None: + return + log(f"Stopping {name} (pid {proc.pid})...") + proc.terminate() + try: + proc.wait(timeout=60) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait() + log(f"{name} stopped") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--config", required=True, choices=list(CONFIGS.keys())) + parser.add_argument("--concurrency", type=int, default=64) + parser.add_argument("--tool", default="nsys", choices=["nsys"]) + parser.add_argument("--skip-bench", action="store_true", help="Only capture service startup/warmup, skip benchmark") + args = parser.parse_args() + + result_dir = RESULT_DIR / f"{args.config}_c{args.concurrency}_{args.tool}" + result_dir.mkdir(parents=True, exist_ok=True) + log(f"Results will be saved to {result_dir}") + + proc = start_service(args.config, args.tool, result_dir) + if proc is None: + sys.exit(1) + + try: + run_warmup(CONFIGS[args.config]["port"], CONFIGS[args.config]["model"], n=30) + if not args.skip_bench: + run_benchmark( + CONFIGS[args.config]["port"], + CONFIGS[args.config]["model"], + args.concurrency, + result_dir, + ) + else: + log("--skip-bench set; sleeping 60s to capture warmup/steady state...") + time.sleep(60) + finally: + stop_service(proc, args.config) + + log(f"All done. Results in {result_dir}") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_sglang_benchmark.sh b/scripts/run_sglang_benchmark.sh new file mode 100755 index 0000000..bb19967 --- /dev/null +++ b/scripts/run_sglang_benchmark.sh @@ -0,0 +1,157 @@ +#!/bin/bash +# Comprehensive benchmark for sglang 8-card DeepSeek-V4-Flash +set -e + +cd /data/user1/yy +mkdir -p bench_results logs + +PYTHON="/data/user1/yy/envs/sglang/bin/python" +BENCH="-m sglang.bench_serving" +HOST="127.0.0.1" +PORT="30000" +MODEL="/data/models/DeepSeek-V4-Flash" +DATASET="/data/user1/yy/datasets/ShareGPT_V4.3_unfiltered_cleaned_split.json" +NUM_PROMPTS=2000 +SEED=42 + +RESULT_DIR="bench_results/sglang_8card_systematic_$(date +%Y%m%d_%H%M%S)" +mkdir -p "${RESULT_DIR}" + +SUMMARY_FILE="${RESULT_DIR}/summary.json" +LOG_FILE="${RESULT_DIR}/benchmark.log" + +# Use a marker file for summary accumulation +SUMMARY_TMP="${RESULT_DIR}/.summary_tmp.json" +echo '[]' > "${SUMMARY_TMP}" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}" +} + +wait_for_server() { + log "Waiting for sglang server at ${HOST}:${PORT} to become ready..." + local retries=0 + local max_retries=120 + while ! curl -s "http://${HOST}:${PORT}/v1/models" > /dev/null 2>&1; do + retries=$((retries + 1)) + if [ "${retries}" -ge "${max_retries}" ]; then + log "ERROR: Server not ready after ${max_retries} retries" + exit 1 + fi + sleep 2 + done + log "Server is ready." +} + +run_bench() { + local name="$1" + shift + local output_file="${RESULT_DIR}/${name}.json" + local detail_log="${RESULT_DIR}/${name}.log" + + log "---------------------------------------------------" + log "Running benchmark: ${name}" + log "Args: $*" + log "Output: ${output_file}" + + ${PYTHON} ${BENCH} \ + --backend sglang \ + --host "${HOST}" \ + --port "${PORT}" \ + --dataset-name sharegpt \ + --dataset-path "${DATASET}" \ + --num-prompts "${NUM_PROMPTS}" \ + --model "${MODEL}" \ + --output-file "${output_file}" \ + --output-details \ + --seed "${SEED}" \ + "$@" \ + > "${detail_log}" 2>&1 + + local status=$? + if [ ${status} -ne 0 ]; then + log "ERROR: Benchmark ${name} failed with exit code ${status}" + return ${status} + fi + + log "Benchmark ${name} completed successfully." + + # Extract key metrics from the log + local req_throughput=$(grep -oP 'Request throughput \(req/s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local input_throughput=$(grep -oP 'Input token throughput \(tok/s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local output_throughput=$(grep -oP 'Output token throughput \(tok/s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local total_throughput=$(grep -oP 'Total token throughput \(tok/s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local mean_ttft=$(grep -oP 'Mean TTFT \(ms\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local mean_tpot=$(grep -oP 'Mean TPOT \(ms\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local mean_e2e=$(grep -oP 'Mean E2E Latency \(ms\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local duration=$(grep -oP 'Benchmark duration \(s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local accept_length=$(grep -oP 'Accept length:\s+\K[0-9.]+' "${detail_log}" || echo "null") + + # Append to summary tmp + ${PYTHON} - <8} {'In tok/s':>10} {'Out tok/s':>10} {'Total tok/s':>12} {'TTFT':>8} {'TPOT':>8} {'E2E':>10}") +print("-" * 110) +for entry in data: + print(f"{entry['name']:<30} {entry['request_throughput']:>8.2f} {entry['input_token_throughput']:>10.2f} {entry['output_token_throughput']:>10.2f} {entry['total_token_throughput']:>12.2f} {entry['mean_ttft_ms']:>8.1f} {entry['mean_tpot_ms']:>8.1f} {entry['mean_e2e_latency_ms']:>10.1f}") +PYEOF +} + +main "$@" diff --git a/scripts/run_sglang_max_throughput.sh b/scripts/run_sglang_max_throughput.sh new file mode 100755 index 0000000..7d1513b --- /dev/null +++ b/scripts/run_sglang_max_throughput.sh @@ -0,0 +1,143 @@ +#!/bin/bash +# Max throughput benchmark for sglang 8-card DeepSeek-V4-Flash +set -e + +cd /data/user1/yy +mkdir -p bench_results logs + +PYTHON="/data/user1/yy/envs/sglang/bin/python" +BENCH="-m sglang.bench_serving" +HOST="127.0.0.1" +PORT="30000" +MODEL="/data/models/DeepSeek-V4-Flash" +DATASET="/data/user1/yy/datasets/ShareGPT_V4.3_unfiltered_cleaned_split.json" +NUM_PROMPTS=2000 +SEED=42 + +RESULT_DIR="bench_results/sglang_8card_max_throughput_$(date +%Y%m%d_%H%M%S)" +mkdir -p "${RESULT_DIR}" + +SUMMARY_FILE="${RESULT_DIR}/summary.json" +LOG_FILE="${RESULT_DIR}/benchmark.log" +SUMMARY_TMP="${RESULT_DIR}/.summary_tmp.json" +echo '[]' > "${SUMMARY_TMP}" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}" +} + +wait_for_server() { + log "Waiting for sglang server at ${HOST}:${PORT} to become ready..." + local retries=0 + local max_retries=120 + while ! curl -s "http://${HOST}:${PORT}/v1/models" > /dev/null 2>&1; do + retries=$((retries + 1)) + if [ "${retries}" -ge "${max_retries}" ]; then + log "ERROR: Server not ready after ${max_retries} retries" + exit 1 + fi + sleep 2 + done + log "Server is ready." +} + +run_bench() { + local name="$1" + shift + local output_file="${RESULT_DIR}/${name}.json" + local detail_log="${RESULT_DIR}/${name}.log" + + log "---------------------------------------------------" + log "Running benchmark: ${name}" + log "Args: $*" + log "Output: ${output_file}" + + ${PYTHON} ${BENCH} \ + --backend sglang \ + --host "${HOST}" \ + --port "${PORT}" \ + --dataset-name sharegpt \ + --dataset-path "${DATASET}" \ + --num-prompts "${NUM_PROMPTS}" \ + --model "${MODEL}" \ + --output-file "${output_file}" \ + --output-details \ + --seed "${SEED}" \ + "$@" \ + > "${detail_log}" 2>&1 + + local status=$? + if [ ${status} -ne 0 ]; then + log "ERROR: Benchmark ${name} failed with exit code ${status}" + return ${status} + fi + + log "Benchmark ${name} completed successfully." + + local req_throughput=$(grep -oP 'Request throughput \(req/s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local input_throughput=$(grep -oP 'Input token throughput \(tok/s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local output_throughput=$(grep -oP 'Output token throughput \(tok/s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local total_throughput=$(grep -oP 'Total token throughput \(tok/s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local mean_ttft=$(grep -oP 'Mean TTFT \(ms\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local mean_tpot=$(grep -oP 'Mean TPOT \(ms\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local mean_e2e=$(grep -oP 'Mean E2E Latency \(ms\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local duration=$(grep -oP 'Benchmark duration \(s\):\s+\K[0-9.]+' "${detail_log}" || echo "null") + local accept_length=$(grep -oP 'Accept length:\s+\K[0-9.]+' "${detail_log}" || echo "null") + + ${PYTHON} - <8} {'In tok/s':>10} {'Out tok/s':>10} {'Total tok/s':>12} {'TTFT':>8} {'TPOT':>8} {'E2E':>10}") +print("-" * 110) +for entry in data: + print(f"{entry['name']:<30} {entry['request_throughput']:>8.2f} {entry['input_token_throughput']:>10.2f} {entry['output_token_throughput']:>10.2f} {entry['total_token_throughput']:>12.2f} {entry['mean_ttft_ms']:>8.1f} {entry['mean_tpot_ms']:>8.1f} {entry['mean_e2e_latency_ms']:>10.1f}") +PYEOF +} + +main "$@" diff --git a/scripts/start_dsv4_dspark_8card.sh b/scripts/start_dsv4_dspark_8card.sh new file mode 100755 index 0000000..53bd04b --- /dev/null +++ b/scripts/start_dsv4_dspark_8card.sh @@ -0,0 +1,65 @@ +#!/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 diff --git a/scripts/start_dsv4_dspark_8card_bf16kv.sh b/scripts/start_dsv4_dspark_8card_bf16kv.sh new file mode 100755 index 0000000..3833a53 --- /dev/null +++ b/scripts/start_dsv4_dspark_8card_bf16kv.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -e + +cd /data/user1/yy +mkdir -p logs + +VENV="/data/user1/yy/envs/vllm-dspark" +VLLM="$VENV/bin/vllm" + +MODEL="/data/models/DeepSeek-V4-Flash-DSpark" +PORT=30004 +TP=8 +LOG="/data/user1/yy/logs/dsv4_flash_dspark_tp${TP}_bf16kv_$(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, kv=bf16) ===" +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 bfloat16 \ + --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 diff --git a/scripts/start_dsv4_dspark_8card_flashinfer.sh b/scripts/start_dsv4_dspark_8card_flashinfer.sh new file mode 100755 index 0000000..458e309 --- /dev/null +++ b/scripts/start_dsv4_dspark_8card_flashinfer.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -e + +cd /data/user1/yy +mkdir -p logs + +VENV="/data/user1/yy/envs/vllm-dspark" +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}_flashinfer_$(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, FlashInfer) ===" +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 \ + --attention-backend FLASHINFER_MLA_SPARSE_DSV4 \ + --spec-method dspark \ + --spec-model "$MODEL" \ + --spec-tokens 5 \ + --speculative-config '{"attention_backend": "FLASHINFER_MLA_SPARSE_DSV4"}' \ + --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 diff --git a/scripts/start_dsv4_dspark_8card_param.sh b/scripts/start_dsv4_dspark_8card_param.sh new file mode 100755 index 0000000..6be3ac5 --- /dev/null +++ b/scripts/start_dsv4_dspark_8card_param.sh @@ -0,0 +1,85 @@ +#!/bin/bash +set -e + +cd /data/user1/yy +mkdir -p logs + +VENV="/data/user1/yy/envs/vllm-dspark" +VLLM="$VENV/bin/vllm" +export PATH="$VENV/bin:$PATH" + +MODEL="/data/models/DeepSeek-V4-Flash-DSpark" +PORT="${PORT:-30004}" +TP="${TP:-8}" +SPEC_TOKENS="${SPEC_TOKENS:-5}" +KV_CACHE_DTYPE="${KV_CACHE_DTYPE:-fp8}" +ATTENTION_BACKEND="${ATTENTION_BACKEND:-}" +SPECULATIVE_CONFIG="${SPECULATIVE_CONFIG:-}" + +LOG_TAG="tp${TP}_${KV_CACHE_DTYPE}_st${SPEC_TOKENS}" +if [[ -n "$ATTENTION_BACKEND" ]]; then + LOG_TAG="${LOG_TAG}_${ATTENTION_BACKEND}" +fi +LOG="/data/user1/yy/logs/dsv4_flash_dspark_${LOG_TAG}_$(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, spec-tokens=$SPEC_TOKENS, kv=$KV_CACHE_DTYPE) ===" +echo "Model: $MODEL" +echo "Port: $PORT" +echo "Log: $LOG" + +rm -f "$PID_FILE" + +cmd=( + "$VLLM" serve "$MODEL" + --trust-remote-code + --tensor-parallel-size "$TP" + --kv-cache-dtype "$KV_CACHE_DTYPE" + --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 "$SPEC_TOKENS" + --no-disable-hybrid-kv-cache-manager + --disable-uvicorn-access-log + --port "$PORT" +) + +if [[ -n "$ATTENTION_BACKEND" ]]; then + cmd+=(--attention-backend "$ATTENTION_BACKEND") +fi + +if [[ -n "$SPECULATIVE_CONFIG" ]]; then + cmd+=(--speculative-config "$SPECULATIVE_CONFIG") +fi + +nohup "${cmd[@]}" > "$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 diff --git a/scripts/start_pd_single_node.sh b/scripts/start_pd_single_node.sh new file mode 100755 index 0000000..3f3a2fd --- /dev/null +++ b/scripts/start_pd_single_node.sh @@ -0,0 +1,138 @@ +#!/bin/bash +set -e + +# sglang PD 分离单节点启动脚本 +# 104 上前 4 张卡做 prefill,后 4 张卡做 decode +# 使用 NIXL/UCX 作为传输后端(Mooncake 无法安装) + +MODEL_PATH="/data/models/DeepSeek-V4-Flash" +BOOTSTRAP_PORT=30010 +PREFILL_PORT=30001 +DECODE_PORT=30002 +LB_PORT=30000 +HOST="0.0.0.0" + +VENV="/data/user1/yy/envs/sglang" +PYTHON="$VENV/bin/python" +LOG_DIR="/data/user1/yy/pd_logs" +mkdir -p "$LOG_DIR" + +# NIXL/UCX 配置:单节点内优先用 CUDA IPC / shared memory,禁用 IB +export SGLANG_DISAGGREGATION_NIXL_BACKEND="UCX" +export UCX_TLS="self,sm,cuda_copy,cuda_ipc,tcp" +export UCX_NET_DEVICES="" + +echo "=== Starting sglang PD disaggregation (single node) ===" +echo "Model: $MODEL_PATH" +echo "Prefill GPUs: 0-3, port $PREFILL_PORT" +echo "Decode GPUs: 4-7, port $DECODE_PORT" +echo "Load balancer port: $LB_PORT" +echo "Logs: $LOG_DIR" +echo "" + +# Clean up old logs +rm -f "$LOG_DIR"/*.log + +# Common args for both prefill and decode +COMMON_ARGS=( + --trust-remote-code + --model-path "$MODEL_PATH" + --tp 4 + --moe-runner-backend marlin + --disable-cuda-graph + --disaggregation-bootstrap-port "$BOOTSTRAP_PORT" + --disaggregation-transfer-backend nixl + --host "$HOST" +) + +# Start prefill server (GPUs 0-3) +echo "[1/3] Starting prefill server on GPUs 0-3..." +CUDA_VISIBLE_DEVICES=0,1,2,3 nohup "$PYTHON" -m sglang.launch_server \ + "${COMMON_ARGS[@]}" \ + --disaggregation-mode prefill \ + --port "$PREFILL_PORT" \ + > "$LOG_DIR/prefill.log" 2>&1 & +PREFILL_PID=$! +echo "Prefill PID: $PREFILL_PID" + +# Wait for prefill health +for i in {1..120}; do + if curl -s "http://127.0.0.1:$PREFILL_PORT/health" > /dev/null 2>&1; then + echo "Prefill server is ready" + break + fi + if ! kill -0 $PREFILL_PID 2>/dev/null; then + echo "ERROR: Prefill server exited early" + tail -50 "$LOG_DIR/prefill.log" + exit 1 + fi + echo "Waiting for prefill server... ($i/120)" + sleep 5 +done + +# Start decode server (GPUs 4-7) +echo "[2/3] Starting decode server on GPUs 4-7..." +CUDA_VISIBLE_DEVICES=0,1,2,3 nohup "$PYTHON" -m sglang.launch_server \ + "${COMMON_ARGS[@]}" \ + --disaggregation-mode decode \ + --base-gpu-id 4 \ + --port "$DECODE_PORT" \ + > "$LOG_DIR/decode.log" 2>&1 & +DECODE_PID=$! +echo "Decode PID: $DECODE_PID" + +# Wait for decode health +for i in {1..120}; do + if curl -s "http://127.0.0.1:$DECODE_PORT/health" > /dev/null 2>&1; then + echo "Decode server is ready" + break + fi + if ! kill -0 $DECODE_PID 2>/dev/null; then + echo "ERROR: Decode server exited early" + tail -50 "$LOG_DIR/decode.log" + exit 1 + fi + echo "Waiting for decode server... ($i/120)" + sleep 5 +done + +# Start PD load balancer +echo "[3/3] Starting PD load balancer on port $LB_PORT..." +nohup "$PYTHON" -m sglang_router.launch_router \ + --pd-disaggregation \ + --mini-lb \ + --prefill "http://127.0.0.1:$PREFILL_PORT" \ + --decode "http://127.0.0.1:$DECODE_PORT" \ + --host "$HOST" \ + --port "$LB_PORT" \ + > "$LOG_DIR/lb.log" 2>&1 & +LB_PID=$! +echo "LB PID: $LB_PID" + +# Wait for LB health +for i in {1..60}; do + if curl -s "http://127.0.0.1:$LB_PORT/health" > /dev/null 2>&1; then + echo "Load balancer is ready" + break + fi + if ! kill -0 $LB_PID 2>/dev/null; then + echo "ERROR: Load balancer exited early" + tail -50 "$LOG_DIR/lb.log" + exit 1 + fi + echo "Waiting for load balancer... ($i/60)" + sleep 2 +done + +echo "" +echo "=== PD disaggregation is ready ===" +echo "Load balancer URL: http://127.0.0.1:$LB_PORT" +echo "Prefill URL: http://127.0.0.1:$PREFILL_PORT" +echo "Decode URL: http://127.0.0.1:$DECODE_PORT" +echo "" +echo "Test command:" +echo " curl http://127.0.0.1:$LB_PORT/v1/completions \\" +echo " -H \"Content-Type: application/json\" \\" +echo " -d '{\"model\":\"DeepSeek-V4-Flash\",\"prompt\":\"Hello\",\"max_tokens\":32}'" +echo "" +echo "To stop: kill $PREFILL_PID $DECODE_PID $LB_PID" diff --git a/scripts/start_sglang_dsv4_8card.sh b/scripts/start_sglang_dsv4_8card.sh new file mode 100755 index 0000000..638ecbf --- /dev/null +++ b/scripts/start_sglang_dsv4_8card.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Start sglang 8-card DeepSeek-V4-Flash service +set -e + +cd /data/user1/yy +mkdir -p logs + +export PYTHONUNBUFFERED=1 +export SGLANG_LOG_LEVEL=info +export PATH="/data/user1/yy/envs/sglang/bin:$PATH" + +LOG_FILE="logs/sglang_8card_$(date +%Y%m%d_%H%M%S).log" + +echo "Starting sglang 8-card DSV4 service, logging to ${LOG_FILE}" + +exec /data/user1/yy/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 \ + --host 0.0.0.0 \ + --port 30000 \ + 2>&1 | tee "${LOG_FILE}" diff --git a/scripts/start_vllm_eagle_dsv4_8card.sh b/scripts/start_vllm_eagle_dsv4_8card.sh new file mode 100644 index 0000000..676998e --- /dev/null +++ b/scripts/start_vllm_eagle_dsv4_8card.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e + +cd /data/user1/yy +mkdir -p logs + +VENV="/data/user1/yy/envs/vllm-main-latest" +PYTHON="$VENV/bin/python" +VLLM="$VENV/bin/vllm" +export PATH="$VENV/bin:$PATH" + +MODEL="/data/models/DeepSeek-V4-Flash" +PORT=30005 +TP=8 +LOG="/data/user1/yy/logs/vllm_eagle_dsv4_tp${TP}_$(date +%Y%m%d_%H%M%S).log" +PID_FILE="/data/user1/yy/vllm_eagle_dsv4.pid" + +export TMPDIR=/data/user1/yy/tmp +export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 + +echo "=== Starting DeepSeek-V4-Flash vLLM EAGLE (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 mtp \ + --spec-model "$MODEL" \ + --spec-tokens 4 \ + --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 -200 "$LOG" + exit 1 + fi + echo "Waiting... ($i/240)" + sleep 5 +done + +echo "ERROR: Server not healthy after 240 retries" +tail -200 "$LOG" +exit 1 diff --git a/scripts/start_vllm_mtp_dsv4_8card.sh b/scripts/start_vllm_mtp_dsv4_8card.sh new file mode 100644 index 0000000..1c6a20c --- /dev/null +++ b/scripts/start_vllm_mtp_dsv4_8card.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -e + +cd /data/user1/yy +mkdir -p logs + +VENV="/data/user1/yy/envs/vllm-dspark" +export PATH="$VENV/bin:$PATH" +PYTHON="$VENV/bin/python" +VLLM="$VENV/bin/vllm" + +MODEL="/data/models/DeepSeek-V4-Flash" +PORT=30005 +TP=8 +LOG="/data/user1/yy/logs/vllm_mtp_dsv4_tp${TP}_$(date +%Y%m%d_%H%M%S).log" +PID_FILE="/data/user1/yy/vllm_mtp_dsv4.pid" + +export TMPDIR=/data/user1/yy/tmp +export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 + +echo "=== Starting DeepSeek-V4-Flash vLLM MTP (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 128 \ + --gpu-memory-utilization 0.85 \ + --tokenizer-mode deepseek_v4 \ + --reasoning-parser deepseek_v4 \ + --spec-method mtp \ + --spec-model "$MODEL" \ + --spec-tokens 4 \ + --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 -200 "$LOG" + exit 1 + fi + echo "Waiting... ($i/240)" + sleep 5 +done + +echo "ERROR: Server not healthy after 240 retries" +tail -200 "$LOG" +exit 1 diff --git a/scripts/start_vllm_pd_single_node.sh b/scripts/start_vllm_pd_single_node.sh new file mode 100755 index 0000000..667282d --- /dev/null +++ b/scripts/start_vllm_pd_single_node.sh @@ -0,0 +1,156 @@ +#!/bin/bash +set -e + +# vLLM + Mooncake PD 分离单节点启动脚本 +# 104 上前 4 张卡做 prefill,后 4 张卡做 decode +# 使用 TCP 传输(因为当前 IB/RDMA 未启用) + +MODEL_PATH="/data/models/DeepSeek-V4-Flash" +BOOTSTRAP_PORT=8998 +PREFILL_PORT=8000 +DECODE_PORT=8001 +ROUTER_PORT=30000 +HOST="0.0.0.0" + +VENV="/data/user1/yy/envs/vllm" +PYTHON="$VENV/bin/python" +VLLM="$VENV/bin/vllm" +VLLM_ROUTER="$VENV/bin/vllm-router" +LOG_DIR="/data/user1/yy/vllm_pd_logs" +mkdir -p "$LOG_DIR" + +echo "=== Starting vLLM PD disaggregation (single node) ===" +echo "Model: $MODEL_PATH" +echo "Prefill GPUs: 0-3, port $PREFILL_PORT" +echo "Decode GPUs: 4-7, port $DECODE_PORT" +echo "Router port: $ROUTER_PORT" +echo "Logs: $LOG_DIR" +echo "" + +# Clean up old logs +rm -f "$LOG_DIR"/*.log + +# 使用 TCP 协议(IB 没起来,不能用 rdma) +export VLLM_MOONCAKE_PROTOCOL=tcp +export VLLM_MOONCAKE_BOOTSTRAP_PORT=$BOOTSTRAP_PORT + +# Common args +PREFILL_KV_CONFIG='{"kv_connector":"MooncakeConnector","kv_role":"kv_producer","kv_load_failure_policy":"fail","kv_buffer_device":"cuda","kv_connector_extra_config":{"enforce_handshake_compat":false,"mooncake_protocol":"tcp"}}' +DECODE_KV_CONFIG='{"kv_connector":"MooncakeConnector","kv_role":"kv_consumer","kv_load_failure_policy":"fail","kv_buffer_device":"cuda","kv_connector_extra_config":{"enforce_handshake_compat":false,"mooncake_protocol":"tcp"}}' + +# Start prefill server (GPUs 0-3) +echo "[1/3] Starting prefill server on GPUs 0-3..." +CUDA_VISIBLE_DEVICES=0,1,2,3 nohup "$VLLM" serve "$MODEL_PATH" \ + --trust-remote-code \ + --kv-cache-dtype fp8 \ + --block-size 256 \ + --port "$PREFILL_PORT" \ + --data-parallel-size 4 \ + --enable-expert-parallel \ + --tokenizer-mode deepseek_v4 \ + --reasoning-parser deepseek_v4 \ + --max-model-len auto \ + --max-num-batched-tokens 16384 \ + --max-num-seqs 8 \ + --enforce-eager \ + --no-disable-hybrid-kv-cache-manager \ + --disable-uvicorn-access-log \ + --kv-transfer-config "$PREFILL_KV_CONFIG" \ + > "$LOG_DIR/prefill.log" 2>&1 & +PREFILL_PID=$! +echo "Prefill PID: $PREFILL_PID" + +# Wait for prefill health +for i in {1..120}; do + if curl -s "http://127.0.0.1:$PREFILL_PORT/health" > /dev/null 2>&1; then + echo "Prefill server is ready" + break + fi + if ! kill -0 $PREFILL_PID 2>/dev/null; then + echo "ERROR: Prefill server exited early" + tail -80 "$LOG_DIR/prefill.log" + exit 1 + fi + echo "Waiting for prefill server... ($i/120)" + sleep 5 +done + +# Start decode server (GPUs 4-7) +echo "[2/3] Starting decode server on GPUs 4-7..." +CUDA_VISIBLE_DEVICES=4,5,6,7 nohup "$VLLM" serve "$MODEL_PATH" \ + --trust-remote-code \ + --kv-cache-dtype fp8 \ + --block-size 256 \ + --port "$DECODE_PORT" \ + --data-parallel-size 4 \ + --enable-expert-parallel \ + --tokenizer-mode deepseek_v4 \ + --reasoning-parser deepseek_v4 \ + --max-model-len auto \ + --max-num-seqs 512 \ + --max-num-batched-tokens 512 \ + --compilation-config '{"mode":0,"cudagraph_mode":"FULL_DECODE_ONLY","max_cudagraph_capture_size":512,"compile_ranges_endpoints":[512]}' \ + --no-disable-hybrid-kv-cache-manager \ + --disable-uvicorn-access-log \ + --kv-transfer-config "$DECODE_KV_CONFIG" \ + > "$LOG_DIR/decode.log" 2>&1 & +DECODE_PID=$! +echo "Decode PID: $DECODE_PID" + +# Wait for decode health +for i in {1..120}; do + if curl -s "http://127.0.0.1:$DECODE_PORT/health" > /dev/null 2>&1; then + echo "Decode server is ready" + break + fi + if ! kill -0 $DECODE_PID 2>/dev/null; then + echo "ERROR: Decode server exited early" + tail -80 "$LOG_DIR/decode.log" + exit 1 + fi + echo "Waiting for decode server... ($i/120)" + sleep 5 +done + +# Start vllm-router +echo "[3/3] Starting vllm-router on port $ROUTER_PORT..." +nohup "$VLLM_ROUTER" \ + --policy round_robin \ + --vllm-pd-disaggregation \ + --prefill "http://127.0.0.1:$PREFILL_PORT" \ + --decode "http://127.0.0.1:$DECODE_PORT" \ + --host 127.0.0.1 \ + --port "$ROUTER_PORT" \ + --intra-node-data-parallel-size 4 \ + --kv-connector mooncake \ + > "$LOG_DIR/router.log" 2>&1 & +ROUTER_PID=$! +echo "Router PID: $ROUTER_PID" + +# Wait for router health +for i in {1..60}; do + if curl -s "http://127.0.0.1:$ROUTER_PORT/health" > /dev/null 2>&1; then + echo "Router is ready" + break + fi + if ! kill -0 $ROUTER_PID 2>/dev/null; then + echo "ERROR: Router exited early" + tail -50 "$LOG_DIR/router.log" + exit 1 + fi + echo "Waiting for router... ($i/60)" + sleep 2 +done + +echo "" +echo "=== vLLM PD disaggregation is ready ===" +echo "Router URL: http://127.0.0.1:$ROUTER_PORT" +echo "Prefill URL: http://127.0.0.1:$PREFILL_PORT" +echo "Decode URL: http://127.0.0.1:$DECODE_PORT" +echo "" +echo "Test command:" +echo " curl http://127.0.0.1:$ROUTER_PORT/v1/completions \\" +echo " -H \"Content-Type: application/json\" \\" +echo " -d '{\"model\":\"DeepSeek-V4-Flash\",\"prompt\":\"Hello\",\"max_tokens\":32}'" +echo "" +echo "To stop: kill $PREFILL_PID $DECODE_PID $ROUTER_PID" diff --git a/scripts/test_block_sparse_attn.py b/scripts/test_block_sparse_attn.py new file mode 100644 index 0000000..c5fe0d4 --- /dev/null +++ b/scripts/test_block_sparse_attn.py @@ -0,0 +1,84 @@ +import torch +import block_sparse_attn +from block_sparse_attn import block_sparse_attn_func, token_streaming_attn_func, block_streaming_attn_func + +print("block_sparse_attn version:", block_sparse_attn.__version__) +print("Available funcs:", [k for k in dir(block_sparse_attn) if not k.startswith("_")]) + +device = "cuda:0" +batch_size = 1 +nheads = 4 +head_dim = 64 +seqlen = 256 + +total_len = batch_size * seqlen +q = torch.randn(total_len, nheads, head_dim, device=device, dtype=torch.float16) +k = torch.randn(total_len, nheads, head_dim, device=device, dtype=torch.float16) +v = torch.randn(total_len, nheads, head_dim, device=device, dtype=torch.float16) + +cu_seqlens_q = torch.arange(0, total_len + 1, seqlen, device=device, dtype=torch.int32) +cu_seqlens_k = cu_seqlens_q.clone() + +# 1. dense attention (all heads mask_type=0) +head_mask_type_dense = torch.zeros(nheads, device=device, dtype=torch.int32) +streaming_info = torch.tensor([1, 3] * nheads, device=device, dtype=torch.int32) +out_dense = block_sparse_attn_func( + q, k, v, + cu_seqlens_q, cu_seqlens_k, + head_mask_type_dense, + streaming_info, + None, + seqlen, seqlen, + 0.0, + is_causal=False, +) +print("dense output shape:", out_dense.shape) +assert out_dense.shape == q.shape + +# 2. block-sparse attention (2 dense heads + 2 blocksparse heads) +num_blocksparse_heads = 2 +nrow = seqlen // 128 +ncol = seqlen // 128 +base_blockmask = torch.zeros(batch_size, num_blocksparse_heads, nrow, ncol, device=device, dtype=torch.bool) +base_blockmask[:, :, :, :ncol//2] = True # attend to first half + +head_mask_type_sparse = torch.tensor([0, 0, 1, 1], device=device, dtype=torch.int32) +out_sparse = block_sparse_attn_func( + q, k, v, + cu_seqlens_q, cu_seqlens_k, + head_mask_type_sparse, + streaming_info, + base_blockmask, + seqlen, seqlen, + 0.0, + is_causal=False, +) +print("block-sparse output shape:", out_sparse.shape) +assert out_sparse.shape == q.shape + +# 3. token-level streaming attention +head_mask_type_stream = torch.full((nheads,), -1, device=device, dtype=torch.int32) +out_stream = token_streaming_attn_func( + q, k, v, + cu_seqlens_q, cu_seqlens_k, + head_mask_type_stream, + streaming_info, + seqlen, seqlen, +) +print("token-streaming output shape:", out_stream.shape) +assert out_stream.shape == q.shape + +# 4. block-level streaming attention +out_block_stream = block_streaming_attn_func( + q, k, v, + cu_seqlens_q, cu_seqlens_k, + head_mask_type_stream, + streaming_info, + seqlen, seqlen, + 0.0, + is_causal=True, +) +print("block-streaming output shape:", out_block_stream.shape) +assert out_block_stream.shape == q.shape + +print("All smoke tests passed!") diff --git a/scripts/wait_and_profile_dspark.sh b/scripts/wait_and_profile_dspark.sh new file mode 100755 index 0000000..944b5c2 --- /dev/null +++ b/scripts/wait_and_profile_dspark.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Wait for GPUs to become idle (no other compute processes), then run DSpark profiles. + +set -e + +ROOT=/data/user1/yy +PROFILE_PID_FILE=/tmp/profile_dspark.pid +MONITOR_LOG=$ROOT/bench_results/dspark_profile_monitor.log +RESULT_BASE=$ROOT/bench_results/dspark_profile_$(date +%Y%m%d_%H%M%S) + +mkdir -p "$RESULT_BASE" "$ROOT/tmp" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$MONITOR_LOG" +} + +is_gpu_idle() { + # Consider idle if no process other than ours uses > 1GB on any GPU. + # Returns 0 (true) if idle, 1 (false) if busy. + nvidia-smi --query-compute-apps=pid,used_memory --format=csv,noheader,nounits | \ + awk -F',' ' + { + pid=$1; mem=$2; + # Exclude our own known PIDs (monitor script, shell, etc.) + if (mem > 1024) { busy++; } + } + END { exit (busy > 0 ? 1 : 0) } + ' +} + +log "Starting GPU idle monitor. Will check every 5 minutes." +log "Results will go to $RESULT_BASE" + +wait_for_idle() { + while true; do + if is_gpu_idle; then + log "GPUs appear idle." + return 0 + else + log "GPUs still busy. Sleeping 5 minutes." + sleep 300 + fi + done +} + +# Run profiles sequentially, checking GPU idle state before each run. +CONFIGS=("dspark-st3:64" "dspark-st5:64" "dspark-st3:1" "nospec:64") + +for entry in "${CONFIGS[@]}"; do + IFS=':' read -r config concurrency <<< "$entry" + log "Waiting before profile: config=$config concurrency=$concurrency" + wait_for_idle + log "Running profile: config=$config concurrency=$concurrency" + "$ROOT/envs/vllm-dspark/bin/python" "$ROOT/scripts/profile_dspark.py" \ + --config "$config" --concurrency "$concurrency" --tool nsys \ + >> "$RESULT_BASE/profile_${config}_c${concurrency}.log" 2>&1 + log "Finished profile: config=$config concurrency=$concurrency" +done + +log "All profiles complete. Results in $RESULT_BASE"