This commit is contained in:
yy-fighting 2026-07-08 14:45:00 +00:00
commit 539b3bc539
57 changed files with 3959 additions and 3429 deletions

View File

@ -9,10 +9,9 @@
│ ├── nvidia_h200.env
│ └── patches/kunlun_p800/ # runtime patches required by some images
├── scripts/ # shared benchmark/orchestrator/utility scripts
│ ├── common/ # reusable components (lib.sh, platform.sh, ...)
│ ├── analysis/ # cross-experiment comparison tools
│ ├── benchmark_dspark_0707/ # legacy DSpark benchmark suite
│ └── ...
│ ├── common/ # reusable components (lib.sh, platform.sh, parse_backend.py, ...)
│ ├── benchmark_dspark_0707/ # legacy DSpark benchmark suite (仍被 experiments/dsv4_h200_dspark/ 引用)
│ └── SLO_STANDARDS.md # 推理服务 SLO 标准
├── experiments/ # experiment-centric directories (preferred)
│ └── dsv4_p800_sglang/
│ ├── README.md
@ -38,7 +37,7 @@
1. **Experiments are the primary organization unit.**
- Each experiment lives under `experiments/<experiment_name>/` and contains its scripts, configuration, and results.
- Shared orchestration code lives in `scripts/common/`; do not copy server start / health check logic into every experiment.
- Legacy experiments may remain under `scripts/<group>/` with outputs in `bench_results/`, but new work should use `experiments/`.
- 新实验必须放在 `experiments/<name>/` 下;`scripts/` 下仅保留 `scripts/common/` 和被现有实验引用的 legacy 脚本。
2. **Benchmark outputs live with their experiment.**
- For `experiments/<name>/`, results go in `experiments/<name>/results/<RUN_ID>/`.
@ -266,13 +265,7 @@ python3 experiments/dsv4_p800_sglang/parse_results.py \
# Legacy layout
/data/user1/yy/envs/sglang/bin/python scripts/benchmark_dspark_0707/parse_results.py \
/data/user1/yy/bench_results/dspark_grid_<run_id>
```
### Cross-experiment comparison
```bash
python3 scripts/analysis/compare_experiments.py
/data/user1/yy/experiments/legacy_bench_results/dspark_grid_<run_id>
```
## Adding a New Platform or Experiment
@ -320,11 +313,11 @@ At minimum, `run_bench.sh` should:
- `scripts/common/lib.sh`: logging, health checks, metadata JSON.
- `scripts/common/platform.sh`: platform auto-detection and env loading.
- `scripts/common/server_docker.sh`: Docker-based server lifecycle.
- `scripts/common/bench_client_docker.sh`: run `sglang.bench_serving` inside a container.
- `scripts/common/warmup.py`: server 预热。
- `scripts/common/parse_backend.py`: raw jsonl -> `results.json` + `report.md`
- `scripts/common/compare.py`: SGLang vs vLLM 横向对比表。
Native (non-Docker) platforms can delegate to existing start scripts or add
new shared helpers under `scripts/common/`.
Docker/XPU 平台可以在 `scripts/common/` 下新增自己的 helper但不要在实验目录复制通用逻辑。
### 4. Example experiments

View File

@ -8,13 +8,12 @@
| 目录/文件 | 说明 |
|---|---|
| `platforms/` | 芯片/加速器平台配置(`*.env`),如 P800、H200 |
| `scripts/common/` | 跨实验复用的 orchestration 组件server 启停、health check、元数据生成 |
| `scripts/analysis/` | 跨实验对比分析工具 |
| `scripts/` | 旧版/legacy benchmark 脚本 |
| `scripts/common/` | 跨实验复用的 orchestration 组件server 启停、health check、元数据生成、结果解析 |
| `scripts/` | 仅保留仍在使用的 legacy DSpark 脚本与 SLO 标准 |
| `experiments/` | 以实验为单位的自包含目录(脚本 + 配置 + 结果) |
| `experiments/legacy_bench_results/` | 历史归档的实验产物(只读),从原 `bench_results/` 迁移而来 |
| `BENCHMARK_WORKFLOW.md` | benchmark 目录与命名规范 |
| `scripts/SLO_STANDARDS.md` | 推理服务 SLO 标准TTFT/TPOT |
| `docs/SLO_STANDARDS.md` | 推理服务 SLO 标准TTFT/TPOT |
> 注:`.gitignore` 已排除 `envs/``deps/``tmp/``datasets/``logs/``*.zip`、原始 `raw_outputs/*.jsonl` 等大文件/中间文件。
@ -34,16 +33,12 @@
### 旧结构scripts/ + bench_results/
仅保留还在被 `experiments/dsv4_h200_dspark/` 引用的 legacy DSpark 脚本,其余旧脚本已删除。
| 实验 | 脚本 | 最终报告 | 说明 |
|---|---|---|---|
| DSpark grid benchmark | `scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh` | `experiments/legacy_bench_results/dspark_grid_20260707-132641/report.md` | P1/P2/P3 全量网格 |
| DSpark spec-tokens 对比 | `scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh` | `experiments/legacy_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` + 对比解析脚本 | `experiments/legacy_bench_results/eagle_grid/dspark_vs_eagle_report.md` | EAGLE 投机解码对比 |
| SGLang vs vLLM 后端对比0707 | `scripts/benchmark_dsv4_backend_comparison.sh` | `experiments/legacy_bench_results/dsv4_backend_comparison_20260707/README.md` | 遗留 raw outputs 清单 |
| SGLang 8-card systematic | `scripts/run_sglang_benchmark.sh` | `experiments/legacy_bench_results/sglang_8card_systematic_20260704_120819/report.md` | 早期 SGLang 系统扫描 |
| SGLang 8-card max throughput | `scripts/run_sglang_max_throughput.sh` | `experiments/legacy_bench_results/sglang_8card_max_throughput_20260705_030839/summary.json` | 最大吞吐扫描 |
| vLLM-dspark Qwen3 | `scripts/bench_vllm_dspark_qwen3.py` | `experiments/legacy_bench_results/vllm_dspark_qwen3_20260705_121256/summary.json` | Qwen3 模型测速 |
| DSV4 后端对比0705 | `scripts/bench_dsv4_comparison.py` | `experiments/legacy_bench_results/dsv4_comparison_20260705_152221/summary.json` | 早期 DSpark 配置对比 |
## 快速复现
@ -83,15 +78,10 @@ bash experiments/dsv4_h200_vllm_dspark_vs_default/run_bench.sh
bash experiments/dsv4_h200_max_context_length/run_bench.sh
```
### DSpark grid旧结构
### DSpark grid / spec-tokens(旧结构)
```bash
bash scripts/benchmark_dspark_0707/run_dspark_benchmark_grid.sh
```
### Spec-tokens 对比
```bash
bash scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh
```
@ -99,9 +89,6 @@ bash scripts/benchmark_dspark_0707/run_dspark_st_comparison.sh
```bash
# 新结构
python3 experiments/dsv4_p800_sglang/parse_results.py \
experiments/dsv4_p800_sglang/results/<run_id>
python3 experiments/dsv4_h200_dspark/parse_results.py \
experiments/dsv4_h200_dspark/results/<run_id>
@ -112,7 +99,7 @@ python3 experiments/dsv4_h200_dspark/parse_results.py \
## SLO 参考
详见 `scripts/SLO_STANDARDS.md`。
详见 `docs/SLO_STANDARDS.md`。
主要关注指标:

178
docs/EXPERIMENT_GUIDE.md Normal file
View File

@ -0,0 +1,178 @@
# 实验规范指南
本仓库用于统一记录和复现不同芯片、不同 backendSGLang / vLLM下的推理测速实验。
## 1. 目录结构
```
experiments/<experiment_name>/
├── config.env # 实验参数唯一来源
├── run_bench.sh # 编排入口
├── start_sglang.sh # SGLang server 启动脚本
├── start_vllm.sh # vLLM server 启动脚本
└── results/<run_id>/
├── comparison.md
├── sglang/
│ ├── results.json
│ ├── report.md
│ ├── raw_outputs/ # .gitignore 忽略
│ └── logs/ # .gitignore 忽略
└── vllm/
├── results.json
├── report.md
├── raw_outputs/ # .gitignore 忽略
└── logs/ # .gitignore 忽略
```
通用工具集中放在 `scripts/common/`,不要复制到每个实验:
- `scripts/common/lib.sh`日志、metadata、JSON 工具
- `scripts/common/platform.sh`:平台自动检测
- `scripts/common/warmup.py`server 预热
- `scripts/common/parse_backend.py`:解析 raw jsonl -> results.json + report.md
- `scripts/common/compare.py`:生成 SGLang vs vLLM 对比表
## 2. 新增一个实验
最快方式:
```bash
cp -r experiments/TEMPLATE experiments/<your_experiment_name>
# 修改 config.env、start_*.sh
bash experiments/<your_experiment_name>/run_bench.sh
```
### 2.1 config.env 必备字段
```bash
EXPERIMENT="<experiment_name>"
MODEL_NAME="DeepSeek-V4-Flash"
MODEL_PATH="/data/models/DeepSeek-V4-Flash"
SGLANG_PORT="${SGLANG_PORT:-30006}"
VLLM_PORT="${VLLM_PORT:-30005}"
VENV_SGLANG="${VENV_SGLANG:-/data/user1/yy/envs/sglang}"
VENV_VLLM="${VENV_VLLM:-/data/user1/yy/envs/vllm}"
export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES:-0,1,2,3,4,5,6,7}"
TP="${TP:-8}"
MAX_MODEL_LEN=...
MAX_NUM_SEQS=...
MAX_RUNNING=...
# 场景:"concurrency input_len output_len num_prompts"
declare -a SCENARIOS=(
"1 512 256 32"
)
VENV_CLIENT="${VENV_CLIENT:-$VENV_SGLANG}"
SGLANG_START_SCRIPT="${SCRIPT_DIR:-.}/start_sglang.sh"
VLLM_START_SCRIPT="${SCRIPT_DIR:-.}/start_vllm.sh"
```
### 2.2 可复现性要求
每个 `results.json` 必须记录:
- `metadata.model`:模型路径
- `metadata.hardware` / `metadata.accelerator` / `metadata.chip`
- `metadata.env`:使用的虚拟环境路径
- `metadata.git_commit` / `metadata.git_dirty`
- `config.server_args`:完整的 server 启动命令
- `config.cuda_visible_devices`
这些通过 `scripts/common/lib.sh` 中的 `write_metadata_json``jq` 注入。
## 3. 平台与硬件适配
### 3.1 芯片级默认配置
芯片相关默认值放到 `platforms/<chip>.env`,例如:
- `platforms/nvidia_h200.env`
- `platforms/kunlun_p800.env`
实验级覆盖通过 `config.env` 实现。
### 3.2 跨芯片注意事项
| 项 | H200 | P800 | 备注 |
|---|---|---|---|
| TP 大小 | 8 | 8/16 | 由 `TP` 控制 |
| KV cache dtype | fp8 | 可能不同 | 在 `start_vllm.sh` 调整 |
| MLA backend | flashinfer_mla | 可能不支持 | 在 `start_sglang.sh`/`start_vllm.sh` 调整 |
| 最大上下文 | 受显存限制 | 受显存限制 | 通过探针测试确定 |
| 设备变量 | `CUDA_VISIBLE_DEVICES` | 可能不同 | 在 `config.env` 设置 |
如果某芯片不支持某个 feature`start_*.sh` 里用条件判断,不要把条件写进通用脚本。
## 4. SLO 标准
默认使用 S2 层级:
- TTFT P95 < 3000 ms
- TPOT mean < 50 ms
`scripts/common/parse_backend.py``scripts/common/compare.py` 默认按这个标准打标。如果需要其他 tier调用 `compare.py` 时传入 `--ttft-limit``--tpot-limit`
## 5. 断点续测
`run_bench.sh` 必须实现 `scenario_already_completed()` 检查:
- 如果某 scenario 的 `raw_outputs/*.jsonl``completed` 数量已达标,直接跳过
- 重新执行脚本即可从中断处继续
## 6. Git 提交规范
### 6.1 必须提交
- 实验代码:`config.env``run_bench.sh``start_*.sh`
- 最终结果:`comparison.md``sglang/results.json``sglang/report.md``vllm/results.json``vllm/report.md`
### 6.2 不要提交
- `raw_outputs/`
- `logs/`
- 中间 server 日志
这些已在 `.gitignore` 中忽略。
### 6.3 提交示例
```bash
git add experiments/<name>/config.env \
experiments/<name>/run_bench.sh \
experiments/<name>/start_*.sh \
experiments/<name>/results/<run_id>/comparison.md \
experiments/<name>/results/<run_id>/sglang/results.json \
experiments/<name>/results/<run_id>/sglang/report.md \
experiments/<name>/results/<run_id>/vllm/results.json \
experiments/<name>/results/<run_id>/vllm/report.md
git commit -m "results: <experiment_name> run <run_id>"
git push origin main
```
## 7. 命名约定
- 实验目录:`{model}_{chip}_{scenario}_{backend_vs_backend}`,例如 `dsv4_h200_sglang_vs_vllm`
- scenario 名称:`c{concurrency}_i{input_len}_o{output_len}`
- raw 输出:`{backend}_{label}_{MMDD}_{concurrency}_{input_len}_{output_len}.jsonl`
- run_id`YYYYMMDD-HHMMSS`
## 8. 常见问题
### 8.1 新芯片上 server 起不来
1. 检查 `platforms/<chip>.env` 是否已定义
2. 检查 `start_*.sh` 中的 backend-specific 参数是否被该芯片支持
3. 先用最小场景input=512, output=256, concurrency=1验证通路
### 8.2 长上下文 OOM
1. 降低并发
2. 降低输出长度
3. 调整 `mem-fraction-static` / `gpu-memory-utilization`
4. 记录能跑通的最大组合,更新矩阵

View File

@ -97,13 +97,7 @@ For a concrete example, see `experiments/dsv4_h200_dspark/`.
## 5. Cross-platform comparison
After running experiments on both P800 and H200, compare them with:
```bash
python3 scripts/analysis/compare_experiments.py \
experiments/dsv4_p800_sglang/results/<run_id> \
experiments/dsv4_h200_dspark/results/<run_id>
```
跨平台对比脚本目前未统一提供。可分别读取各实验 `results/<run_id>/results.json` 中的结构化数据,按 scenario 聚合后生成对比表。
## Notes

View File

@ -0,0 +1,45 @@
# TEMPLATESGLang vs vLLM 实验模板
这是一个最小可运行的实验模板。要新增一个实验:
```bash
cp -r experiments/TEMPLATE experiments/<your_experiment_name>
# 修改 config.env 里的参数
# 按需调整 start_sglang.sh / start_vllm.sh 的启动参数
bash experiments/<your_experiment_name>/run_bench.sh
```
## 目录约定
- `config.env`:实验参数唯一来源
- `start_sglang.sh` / `start_vllm.sh`server 启动脚本
- `run_bench.sh`:编排入口
- `results/<run_id>/`:自动生成的结果目录
- `sglang/`, `vllm/`:各自 backend 的结果
- `comparison.md`:横向对比表
## 通用工具位置
不要在本目录下复制解析脚本,直接引用:
- 预热:`../../scripts/common/warmup.py`
- 结果解析:`../../scripts/common/parse_backend.py`
- 对比报告:`../../scripts/common/compare.py`
- 公共函数:`../../scripts/common/lib.sh`, `../../scripts/common/platform.sh`
## 提交规范
只提交最终文件:
```bash
git add experiments/<your_experiment_name>/config.env \
experiments/<your_experiment_name>/run_bench.sh \
experiments/<your_experiment_name>/start_*.sh \
experiments/<your_experiment_name>/results/<run_id>/comparison.md \
experiments/<your_experiment_name>/results/<run_id>/sglang/results.json \
experiments/<your_experiment_name>/results/<run_id>/sglang/report.md \
experiments/<your_experiment_name>/results/<run_id>/vllm/results.json \
experiments/<your_experiment_name>/results/<run_id>/vllm/report.md
```
`raw_outputs/``logs/` 已在 `.gitignore` 中忽略。

View File

@ -0,0 +1,33 @@
# Template configuration for a SGLang vs vLLM benchmark experiment.
# Copy this directory to experiments/<your_experiment_name>/ and adjust the values.
EXPERIMENT="TEMPLATE"
MODEL_NAME="DeepSeek-V4-Flash"
MODEL_PATH="/data/models/DeepSeek-V4-Flash"
SERVED_MODEL_NAME="deepseek-v4-flash"
SGLANG_PORT="${SGLANG_PORT:-30006}"
VLLM_PORT="${VLLM_PORT:-30005}"
VENV_SGLANG="${VENV_SGLANG:-/data/user1/yy/envs/sglang}"
VENV_VLLM="${VENV_VLLM:-/data/user1/yy/envs/vllm}"
# Hardware: override in platforms/<chip>.env or set explicitly.
export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES:-0,1,2,3,4,5,6,7}"
TP="${TP:-8}"
# Server limits.
MAX_MODEL_LEN="${MAX_MODEL_LEN:-32768}"
MAX_NUM_SEQS="${MAX_NUM_SEQS:-256}"
MAX_RUNNING="${MAX_RUNNING:-256}"
# Scenarios: "concurrency input_len output_len num_prompts".
declare -a SCENARIOS=(
"1 512 256 32"
"32 512 256 128"
)
VENV_CLIENT="${VENV_CLIENT:-$VENV_SGLANG}"
SGLANG_START_SCRIPT="${SCRIPT_DIR:-.}/start_sglang.sh"
VLLM_START_SCRIPT="${SCRIPT_DIR:-.}/start_vllm.sh"

265
experiments/TEMPLATE/run_bench.sh Executable file
View File

@ -0,0 +1,265 @@
#!/usr/bin/env bash
# Template orchestrator for a SGLang vs vLLM benchmark experiment.
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXPERIMENT_NAME="$(basename "$SCRIPT_DIR")"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/../../scripts/common/lib.sh"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/../../scripts/common/platform.sh"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/config.env"
RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}"
RESULT_BASE="${SCRIPT_DIR}/results"
log_dir_global="${RESULT_BASE}/${RUN_ID}/logs"
mkdir -p "$log_dir_global"
log_init "${log_dir_global}/orchestrator.log"
log "experiment=${EXPERIMENT_NAME}"
log "run_id=${RUN_ID}"
log "platform=${PLATFORM}"
log "hardware=${HARDWARE}"
log "model=${MODEL_PATH}"
log "max_model_len=${MAX_MODEL_LEN}"
log "scenarios=${#SCENARIOS[@]}"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
is_server_healthy() {
local port="$1"
curl --fail --silent --show-error --max-time 5 "http://127.0.0.1:${port}/health" >/dev/null 2>&1
}
stop_server() {
local backend="$1"
local pid_file="/data/user1/yy/${EXPERIMENT_NAME}_${backend}.pid"
if [[ -f "$pid_file" ]]; then
local pid
pid="$(cat "$pid_file")"
if kill -0 "$pid" 2>/dev/null; then
log "stopping ${backend} 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 cleanup.
if [[ "$backend" == "sglang" ]]; then
pkill -9 -f "sglang serve.*${MODEL_NAME}" 2>/dev/null || true
else
pkill -9 -f "vllm serve.*${MODEL_NAME}" 2>/dev/null || true
fi
sleep 2
}
start_server() {
local backend="$1"
local start_script
if [[ "$backend" == "sglang" ]]; then
start_script="${SGLANG_START_SCRIPT}"
local port="$SGLANG_PORT"
else
start_script="${VLLM_START_SCRIPT}"
local port="$VLLM_PORT"
fi
log "starting ${backend} server with ${start_script}"
bash "${start_script}" >> "${log_dir_global}/${backend}.server.outer.log" 2>&1
if ! is_server_healthy "$port"; then
log "error: ${backend} server failed to become healthy"
return 1
fi
log "${backend} server is healthy on port ${port}"
}
run_warmup() {
local backend="$1"
local port
if [[ "$backend" == "sglang" ]]; then
port="$SGLANG_PORT"
else
port="$VLLM_PORT"
fi
log "warming up ${backend}"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/warmup.py" \
--backend "$backend" \
--port "$port" \
--input-len 4000 \
--output-len 512 \
--num 2 \
--env-python "${VENV_CLIENT}/bin/python" \
>> "${log_dir_global}/${backend}.warmup.log" 2>&1
log "warmup for ${backend} completed"
}
scenario_already_completed() {
local output_file="$1"
local expected="$2"
[[ -s "$output_file" ]] || return 1
local completed
completed="$(${VENV_CLIENT}/bin/python -c "
import json, sys
path = sys.argv[1]
try:
with open(path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line:
data = json.loads(line)
print(data.get('completed', 0))
break
except Exception:
print(0)
" "$output_file")"
[[ "${completed:-0}" -ge "$expected" ]]
}
run_benchmark() {
local backend="$1"
local result_root="${RESULT_BASE}/${RUN_ID}/${backend}"
local raw_dir="${result_root}/raw_outputs"
local phase_log_dir="${result_root}/logs"
mkdir -p "$raw_dir" "$phase_log_dir"
local port
if [[ "$backend" == "sglang" ]]; then
port="$SGLANG_PORT"
else
port="$VLLM_PORT"
fi
log "===== ${backend} START (max_model_len=${MAX_MODEL_LEN}) ====="
stop_server "$backend"
start_server "$backend"
run_warmup "$backend"
for scenario in "${SCENARIOS[@]}"; do
read -r concurrency input_len output_len num_prompts <<< "$scenario"
output_file="${raw_dir}/${backend}_phase1_$(date '+%m%d')_${concurrency}_${input_len}_${output_len}.jsonl"
detail_log="${phase_log_dir}/${backend}_phase1_c${concurrency}_i${input_len}_o${output_len}.log"
if scenario_already_completed "$output_file" "$num_prompts"; then
log "skipping already-completed ${backend} scenario: c=${concurrency} i=${input_len} o=${output_len}"
continue
fi
log "running ${backend} scenario: c=${concurrency} i=${input_len} o=${output_len} n=${num_prompts}"
"${VENV_CLIENT}/bin/python" -m sglang.bench_serving \
--backend "$backend" \
--host 127.0.0.1 \
--port "$port" \
--dataset-name random \
--random-input-len "$input_len" \
--random-output-len "$output_len" \
--num-prompts "$num_prompts" \
--max-concurrency "$concurrency" \
--request-rate 10000 \
--output-file "$output_file" \
--output-details \
> "$detail_log" 2>&1 || {
log "ERROR: ${backend} scenario c=${concurrency} i=${input_len} o=${output_len} failed; see ${detail_log}"
continue
}
log "finished ${backend} scenario: output=${output_file}"
done
stop_server "$backend"
log "===== ${backend} DONE ====="
}
parse_backend() {
local backend="$1"
local result_root="${RESULT_BASE}/${RUN_ID}/${backend}"
log "parsing ${backend} results in ${result_root}"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/parse_backend.py" "$result_root" --backend "$backend" \
>> "${result_root}/logs/parse.log" 2>&1 || {
log "WARNING: parser failed for ${backend}; see ${result_root}/logs/parse.log"
}
}
write_backend_metadata() {
local backend="$1"
local result_root="${RESULT_BASE}/${RUN_ID}/${backend}"
ensure_result_root "$result_root"
local meta_json="${result_root}/results.json"
local env_path
if [[ "$backend" == "sglang" ]]; then
env_path="$VENV_SGLANG"
else
env_path="$VENV_VLLM"
fi
# Capture the exact server launch args for reproducibility.
local server_args
if [[ "$backend" == "sglang" ]]; then
server_args="sglang serve --trust-remote-code --model-path $MODEL_PATH --tp $TP --moe-runner-backend marlin --context-length $MAX_MODEL_LEN --max-running-requests $MAX_RUNNING --mem-fraction-static 0.88 --host 0.0.0.0 --port $SGLANG_PORT"
else
server_args="vllm serve $MODEL_PATH --trust-remote-code --tensor-parallel-size $TP --kv-cache-dtype fp8 --max-model-len $MAX_MODEL_LEN --max-num-seqs $MAX_NUM_SEQS --block-size 256 --gpu-memory-utilization 0.90 --tokenizer-mode deepseek_v4 --reasoning-parser deepseek_v4 --no-disable-hybrid-kv-cache-manager --disable-uvicorn-access-log --port $VLLM_PORT"
fi
write_metadata_json \
"$meta_json" \
"${EXPERIMENT_NAME}_${backend}" \
"$RUN_ID" \
"$MODEL_PATH" \
"$backend" \
"$backend" \
"$HARDWARE" \
"$ACCELERATOR" \
"$CHIP" \
"experiments/${EXPERIMENT_NAME}/run_bench.sh" \
"$env_path" \
"${HARDWARE} ${backend} TP=${TP} benchmark for ${MODEL_NAME}"
jq --arg backend "$backend" \
--arg server_args "$server_args" \
'.config = {
"tp": ($TP | tonumber),
"cuda_visible_devices": $CUDA_VISIBLE_DEVICES,
"max_model_len": ($MAX_MODEL_LEN | tonumber),
"backend": $backend,
"server_start_script": "experiments/\($EXPERIMENT_NAME)/start_\($backend).sh",
"server_args": $server_args
}' "$meta_json" > "${meta_json}.tmp" && mv "${meta_json}.tmp" "$meta_json"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
stop_server sglang
stop_server vllm
write_backend_metadata sglang
write_backend_metadata vllm
run_benchmark sglang
parse_backend sglang
run_benchmark vllm
parse_backend vllm
log "generating comparison report"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/compare.py" \
--sglang "${RESULT_BASE}/${RUN_ID}/sglang" \
--vllm "${RESULT_BASE}/${RUN_ID}/vllm" \
--output "${RESULT_BASE}/${RUN_ID}/comparison.md" \
>> "${log_dir_global}/compare.log" 2>&1 || {
log "WARNING: comparison script failed; see ${log_dir_global}/compare.log"
}
log "all results saved to ${RESULT_BASE}/${RUN_ID}"

View File

@ -0,0 +1,62 @@
#!/bin/bash
# Start SGLang server for this experiment.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/config.env"
cd /data/user1/yy
mkdir -p logs
export PATH="${VENV_SGLANG}/bin:$PATH"
export PYTHONUNBUFFERED=1
export SGLANG_LOG_LEVEL=info
export TMPDIR=/data/user1/yy/tmp
export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES}"
LOG="/data/user1/yy/logs/${EXPERIMENT}_sglang_$(date +%Y%m%d_%H%M%S).log"
PID_FILE="/data/user1/yy/${EXPERIMENT}_sglang.pid"
rm -f "$PID_FILE"
echo "=== Starting SGLang server (TP=$TP, max_model_len=$MAX_MODEL_LEN) ==="
echo "Model: $MODEL_PATH"
echo "Port: $SGLANG_PORT"
echo "Log: $LOG"
nohup sglang serve \
--trust-remote-code \
--model-path "$MODEL_PATH" \
--tp "$TP" \
--moe-runner-backend marlin \
--context-length "$MAX_MODEL_LEN" \
--max-running-requests "$MAX_RUNNING" \
--mem-fraction-static 0.88 \
--host 0.0.0.0 \
--port "$SGLANG_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 --fail --silent --show-error --max-time 5 "http://127.0.0.1:${SGLANG_PORT}/health" >/dev/null 2>&1; then
echo "SGLang server is ready at http://127.0.0.1:${SGLANG_PORT}"
echo "Log: $LOG"
exit 0
fi
if ! kill -0 $PID 2>/dev/null; then
echo "ERROR: SGLang server exited early"
tail -200 "$LOG"
exit 1
fi
echo "Waiting... ($i/240)"
sleep 5
done
echo "ERROR: SGLang server not healthy after 240 retries"
tail -200 "$LOG"
exit 1

View File

@ -0,0 +1,65 @@
#!/bin/bash
# Start vLLM server for this experiment.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/config.env"
cd /data/user1/yy
mkdir -p logs
VENV="${VENV_VLLM}"
export PATH="$VENV/bin:$PATH"
export PYTHONUNBUFFERED=1
export TMPDIR=/data/user1/yy/tmp
export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES}"
LOG="/data/user1/yy/logs/${EXPERIMENT}_vllm_$(date +%Y%m%d_%H%M%S).log"
PID_FILE="/data/user1/yy/${EXPERIMENT}_vllm.pid"
rm -f "$PID_FILE"
echo "=== Starting vLLM server (TP=$TP, max_model_len=$MAX_MODEL_LEN) ==="
echo "Model: $MODEL_PATH"
echo "Port: $VLLM_PORT"
echo "Log: $LOG"
nohup vllm serve "$MODEL_PATH" \
--trust-remote-code \
--tensor-parallel-size "$TP" \
--kv-cache-dtype fp8 \
--max-model-len "$MAX_MODEL_LEN" \
--max-num-seqs "$MAX_NUM_SEQS" \
--block-size 256 \
--gpu-memory-utilization 0.90 \
--tokenizer-mode deepseek_v4 \
--reasoning-parser deepseek_v4 \
--no-disable-hybrid-kv-cache-manager \
--disable-uvicorn-access-log \
--port "$VLLM_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 --fail --silent --show-error --max-time 5 "http://127.0.0.1:${VLLM_PORT}/health" >/dev/null 2>&1; then
echo "vLLM server is ready at http://127.0.0.1:${VLLM_PORT}"
echo "Log: $LOG"
exit 0
fi
if ! kill -0 $PID 2>/dev/null; then
echo "ERROR: vLLM server exited early"
tail -200 "$LOG"
exit 1
fi
echo "Waiting... ($i/240)"
sleep 5
done
echo "ERROR: vLLM server not healthy after 240 retries"
tail -200 "$LOG"
exit 1

View File

@ -1,219 +0,0 @@
#!/usr/bin/env python3
"""Parse raw sglang.bench_serving JSONL outputs for one backend.
Reads JSONL files like {sglang|vllm}_phase1_MMDD_concurrency_inputlen_outputlen.jsonl
and generates results.json + report.md in the given result root.
Usage:
python3 parse_backend.py <result_root> [--backend sglang|vllm]
"""
import argparse
import json
import re
import sys
from pathlib import Path
def parse_jsonl(path: Path) -> dict | None:
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
return json.loads(line)
except json.JSONDecodeError:
continue
return None
def compute_metrics(data: dict) -> dict:
completed = data.get("completed", 0)
total = len(data.get("input_lens", []))
failed = total - completed if total > 0 else 0
duration_s = data.get("duration", 0.0)
return {
"success": completed,
"failed": failed,
"duration_s": duration_s,
"request_throughput": data.get("request_throughput", 0.0),
"input_token_throughput": data.get("input_throughput", 0.0),
"output_token_throughput": data.get("output_throughput", 0.0),
"total_token_throughput": data.get("total_throughput", 0.0),
"total_input_tokens": data.get("total_input_tokens", 0),
"total_output_tokens": data.get("total_output_tokens", 0),
"e2e_ms": {
"mean": data.get("mean_e2e_latency_ms", 0.0),
"p50": data.get("median_e2e_latency_ms", 0.0),
"p90": data.get("p90_e2e_latency_ms", 0.0),
"p95": data.get("p95_e2e_latency_ms", 0.0),
"p99": data.get("p99_e2e_latency_ms", 0.0),
},
"ttft_ms": {
"mean": data.get("mean_ttft_ms", 0.0),
"p50": data.get("median_ttft_ms", 0.0),
"p90": data.get("p90_ttft_ms", 0.0),
"p95": data.get("p95_ttft_ms", 0.0),
"p99": data.get("p99_ttft_ms", 0.0),
},
"tpot_ms": {
"mean": data.get("mean_tpot_ms", 0.0),
"p50": data.get("median_tpot_ms", 0.0),
"p90": data.get("p90_tpot_ms", 0.0),
"p95": data.get("p95_tpot_ms", 0.0),
"p99": data.get("p99_tpot_ms", 0.0),
},
"itl_ms": {
"mean": data.get("mean_itl_ms", 0.0),
"p50": data.get("median_itl_ms", 0.0),
"p90": data.get("p90_itl_ms", 0.0),
"p95": data.get("p95_itl_ms", 0.0),
"p99": data.get("p99_itl_ms", 0.0),
},
}
def scenario_name(concurrency: int, input_len: int, output_len: int) -> str:
return f"c{concurrency}_i{input_len}_o{output_len}"
def slo_status(metrics: dict) -> dict:
"""Check S2-tier SLO: TTFT P95 < 3s, TPOT mean < 50ms."""
ttft_ok = metrics["ttft_ms"]["p95"] < 3000.0
tpot_ok = metrics["tpot_ms"]["mean"] < 50.0
if ttft_ok and tpot_ok:
mark = ""
elif ttft_ok or tpot_ok:
mark = "⚠️"
else:
mark = ""
return {
"ttft_p95_ok": ttft_ok,
"tpot_mean_ok": tpot_ok,
"overall": mark,
}
def append_scenario(results_json: Path, scenario: dict) -> None:
with open(results_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["scenarios"].append(scenario)
with open(results_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def generate_report(result_root: Path, backend: str, scenarios: list[dict]) -> None:
report_path = result_root / "report.md"
with open(report_path, "w", encoding="utf-8") as f:
f.write(f"# H200 {backend.upper()} Comparison Benchmark Report\n\n")
f.write(f"- Result root: `{result_root}`\n")
f.write(f"- Model: `/data/models/DeepSeek-V4-Flash`\n")
f.write(f"- Backend: {backend.upper()} (TP=8)\n")
f.write(f"- Benchmark client: `sglang.bench_serving --backend {backend}`\n\n")
f.write("## Results\n\n")
f.write("| Scenario | Phase | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | SLO |\n")
f.write("|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n")
for s in scenarios:
cfg = s["config"]
m = s["metrics"]
slo = s.get("slo_status", {}).get("overall", "")
f.write(
f"| {s['name']} | {cfg['phase']} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | "
f"{m['duration_s']:.2f} | {m['success']} | {m['request_throughput']:.2f} | "
f"{m['input_token_throughput']:.2f} | {m['output_token_throughput']:.2f} | "
f"{m['total_token_throughput']:.2f} | "
f"{m['ttft_ms']['mean']:.2f} | {m['ttft_ms']['p95']:.2f} | {m['ttft_ms']['p99']:.2f} | "
f"{m['tpot_ms']['mean']:.2f} | {m['tpot_ms']['p95']:.2f} | {m['tpot_ms']['p99']:.2f} | "
f"{m['e2e_ms']['mean']:.2f} | {m['e2e_ms']['p95']:.2f} | {m['e2e_ms']['p99']:.2f} | {slo} |\n"
)
f.write("\n")
f.write("SLO: S2 tier — TTFT P95 < 3000ms, TPOT mean < 50ms. ✅ pass, ⚠️ partial, ❌ fail.\n\n")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("result_root", type=Path)
parser.add_argument("--backend", default=None, choices=["sglang", "vllm"])
args = parser.parse_args()
result_root = args.result_root
raw_dir = result_root / "raw_outputs"
results_json = result_root / "results.json"
if not raw_dir.exists():
raise SystemExit(f"raw_outputs directory not found: {raw_dir}")
backend = args.backend
if backend is None:
# Infer from filenames if not provided.
for p in raw_dir.iterdir():
if p.name.startswith("sglang_"):
backend = "sglang"
break
if p.name.startswith("vllm_"):
backend = "vllm"
break
if backend is None:
raise SystemExit("Could not infer backend from raw outputs")
scenarios = []
for jsonl_path in sorted(raw_dir.glob(f"{backend}_*.jsonl")):
parts = jsonl_path.stem.split("_")
if len(parts) < 5:
continue
label = parts[1]
if label == "sharegpt":
phase = "sharegpt"
dataset = "sharegpt"
else:
phase = label
dataset = "random"
# The last three numeric tokens are: concurrency, input_len/output_ctx, output_len.
try:
concurrency, input_len, output_len = int(parts[-3]), int(parts[-2]), int(parts[-1])
except ValueError:
continue
data = parse_jsonl(jsonl_path)
if data is None:
continue
metrics = compute_metrics(data)
scenario = {
"name": scenario_name(concurrency, input_len, output_len),
"config": {
"phase": phase,
"concurrency": concurrency,
"input_len": input_len,
"output_len": output_len,
"dataset": dataset,
"num_prompts": metrics["success"] + metrics["failed"],
},
"metrics": metrics,
"slo_status": slo_status(metrics),
"raw_file": str(jsonl_path),
}
scenarios.append(scenario)
if not scenarios:
print("No benchmark outputs found to parse")
return
if results_json.exists():
with open(results_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["scenarios"] = scenarios
with open(results_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
generate_report(result_root, backend, scenarios)
print(f"Parsed {len(scenarios)} scenarios into {result_root}/report.md")
if __name__ == "__main__":
main()

View File

@ -17,5 +17,5 @@
## Notes
- SLO check uses TTFT P95 and TPOT mean (the same criteria as `scripts/SLO_STANDARDS.md`).
- SLO check uses TTFT P95 and TPOT mean (the same criteria as `docs/SLO_STANDARDS.md`).
- A ⚠️ indicates one of the two metrics is out of target; ❌ indicates both are out.

View File

@ -95,7 +95,7 @@ run_warmup() {
num=1
log "warming up ${backend} (input=${input_len}, output=${output_len}, num=${num})"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/warmup.py" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/warmup.py" \
--backend "$backend" \
--port "$port" \
--input-len "$input_len" \
@ -189,7 +189,7 @@ parse_backend() {
local backend="$1"
local result_root="${RESULT_BASE}/${RUN_ID}/${backend}"
log "parsing ${backend} results in ${result_root}"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/parse_backend.py" "$result_root" --backend "$backend" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/parse_backend.py" "$result_root" --backend "$backend" \
>> "${result_root}/logs/parse.log" 2>&1 || {
log "WARNING: parser failed for ${backend}; see ${result_root}/logs/parse.log"
}
@ -264,7 +264,7 @@ parse_backend vllm
# Generate comparison.
log "generating comparison report"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/compare.py" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/compare.py" \
--sglang "${RESULT_BASE}/${RUN_ID}/sglang" \
--vllm "${RESULT_BASE}/${RUN_ID}/vllm" \
--output "${RESULT_BASE}/${RUN_ID}/comparison.md" \

View File

@ -1,85 +0,0 @@
#!/usr/bin/env python3
"""Generate a side-by-side comparison of SGLang and vLLM results.
Usage:
python3 compare.py --sglang <sglang_result_root> --vllm <vllm_result_root> \
[--output comparison.md]
"""
import argparse
import json
from collections import defaultdict
from pathlib import Path
def load_result(result_root: Path) -> dict:
path = result_root / "results.json"
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def slo_status(ttft_p95_ms: float, tpot_mean_ms: float) -> str:
# S2 tier targets from scripts/SLO_STANDARDS.md.
ttft_ok = ttft_p95_ms < 3000.0
tpot_ok = tpot_mean_ms < 50.0
if ttft_ok and tpot_ok:
return ""
if ttft_ok or tpot_ok:
return "⚠️"
return ""
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--sglang", type=Path, required=True)
parser.add_argument("--vllm", type=Path, required=True)
parser.add_argument("-o", "--output", type=Path, default=Path("comparison.md"))
args = parser.parse_args()
sglang_data = load_result(args.sglang)
vllm_data = load_result(args.vllm)
by_scenario = defaultdict(dict)
for data in (sglang_data, vllm_data):
backend = data["metadata"]["engine"]
for s in data.get("scenarios", []):
key = s["name"]
by_scenario[key][backend] = s
with open(args.output, "w", encoding="utf-8") as f:
f.write("# SGLang vs vLLM on DeepSeek-V4-Flash (H200, TP=8)\n\n")
f.write("## Summary\n\n")
f.write("- Model: `/data/models/DeepSeek-V4-Flash`\n")
f.write("- Hardware: 8x NVIDIA H200 143GB\n")
f.write("- Tensor Parallelism: 8\n")
f.write("- Benchmark client: `sglang.bench_serving`\n")
f.write("- SLO reference: S2 tier (TTFT P95 < 3s, TPOT < 50ms)\n\n")
f.write("## Side-by-side results\n\n")
f.write("| Scenario | Backend | Conc | Input | Output | Req/s | OutTok/s | TTFT P95(ms) | TTFT P99(ms) | TPOT Mean(ms) | TPOT P95(ms) | TPOT P99(ms) | E2E P99(ms) | SLO |\n")
f.write("|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n")
for scenario_name in sorted(by_scenario.keys()):
for backend in ("sglang", "vllm"):
s = by_scenario[scenario_name].get(backend)
if s is None:
continue
cfg = s["config"]
m = s["metrics"]
status = slo_status(m["ttft_ms"]["p95"], m["tpot_ms"]["mean"])
f.write(
f"| {scenario_name} | {backend} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | "
f"{m['request_throughput']:.2f} | {m['output_token_throughput']:.2f} | "
f"{m['ttft_ms']['p95']:.2f} | {m['ttft_ms']['p99']:.2f} | "
f"{m['tpot_ms']['mean']:.2f} | {m['tpot_ms']['p95']:.2f} | {m['tpot_ms']['p99']:.2f} | "
f"{m['e2e_ms']['p99']:.2f} | {status} |\n"
)
f.write("\n## Notes\n\n")
f.write("- SLO check uses TTFT P95 and TPOT mean (the same criteria as `scripts/SLO_STANDARDS.md`).\n")
f.write("- A ⚠️ indicates one of the two metrics is out of target; ❌ indicates both are out.\n")
print(f"Wrote comparison to {args.output}")
if __name__ == "__main__":
main()

View File

@ -1,219 +0,0 @@
#!/usr/bin/env python3
"""Parse raw sglang.bench_serving JSONL outputs for one backend.
Reads JSONL files like {sglang|vllm}_phase1_MMDD_concurrency_inputlen_outputlen.jsonl
and generates results.json + report.md in the given result root.
Usage:
python3 parse_backend.py <result_root> [--backend sglang|vllm]
"""
import argparse
import json
import re
import sys
from pathlib import Path
def parse_jsonl(path: Path) -> dict | None:
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
return json.loads(line)
except json.JSONDecodeError:
continue
return None
def compute_metrics(data: dict) -> dict:
completed = data.get("completed", 0)
total = len(data.get("input_lens", []))
failed = total - completed if total > 0 else 0
duration_s = data.get("duration", 0.0)
return {
"success": completed,
"failed": failed,
"duration_s": duration_s,
"request_throughput": data.get("request_throughput", 0.0),
"input_token_throughput": data.get("input_throughput", 0.0),
"output_token_throughput": data.get("output_throughput", 0.0),
"total_token_throughput": data.get("total_throughput", 0.0),
"total_input_tokens": data.get("total_input_tokens", 0),
"total_output_tokens": data.get("total_output_tokens", 0),
"e2e_ms": {
"mean": data.get("mean_e2e_latency_ms", 0.0),
"p50": data.get("median_e2e_latency_ms", 0.0),
"p90": data.get("p90_e2e_latency_ms", 0.0),
"p95": data.get("p95_e2e_latency_ms", 0.0),
"p99": data.get("p99_e2e_latency_ms", 0.0),
},
"ttft_ms": {
"mean": data.get("mean_ttft_ms", 0.0),
"p50": data.get("median_ttft_ms", 0.0),
"p90": data.get("p90_ttft_ms", 0.0),
"p95": data.get("p95_ttft_ms", 0.0),
"p99": data.get("p99_ttft_ms", 0.0),
},
"tpot_ms": {
"mean": data.get("mean_tpot_ms", 0.0),
"p50": data.get("median_tpot_ms", 0.0),
"p90": data.get("p90_tpot_ms", 0.0),
"p95": data.get("p95_tpot_ms", 0.0),
"p99": data.get("p99_tpot_ms", 0.0),
},
"itl_ms": {
"mean": data.get("mean_itl_ms", 0.0),
"p50": data.get("median_itl_ms", 0.0),
"p90": data.get("p90_itl_ms", 0.0),
"p95": data.get("p95_itl_ms", 0.0),
"p99": data.get("p99_itl_ms", 0.0),
},
}
def scenario_name(concurrency: int, input_len: int, output_len: int) -> str:
return f"c{concurrency}_i{input_len}_o{output_len}"
def slo_status(metrics: dict) -> dict:
"""Check S2-tier SLO: TTFT P95 < 3s, TPOT mean < 50ms."""
ttft_ok = metrics["ttft_ms"]["p95"] < 3000.0
tpot_ok = metrics["tpot_ms"]["mean"] < 50.0
if ttft_ok and tpot_ok:
mark = ""
elif ttft_ok or tpot_ok:
mark = "⚠️"
else:
mark = ""
return {
"ttft_p95_ok": ttft_ok,
"tpot_mean_ok": tpot_ok,
"overall": mark,
}
def append_scenario(results_json: Path, scenario: dict) -> None:
with open(results_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["scenarios"].append(scenario)
with open(results_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def generate_report(result_root: Path, backend: str, scenarios: list[dict]) -> None:
report_path = result_root / "report.md"
with open(report_path, "w", encoding="utf-8") as f:
f.write(f"# H200 {backend.upper()} Comparison Benchmark Report\n\n")
f.write(f"- Result root: `{result_root}`\n")
f.write(f"- Model: `/data/models/DeepSeek-V4-Flash`\n")
f.write(f"- Backend: {backend.upper()} (TP=8)\n")
f.write(f"- Benchmark client: `sglang.bench_serving --backend {backend}`\n\n")
f.write("## Results\n\n")
f.write("| Scenario | Phase | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | SLO |\n")
f.write("|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n")
for s in scenarios:
cfg = s["config"]
m = s["metrics"]
slo = s.get("slo_status", {}).get("overall", "")
f.write(
f"| {s['name']} | {cfg['phase']} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | "
f"{m['duration_s']:.2f} | {m['success']} | {m['request_throughput']:.2f} | "
f"{m['input_token_throughput']:.2f} | {m['output_token_throughput']:.2f} | "
f"{m['total_token_throughput']:.2f} | "
f"{m['ttft_ms']['mean']:.2f} | {m['ttft_ms']['p95']:.2f} | {m['ttft_ms']['p99']:.2f} | "
f"{m['tpot_ms']['mean']:.2f} | {m['tpot_ms']['p95']:.2f} | {m['tpot_ms']['p99']:.2f} | "
f"{m['e2e_ms']['mean']:.2f} | {m['e2e_ms']['p95']:.2f} | {m['e2e_ms']['p99']:.2f} | {slo} |\n"
)
f.write("\n")
f.write("SLO: S2 tier — TTFT P95 < 3000ms, TPOT mean < 50ms. ✅ pass, ⚠️ partial, ❌ fail.\n\n")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("result_root", type=Path)
parser.add_argument("--backend", default=None, choices=["sglang", "vllm"])
args = parser.parse_args()
result_root = args.result_root
raw_dir = result_root / "raw_outputs"
results_json = result_root / "results.json"
if not raw_dir.exists():
raise SystemExit(f"raw_outputs directory not found: {raw_dir}")
backend = args.backend
if backend is None:
# Infer from filenames if not provided.
for p in raw_dir.iterdir():
if p.name.startswith("sglang_"):
backend = "sglang"
break
if p.name.startswith("vllm_"):
backend = "vllm"
break
if backend is None:
raise SystemExit("Could not infer backend from raw outputs")
scenarios = []
for jsonl_path in sorted(raw_dir.glob(f"{backend}_*.jsonl")):
parts = jsonl_path.stem.split("_")
if len(parts) < 5:
continue
label = parts[1]
if label == "sharegpt":
phase = "sharegpt"
dataset = "sharegpt"
else:
phase = label
dataset = "random"
# The last three numeric tokens are: concurrency, input_len/output_ctx, output_len.
try:
concurrency, input_len, output_len = int(parts[-3]), int(parts[-2]), int(parts[-1])
except ValueError:
continue
data = parse_jsonl(jsonl_path)
if data is None:
continue
metrics = compute_metrics(data)
scenario = {
"name": scenario_name(concurrency, input_len, output_len),
"config": {
"phase": phase,
"concurrency": concurrency,
"input_len": input_len,
"output_len": output_len,
"dataset": dataset,
"num_prompts": metrics["success"] + metrics["failed"],
},
"metrics": metrics,
"slo_status": slo_status(metrics),
"raw_file": str(jsonl_path),
}
scenarios.append(scenario)
if not scenarios:
print("No benchmark outputs found to parse")
return
if results_json.exists():
with open(results_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["scenarios"] = scenarios
with open(results_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
generate_report(result_root, backend, scenarios)
print(f"Parsed {len(scenarios)} scenarios into {result_root}/report.md")
if __name__ == "__main__":
main()

View File

@ -19,5 +19,5 @@
## Notes
- SLO check uses TTFT P95 and TPOT mean (the same criteria as `scripts/SLO_STANDARDS.md`).
- SLO check uses TTFT P95 and TPOT mean (the same criteria as `docs/SLO_STANDARDS.md`).
- A ⚠️ indicates one of the two metrics is out of target; ❌ indicates both are out.

View File

@ -95,7 +95,7 @@ run_warmup() {
num=1
log "warming up ${backend} (input=${input_len}, output=${output_len}, num=${num})"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/warmup.py" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/warmup.py" \
--backend "$backend" \
--port "$port" \
--input-len "$input_len" \
@ -189,7 +189,7 @@ parse_backend() {
local backend="$1"
local result_root="${RESULT_BASE}/${RUN_ID}/${backend}"
log "parsing ${backend} results in ${result_root}"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/parse_backend.py" "$result_root" --backend "$backend" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/parse_backend.py" "$result_root" --backend "$backend" \
>> "${result_root}/logs/parse.log" 2>&1 || {
log "WARNING: parser failed for ${backend}; see ${result_root}/logs/parse.log"
}
@ -264,7 +264,7 @@ parse_backend vllm
# Generate comparison.
log "generating comparison report"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/compare.py" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/compare.py" \
--sglang "${RESULT_BASE}/${RUN_ID}/sglang" \
--vllm "${RESULT_BASE}/${RUN_ID}/vllm" \
--output "${RESULT_BASE}/${RUN_ID}/comparison.md" \

View File

@ -1,76 +0,0 @@
#!/usr/bin/env python3
"""Send a small number of warmup requests to a running backend.
Uses sglang.bench_serving with a single request so that the same code path
(prefill / decode kernels, CUDA graphs, etc.) is exercised before the real
benchmark begins. Discards the output.
"""
import argparse
import subprocess
import sys
import tempfile
from pathlib import Path
def run_warmup(backend: str, host: str, port: int, input_len: int, output_len: int, num: int, env_python: Path) -> None:
with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=True) as tmp:
cmd = [
str(env_python),
"-m",
"sglang.bench_serving",
"--backend",
backend,
"--host",
host,
"--port",
str(port),
"--dataset-name",
"random",
"--random-input-len",
str(input_len),
"--random-output-len",
str(output_len),
"--num-prompts",
str(num),
"--max-concurrency",
"1",
"--request-rate",
"10000",
"--output-file",
tmp.name,
"--output-details",
]
print(f"[warmup] {' '.join(cmd)}", flush=True)
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print("[warmup] FAILED", file=sys.stderr)
print(result.stdout, file=sys.stderr)
print(result.stderr, file=sys.stderr)
sys.exit(1)
print(f"[warmup] OK: backend={backend} port={port} input={input_len} output={output_len} num={num}")
def main():
parser = argparse.ArgumentParser(description="Warmup a serving backend.")
parser.add_argument("--backend", required=True, choices=["sglang", "vllm"])
parser.add_argument("--port", type=int, required=True)
parser.add_argument("--input-len", type=int, required=True)
parser.add_argument("--output-len", type=int, required=True)
parser.add_argument("--num", type=int, default=1)
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--env-python", default="/data/user1/yy/envs/sglang/bin/python")
args = parser.parse_args()
run_warmup(
backend=args.backend,
host=args.host,
port=args.port,
input_len=args.input_len,
output_len=args.output_len,
num=args.num,
env_python=Path(args.env_python),
)
if __name__ == "__main__":
main()

View File

@ -1,85 +0,0 @@
#!/usr/bin/env python3
"""Generate a side-by-side comparison of SGLang and vLLM results.
Usage:
python3 compare.py --sglang <sglang_result_root> --vllm <vllm_result_root> \
[--output comparison.md]
"""
import argparse
import json
from collections import defaultdict
from pathlib import Path
def load_result(result_root: Path) -> dict:
path = result_root / "results.json"
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def slo_status(ttft_p95_ms: float, tpot_mean_ms: float) -> str:
# S2 tier targets from scripts/SLO_STANDARDS.md.
ttft_ok = ttft_p95_ms < 3000.0
tpot_ok = tpot_mean_ms < 50.0
if ttft_ok and tpot_ok:
return ""
if ttft_ok or tpot_ok:
return "⚠️"
return ""
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--sglang", type=Path, required=True)
parser.add_argument("--vllm", type=Path, required=True)
parser.add_argument("-o", "--output", type=Path, default=Path("comparison.md"))
args = parser.parse_args()
sglang_data = load_result(args.sglang)
vllm_data = load_result(args.vllm)
by_scenario = defaultdict(dict)
for data in (sglang_data, vllm_data):
backend = data["metadata"]["engine"]
for s in data.get("scenarios", []):
key = s["name"]
by_scenario[key][backend] = s
with open(args.output, "w", encoding="utf-8") as f:
f.write("# SGLang vs vLLM on DeepSeek-V4-Flash (H200, TP=8)\n\n")
f.write("## Summary\n\n")
f.write("- Model: `/data/models/DeepSeek-V4-Flash`\n")
f.write("- Hardware: 8x NVIDIA H200 143GB\n")
f.write("- Tensor Parallelism: 8\n")
f.write("- Benchmark client: `sglang.bench_serving`\n")
f.write("- SLO reference: S2 tier (TTFT P95 < 3s, TPOT < 50ms)\n\n")
f.write("## Side-by-side results\n\n")
f.write("| Scenario | Backend | Conc | Input | Output | Req/s | OutTok/s | TTFT P95(ms) | TTFT P99(ms) | TPOT Mean(ms) | TPOT P95(ms) | TPOT P99(ms) | E2E P99(ms) | SLO |\n")
f.write("|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n")
for scenario_name in sorted(by_scenario.keys()):
for backend in ("sglang", "vllm"):
s = by_scenario[scenario_name].get(backend)
if s is None:
continue
cfg = s["config"]
m = s["metrics"]
status = slo_status(m["ttft_ms"]["p95"], m["tpot_ms"]["mean"])
f.write(
f"| {scenario_name} | {backend} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | "
f"{m['request_throughput']:.2f} | {m['output_token_throughput']:.2f} | "
f"{m['ttft_ms']['p95']:.2f} | {m['ttft_ms']['p99']:.2f} | "
f"{m['tpot_ms']['mean']:.2f} | {m['tpot_ms']['p95']:.2f} | {m['tpot_ms']['p99']:.2f} | "
f"{m['e2e_ms']['p99']:.2f} | {status} |\n"
)
f.write("\n## Notes\n\n")
f.write("- SLO check uses TTFT P95 and TPOT mean (the same criteria as `scripts/SLO_STANDARDS.md`).\n")
f.write("- A ⚠️ indicates one of the two metrics is out of target; ❌ indicates both are out.\n")
print(f"Wrote comparison to {args.output}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,61 @@
# SGLang vs vLLM on DeepSeek-V4-Flash (H200, TP=8)
## Summary
- Model: `/data/models/DeepSeek-V4-Flash`
- Hardware: 8x NVIDIA H200 143GB
- Tensor Parallelism: 8
- Benchmark client: `sglang.bench_serving`
- SLO reference: S2 tier (TTFT P95 < 3s, TPOT < 50ms)
## Side-by-side results
| Scenario | Backend | Conc | Input | Output | Req/s | OutTok/s | TTFT P95(ms) | TTFT P99(ms) | TPOT Mean(ms) | TPOT P95(ms) | TPOT P99(ms) | E2E P99(ms) | SLO |
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| c10_i131072_o1024 | sglang | 10 | 131072 | 1024 | 0.68 | 319.74 | 4147.51 | 7409.82 | 25.69 | 49.89 | 58.92 | 26432.66 | ⚠️ |
| c10_i131072_o1024 | vllm | 10 | 131072 | 1024 | 0.39 | 183.08 | 6596.15 | 6603.12 | 47.10 | 77.53 | 84.06 | 48426.64 | ⚠️ |
| c10_i131072_o256 | sglang | 10 | 131072 | 256 | 0.34 | 42.70 | 26046.62 | 26064.38 | 1374.50 | 1810.56 | 19553.62 | 58326.41 | ❌ |
| c10_i131072_o256 | vllm | 10 | 131072 | 256 | 0.38 | 48.09 | 23250.03 | 24238.33 | 150.22 | 299.91 | 301.72 | 51439.08 | ❌ |
| c10_i131072_o4096 | sglang | 10 | 131072 | 4096 | 0.27 | 517.96 | 6177.42 | 6920.28 | 14.21 | 19.92 | 25.87 | 54286.63 | ⚠️ |
| c10_i131072_o4096 | vllm | 10 | 131072 | 4096 | 0.23 | 445.81 | 2096.29 | 2168.81 | 18.57 | 21.47 | 21.61 | 67041.13 | ✅ |
| c10_i262144_o1024 | sglang | 10 | 262144 | 1024 | 0.16 | 76.30 | 39446.96 | 43965.67 | 108.42 | 202.43 | 202.90 | 120180.87 | ❌ |
| c10_i262144_o1024 | vllm | 10 | 262144 | 1024 | 0.18 | 84.63 | 34130.22 | 38144.40 | 98.87 | 142.57 | 266.08 | 108658.89 | ❌ |
| c10_i262144_o256 | sglang | 10 | 262144 | 256 | 0.15 | 18.70 | 58074.31 | 62652.59 | 2953.70 | 3625.59 | 41185.32 | 134628.33 | ❌ |
| c10_i262144_o256 | vllm | 10 | 262144 | 256 | 0.18 | 22.76 | 50632.44 | 51883.02 | 229.20 | 339.27 | 344.95 | 110216.92 | ❌ |
| c10_i262144_o4096 | sglang | 10 | 262144 | 4096 | 0.14 | 272.46 | 18088.12 | 22579.09 | 28.00 | 43.87 | 45.07 | 120183.75 | ⚠️ |
| c10_i262144_o4096 | vllm | 10 | 262144 | 4096 | 0.11 | 215.86 | 34100.32 | 38022.94 | 37.18 | 56.07 | 62.00 | 157944.21 | ⚠️ |
| c25_i65536_o1024 | sglang | 25 | 65536 | 1024 | 0.67 | 346.70 | 19996.43 | 24478.11 | 149.27 | 395.00 | 2037.09 | 69586.94 | ❌ |
| c25_i65536_o1024 | vllm | 25 | 65536 | 1024 | 0.64 | 332.61 | 24861.20 | 28942.75 | 72.36 | 196.24 | 271.32 | 72689.84 | ❌ |
| c25_i65536_o256 | sglang | 25 | 65536 | 256 | 0.71 | 84.45 | 27219.06 | 31612.70 | 292.84 | 801.18 | 938.43 | 68854.26 | ❌ |
| c25_i65536_o256 | vllm | 25 | 65536 | 256 | 0.81 | 95.60 | 24902.72 | 28974.45 | 180.78 | 273.59 | 278.16 | 60781.45 | ❌ |
| c25_i65536_o4096 | sglang | 25 | 65536 | 4096 | 0.41 | 950.01 | 18559.58 | 23249.71 | 23.78 | 42.50 | 82.84 | 96670.12 | ⚠️ |
| c25_i65536_o4096 | vllm | 25 | 65536 | 4096 | 0.32 | 741.71 | 24847.97 | 28840.96 | 28.80 | 46.66 | 63.40 | 129654.04 | ⚠️ |
| c2_i131072_o1024 | sglang | 2 | 131072 | 1024 | 0.60 | 136.10 | 479.99 | 482.72 | 8.56 | 9.72 | 9.79 | 5480.98 | ✅ |
| c2_i131072_o1024 | vllm | 2 | 131072 | 1024 | 0.70 | 158.36 | 676.14 | 695.90 | 6.49 | 8.20 | 8.34 | 4803.24 | ✅ |
| c2_i131072_o256 | sglang | 2 | 131072 | 256 | 1.43 | 139.48 | 589.14 | 605.33 | 8.29 | 8.96 | 9.05 | 1750.12 | ✅ |
| c2_i131072_o256 | vllm | 2 | 131072 | 256 | 1.51 | 147.12 | 684.63 | 700.28 | 7.08 | 9.27 | 9.45 | 1556.46 | ✅ |
| c2_i131072_o4096 | sglang | 2 | 131072 | 4096 | 0.11 | 244.38 | 584.43 | 599.22 | 7.53 | 7.61 | 7.62 | 28360.35 | ✅ |
| c2_i131072_o4096 | vllm | 2 | 131072 | 4096 | 0.12 | 262.22 | 671.34 | 688.15 | 7.09 | 7.17 | 7.18 | 26496.04 | ✅ |
| c2_i262144_o1024 | sglang | 2 | 262144 | 1024 | 0.51 | 114.94 | 1007.07 | 1024.10 | 8.36 | 9.03 | 9.06 | 6240.65 | ✅ |
| c2_i262144_o1024 | vllm | 2 | 262144 | 1024 | 0.19 | 43.14 | 8722.76 | 9009.70 | 20.17 | 52.24 | 57.91 | 13826.13 | ⚠️ |
| c2_i262144_o256 | sglang | 2 | 262144 | 256 | 0.29 | 28.44 | 9538.91 | 9980.78 | 14.10 | 26.98 | 29.16 | 11941.34 | ⚠️ |
| c2_i262144_o256 | vllm | 2 | 262144 | 256 | 0.23 | 22.74 | 8625.18 | 8952.31 | 10.18 | 23.01 | 25.44 | 10378.60 | ⚠️ |
| c2_i262144_o4096 | sglang | 2 | 262144 | 4096 | 0.08 | 189.78 | 9453.57 | 9896.38 | 7.82 | 8.48 | 8.60 | 38723.14 | ⚠️ |
| c2_i262144_o4096 | vllm | 2 | 262144 | 4096 | 0.08 | 180.76 | 8759.63 | 9062.80 | 8.63 | 11.53 | 12.01 | 35881.75 | ⚠️ |
| c5_i131072_o1024 | sglang | 5 | 131072 | 1024 | 0.87 | 321.54 | 1231.23 | 1298.36 | 9.03 | 10.47 | 10.71 | 7298.25 | ✅ |
| c5_i131072_o1024 | vllm | 5 | 131072 | 1024 | 0.90 | 331.65 | 856.18 | 917.80 | 8.93 | 10.48 | 10.62 | 6989.27 | ✅ |
| c5_i131072_o256 | sglang | 5 | 131072 | 256 | 1.05 | 146.97 | 3270.23 | 4685.65 | 19.91 | 40.81 | 46.10 | 7815.89 | ⚠️ |
| c5_i131072_o256 | vllm | 5 | 131072 | 256 | 0.39 | 54.07 | 9384.23 | 9677.91 | 51.54 | 107.16 | 117.25 | 19860.69 | ❌ |
| c5_i131072_o4096 | sglang | 5 | 131072 | 4096 | 0.20 | 378.76 | 4667.56 | 6275.51 | 11.98 | 22.07 | 23.60 | 31176.81 | ⚠️ |
| c5_i131072_o4096 | vllm | 5 | 131072 | 4096 | 0.23 | 447.85 | 1054.01 | 1104.66 | 8.48 | 9.13 | 9.14 | 28818.20 | ✅ |
| c5_i262144_o1024 | sglang | 5 | 262144 | 1024 | 0.15 | 56.42 | 20522.02 | 25944.29 | 54.11 | 118.98 | 124.02 | 53444.22 | ❌ |
| c5_i262144_o1024 | vllm | 5 | 262144 | 1024 | 0.16 | 60.32 | 22564.89 | 26639.92 | 47.49 | 100.69 | 109.86 | 53790.59 | ⚠️ |
| c5_i262144_o256 | sglang | 5 | 262144 | 256 | 0.16 | 21.94 | 25875.40 | 30596.95 | 97.05 | 231.75 | 252.98 | 43756.53 | ❌ |
| c5_i262144_o256 | vllm | 5 | 262144 | 256 | 0.19 | 25.85 | 21986.99 | 26381.86 | 123.42 | 202.78 | 215.56 | 45761.90 | ❌ |
| c5_i262144_o4096 | sglang | 5 | 262144 | 4096 | 0.11 | 209.50 | 16539.50 | 20323.40 | 25.05 | 56.69 | 60.22 | 60383.57 | ⚠️ |
| c5_i262144_o4096 | vllm | 5 | 262144 | 4096 | 0.11 | 205.12 | 22171.78 | 26533.69 | 25.69 | 63.32 | 68.33 | 64086.27 | ⚠️ |
## Notes
- SLO check uses TTFT P95 and TPOT mean (the same criteria as `docs/SLO_STANDARDS.md`).
- A ⚠️ indicates one of the two metrics is out of target; ❌ indicates both are out.

View File

@ -0,0 +1,35 @@
# H200 SGLANG Comparison Benchmark Report
- Result root: `/data/user1/yy/experiments/dsv4_h200_long_context_matrix/results/20260708-100016/sglang`
- Model: `/data/models/DeepSeek-V4-Flash`
- Backend: SGLANG (TP=8)
- Benchmark client: `sglang.bench_serving --backend sglang`
## Results
| Scenario | Phase | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | SLO |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| c10_i131072_o1024 | 128k | 10 | 131072 | 1024 | 29.58 | 20 | 0.68 | 49838.58 | 319.74 | 50158.31 | 1968.70 | 4147.51 | 7409.82 | 25.69 | 49.89 | 58.92 | 13268.98 | 25531.40 | 26432.66 | ⚠️ |
| c10_i131072_o256 | 128k | 10 | 131072 | 256 | 59.65 | 20 | 0.34 | 24716.54 | 42.70 | 24759.24 | 9130.87 | 26046.62 | 26064.38 | 1374.50 | 1810.56 | 19553.62 | 29314.12 | 58034.97 | 58326.41 | ❌ |
| c10_i131072_o4096 | 128k | 10 | 131072 | 4096 | 73.62 | 20 | 0.27 | 20027.92 | 517.96 | 20545.89 | 2624.32 | 6177.42 | 6920.28 | 14.21 | 19.92 | 25.87 | 28500.68 | 53466.72 | 54286.63 | ⚠️ |
| c2_i131072_o1024 | 128k | 2 | 131072 | 1024 | 6.62 | 4 | 0.60 | 36602.18 | 136.10 | 36738.28 | 363.59 | 479.99 | 482.72 | 8.56 | 9.72 | 9.79 | 2166.16 | 4983.98 | 5480.98 | ✅ |
| c2_i131072_o256 | 128k | 2 | 131072 | 256 | 2.79 | 4 | 1.43 | 86882.00 | 139.48 | 87021.48 | 423.87 | 589.14 | 605.33 | 8.29 | 8.96 | 9.05 | 1238.88 | 1727.94 | 1750.12 | ✅ |
| c2_i131072_o4096 | 128k | 2 | 131072 | 4096 | 37.21 | 4 | 0.11 | 6512.27 | 244.38 | 6756.65 | 417.65 | 584.43 | 599.22 | 7.53 | 7.61 | 7.62 | 17481.60 | 27763.29 | 28360.35 | ✅ |
| c5_i131072_o1024 | 128k | 5 | 131072 | 1024 | 11.51 | 10 | 0.87 | 64181.45 | 321.54 | 64502.99 | 747.50 | 1231.23 | 1298.36 | 9.03 | 10.47 | 10.71 | 4011.50 | 6687.19 | 7298.25 | ✅ |
| c5_i131072_o256 | 128k | 5 | 131072 | 256 | 9.50 | 10 | 1.05 | 77752.11 | 146.97 | 77899.07 | 1222.22 | 3270.23 | 4685.65 | 19.91 | 40.81 | 46.10 | 4366.18 | 7814.85 | 7815.89 | ⚠️ |
| c5_i131072_o4096 | 128k | 5 | 131072 | 4096 | 50.32 | 10 | 0.20 | 14676.36 | 378.76 | 15055.12 | 1452.15 | 4667.56 | 6275.51 | 11.98 | 22.07 | 23.60 | 19785.61 | 30849.59 | 31176.81 | ⚠️ |
| c10_i262144_o1024 | 256k | 10 | 262144 | 1024 | 123.96 | 20 | 0.16 | 22467.00 | 76.30 | 22543.30 | 12461.07 | 39446.96 | 43965.67 | 108.42 | 202.43 | 202.90 | 60222.02 | 119241.30 | 120180.87 | ❌ |
| c10_i262144_o256 | 256k | 10 | 262144 | 256 | 136.21 | 20 | 0.15 | 20447.06 | 18.70 | 20465.76 | 21481.91 | 58074.31 | 62652.59 | 2953.70 | 3625.59 | 41185.32 | 67539.67 | 134322.68 | 134628.33 | ❌ |
| c10_i262144_o4096 | 256k | 10 | 262144 | 4096 | 139.95 | 20 | 0.14 | 19900.66 | 272.46 | 20173.12 | 8041.92 | 18088.12 | 22579.09 | 28.00 | 43.87 | 45.07 | 61533.12 | 119183.37 | 120183.75 | ⚠️ |
| c2_i262144_o1024 | 256k | 2 | 262144 | 1024 | 7.84 | 4 | 0.51 | 64353.10 | 114.94 | 64468.04 | 817.04 | 1007.07 | 1024.10 | 8.36 | 9.03 | 9.06 | 2623.61 | 5714.71 | 6240.65 | ✅ |
| c2_i262144_o256 | 256k | 2 | 262144 | 256 | 13.68 | 4 | 0.29 | 36885.02 | 28.44 | 36913.47 | 4623.18 | 9538.91 | 9980.78 | 14.10 | 26.98 | 29.16 | 6497.61 | 11731.18 | 11941.34 | ⚠️ |
| c2_i262144_o4096 | 256k | 2 | 262144 | 4096 | 47.91 | 4 | 0.08 | 10528.36 | 189.78 | 10718.13 | 4475.72 | 9453.57 | 9896.38 | 7.82 | 8.48 | 8.60 | 22625.15 | 38039.50 | 38723.14 | ⚠️ |
| c5_i262144_o1024 | 256k | 5 | 262144 | 1024 | 65.58 | 10 | 0.15 | 21254.00 | 56.42 | 21310.42 | 9553.36 | 20522.02 | 25944.29 | 54.11 | 118.98 | 124.02 | 29705.24 | 53300.68 | 53444.22 | ❌ |
| c5_i262144_o256 | 256k | 5 | 262144 | 256 | 63.62 | 10 | 0.16 | 21909.37 | 21.94 | 21931.32 | 12937.52 | 25875.40 | 30596.95 | 97.05 | 231.75 | 252.98 | 27766.49 | 43745.44 | 43756.53 | ❌ |
| c5_i262144_o4096 | 256k | 5 | 262144 | 4096 | 90.98 | 10 | 0.11 | 15321.51 | 209.50 | 15531.02 | 6888.18 | 16539.50 | 20323.40 | 25.05 | 56.69 | 60.22 | 40104.55 | 58680.97 | 60383.57 | ⚠️ |
| c25_i65536_o1024 | 64k | 25 | 65536 | 1024 | 74.65 | 50 | 0.67 | 24668.22 | 346.70 | 25014.92 | 6120.96 | 19996.43 | 24478.11 | 149.27 | 395.00 | 2037.09 | 34945.80 | 68400.96 | 69586.94 | ❌ |
| c25_i65536_o256 | 64k | 25 | 65536 | 256 | 70.02 | 50 | 0.71 | 26299.26 | 84.45 | 26383.70 | 9330.86 | 27219.06 | 31612.70 | 292.84 | 801.18 | 938.43 | 34392.33 | 68802.89 | 68854.26 | ❌ |
| c25_i65536_o4096 | 64k | 25 | 65536 | 4096 | 122.10 | 50 | 0.41 | 15081.87 | 950.01 | 16031.88 | 5499.83 | 18559.58 | 23249.71 | 23.78 | 42.50 | 82.84 | 52152.37 | 93852.74 | 96670.12 | ⚠️ |
SLO: S2 tier — TTFT P95 < 3000ms, TPOT mean < 50ms. pass, partial, fail.

View File

@ -0,0 +1,35 @@
# H200 VLLM Comparison Benchmark Report
- Result root: `/data/user1/yy/experiments/dsv4_h200_long_context_matrix/results/20260708-100016/vllm`
- Model: `/data/models/DeepSeek-V4-Flash`
- Backend: VLLM (TP=8)
- Benchmark client: `sglang.bench_serving --backend vllm`
## Results
| Scenario | Phase | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | SLO |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| c10_i131072_o1024 | 128k | 10 | 131072 | 1024 | 51.67 | 20 | 0.39 | 28537.46 | 183.08 | 28720.55 | 2870.79 | 6596.15 | 6603.12 | 47.10 | 77.53 | 84.06 | 24302.31 | 47488.34 | 48426.64 | ⚠️ |
| c10_i131072_o256 | 128k | 10 | 131072 | 256 | 52.96 | 20 | 0.38 | 27838.09 | 48.09 | 27886.18 | 10289.92 | 23250.03 | 24238.33 | 150.22 | 299.91 | 301.72 | 25961.91 | 51418.73 | 51439.08 | ❌ |
| c10_i131072_o4096 | 128k | 10 | 131072 | 4096 | 85.53 | 20 | 0.23 | 17238.00 | 445.81 | 17683.81 | 937.55 | 2096.29 | 2168.81 | 18.57 | 21.47 | 21.61 | 34484.49 | 65903.20 | 67041.13 | ✅ |
| c2_i131072_o1024 | 128k | 2 | 131072 | 1024 | 5.69 | 4 | 0.70 | 42587.41 | 158.36 | 42745.76 | 384.25 | 676.14 | 695.90 | 6.49 | 8.20 | 8.34 | 1937.38 | 4390.02 | 4803.24 | ✅ |
| c2_i131072_o256 | 128k | 2 | 131072 | 256 | 2.64 | 4 | 1.51 | 91644.87 | 147.12 | 91791.99 | 469.80 | 684.63 | 700.28 | 7.08 | 9.27 | 9.45 | 1208.21 | 1556.10 | 1556.46 | ✅ |
| c2_i131072_o4096 | 128k | 2 | 131072 | 4096 | 34.68 | 4 | 0.12 | 6987.68 | 262.22 | 7249.90 | 386.27 | 671.34 | 688.15 | 7.09 | 7.17 | 7.18 | 16427.81 | 25979.54 | 26496.04 | ✅ |
| c5_i131072_o1024 | 128k | 5 | 131072 | 1024 | 11.16 | 10 | 0.90 | 66199.25 | 331.65 | 66530.90 | 616.34 | 856.18 | 917.80 | 8.93 | 10.48 | 10.62 | 3905.82 | 6409.85 | 6989.27 | ✅ |
| c5_i131072_o256 | 128k | 5 | 131072 | 256 | 25.82 | 10 | 0.39 | 28605.35 | 54.07 | 28659.43 | 4405.54 | 9384.23 | 9677.91 | 51.54 | 107.16 | 117.25 | 12185.89 | 19857.41 | 19860.69 | ❌ |
| c5_i131072_o4096 | 128k | 5 | 131072 | 4096 | 42.56 | 10 | 0.23 | 17353.52 | 447.85 | 17801.37 | 615.76 | 1054.01 | 1104.66 | 8.48 | 9.13 | 9.14 | 16241.78 | 28711.60 | 28818.20 | ✅ |
| c10_i262144_o1024 | 256k | 10 | 262144 | 1024 | 111.77 | 20 | 0.18 | 24919.08 | 84.63 | 25003.71 | 15133.21 | 34130.22 | 38144.40 | 98.87 | 142.57 | 266.08 | 54087.22 | 107356.89 | 108658.89 | ❌ |
| c10_i262144_o256 | 256k | 10 | 262144 | 256 | 111.93 | 20 | 0.18 | 24883.63 | 22.76 | 24906.39 | 28922.71 | 50632.44 | 51883.02 | 229.20 | 339.27 | 344.95 | 55262.71 | 110121.03 | 110216.92 | ❌ |
| c10_i262144_o4096 | 256k | 10 | 262144 | 4096 | 176.65 | 20 | 0.11 | 15766.52 | 215.86 | 15982.38 | 11662.77 | 34100.32 | 38022.94 | 37.18 | 56.07 | 62.00 | 79905.41 | 156158.99 | 157944.21 | ⚠️ |
| c2_i262144_o1024 | 256k | 2 | 262144 | 1024 | 20.88 | 4 | 0.19 | 24156.04 | 43.14 | 24199.18 | 5483.04 | 8722.76 | 9009.70 | 20.17 | 52.24 | 57.91 | 9282.52 | 13247.86 | 13826.13 | ⚠️ |
| c2_i262144_o256 | 256k | 2 | 262144 | 256 | 17.11 | 4 | 0.23 | 29488.06 | 22.74 | 29510.80 | 6791.08 | 8625.18 | 8952.31 | 10.18 | 23.01 | 25.44 | 8281.06 | 10221.13 | 10378.60 | ⚠️ |
| c2_i262144_o4096 | 256k | 2 | 262144 | 4096 | 50.30 | 4 | 0.08 | 10027.95 | 180.76 | 10208.71 | 5468.77 | 8759.63 | 9062.80 | 8.63 | 11.53 | 12.01 | 23992.03 | 35306.58 | 35881.75 | ⚠️ |
| c5_i262144_o1024 | 256k | 5 | 262144 | 1024 | 61.34 | 10 | 0.16 | 22724.99 | 60.32 | 22785.31 | 11196.96 | 22564.89 | 26639.92 | 47.49 | 100.69 | 109.86 | 28871.28 | 47913.60 | 53790.59 | ⚠️ |
| c5_i262144_o256 | 256k | 5 | 262144 | 256 | 54.01 | 10 | 0.19 | 25809.33 | 25.85 | 25835.18 | 9094.42 | 21986.99 | 26381.86 | 123.42 | 202.78 | 215.56 | 26745.69 | 45719.33 | 45761.90 | ❌ |
| c5_i262144_o4096 | 256k | 5 | 262144 | 4096 | 92.92 | 10 | 0.11 | 15001.24 | 205.12 | 15206.37 | 9122.40 | 22171.78 | 26533.69 | 25.69 | 63.32 | 68.33 | 41465.64 | 61304.00 | 64086.27 | ⚠️ |
| c25_i65536_o1024 | 64k | 25 | 65536 | 1024 | 77.81 | 50 | 0.64 | 23665.15 | 332.61 | 23997.76 | 8358.86 | 24861.20 | 28942.75 | 72.36 | 196.24 | 271.32 | 36604.32 | 71227.08 | 72689.84 | ❌ |
| c25_i65536_o256 | 64k | 25 | 65536 | 256 | 61.85 | 50 | 0.81 | 29772.41 | 95.60 | 29868.01 | 11442.71 | 24902.72 | 28974.45 | 180.78 | 273.59 | 278.16 | 30201.94 | 60181.25 | 60781.45 | ❌ |
| c25_i65536_o4096 | 64k | 25 | 65536 | 4096 | 156.39 | 50 | 0.32 | 11775.02 | 741.71 | 12516.73 | 7847.53 | 24847.97 | 28840.96 | 28.80 | 46.66 | 63.40 | 69020.59 | 126999.05 | 129654.04 | ⚠️ |
SLO: S2 tier — TTFT P95 < 3000ms, TPOT mean < 50ms. pass, partial, fail.

View File

@ -93,7 +93,7 @@ run_warmup() {
fi
log "warming up ${backend} (input=${input_len}, output=${output_len}, num=1)"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/warmup.py" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/warmup.py" \
--backend "$backend" \
--port "$port" \
--input-len "$input_len" \
@ -201,7 +201,7 @@ parse_backend() {
local backend="$1"
local result_root="${RESULT_BASE}/${RUN_ID}/${backend}"
log "parsing ${backend} results in ${result_root}"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/parse_backend.py" "$result_root" --backend "$backend" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/parse_backend.py" "$result_root" --backend "$backend" \
>> "${result_root}/logs/parse.log" 2>&1 || {
log "WARNING: parser failed for ${backend}; see ${result_root}/logs/parse.log"
}
@ -282,7 +282,7 @@ parse_backend sglang
parse_backend vllm
log "generating comparison report"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/compare.py" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/compare.py" \
--sglang "${RESULT_BASE}/${RUN_ID}/sglang" \
--vllm "${RESULT_BASE}/${RUN_ID}/vllm" \
--output "${RESULT_BASE}/${RUN_ID}/comparison.md" \

View File

@ -1,76 +0,0 @@
#!/usr/bin/env python3
"""Send a small number of warmup requests to a running backend.
Uses sglang.bench_serving with a single request so that the same code path
(prefill / decode kernels, CUDA graphs, etc.) is exercised before the real
benchmark begins. Discards the output.
"""
import argparse
import subprocess
import sys
import tempfile
from pathlib import Path
def run_warmup(backend: str, host: str, port: int, input_len: int, output_len: int, num: int, env_python: Path) -> None:
with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=True) as tmp:
cmd = [
str(env_python),
"-m",
"sglang.bench_serving",
"--backend",
backend,
"--host",
host,
"--port",
str(port),
"--dataset-name",
"random",
"--random-input-len",
str(input_len),
"--random-output-len",
str(output_len),
"--num-prompts",
str(num),
"--max-concurrency",
"1",
"--request-rate",
"10000",
"--output-file",
tmp.name,
"--output-details",
]
print(f"[warmup] {' '.join(cmd)}", flush=True)
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print("[warmup] FAILED", file=sys.stderr)
print(result.stdout, file=sys.stderr)
print(result.stderr, file=sys.stderr)
sys.exit(1)
print(f"[warmup] OK: backend={backend} port={port} input={input_len} output={output_len} num={num}")
def main():
parser = argparse.ArgumentParser(description="Warmup a serving backend.")
parser.add_argument("--backend", required=True, choices=["sglang", "vllm"])
parser.add_argument("--port", type=int, required=True)
parser.add_argument("--input-len", type=int, required=True)
parser.add_argument("--output-len", type=int, required=True)
parser.add_argument("--num", type=int, default=1)
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--env-python", default="/data/user1/yy/envs/sglang/bin/python")
args = parser.parse_args()
run_warmup(
backend=args.backend,
host=args.host,
port=args.port,
input_len=args.input_len,
output_len=args.output_len,
num=args.num,
env_python=Path(args.env_python),
)
if __name__ == "__main__":
main()

View File

@ -1,85 +0,0 @@
#!/usr/bin/env python3
"""Generate a side-by-side comparison of SGLang and vLLM results.
Usage:
python3 compare.py --sglang <sglang_result_root> --vllm <vllm_result_root> \
[--output comparison.md]
"""
import argparse
import json
from collections import defaultdict
from pathlib import Path
def load_result(result_root: Path) -> dict:
path = result_root / "results.json"
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def slo_status(ttft_p95_ms: float, tpot_mean_ms: float) -> str:
# S2 tier targets from scripts/SLO_STANDARDS.md.
ttft_ok = ttft_p95_ms < 3000.0
tpot_ok = tpot_mean_ms < 50.0
if ttft_ok and tpot_ok:
return ""
if ttft_ok or tpot_ok:
return "⚠️"
return ""
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--sglang", type=Path, required=True)
parser.add_argument("--vllm", type=Path, required=True)
parser.add_argument("-o", "--output", type=Path, default=Path("comparison.md"))
args = parser.parse_args()
sglang_data = load_result(args.sglang)
vllm_data = load_result(args.vllm)
by_scenario = defaultdict(dict)
for data in (sglang_data, vllm_data):
backend = data["metadata"]["engine"]
for s in data.get("scenarios", []):
key = s["name"]
by_scenario[key][backend] = s
with open(args.output, "w", encoding="utf-8") as f:
f.write("# SGLang vs vLLM on DeepSeek-V4-Flash (H200, TP=8)\n\n")
f.write("## Summary\n\n")
f.write("- Model: `/data/models/DeepSeek-V4-Flash`\n")
f.write("- Hardware: 8x NVIDIA H200 143GB\n")
f.write("- Tensor Parallelism: 8\n")
f.write("- Benchmark client: `sglang.bench_serving`\n")
f.write("- SLO reference: S2 tier (TTFT P95 < 3s, TPOT < 50ms)\n\n")
f.write("## Side-by-side results\n\n")
f.write("| Scenario | Backend | Conc | Input | Output | Req/s | OutTok/s | TTFT P95(ms) | TTFT P99(ms) | TPOT Mean(ms) | TPOT P95(ms) | TPOT P99(ms) | E2E P99(ms) | SLO |\n")
f.write("|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n")
for scenario_name in sorted(by_scenario.keys()):
for backend in ("sglang", "vllm"):
s = by_scenario[scenario_name].get(backend)
if s is None:
continue
cfg = s["config"]
m = s["metrics"]
status = slo_status(m["ttft_ms"]["p95"], m["tpot_ms"]["mean"])
f.write(
f"| {scenario_name} | {backend} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | "
f"{m['request_throughput']:.2f} | {m['output_token_throughput']:.2f} | "
f"{m['ttft_ms']['p95']:.2f} | {m['ttft_ms']['p99']:.2f} | "
f"{m['tpot_ms']['mean']:.2f} | {m['tpot_ms']['p95']:.2f} | {m['tpot_ms']['p99']:.2f} | "
f"{m['e2e_ms']['p99']:.2f} | {status} |\n"
)
f.write("\n## Notes\n\n")
f.write("- SLO check uses TTFT P95 and TPOT mean (the same criteria as `scripts/SLO_STANDARDS.md`).\n")
f.write("- A ⚠️ indicates one of the two metrics is out of target; ❌ indicates both are out.\n")
print(f"Wrote comparison to {args.output}")
if __name__ == "__main__":
main()

View File

@ -1,221 +0,0 @@
#!/usr/bin/env python3
"""Parse raw sglang.bench_serving JSONL outputs for one backend.
Reads JSONL files like {sglang|vllm}_phase1_MMDD_concurrency_inputlen_outputlen.jsonl
and generates results.json + report.md in the given result root.
Usage:
python3 parse_backend.py <result_root> [--backend sglang|vllm]
"""
import argparse
import json
import re
import sys
from pathlib import Path
def parse_jsonl(path: Path) -> dict | None:
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
return json.loads(line)
except json.JSONDecodeError:
continue
return None
def compute_metrics(data: dict) -> dict:
completed = data.get("completed", 0)
total = len(data.get("input_lens", []))
failed = total - completed if total > 0 else 0
duration_s = data.get("duration", 0.0)
return {
"success": completed,
"failed": failed,
"duration_s": duration_s,
"request_throughput": data.get("request_throughput", 0.0),
"input_token_throughput": data.get("input_throughput", 0.0),
"output_token_throughput": data.get("output_throughput", 0.0),
"total_token_throughput": data.get("total_throughput", 0.0),
"total_input_tokens": data.get("total_input_tokens", 0),
"total_output_tokens": data.get("total_output_tokens", 0),
"e2e_ms": {
"mean": data.get("mean_e2e_latency_ms", 0.0),
"p50": data.get("median_e2e_latency_ms", 0.0),
"p90": data.get("p90_e2e_latency_ms", 0.0),
"p95": data.get("p95_e2e_latency_ms", 0.0),
"p99": data.get("p99_e2e_latency_ms", 0.0),
},
"ttft_ms": {
"mean": data.get("mean_ttft_ms", 0.0),
"p50": data.get("median_ttft_ms", 0.0),
"p90": data.get("p90_ttft_ms", 0.0),
"p95": data.get("p95_ttft_ms", 0.0),
"p99": data.get("p99_ttft_ms", 0.0),
},
"tpot_ms": {
"mean": data.get("mean_tpot_ms", 0.0),
"p50": data.get("median_tpot_ms", 0.0),
"p90": data.get("p90_tpot_ms", 0.0),
"p95": data.get("p95_tpot_ms", 0.0),
"p99": data.get("p99_tpot_ms", 0.0),
},
"itl_ms": {
"mean": data.get("mean_itl_ms", 0.0),
"p50": data.get("median_itl_ms", 0.0),
"p90": data.get("p90_itl_ms", 0.0),
"p95": data.get("p95_itl_ms", 0.0),
"p99": data.get("p99_itl_ms", 0.0),
},
}
def scenario_name(concurrency: int, input_len: int, output_len: int) -> str:
return f"c{concurrency}_i{input_len}_o{output_len}"
def slo_status(metrics: dict) -> dict:
"""Check S2-tier SLO: TTFT P95 < 3s, TPOT mean < 50ms."""
ttft_ok = metrics["ttft_ms"]["p95"] < 3000.0
tpot_ok = metrics["tpot_ms"]["mean"] < 50.0
if ttft_ok and tpot_ok:
mark = ""
elif ttft_ok or tpot_ok:
mark = "⚠️"
else:
mark = ""
return {
"ttft_p95_ok": ttft_ok,
"tpot_mean_ok": tpot_ok,
"overall": mark,
}
def append_scenario(results_json: Path, scenario: dict) -> None:
with open(results_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["scenarios"].append(scenario)
with open(results_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def generate_report(result_root: Path, backend: str, scenarios: list[dict]) -> None:
report_path = result_root / "report.md"
with open(report_path, "w", encoding="utf-8") as f:
f.write(f"# H200 {backend.upper()} Comparison Benchmark Report\n\n")
f.write(f"- Result root: `{result_root}`\n")
f.write(f"- Model: `/data/models/DeepSeek-V4-Flash`\n")
f.write(f"- Backend: {backend.upper()} (TP=8)\n")
f.write(f"- Benchmark client: `sglang.bench_serving --backend {backend}`\n\n")
f.write("## Results\n\n")
f.write("| Scenario | Phase | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | SLO |\n")
f.write("|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n")
for s in scenarios:
cfg = s["config"]
m = s["metrics"]
slo = s.get("slo_status", {}).get("overall", "")
f.write(
f"| {s['name']} | {cfg['phase']} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | "
f"{m['duration_s']:.2f} | {m['success']} | {m['request_throughput']:.2f} | "
f"{m['input_token_throughput']:.2f} | {m['output_token_throughput']:.2f} | "
f"{m['total_token_throughput']:.2f} | "
f"{m['ttft_ms']['mean']:.2f} | {m['ttft_ms']['p95']:.2f} | {m['ttft_ms']['p99']:.2f} | "
f"{m['tpot_ms']['mean']:.2f} | {m['tpot_ms']['p95']:.2f} | {m['tpot_ms']['p99']:.2f} | "
f"{m['e2e_ms']['mean']:.2f} | {m['e2e_ms']['p95']:.2f} | {m['e2e_ms']['p99']:.2f} | {slo} |\n"
)
f.write("\n")
f.write("SLO: S2 tier — TTFT P95 < 3000ms, TPOT mean < 50ms. ✅ pass, ⚠️ partial, ❌ fail.\n\n")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("result_root", type=Path)
parser.add_argument("--backend", default=None, choices=["sglang", "vllm"])
args = parser.parse_args()
result_root = args.result_root
raw_dir = result_root / "raw_outputs"
results_json = result_root / "results.json"
if not raw_dir.exists():
raise SystemExit(f"raw_outputs directory not found: {raw_dir}")
backend = args.backend
if backend is None:
# Infer from filenames if not provided.
for p in raw_dir.iterdir():
if p.name.startswith("sglang_"):
backend = "sglang"
break
if p.name.startswith("vllm_"):
backend = "vllm"
break
if backend is None:
raise SystemExit("Could not infer backend from raw outputs")
scenarios = []
for jsonl_path in sorted(raw_dir.glob(f"{backend}_*.jsonl")):
parts = jsonl_path.stem.split("_")
if len(parts) < 5:
continue
label = parts[1]
if label.startswith("phase"):
phase = label
dataset = "random"
elif label == "sharegpt":
phase = "sharegpt"
dataset = "sharegpt"
else:
continue
# The last three numeric tokens are: concurrency, input_len/output_ctx, output_len.
try:
concurrency, input_len, output_len = int(parts[-3]), int(parts[-2]), int(parts[-1])
except ValueError:
continue
data = parse_jsonl(jsonl_path)
if data is None:
continue
metrics = compute_metrics(data)
scenario = {
"name": scenario_name(concurrency, input_len, output_len),
"config": {
"phase": phase,
"concurrency": concurrency,
"input_len": input_len,
"output_len": output_len,
"dataset": dataset,
"num_prompts": metrics["success"] + metrics["failed"],
},
"metrics": metrics,
"slo_status": slo_status(metrics),
"raw_file": str(jsonl_path),
}
scenarios.append(scenario)
if not scenarios:
print("No benchmark outputs found to parse")
return
if results_json.exists():
with open(results_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["scenarios"] = scenarios
with open(results_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
generate_report(result_root, backend, scenarios)
print(f"Parsed {len(scenarios)} scenarios into {result_root}/report.md")
if __name__ == "__main__":
main()

View File

@ -37,5 +37,5 @@
## Notes
- SLO check uses TTFT P95 and TPOT mean (the same criteria as `scripts/SLO_STANDARDS.md`).
- SLO check uses TTFT P95 and TPOT mean (the same criteria as `docs/SLO_STANDARDS.md`).
- A ⚠️ indicates one of the two metrics is out of target; ❌ indicates both are out.

View File

@ -100,7 +100,7 @@ run_warmup() {
fi
log "warming up ${backend} (phase=${phase}, input=${input_len}, output=${output_len}, num=${num})"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/warmup.py" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/warmup.py" \
--backend "$backend" \
--port "$port" \
--input-len "$input_len" \
@ -264,7 +264,7 @@ parse_backend() {
local backend="$1"
local result_root="${RESULT_BASE}/${RUN_ID}/${backend}"
log "parsing ${backend} results in ${result_root}"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/parse_backend.py" "$result_root" --backend "$backend" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/parse_backend.py" "$result_root" --backend "$backend" \
>> "${result_root}/logs/parse.log" 2>&1 || {
log "WARNING: parser failed for ${backend}; see ${result_root}/logs/parse.log"
}
@ -348,7 +348,7 @@ parse_backend vllm
# Generate comparison.
log "generating comparison report"
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/compare.py" \
"${VENV_CLIENT}/bin/python" "${SCRIPT_DIR}/../../scripts/common/compare.py" \
--sglang "${RESULT_BASE}/${RUN_ID}/sglang" \
--vllm "${RESULT_BASE}/${RUN_ID}/vllm" \
--output "${RESULT_BASE}/${RUN_ID}/comparison.md" \

View File

@ -1,76 +0,0 @@
#!/usr/bin/env python3
"""Send a small number of warmup requests to a running backend.
Uses sglang.bench_serving with a single request so that the same code path
(prefill / decode kernels, CUDA graphs, etc.) is exercised before the real
benchmark begins. Discards the output.
"""
import argparse
import subprocess
import sys
import tempfile
from pathlib import Path
def run_warmup(backend: str, host: str, port: int, input_len: int, output_len: int, num: int, env_python: Path) -> None:
with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=True) as tmp:
cmd = [
str(env_python),
"-m",
"sglang.bench_serving",
"--backend",
backend,
"--host",
host,
"--port",
str(port),
"--dataset-name",
"random",
"--random-input-len",
str(input_len),
"--random-output-len",
str(output_len),
"--num-prompts",
str(num),
"--max-concurrency",
"1",
"--request-rate",
"10000",
"--output-file",
tmp.name,
"--output-details",
]
print(f"[warmup] {' '.join(cmd)}", flush=True)
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print("[warmup] FAILED", file=sys.stderr)
print(result.stdout, file=sys.stderr)
print(result.stderr, file=sys.stderr)
sys.exit(1)
print(f"[warmup] OK: backend={backend} port={port} input={input_len} output={output_len} num={num}")
def main():
parser = argparse.ArgumentParser(description="Warmup a serving backend.")
parser.add_argument("--backend", required=True, choices=["sglang", "vllm"])
parser.add_argument("--port", type=int, required=True)
parser.add_argument("--input-len", type=int, required=True)
parser.add_argument("--output-len", type=int, required=True)
parser.add_argument("--num", type=int, default=1)
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--env-python", default="/data/user1/yy/envs/sglang/bin/python")
args = parser.parse_args()
run_warmup(
backend=args.backend,
host=args.host,
port=args.port,
input_len=args.input_len,
output_len=args.output_len,
num=args.num,
env_python=Path(args.env_python),
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,30 @@
# vLLM+DSpark vs vLLM default on DeepSeek-V4-Flash (H200, TP=8)
## Summary
- DSpark model: `/data/models/DeepSeek-V4-Flash-DSpark`
- Default model: `/data/models/DeepSeek-V4-Flash`
- Hardware: 8x NVIDIA H200 143GB
- Tensor Parallelism: 8
- Benchmark client: `sglang.bench_serving --backend vllm`
- DSpark flags: `--spec-method dspark --spec-model <model> --spec-tokens 5`
## Side-by-side results (TTFT focused)
| Scenario | Backend | Conc | Input | Output | Req/s | OutTok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) |
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| c128_i512_o256 | vllm-dspark | 128 | 512 | 256 | 23.94 | 3160.81 | 778.36 | 1071.85 | 1075.35 | 28.95 | 78.75 | 3632.34 |
| c128_i512_o256 | vllm-default | 128 | 512 | 256 | 29.99 | 3959.89 | 617.72 | 767.18 | 770.89 | 18.31 | 65.44 | 2695.91 |
| c1_i4000_o512 | vllm-dspark | 1 | 4000 | 512 | 1.73 | 486.20 | 123.58 | 176.29 | 190.52 | 1.66 | 3.56 | 578.26 |
| c1_i4000_o512 | vllm-default | 1 | 4000 | 512 | 0.50 | 140.13 | 131.50 | 191.53 | 268.69 | 6.68 | 6.73 | 2008.09 |
| c1_i512_o256 | vllm-dspark | 1 | 512 | 256 | 2.56 | 372.62 | 86.51 | 185.00 | 188.81 | 2.09 | 3.83 | 389.62 |
| c1_i512_o256 | vllm-default | 1 | 512 | 256 | 0.99 | 143.82 | 49.50 | 68.50 | 71.09 | 6.66 | 6.71 | 1010.98 |
| c32_i4000_o512 | vllm-dspark | 32 | 4000 | 512 | 11.49 | 3242.88 | 298.42 | 434.24 | 522.34 | 9.68 | 54.60 | 2476.82 |
| c32_i4000_o512 | vllm-default | 32 | 4000 | 512 | 5.38 | 1518.49 | 382.74 | 681.98 | 684.75 | 16.43 | 25.49 | 4927.71 |
| c32_i512_o256 | vllm-dspark | 32 | 512 | 256 | 16.53 | 2182.83 | 262.56 | 527.21 | 532.11 | 12.06 | 28.16 | 1738.79 |
| c32_i512_o256 | vllm-default | 32 | 512 | 256 | 12.98 | 1713.20 | 122.62 | 272.00 | 304.80 | 15.77 | 23.26 | 2154.65 |
## Notes
- TTFT mean/P95/P99 are the main focus for verifying whether DSpark increases time-to-first-token.
- Mean TPOT and E2E are included to check whether speculative decoding pays back after first token.

View File

@ -0,0 +1,18 @@
# H200 vLLM default Benchmark Report
- Result root: `/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/default`
- Backend: vLLM default (TP=8)
- Benchmark client: `sglang.bench_serving --backend vllm`
## Results
| Scenario | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | SLO |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| c128_i512_o256 | 128 | 512 | 256 | 4.27 | 128 | 29.99 | 8259.06 | 3959.89 | 12218.95 | 617.72 | 767.18 | 770.89 | 18.31 | 28.68 | 65.44 | 2695.91 | 4153.36 | 4205.54 | ✅ |
| c1_i4000_o512 | 1 | 4000 | 512 | 64.29 | 32 | 0.50 | 1011.09 | 140.13 | 1151.23 | 131.50 | 191.53 | 268.69 | 6.68 | 6.73 | 6.73 | 2008.09 | 3523.51 | 3599.32 | ✅ |
| c1_i512_o256 | 1 | 512 | 256 | 32.38 | 32 | 0.99 | 252.30 | 143.82 | 396.12 | 49.50 | 68.50 | 71.09 | 6.66 | 6.69 | 6.71 | 1010.98 | 1735.31 | 1759.78 | ✅ |
| c32_i4000_o512 | 32 | 4000 | 512 | 11.90 | 64 | 5.38 | 11297.00 | 1518.49 | 12815.49 | 382.74 | 681.98 | 684.75 | 16.43 | 20.90 | 25.49 | 4927.71 | 8743.37 | 9349.37 | ✅ |
| c32_i512_o256 | 32 | 512 | 256 | 9.86 | 128 | 12.98 | 3573.19 | 1713.20 | 5286.40 | 122.62 | 272.00 | 304.80 | 15.77 | 19.50 | 23.26 | 2154.65 | 4102.71 | 4275.05 | ✅ |
SLO: S2 tier — TTFT P95 < 3000ms, TPOT mean < 50ms. pass, partial, fail.

View File

@ -0,0 +1,304 @@
{
"metadata": {
"experiment": "dsv4_h200_vllm_dspark_vs_default_default",
"run_id": "20260708-142121",
"timestamp": "2026-07-08T14:21:25+00:00",
"model": "/data/models/DeepSeek-V4-Flash",
"backend": "vllm",
"engine": "vllm-default",
"hardware": "8x NVIDIA H200 143GB",
"accelerator": "NVIDIA H200",
"chip": "nvidia_h200",
"script": "experiments/dsv4_h200_vllm_dspark_vs_default/run_bench.sh",
"env": "/data/user1/yy/envs/vllm",
"git_commit": "1a6b173",
"git_dirty": "dirty",
"description": "H200 vllm-default TP=8 benchmark for TTFT comparison"
},
"config": {
"tp": 8,
"cuda_visible_devices": "0,1,2,3,4,5,6,7",
"max_model_len": 32768,
"max_num_seqs": 256,
"backend": "default",
"server_start_script": "experiments/dsv4_h200_vllm_dspark_vs_default/start_default.sh",
"server_args": "vllm serve /data/models/DeepSeek-V4-Flash --trust-remote-code --tensor-parallel-size 8 --kv-cache-dtype fp8 --max-model-len 32768 --max-num-seqs 256 --block-size 256 --gpu-memory-utilization 0.90 --tokenizer-mode deepseek_v4 --reasoning-parser deepseek_v4 --no-disable-hybrid-kv-cache-manager --disable-uvicorn-access-log --port 30004"
},
"scenarios": [
{
"name": "c128_i512_o256",
"config": {
"concurrency": 128,
"input_len": 512,
"output_len": 256,
"dataset": "random",
"num_prompts": 128
},
"metrics": {
"success": 128,
"failed": 0,
"duration_s": 4.267798511005822,
"request_throughput": 29.992043830071385,
"input_token_throughput": 8259.059069705907,
"output_token_throughput": 3959.8870369391125,
"total_token_throughput": 12218.94610664502,
"total_input_tokens": 35248,
"total_output_tokens": 16900,
"e2e_ms": {
"mean": 2695.9114590795252,
"p50": 2786.5102920040954,
"p90": 4094.3853520046105,
"p95": 4153.361671900348,
"p99": 4205.538639774604
},
"ttft_ms": {
"mean": 617.7214959924413,
"p50": 563.3532095089322,
"p90": 764.0312482006266,
"p95": 767.1767810017627,
"p99": 770.8853784023086
},
"tpot_ms": {
"mean": 18.314879062516017,
"p50": 15.941864871586276,
"p90": 23.19956007269117,
"p95": 28.68311282203317,
"p99": 65.43692430894995
},
"itl_ms": {
"mean": 15.86385086378528,
"p50": 14.933839993318543,
"p90": 17.340479693666566,
"p95": 18.231574493984226,
"p99": 21.184932928154012
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/default/raw_outputs/default_0708_128_512_256.jsonl"
},
{
"name": "c1_i4000_o512",
"config": {
"concurrency": 1,
"input_len": 4000,
"output_len": 512,
"dataset": "random",
"num_prompts": 32
},
"metrics": {
"success": 32,
"failed": 0,
"duration_s": 64.28883961599786,
"request_throughput": 0.49775357886591887,
"input_token_throughput": 1011.0930666700768,
"output_token_throughput": 140.13318725009572,
"total_token_throughput": 1151.2262539201727,
"total_input_tokens": 65002,
"total_output_tokens": 9009,
"e2e_ms": {
"mean": 2008.0858264368544,
"p50": 2064.2024725020747,
"p90": 3374.621986798593,
"p95": 3523.513536003884,
"p99": 3599.319600486051
},
"ttft_ms": {
"mean": 131.49705237538,
"p50": 128.75337050354574,
"p90": 188.4824504086282,
"p95": 191.5273676990182,
"p99": 268.6876055170434
},
"tpot_ms": {
"mean": 6.678438385450706,
"p50": 6.647801321133651,
"p90": 6.729117710662969,
"p95": 6.730015678053446,
"p99": 6.730549147956696
},
"itl_ms": {
"mean": 6.689239366715674,
"p50": 6.692727998597547,
"p90": 6.8432597909122705,
"p95": 6.884838393307291,
"p99": 6.988287522690371
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/default/raw_outputs/default_0708_1_4000_512.jsonl"
},
{
"name": "c1_i512_o256",
"config": {
"concurrency": 1,
"input_len": 512,
"output_len": 256,
"dataset": "random",
"num_prompts": 32
},
"metrics": {
"success": 32,
"failed": 0,
"duration_s": 32.381663249005214,
"request_throughput": 0.9882135995896709,
"input_token_throughput": 252.30328464523785,
"output_token_throughput": 143.81596041528428,
"total_token_throughput": 396.11924506052213,
"total_input_tokens": 8170,
"total_output_tokens": 4657,
"e2e_ms": {
"mean": 1010.982028125909,
"p50": 1195.345941996493,
"p90": 1688.3191696979338,
"p95": 1735.3133527984028,
"p99": 1759.781412960583
},
"ttft_ms": {
"mean": 49.50495168895941,
"p50": 44.729964502039365,
"p90": 65.38926539797103,
"p95": 68.496801002766,
"p99": 71.08588116068859
},
"tpot_ms": {
"mean": 6.659676006644546,
"p50": 6.654429546349644,
"p90": 6.684645557662856,
"p95": 6.69398312746385,
"p99": 6.711085371917718
},
"itl_ms": {
"mean": 6.652042307463047,
"p50": 6.637780999881215,
"p90": 6.787886397796684,
"p95": 6.841546000214294,
"p99": 7.143319878960029
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/default/raw_outputs/default_0708_1_512_256.jsonl"
},
{
"name": "c32_i4000_o512",
"config": {
"concurrency": 32,
"input_len": 4000,
"output_len": 512,
"dataset": "random",
"num_prompts": 64
},
"metrics": {
"success": 64,
"failed": 0,
"duration_s": 11.899348942009965,
"request_throughput": 5.378445519321792,
"input_token_throughput": 11297.004622279226,
"output_token_throughput": 1518.4864388847727,
"total_token_throughput": 12815.491061163999,
"total_input_tokens": 134427,
"total_output_tokens": 18069,
"e2e_ms": {
"mean": 4927.714462639642,
"p50": 4734.132497498649,
"p90": 8425.151473002916,
"p95": 8743.374133249745,
"p99": 9349.369201347727
},
"ttft_ms": {
"mean": 382.74424496808024,
"p50": 390.3064074984286,
"p90": 678.5326104989508,
"p95": 681.9790261564776,
"p99": 684.7464643983403
},
"tpot_ms": {
"mean": 16.425838503257715,
"p50": 17.044603306082735,
"p90": 18.812656380989317,
"p95": 20.897049261926014,
"p99": 25.487691384272917
},
"itl_ms": {
"mean": 16.239184021720266,
"p50": 11.25292000506306,
"p90": 12.360760505544022,
"p95": 48.09924705259619,
"p99": 161.26204263797263
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/default/raw_outputs/default_0708_32_4000_512.jsonl"
},
{
"name": "c32_i512_o256",
"config": {
"concurrency": 32,
"input_len": 512,
"output_len": 256,
"dataset": "random",
"num_prompts": 128
},
"metrics": {
"success": 128,
"failed": 0,
"duration_s": 9.864565095995204,
"request_throughput": 12.975736766333995,
"input_token_throughput": 3573.1935120292237,
"output_token_throughput": 1713.2027449300354,
"total_token_throughput": 5286.3962569592595,
"total_input_tokens": 35248,
"total_output_tokens": 16900,
"e2e_ms": {
"mean": 2154.6506077650065,
"p50": 2170.227252499899,
"p90": 3869.9233716019075,
"p95": 4102.707951706542,
"p99": 4275.054011319589
},
"ttft_ms": {
"mean": 122.61577058620787,
"p50": 76.25793749321019,
"p90": 266.052756505087,
"p95": 272.00344715092797,
"p99": 304.79746079756296
},
"tpot_ms": {
"mean": 15.771494928385172,
"p50": 16.114622466954064,
"p90": 17.704943566836604,
"p95": 19.49543390091434,
"p99": 23.258708736311746
},
"itl_ms": {
"mean": 15.513364437561801,
"p50": 11.447760996816214,
"p90": 26.607878498907667,
"p95": 30.321172751428094,
"p99": 100.95071544419625
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/default/raw_outputs/default_0708_32_512_256.jsonl"
}
]
}

View File

@ -0,0 +1,18 @@
# H200 vLLM+DSpark Benchmark Report
- Result root: `/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/dspark`
- Backend: vLLM+DSpark (TP=8)
- Benchmark client: `sglang.bench_serving --backend vllm`
## Results
| Scenario | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | SLO |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| c128_i512_o256 | 128 | 512 | 256 | 5.35 | 128 | 23.94 | 6592.44 | 3160.81 | 9753.24 | 778.36 | 1071.85 | 1075.35 | 28.95 | 69.00 | 78.75 | 3632.34 | 5022.66 | 5229.14 | ✅ |
| c1_i4000_o512 | 1 | 4000 | 512 | 18.53 | 32 | 1.73 | 3508.05 | 486.20 | 3994.25 | 123.58 | 176.29 | 190.52 | 1.66 | 2.30 | 3.56 | 578.26 | 964.23 | 1147.23 | ✅ |
| c1_i512_o256 | 1 | 512 | 256 | 12.50 | 32 | 2.56 | 653.70 | 372.62 | 1026.32 | 86.51 | 185.00 | 188.81 | 2.09 | 3.41 | 3.83 | 389.62 | 777.58 | 915.24 | ✅ |
| c32_i4000_o512 | 32 | 4000 | 512 | 5.57 | 64 | 11.49 | 24125.91 | 3242.88 | 27368.79 | 298.42 | 434.24 | 522.34 | 9.68 | 14.02 | 54.60 | 2476.82 | 4615.41 | 4710.26 | ✅ |
| c32_i512_o256 | 32 | 512 | 256 | 7.74 | 128 | 16.53 | 4552.68 | 2182.83 | 6735.51 | 262.56 | 527.21 | 532.11 | 12.06 | 22.73 | 28.16 | 1738.79 | 3830.80 | 5143.02 | ✅ |
SLO: S2 tier — TTFT P95 < 3000ms, TPOT mean < 50ms. pass, partial, fail.

View File

@ -0,0 +1,304 @@
{
"metadata": {
"experiment": "dsv4_h200_vllm_dspark_vs_default_dspark",
"run_id": "20260708-142121",
"timestamp": "2026-07-08T14:21:25+00:00",
"model": "/data/models/DeepSeek-V4-Flash-DSpark",
"backend": "vllm",
"engine": "vllm-dspark",
"hardware": "8x NVIDIA H200 143GB",
"accelerator": "NVIDIA H200",
"chip": "nvidia_h200",
"script": "experiments/dsv4_h200_vllm_dspark_vs_default/run_bench.sh",
"env": "/data/user1/yy/envs/vllm-dspark",
"git_commit": "1a6b173",
"git_dirty": "dirty",
"description": "H200 vllm-dspark TP=8 benchmark for TTFT comparison"
},
"config": {
"tp": 8,
"cuda_visible_devices": "0,1,2,3,4,5,6,7",
"max_model_len": 32768,
"max_num_seqs": 256,
"backend": "dspark",
"server_start_script": "experiments/dsv4_h200_vllm_dspark_vs_default/start_dspark.sh",
"server_args": "vllm serve /data/models/DeepSeek-V4-Flash-DSpark --trust-remote-code --tensor-parallel-size 8 --kv-cache-dtype fp8 --max-model-len 32768 --max-num-seqs 256 --block-size 256 --gpu-memory-utilization 0.90 --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 30007"
},
"scenarios": [
{
"name": "c128_i512_o256",
"config": {
"concurrency": 128,
"input_len": 512,
"output_len": 256,
"dataset": "random",
"num_prompts": 128
},
"metrics": {
"success": 128,
"failed": 0,
"duration_s": 5.346734229999129,
"request_throughput": 23.939847109255112,
"input_token_throughput": 6592.435397711127,
"output_token_throughput": 3160.807938643839,
"total_token_throughput": 9753.243336354966,
"total_input_tokens": 35248,
"total_output_tokens": 16900,
"e2e_ms": {
"mean": 3632.342373304141,
"p50": 4026.272206501744,
"p90": 4568.2280586028355,
"p95": 5022.660237102536,
"p99": 5229.135414937482
},
"ttft_ms": {
"mean": 778.3609630697583,
"p50": 860.7529874934698,
"p90": 1069.5359163975809,
"p95": 1071.847062408051,
"p99": 1075.3475757954584
},
"tpot_ms": {
"mean": 28.950386059739248,
"p50": 25.412556098403837,
"p90": 47.2479180524213,
"p95": 69.00331620594505,
"p99": 78.74700601221775
},
"itl_ms": {
"mean": 93.81264048253927,
"p50": 40.38110149849672,
"p90": 167.10851929528872,
"p95": 178.33137240304495,
"p99": 226.998560061038
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/dspark/raw_outputs/dspark_0708_128_512_256.jsonl"
},
{
"name": "c1_i4000_o512",
"config": {
"concurrency": 1,
"input_len": 4000,
"output_len": 512,
"dataset": "random",
"num_prompts": 32
},
"metrics": {
"success": 32,
"failed": 0,
"duration_s": 18.529377975006355,
"request_throughput": 1.7269872762681893,
"input_token_throughput": 3508.050841624526,
"output_token_throughput": 486.2008866218786,
"total_token_throughput": 3994.2517282464046,
"total_input_tokens": 65002,
"total_output_tokens": 9009,
"e2e_ms": {
"mean": 578.2551125621467,
"p50": 591.3451755041024,
"p90": 892.7950516954298,
"p95": 964.232088495919,
"p99": 1147.2280103387313
},
"ttft_ms": {
"mean": 123.576120467078,
"p50": 116.67309649055824,
"p90": 165.92405038827567,
"p95": 176.29006390707215,
"p99": 190.51729560960666
},
"tpot_ms": {
"mean": 1.663761482756609,
"p50": 1.5393092801409114,
"p90": 1.721910965739694,
"p95": 2.2984976072946695,
"p99": 3.557179712464779
},
"itl_ms": {
"mean": 8.58824287840908,
"p50": 8.61248900037026,
"p90": 8.832874392101076,
"p95": 8.92088470282033,
"p99": 9.17154152484727
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/dspark/raw_outputs/dspark_0708_1_4000_512.jsonl"
},
{
"name": "c1_i512_o256",
"config": {
"concurrency": 1,
"input_len": 512,
"output_len": 256,
"dataset": "random",
"num_prompts": 32
},
"metrics": {
"success": 32,
"failed": 0,
"duration_s": 12.498076534990105,
"request_throughput": 2.560393986259529,
"input_token_throughput": 653.7005896168861,
"output_token_throughput": 372.61733731283215,
"total_token_throughput": 1026.3179269297182,
"total_input_tokens": 8170,
"total_output_tokens": 4657,
"e2e_ms": {
"mean": 389.62139918930916,
"p50": 377.8842995016021,
"p90": 646.4735688015936,
"p95": 777.5833995023276,
"p99": 915.2371472108645
},
"ttft_ms": {
"mean": 86.50895553273585,
"p50": 65.89411200548057,
"p90": 180.50938470114488,
"p95": 184.9997164463275,
"p99": 188.8067441045132
},
"tpot_ms": {
"mean": 2.0946395465617687,
"p50": 1.810156284521167,
"p90": 3.2686956743572244,
"p95": 3.410268214368921,
"p99": 3.8341900713573533
},
"itl_ms": {
"mean": 8.582383006246063,
"p50": 8.498353497998323,
"p90": 8.866577497974504,
"p95": 9.046812405722449,
"p99": 11.975977418769617
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/dspark/raw_outputs/dspark_0708_1_512_256.jsonl"
},
{
"name": "c32_i4000_o512",
"config": {
"concurrency": 32,
"input_len": 4000,
"output_len": 512,
"dataset": "random",
"num_prompts": 64
},
"metrics": {
"success": 64,
"failed": 0,
"duration_s": 5.57189327001106,
"request_throughput": 11.486221450877318,
"input_token_throughput": 24125.910796516957,
"output_token_throughput": 3242.883365560973,
"total_token_throughput": 27368.794162077927,
"total_input_tokens": 134427,
"total_output_tokens": 18069,
"e2e_ms": {
"mean": 2476.816028921121,
"p50": 2355.44883399416,
"p90": 4230.580038897461,
"p95": 4615.411171296,
"p99": 4710.255208882008
},
"ttft_ms": {
"mean": 298.4201381239018,
"p50": 329.9452685023425,
"p90": 424.3153512070421,
"p95": 434.23709184717154,
"p99": 522.339181041898
},
"tpot_ms": {
"mean": 9.682042273616121,
"p50": 7.969379892329501,
"p90": 10.570340490525057,
"p95": 14.016753123090474,
"p99": 54.6038999076698
},
"itl_ms": {
"mean": 42.37590053159988,
"p50": 21.612419492157642,
"p90": 113.86398609756725,
"p95": 132.79422069972497,
"p99": 167.44969745966955
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/dspark/raw_outputs/dspark_0708_32_4000_512.jsonl"
},
{
"name": "c32_i512_o256",
"config": {
"concurrency": 32,
"input_len": 512,
"output_len": 256,
"dataset": "random",
"num_prompts": 128
},
"metrics": {
"success": 128,
"failed": 0,
"duration_s": 7.7422533049975755,
"request_throughput": 16.532654636522523,
"input_token_throughput": 4552.67977053239,
"output_token_throughput": 2182.8270574783646,
"total_token_throughput": 6735.506828010754,
"total_input_tokens": 35248,
"total_output_tokens": 16900,
"e2e_ms": {
"mean": 1738.7919132729621,
"p50": 1538.9780115001486,
"p90": 2865.2426461980212,
"p95": 3830.7956773496635,
"p99": 5143.022389483815
},
"ttft_ms": {
"mean": 262.5563681242511,
"p50": 225.63749099936103,
"p90": 521.6669989036745,
"p95": 527.2058025482693,
"p99": 532.1059286133095
},
"tpot_ms": {
"mean": 12.059512611469799,
"p50": 10.357537249850187,
"p90": 20.459189923756977,
"p95": 22.732079964678167,
"p99": 28.16360305609708
},
"itl_ms": {
"mean": 48.537332998996746,
"p50": 27.78467499592807,
"p90": 105.31790559180081,
"p95": 108.22369960369542,
"p99": 175.24975506879855
}
},
"slo_status": {
"ttft_p95_ok": true,
"tpot_mean_ok": true,
"overall": "✅"
},
"raw_file": "/data/user1/yy/experiments/dsv4_h200_vllm_dspark_vs_default/results/20260708-142121/dspark/raw_outputs/dspark_0708_32_512_256.jsonl"
}
]
}

View File

@ -1,77 +0,0 @@
#!/usr/bin/env python3
"""Load results.json from multiple experiments and produce a comparison report.
Usage:
python3 scripts/analysis/compare_experiments.py
python3 scripts/analysis/compare_experiments.py --output comparison.md
"""
import argparse
import json
from collections import defaultdict
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
def load_results():
"""Glob experiments/*/results/*/results.json and load them."""
results = []
for path in sorted(REPO_ROOT.glob("experiments/*/results/*/results.json")):
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
data["_source"] = str(path.relative_to(REPO_ROOT))
results.append(data)
except (json.JSONDecodeError, OSError) as e:
print(f"WARN: failed to load {path}: {e}")
return results
def main():
parser = argparse.ArgumentParser(description="Compare benchmark experiments.")
parser.add_argument("--output", "-o", default="comparison_report.md", help="Output markdown file")
args = parser.parse_args()
results = load_results()
if not results:
print("No experiments found under experiments/*/results/*/results.json")
return
# Group by scenario name.
by_scenario = defaultdict(list)
for data in results:
meta = data.get("metadata", {})
for scenario in data.get("scenarios", []):
key = scenario.get("name", "unknown")
by_scenario[key].append((meta, scenario))
output_path = Path(args.output)
with open(output_path, "w", encoding="utf-8") as f:
f.write("# Cross-Experiment Comparison\n\n")
f.write("| Experiment | Chip | Engine | Scenario | Conc | In/Out | Req/s | OutTok/s | TTFT p99 | TPOT p99 | E2E p99 |\n")
f.write("|---|---|---|---|---:|---:|---:|---:|---:|---:|---:|\n")
for scenario_name in sorted(by_scenario.keys()):
for meta, scenario in by_scenario[scenario_name]:
lat = scenario.get("latencies", {})
f.write(
f"| {meta.get('experiment', '')} "
f"| {meta.get('chip', '')} "
f"| {meta.get('engine', '')} "
f"| {scenario_name} "
f"| {scenario.get('concurrency', '')} "
f"| {scenario.get('input_len', '')}/{scenario.get('output_len', '')} "
f"| {scenario.get('request_throughput') or ''} "
f"| {scenario.get('output_token_throughput') or ''} "
f"| {lat.get('ttft_ms', {}).get('p99') or ''} "
f"| {lat.get('tpot_ms', {}).get('p99') or ''} "
f"| {lat.get('e2e_ms', {}).get('p99') or ''} |\n"
)
print(f"Loaded {len(results)} experiment runs.")
print(f"Wrote {output_path}")
if __name__ == "__main__":
main()

View File

@ -1,87 +0,0 @@
#!/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

@ -1,323 +0,0 @@
#!/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

@ -1,266 +0,0 @@
#!/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

@ -1,128 +0,0 @@
#!/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 "$@"

View File

@ -17,10 +17,9 @@ def load_result(result_root: Path) -> dict:
return json.load(f)
def slo_status(ttft_p95_ms: float, tpot_mean_ms: float) -> str:
# S2 tier targets from scripts/SLO_STANDARDS.md.
ttft_ok = ttft_p95_ms < 3000.0
tpot_ok = tpot_mean_ms < 50.0
def slo_status(ttft_p95_ms: float, tpot_mean_ms: float, ttft_limit_ms: float = 3000.0, tpot_limit_ms: float = 50.0) -> str:
ttft_ok = ttft_p95_ms < ttft_limit_ms
tpot_ok = tpot_mean_ms < tpot_limit_ms
if ttft_ok and tpot_ok:
return ""
if ttft_ok or tpot_ok:
@ -33,11 +32,16 @@ def main():
parser.add_argument("--sglang", type=Path, required=True)
parser.add_argument("--vllm", type=Path, required=True)
parser.add_argument("-o", "--output", type=Path, default=Path("comparison.md"))
parser.add_argument("--ttft-limit", type=float, default=3000.0)
parser.add_argument("--tpot-limit", type=float, default=50.0)
args = parser.parse_args()
sglang_data = load_result(args.sglang)
vllm_data = load_result(args.vllm)
model = sglang_data.get("metadata", {}).get("model", "unknown")
hardware = sglang_data.get("metadata", {}).get("hardware", "unknown")
by_scenario = defaultdict(dict)
for data in (sglang_data, vllm_data):
backend = data["metadata"]["engine"]
@ -46,13 +50,12 @@ def main():
by_scenario[key][backend] = s
with open(args.output, "w", encoding="utf-8") as f:
f.write("# SGLang vs vLLM on DeepSeek-V4-Flash (H200, TP=8)\n\n")
f.write(f"# SGLang vs vLLM ({hardware})\n\n")
f.write("## Summary\n\n")
f.write("- Model: `/data/models/DeepSeek-V4-Flash`\n")
f.write("- Hardware: 8x NVIDIA H200 143GB\n")
f.write("- Tensor Parallelism: 8\n")
f.write(f"- Model: `{model}`\n")
f.write(f"- Hardware: {hardware}\n")
f.write("- Benchmark client: `sglang.bench_serving`\n")
f.write("- SLO reference: S2 tier (TTFT P95 < 3s, TPOT < 50ms)\n\n")
f.write(f"- SLO reference: TTFT P95 < {args.ttft_limit}ms, TPOT mean < {args.tpot_limit}ms\n\n")
f.write("## Side-by-side results\n\n")
f.write("| Scenario | Backend | Conc | Input | Output | Req/s | OutTok/s | TTFT P95(ms) | TTFT P99(ms) | TPOT Mean(ms) | TPOT P95(ms) | TPOT P99(ms) | E2E P99(ms) | SLO |\n")
@ -65,7 +68,7 @@ def main():
continue
cfg = s["config"]
m = s["metrics"]
status = slo_status(m["ttft_ms"]["p95"], m["tpot_ms"]["mean"])
status = slo_status(m["ttft_ms"]["p95"], m["tpot_ms"]["mean"], args.ttft_limit, args.tpot_limit)
f.write(
f"| {scenario_name} | {backend} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | "
f"{m['request_throughput']:.2f} | {m['output_token_throughput']:.2f} | "
@ -75,7 +78,7 @@ def main():
)
f.write("\n## Notes\n\n")
f.write("- SLO check uses TTFT P95 and TPOT mean (the same criteria as `scripts/SLO_STANDARDS.md`).\n")
f.write("- SLO check uses TTFT P95 and TPOT mean.\n")
f.write("- A ⚠️ indicates one of the two metrics is out of target; ❌ indicates both are out.\n")
print(f"Wrote comparison to {args.output}")

View File

@ -1,16 +1,14 @@
#!/usr/bin/env python3
"""Parse raw sglang.bench_serving JSONL outputs for one backend.
Reads JSONL files like {sglang|vllm}_phase1_MMDD_concurrency_inputlen_outputlen.jsonl
and generates results.json + report.md in the given result root.
Reads JSONL files like {backend}_{label}_MMDD_concurrency_inputlen_outputlen.jsonl
and updates results.json + report.md in the given result root.
Usage:
python3 parse_backend.py <result_root> [--backend sglang|vllm]
"""
import argparse
import json
import re
import sys
from pathlib import Path
@ -78,10 +76,10 @@ def scenario_name(concurrency: int, input_len: int, output_len: int) -> str:
return f"c{concurrency}_i{input_len}_o{output_len}"
def slo_status(metrics: dict) -> dict:
"""Check S2-tier SLO: TTFT P95 < 3s, TPOT mean < 50ms."""
ttft_ok = metrics["ttft_ms"]["p95"] < 3000.0
tpot_ok = metrics["tpot_ms"]["mean"] < 50.0
def slo_status(metrics: dict, ttft_limit_ms: float = 3000.0, tpot_limit_ms: float = 50.0) -> dict:
"""Check SLO: TTFT P95 < limit, TPOT mean < limit. Defaults to S2 tier."""
ttft_ok = metrics["ttft_ms"]["p95"] < ttft_limit_ms
tpot_ok = metrics["tpot_ms"]["mean"] < tpot_limit_ms
if ttft_ok and tpot_ok:
mark = ""
elif ttft_ok or tpot_ok:
@ -95,21 +93,16 @@ def slo_status(metrics: dict) -> dict:
}
def append_scenario(results_json: Path, scenario: dict) -> None:
with open(results_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["scenarios"].append(scenario)
with open(results_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def generate_report(result_root: Path, backend: str, scenarios: list[dict]) -> None:
def generate_report(result_root: Path, backend: str, scenarios: list[dict], metadata: dict | None) -> None:
report_path = result_root / "report.md"
model = metadata.get("model", "unknown") if metadata else "unknown"
hardware = metadata.get("hardware", "unknown") if metadata else "unknown"
with open(report_path, "w", encoding="utf-8") as f:
f.write(f"# H200 {backend.upper()} Comparison Benchmark Report\n\n")
f.write(f"# {hardware} {backend.upper()} Benchmark Report\n\n")
f.write(f"- Result root: `{result_root}`\n")
f.write(f"- Model: `/data/models/DeepSeek-V4-Flash`\n")
f.write(f"- Backend: {backend.upper()} (TP=8)\n")
f.write(f"- Model: `{model}`\n")
f.write(f"- Backend: {backend.upper()}\n")
f.write(f"- Benchmark client: `sglang.bench_serving --backend {backend}`\n\n")
f.write("## Results\n\n")
@ -148,7 +141,6 @@ def main() -> None:
backend = args.backend
if backend is None:
# Infer from filenames if not provided.
for p in raw_dir.iterdir():
if p.name.startswith("sglang_"):
backend = "sglang"
@ -159,6 +151,14 @@ def main() -> None:
if backend is None:
raise SystemExit("Could not infer backend from raw outputs")
metadata = None
if results_json.exists():
with open(results_json, "r", encoding="utf-8") as f:
try:
metadata = json.load(f).get("metadata")
except json.JSONDecodeError:
pass
scenarios = []
for jsonl_path in sorted(raw_dir.glob(f"{backend}_*.jsonl")):
parts = jsonl_path.stem.split("_")
@ -173,7 +173,6 @@ def main() -> None:
phase = label
dataset = "random"
# The last three numeric tokens are: concurrency, input_len/output_ctx, output_len.
try:
concurrency, input_len, output_len = int(parts[-3]), int(parts[-2]), int(parts[-1])
except ValueError:
@ -211,7 +210,7 @@ def main() -> None:
with open(results_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
generate_report(result_root, backend, scenarios)
generate_report(result_root, backend, scenarios, metadata)
print(f"Parsed {len(scenarios)} scenarios into {result_root}/report.md")

View File

@ -1,27 +0,0 @@
#!/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."

View File

@ -1,261 +0,0 @@
#!/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()

View File

@ -1,157 +0,0 @@
#!/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

@ -1,143 +0,0 @@
#!/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

@ -1,63 +0,0 @@
#!/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

@ -1,66 +0,0 @@
#!/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

@ -1,85 +0,0 @@
#!/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

View File

@ -1,138 +0,0 @@
#!/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

@ -1,27 +0,0 @@
#!/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

@ -1,65 +0,0 @@
#!/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

@ -1,66 +0,0 @@
#!/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

@ -1,156 +0,0 @@
#!/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

@ -1,60 +0,0 @@
#!/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"