Initial commit: benchmark scripts and final reports

This commit is contained in:
Benchmark Bot 2026-07-08 02:17:13 +00:00
commit eb431d3925
50 changed files with 5863 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@ -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/

122
BENCHMARK_WORKFLOW.md Normal file
View File

@ -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/<experiment>_<timestamp>/`.
- 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/<run>/` 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/<experiment>_${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/<service>_<timestamp>.log`
- Keep server logs separate from benchmark result logs.
## Naming Conventions
### Result directories
```
bench_results/<experiment>_<YYYYMMDD-HHMMSS>/
```
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/<service>_YYYYMMDD_HHMMSS.log
logs/<experiment>_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_<run_id>
```
## Checklist Before Committing / Archiving
- [ ] No `.jsonl`, `.json`, `.log`, or `.md` files left in `/data/user1/yy/` root.
- [ ] All outputs moved to `bench_results/<experiment>_<timestamp>/`.
- [ ] `bench_results/<run>/README.md` exists and documents provenance.
- [ ] Scripts moved to `scripts/` (or `scripts/<group>/`).
- [ ] Script path references updated after moving.

63
README.md Normal file
View File

@ -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**S2DeepSeek-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当前配置

View File

@ -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=8FP8 KV cache`--spec-method dspark --spec-tokens 5`block-size=256max-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 升到 35msP99 超过 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 的真实收益。

View File

@ -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 |

View File

@ -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`
- Warmup100 条
- 压测客户端:`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 提升到 100P99 TTFT 相比首次 grid 测试有明显改善;建议保持 100 条 warmup。

View File

@ -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`).

View File

@ -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"
}
]

View File

@ -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`
- Warmup100 条
## 核心指标对比
| 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 / DSpark3.09
- 平均 Mean TPOT 比值EAGLE / DSpark3.21
- 平均 Mean TTFT 比值EAGLE / DSpark1.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 模型架构)会显著影响不同并发和输入长度下的表现,建议按实际业务负载做最终选型。

View File

@ -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`

View File

@ -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"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View File

@ -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=88× 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 为 3712E2E 延迟约 4.25sc=64 时吞吐提升到 4788但延迟增加到 6.56s。
3. **c=128 达到最高吞吐**total tok/s = 7129output tok/s = 2996但 E2E 延迟接近 8.6s,适合离线/批处理场景。
4. **RPS=16 时系统过载**:实际 req/s 达到 14.5TPOT 飙升到 87msE2E 延迟 15s说明 RPS>14 已超出稳定运行区间。
5. **RPS=8 是稳定高吞吐边界**total tok/s = 3875E2E 延迟 4.54sTTFT 仅 217ms体验与吞吐兼顾。
6. **EAGLE 投机采样稳定生效**Accept Length 稳定在 2.602.62,有效降低了 decode 步数。
## 场景建议
| 场景 | 推荐配置 | 理由 |
|------|----------|------|
| 低延迟在线 API | `--max-concurrency 8` | TTFT 147msTPOT 8.4msE2E 1.79s |
| 平衡型在线服务 | `--max-concurrency 16``--request-rate 4` | 吞吐约 3k tok/sE2E 2.61.7s |
| 高吞吐在线服务 | `--max-concurrency 32``--request-rate 8` | 吞吐 3.73.9k tok/sE2E 4.34.5s |
| 最大吞吐/离线批处理 | `--max-concurrency 128` | 吞吐 7.1k tok/sE2E 8.6s |
| 避免使用 | `--request-rate 16` | 系统过载E2E 15sTPOT 87ms |
## 原始数据
- 汇总 JSON`bench_results/sglang_8card_systematic_20260704_120819/summary.json`
- 每个测试点的详细结果与日志:`bench_results/sglang_8card_systematic_20260704_120819/`
## 附录 ABenchmark 参数说明
本次所有测试均使用 `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 数 |
---
## 附录 BToken 成本计算
### 假设
- 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 147msTPOT 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/sc=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 510msE2E 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 tokensP90 516 tokensP99 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`

View File

@ -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"
}
]

View File

@ -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'"
]
}
]
}

View File

@ -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": []
}
]
}

View File

@ -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": []
}
]
}

110
cleanup_summary.md Normal file
View File

@ -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/` 访问。

View File

@ -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`
- GPU8× NVIDIA H200SM90TP=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` layoutUE8M0 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

View File

@ -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。
相比 EAGLEDSpark 的 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 Parallel8
- KV cache dtypefp8
- Block size256
- Max model lenauto
- Max num seqs256
- 输出长度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 <PORT> --backend openai \
--dataset-name sharegpt --dataset-path <DATASET> \
--sharegpt-output-len 256 --num-prompts 200 \
--max-concurrency <C> --endpoint /v1/completions \
--model <MODEL> --seed 42 --save-result --result-dir <DIR>
```
**SGLang**
```bash
python -m sglang.bench_serving --backend sglang --host 127.0.0.1 --port 30000 \
--dataset-name sharegpt --dataset-path <DATASET> \
--num-prompts 2000 --sharegpt-output-len 256 \
--max-concurrency <C> --model <MODEL> --seed 42 --output-file <FILE>
```
> 注SGLang 历史测试使用 2000 promptsvLLM-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 msmean 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.61DSpark 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-dspark0.23.1rc)比 vLLM 0.24.0 更快:
- 并发 10.59 vs 0.58 req/s接近
- 并发 165.61 vs 5.05 req/s+11%
- 并发 647.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 promptsvLLM 本次仅 200 prompts绝对数值需谨慎对比。
---
## 6. 最优参数建议
### 6.1 低延迟场景(单用户 / 低并发)
- 推荐:**DSpark `--spec-tokens 5`**
- 原因:单请求下 st=5 的 req/s 最高1.07TPOT/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/s3507 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 长度分布会影响结果。

33
scripts/SLO_STANDARDS.md Normal file
View File

@ -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 SLOP95 为主要关注指标)
| 指标 | 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 为准。_

View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Analyze nsys traces for DSpark profiling.
Usage:
python scripts/analyze_dspark_nsys.py <nsys-rep-file>
"""
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 <nsys-rep-file>")
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()

View File

@ -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()

View File

@ -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()

View File

@ -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/<result_root>/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_<run_id>
# 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_<run_id>
```
## 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).

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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`",
"- Warmup100 条",
"",
"## 核心指标对比",
"",
"| 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()

View File

@ -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()

View File

@ -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: <timestamp>_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"- Warmup100 条",
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()

View File

@ -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 "$@"

View File

@ -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 "$@"

View File

@ -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 "$@"

27
scripts/install_vllm_dspark.sh Executable file
View File

@ -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."

261
scripts/profile_dspark.py Executable file
View File

@ -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()

157
scripts/run_sglang_benchmark.sh Executable file
View File

@ -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} - <<PYEOF
import json
entry = {
"name": "${name}",
"args": "$*",
"duration_s": ${duration},
"request_throughput": ${req_throughput},
"input_token_throughput": ${input_throughput},
"output_token_throughput": ${output_throughput},
"total_token_throughput": ${total_throughput},
"mean_ttft_ms": ${mean_ttft},
"mean_tpot_ms": ${mean_tpot},
"mean_e2e_latency_ms": ${mean_e2e},
"accept_length": ${accept_length},
"output_file": "${output_file}"
}
with open("${SUMMARY_TMP}", "r") as f:
data = json.load(f)
data.append(entry)
with open("${SUMMARY_TMP}", "w") as f:
json.dump(data, f, indent=2)
PYEOF
}
main() {
log "Starting systematic benchmark for sglang 8-card DeepSeek-V4-Flash"
log "Model: ${MODEL}"
log "Dataset: ${DATASET}"
log "Num prompts per test: ${NUM_PROMPTS}"
log "Results will be saved to: ${RESULT_DIR}"
wait_for_server
# Concurrency sweep: simulate different real-world load levels
log "=== Phase 1: Concurrency sweep ==="
for c in 1 8 16 32 64 128; do
run_bench "sharegpt_concurrency_${c}" --max-concurrency "${c}"
done
# Request-rate sweep: simulate Poisson-like traffic
log "=== Phase 2: Request rate sweep ==="
for r in 1 2 4 8 16; do
run_bench "sharegpt_rps_${r}" --request-rate "${r}"
done
# Move final summary
mv "${SUMMARY_TMP}" "${SUMMARY_FILE}"
log "=== All benchmarks completed ==="
log "Summary saved to: ${SUMMARY_FILE}"
log "Detailed results in: ${RESULT_DIR}"
# Print summary table
${PYTHON} - <<PYEOF
import json
with open("${SUMMARY_FILE}", "r") as f:
data = json.load(f)
print("\n============ Benchmark Summary ============")
print(f"{'Name':<30} {'Req/s':>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 "$@"

View File

@ -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} - <<PYEOF
import json
entry = {
"name": "${name}",
"args": "$*",
"duration_s": ${duration},
"request_throughput": ${req_throughput},
"input_token_throughput": ${input_throughput},
"output_token_throughput": ${output_throughput},
"total_token_throughput": ${total_throughput},
"mean_ttft_ms": ${mean_ttft},
"mean_tpot_ms": ${mean_tpot},
"mean_e2e_latency_ms": ${mean_e2e},
"accept_length": ${accept_length},
"output_file": "${output_file}"
}
with open("${SUMMARY_TMP}", "r") as f:
data = json.load(f)
data.append(entry)
with open("${SUMMARY_TMP}", "w") as f:
json.dump(data, f, indent=2)
PYEOF
}
main() {
log "Starting max-throughput benchmark for sglang 8-card DeepSeek-V4-Flash"
log "Model: ${MODEL}"
log "Dataset: ${DATASET}"
log "Num prompts per test: ${NUM_PROMPTS}"
log "Results will be saved to: ${RESULT_DIR}"
wait_for_server
log "=== Max concurrency sweep ==="
for c in 128 256 512 1024; do
run_bench "sharegpt_concurrency_${c}" --max-concurrency "${c}"
done
mv "${SUMMARY_TMP}" "${SUMMARY_FILE}"
log "=== All max-throughput benchmarks completed ==="
log "Summary saved to: ${SUMMARY_FILE}"
log "Detailed results in: ${RESULT_DIR}"
${PYTHON} - <<PYEOF
import json
with open("${SUMMARY_FILE}", "r") as f:
data = json.load(f)
print("\n============ Max Throughput Summary ============")
print(f"{'Name':<30} {'Req/s':>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 "$@"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

138
scripts/start_pd_single_node.sh Executable file
View File

@ -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"

View File

@ -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}"

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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!")

View File

@ -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"