SSKJ Dev a4e38b9e33 Reorganize experiments into hardware-specific subdirectories
Move all experiments under hardware-specific folders:
- experiments/h200/     : H200 GPU experiments (15 dirs)
- experiments/h20/      : H20 GPU experiments (2 dirs)
- experiments/p800/     : Kunlun P800 experiments (3 dirs)
- experiments/pro6000/    : RTX 6000D experiments (2 dirs)

This improves discoverability and keeps hardware-specific configs
isolated from each other.
2026-07-16 04:11:07 +00:00

10 KiB
Raw Permalink Blame History

dsv4_h200_vllm_tp2_custom_bench

目的

当 vLLM 使用 TP=2Tensor Parallel = 2部署时单张 H200 上可以同时运行 4 个独立服务8 卡 / 2 = 4 服务),每个服务独占 2 张 GPU

服务 端口 GPU 对
1 30005 0, 1
2 30006 2, 3
3 30007 4, 5
4 30008 6, 7

此时 sglang.bench_serving 存在以下问题:

  1. 低并发下无法测出真实吞吐:并发数不大时,请求可能只落在一个服务上,其他 3 个服务空闲bench 报告的吞吐只是单服务的 1/4。
  2. TTFT 测量不够精确sglang.bench_serving 的计时粒度较粗对于低延迟场景TP=2 下 TTFT 可能 < 100ms误差较大。
  3. 不支持多服务负载均衡:只能测一个端口,无法反映整个 8 卡集群性能。

因此本实验使用 自定义压测客户端 bench_client.py,直接调用 OpenAI /v1/chat/completions API通过 aiohttp 并发发送请求,并记录每个请求的精确时间戳。同时通过 负载均衡 将请求均匀分发到 4 个服务上。

与 sglang.bench_serving 的区别

特性 sglang.bench_serving bench_client.py (本实验)
协议 内部协议 OpenAI API (/v1/chat/completions)
并发控制 进程级 asyncio.Semaphore
TTFT 测量 粗略 精确到 time.perf_counter()
TPOT 测量 基于总时间估算 基于首 token 后每个 token 的间隔
多服务支持 单端口 轮询/随机分发到多个端口
低并发精度 一般 高(适合 TP=2 场景)
集群吞吐 只能测单服务 可测 4 服务总吞吐

文件说明

文件 说明
bench_client.py 自定义异步压测客户端,支持多服务负载均衡
config.env 实验配置场景、模型路径、端口列表、LB 策略等)
run_bench.sh 编排脚本:启动 4 个服务 → 运行压测 → 解析结果
start_server.sh 同时启动 4 个 vLLM TP=2 服务(每服务 2 GPU
parse_results.py 解析 bench_client.py 输出的 JSONL生成 results.json + report.md

快速运行

# 完整流程(自动启动 4 个服务、压测、生成报告)
bash experiments/dsv4_h200_vllm_tp2_custom_bench/run_bench.sh

# 跳过服务管理(已有 4 个服务在运行)
SKIP_MANAGE_SERVER=1 bash experiments/dsv4_h200_vllm_tp2_custom_bench/run_bench.sh

# 单独运行压测客户端(多服务,带健康检查)
python3 experiments/dsv4_h200_vllm_tp2_custom_bench/bench_client.py \
  --host 127.0.0.1 \
  --ports 30005,30006,30007,30008 \
  --model deepseek-v4-flash \
  --concurrency 32 --input-len 512 --output-len 256 --num-prompts 640 \
  --lb-strategy round_robin \
  --health-check-interval 5 \
  --health-max-failures 2 \
  --health-recovery-interval 10 \
  --request-max-retries 2 \
  --output-file /tmp/test.jsonl

# 单独运行压测客户端(单服务,向后兼容)
python3 experiments/dsv4_h200_vllm_tp2_custom_bench/bench_client.py \
  --host 127.0.0.1 --port 30005 \
  --model deepseek-v4-flash \
  --concurrency 8 --input-len 512 --output-len 256 --num-prompts 160 \
  --output-file /tmp/test_single.jsonl

# 解析已有结果
python3 experiments/dsv4_h200_vllm_tp2_custom_bench/parse_results.py \
  experiments/dsv4_h200_vllm_tp2_custom_bench/results/<RUN_ID>

场景设计

本实验覆盖了从 单并发128 并发 的梯度,以及不同输入/输出长度组合:

并发 输入长度 输出长度 请求数 说明
1 512 256 20 单并发基线,测纯延迟
4 512 256 80 低并发4 服务各 1 req
8 512 256 160 中低并发
16 512 256 320 中等并发
32 512 256 640 中高并发
64 512 256 1280 高并发
128 512 256 2560 极限并发,测集群吞吐上限
1 2048 512 20 长输入基线
8 2048 512 160 长输入中并发
32 2048 512 640 长输入高并发
128 2048 512 2560 长输入极限并发
1 4096 1024 20 超长输入基线
8 4096 1024 160 超长输入中并发
32 4096 1024 640 超长输入高并发
128 4096 1024 2560 超长输入极限并发

健康检查与故障自动跳过

bench_client.py 内置了 后台健康检查 + 请求级重试 机制,确保某个服务挂了不会导致整个 benchmark 失败。

健康检查机制

参数 默认值 说明
--health-check-interval 5.0 每 N 秒检查一次所有服务
--health-max-failures 2 连续失败 N 次才标记为不健康
--health-recovery-interval 10.0 不健康服务 N 秒后自动重试恢复
--request-max-retries 2 单个请求失败最多重试 N 次

故障处理流程

请求 → 负载均衡选择端口 → 发送请求
  ↓
成功 → 记录结果
  ↓
失败 → 换另一个端口重试(最多 --request-max-retries 次)
  ↓
全部失败 → 记录失败,继续下一个请求

后台同时运行健康检查:

  • 每 5 秒对所有端口执行 /health 探测
  • 连续 2 次失败的服务被标记为 unhealthy,不再接收新请求
  • 10 秒后自动尝试恢复,如果恢复成功则重新加入负载均衡池

输出中的故障信息

汇总 JSON 包含以下字段:

{
  "completed": 638,
  "failed": 2,
  "retried": 5,
  "healthy_ports_at_end": [30005, 30006, 30007],
  "per_port_stats": {
    "30005": {"count": 160, "failed": 0, ...},
    "30006": {"count": 159, "failed": 1, ...},
    "30007": {"count": 160, "failed": 0, ...},
    "30008": {"count": 159, "failed": 1, ...}
  }
}
  • failed: 最终失败的请求数(重试后仍失败)
  • retried: 至少重试过一次的请求数
  • healthy_ports_at_end: 压测结束时仍健康的服务端口
  • per_port_stats[].failed: 每个端口的失败数

场景间健康检查

run_bench.sh 在每个场景结束后会打印一次服务健康状态:

[2026-07-10T08:45:00+00:00] service health check: healthy=[30005 30006 30007 30008] unhealthy=[]
[2026-07-10T08:45:30+00:00] service health check: healthy=[30005 30006 30007] unhealthy=[30008]
[2026-07-10T08:45:30+00:00] WARNING: 1 service(s) unhealthy: 30008

这样即使某个服务在压测中途 OOM 崩溃其他服务仍能继续工作benchmark 不会中断。

自定义故障参数

python3 bench_client.py \
  --ports 30005,30006,30007,30008 \
  --health-check-interval 3 \
  --health-max-failures 1 \
  --health-recovery-interval 5 \
  --request-max-retries 3 \
  ...
  • 更敏感:间隔 3 秒、1 次失败即标记不健康、5 秒恢复
  • 更容错:单个请求最多重试 3 次

负载均衡策略

bench_client.py 支持三种负载均衡策略:

策略 说明 适用场景
round_robin 请求按顺序轮询分配到各端口 默认,最公平,推荐
random 随机选择一个端口 简单,分布可能不均
least_conn 选择当前连接数最少的端口 需要追踪 in-flight当前为 random 占位

通过 --lb-strategy 参数选择:

python3 bench_client.py --lb-strategy round_robin --ports 30005,30006,30007,30008 ...

输出格式

JSONL 原始数据 (raw_outputs/vllm_*.jsonl)

每行一个 JSON 对象,包含请求分发到的目标端口:

{
  "request_id": 0,
  "input_tokens": 512,
  "output_tokens": 256,
  "first_token_time_ms": 45.23,
  "e2e_latency_ms": 5234.56,
  "inter_token_latencies": [18.5, 19.2, ...],
  "tpot_ms": 19.8,
  "mean_itl_ms": 19.5,
  "success": true,
  "error": "",
  "target_port": 30005
}

最后一行是汇总数据,包含 各端口统计

{
  "completed": 640,
  "failed": 0,
  "duration": 12.34,
  "request_throughput": 51.88,
  "mean_ttft_ms": 48.5,
  "p95_ttft_ms": 62.3,
  "per_port_stats": {
    "30005": {"count": 160, "mean_ttft_ms": 47.2, "p95_ttft_ms": 58.1, "mean_e2e_ms": 5200.0},
    "30006": {"count": 160, "mean_ttft_ms": 49.1, "p95_ttft_ms": 64.3, "mean_e2e_ms": 5250.0},
    "30007": {"count": 160, "mean_ttft_ms": 48.8, "p95_ttft_ms": 61.5, "mean_e2e_ms": 5230.0},
    "30008": {"count": 160, "mean_ttft_ms": 48.9, "p95_ttft_ms": 65.2, "mean_e2e_ms": 5270.0}
  },
  "lb_strategy": "round_robin",
  "num_services": 4
}

结构化结果 (results.json)

遵循仓库统一的 JSON Schema

人类可读报告 (report.md)

Markdown 表格,包含所有场景的 TTFT/TPOT/E2E 均值与分位数,以及 SLO 达标状态。

单独启动/停止 4 个服务

# 启动 4 个服务
bash experiments/dsv4_h200_vllm_tp2_custom_bench/start_server.sh

# 停止run_bench.sh 会自动停止,但也可以手动)
for port in 30005 30006 30007 30008; do
  pid_file="experiments/dsv4_h200_vllm_tp2_custom_bench/server_port${port}.pid"
  [[ -f "$pid_file" ]] && kill "$(cat "$pid_file")" 2>/dev/null || true
  rm -f "$pid_file"
done
pkill -9 -f "vllm serve.*DeepSeek-V4-Flash" 2>/dev/null || true

注意事项

  • bench_client.py 优先使用 aiohttp,如果未安装则自动回退到 urllib(但会丢失 streaming 和精确 TTFT/ITL 测量)。
  • 默认 temperature=0.0 以保证输出长度稳定。
  • 输入 token 数通过空格分隔的单词数估算,实际 tokenizer 可能略有偏差。
  • 4 个服务共享同一模型文件,确保 /data/models/DeepSeek-V4-Flash 存在且可访问。
  • 每个服务占用约 2 张 GPU 的 90% 显存,确保无其他进程占用 GPU。
  • 健康检查依赖 /health 端点,确保 vLLM 服务已启用该端点vLLM 默认启用)。
  • 如果某个服务频繁崩溃,检查 GPU 显存是否充足OOM 是最常见原因)。