去年我们团队在做大模型推理服务升级时,碰到了一个让人头疼的难题:8 张 H800 跑 Llama-3-70B,用 vLLM 怎么调都只能达到 180 tokens/s 的吞吐量,而业务高峰期要求至少 300 tokens/s 才能守住 SLA。试遍了各种参数调优和显存配置,效果始终不上不下。
直到我们把引擎切换到 SGLang,同样的硬件,吞吐量直接飙到了 420 tokens/s,延迟还降低了 40%。这一下子勾起了我对 SGLang 底层原理的浓厚兴趣,于是花了两周时间深入啃了它的源码与设计理念。
1.1 SGLang 的核心优势
SGLang 之所以快,并不是靠什么黑魔法,而是在几个关键点上做了比 vLLM 更激进的优化:
RadixAttention:这是 SGLang 的杀手锏。传统的 KV Cache 是按请求独立管理的,而 RadixAttention 用一棵基数树来统筹所有请求的 KV Cache,相同的前缀可以共享。这在多轮对话、Few-shot 学习等场景下提升巨大。
连续批处理的调度优化:SGLang 的调度器在处理动态 batch 时更加果断,可以在一个 forward pass 中同时兼顾 prefill 和 decode 阶段的请求,而不像早期 vLLM 那样必须等待。
FlashInfer 后端:SGLang 深度集成了 FlashInfer,这个 CUDA kernel 库针对 H100/H800 的 SM90 架构做了极致优化,在特定场景下比 FlashAttention-2 快 15-20%。
1.2 环境要求
动手之前,请确保环境满足以下条件:
| 组件 |
最低版本 |
推荐版本 |
| CUDA |
12.1 |
12.4 |
| Python |
3.9 |
3.11 |
| PyTorch |
2.1.0 |
2.4.0 |
| Driver |
525.x |
550.x |
| SGLang |
0.2.0 |
0.3.x (最新) |
H800 的 HBM3 带宽高达 3.35 TB/s,想充分榨干它,驱动版本至关重要。我就踩过一个坑:用 525 驱动跑 SGLang,显存带宽只能跑到 2.8 TB/s 左右,升级到 550 之后直接拉满。
二、详细部署步骤
2.1 环境准备
我个人习惯用 conda 管理环境,但生产环境下更推荐 Docker:
# 创建专用环境
conda create -n sglang python=3.11 -y
conda activate sglang
# 安装 PyTorch(注意要装 CUDA 12.4 版本)
pip install torch==2.4.0 --index-url https://download.pytorch.org/whl/cu124
# 安装 SGLang(包含 FlashInfer)
pip install "sglang[all]"
# 如果你要用 FlashInfer 后端(强烈推荐),需要单独安装
pip install flashinfer -i https://flashinfer.ai/whl/cu124/torch2.4/
这里有个坑:FlashInfer 的 wheel 包严格要求 CUDA 版本和 PyTorch 版本精确匹配,装错了会导致 CUDA kernel 加载失败,然后抛出一堆让人摸不着头脑的错误。
2.2 验证 GPU 状态
启动服务之前,先给 GPU 做一个“体检”:
# 检查 GPU 是否被正确识别
nvidia-smi -L
# 检查 NVLink 拓扑(H800 多卡必须检查)
nvidia-smi topo -m
# 检查 GPU 时钟频率
nvidia-smi -q -d CLOCK
H800 的 NVLink 带宽是 900 GB/s,如果 topo -m 显示卡之间是 PIX 或者 PHB 而不是 NV18,说明你的 PCIe 拓扑有问题,需要排查硬件连接。
2.3 启动 SGLang 服务
最简单的启动姿势:
python -m sglang.launch_server \
--model-path meta-llama/Meta-Llama-3-70B-Instruct \
--tp 8 \
--port 30000 \
--host 0.0.0.0
但默认配置远远发挥不出 H800 的全部实力。下面是我调优后的生产配置:
python -m sglang.launch_server \
--model-path /data/models/Meta-Llama-3-70B-Instruct \
--tp 8 \
--port 30000 \
--host 0.0.0.0 \
--mem-fraction-static 0.88 \
--max-running-requests 256 \
--max-total-tokens 131072 \
--chunked-prefill-size 8192 \
--schedule-policy lpm \
--enable-flashinfer \
--attention-backend flashinfer \
--sampling-backend flashinfer \
--enable-torch-compile \
--dtype bfloat16 \
--trust-remote-code
这些参数究竟在干什么?逐个拆解一下:
--mem-fraction-static 0.88:静态分配 88% 的显存给 KV Cache。H800 单卡 80GB 显存,模型权重占用约 140GB / 8 = 17.5GB 每卡,剩下的空间要尽量留给 KV Cache。设太高容易 OOM,设太低又浪费显存。
--max-running-requests 256:同时处理的最大请求数。这个值和你的 batch size、sequence length 都有关系。我的经验是尽量设成 2 的幂次,方便 CUDA kernel 对齐。
--chunked-prefill-size 8192:分块 prefill 的大小。长 prompt 会被切成这个大小的 chunk 逐块处理,能有效降低首 token 延迟。
--schedule-policy lpm:调度策略选择 LPM(Longest Prefix Match),配合 RadixAttention 效果最佳。
2.4 验证服务状态
# 健康检查
curl http://localhost:30000/health
# 获取模型信息
curl http://localhost:30000/get_model_info
# 简单测试
curl http://localhost:30000/generate \
-H "Content-Type: application/json" \
-d '{
"text": "Hello, my name is",
"sampling_params": {
"temperature": 0.8,
"max_new_tokens": 100
}
}'
三、性能优化配置
3.1 RadixAttention 优化
RadixAttention 是 SGLang 的核心创新。它的原理是把所有请求的 KV Cache 组织成一棵基数树,相同前缀的 token 共享同一份缓存。
假设你有一个 Few-shot 的 prompt 模板:
You are a helpful assistant. Here are some examples:
Q: What is 2+2?
A: 4
Q: What is the capital of France?
A: Paris
Q: {user_question}
A:
在传统方案里,每个请求都要重新计算前面那大段 prompt 的 KV Cache。而在 SGLang 里,第一个请求计算完后,后续请求直接复用,只需计算 {user_question} 这一小截。
我写了一个测试脚本来验证这个效果:
import sglang as sgl
from sglang import RuntimeEndpoint
import time
import asyncio
# 连接到 SGLang 服务
runtime = RuntimeEndpoint("http://localhost:30000")
# 定义一个 Few-shot 的 prompt
system_prompt = """You are a helpful assistant specializing in code review.
Here are some examples of good code reviews:
Example 1:
Code: for i in range(len(arr)): print(arr[i])
Review: Consider using direct iteration: for item in arr: print(item)
Example 2:
Code: if x == True: return True
Review: Simplify to: return x
Example 3:
Code: result = []
for i in items:
result.append(i * 2)
Review: Use list comprehension: result = [i * 2 for i in items]
Now review the following code:
"""
# 测试不同请求数量下的吞吐量
async def benchmark_radix(num_requests: int):
questions = [
"Code: x = x + 1\nReview:",
"Code: if len(s) == 0:\nReview:",
"Code: d = dict()\nReview:",
"Code: return a if a > b else b\nReview:",
]
start_time = time.time()
tasks = []
for i in range(num_requests):
prompt = system_prompt + questions[i % len(questions)]
task = runtime.async_generate(
prompt,
sampling_params={"max_new_tokens": 50, "temperature": 0.7}
)
tasks.append(task)
results = await asyncio.gather(*tasks)
elapsed = time.time() - start_time
total_tokens = sum(len(r["text"].split()) for r in results)
print(f"Requests: {num_requests}, Time: {elapsed:.2f}s, Throughput: {total_tokens/elapsed:.1f} tokens/s")
# 打印 RadixAttention 的缓存命中率
metrics = runtime.get_metrics()
print(f"Cache hit rate: {metrics.get('radix_cache_hit_rate', 'N/A')}")
# 运行测试
asyncio.run(benchmark_radix(100))
在我的测试中,开启 RadixAttention 后,这种 Few-shot 场景的吞吐量提升了 2.3 倍。
3.2 FlashInfer 后端调优
FlashInfer 是 SGLang 性能的命脉。它针对 H100/H800 的 SM90 架构做了深度优化,包括:
- 利用 TMA(Tensor Memory Accelerator)进行异步内存拷贝
- 针对 FP8 的原生支持
- 更优的 warp-level 并行策略
要充分发挥 FlashInfer 的威力,注意这几个参数:
# 设置 FlashInfer 的 CUDA stream 数量
export SGLANG_FLASHINFER_NUM_STREAMS=4
# 启用 FlashInfer 的 FP8 KV Cache(可以节省一半显存)
python -m sglang.launch_server \
--model-path /data/models/Meta-Llama-3-70B-Instruct \
--tp 8 \
--kv-cache-dtype fp8_e5m2 \
--enable-flashinfer \
...
FP8 KV Cache 是一个很棒的优化点。理论上会有一点精度损失,但实测几乎感觉不到。我用 MT-Bench 评测过,FP16 得分 8.2,FP8 得分 8.1,这点差距基本可以忽略。
3.3 Tensor Parallel 优化
8 卡 H800 做 TP-8 时,卡间通信很容易成为瓶颈。优化通信有几个关键动作:
# 确保使用 NCCL 2.19+
export NCCL_DEBUG=INFO
# 设置 NCCL 通信算法
export NCCL_ALGO=Ring
# 对于 H800,推荐使用 NVLS(NVLink SHARP)
export NCCL_NVLS_ENABLE=1
# 设置 NCCL 缓冲区大小
export NCCL_BUFFSIZE=8388608
另外,H800 的 NVLink 拓扑对性能影响非常大。理想状态下,8 张卡应该通过 NVSwitch 全互联。如果没有 NVSwitch,至少要确保同一个 TP group 的卡落在同一个 NVLink domain 内。
检查拓扑:
nvidia-smi topo -m
输出应该类似:
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7
GPU0 X NV18 NV18 NV18 NV18 NV18 NV18 NV18
GPU1 NV18 X NV18 NV18 NV18 NV18 NV18 NV18
...
NV18 表示通过 NVLink 18 lanes 连接,这是最优状态。如果看到 PIX 或 SYS,说明走的是 PCIe,性能会大打折扣。
3.4 动态批处理调优
SGLang 的动态批处理比 vLLM 更激进。关键参数:
python -m sglang.launch_server \
--max-running-requests 256 \
--max-prefill-tokens 16384 \
--schedule-conservativeness 0.8 \
--enable-mixed-chunk \
...
--schedule-conservativeness:这个参数控制调度器的保守程度。0.0 表示最激进,会尽可能多地往 batch 里塞请求;1.0 表示最保守。生产环境推荐 0.7-0.9,太激进容易导致延迟抖动。
--enable-mixed-chunk:允许在同一个 batch 中混合 prefill 和 decode 请求。这是拉升吞吐量的利器,但也会增加调度复杂度。
四、生产环境配置示例
4.1 完整的启动脚本
#!/bin/bash
# sglang_server.sh - Production-ready SGLang server
set -e
# 环境变量
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export NCCL_DEBUG=WARN
export NCCL_NVLS_ENABLE=1
export NCCL_ALGO=Ring
export NCCL_BUFFSIZE=8388608
export SGLANG_FLASHINFER_NUM_STREAMS=4
# 模型路径
MODEL_PATH="/data/models/Meta-Llama-3-70B-Instruct"
# 日志配置
LOG_DIR="/var/log/sglang"
mkdir -p $LOG_DIR
# 启动服务
python -m sglang.launch_server \
--model-path $MODEL_PATH \
--tp 8 \
--port 30000 \
--host 0.0.0.0 \
--mem-fraction-static 0.88 \
--max-running-requests 256 \
--max-total-tokens 131072 \
--chunked-prefill-size 8192 \
--schedule-policy lpm \
--schedule-conservativeness 0.8 \
--enable-flashinfer \
--attention-backend flashinfer \
--sampling-backend flashinfer \
--kv-cache-dtype fp8_e5m2 \
--enable-mixed-chunk \
--enable-torch-compile \
--dtype bfloat16 \
--trust-remote-code \
--log-level info \
2>&1 | tee -a $LOG_DIR/sglang_$(date +%Y%m%d).log
4.2 Systemd 服务配置
# /etc/systemd/system/sglang.service
[Unit]
Description=SGLang LLM Inference Server
After=network.target
[Service]
Type=simple
User=llm
Group=llm
WorkingDirectory=/opt/sglang
ExecStart=/opt/sglang/sglang_server.sh
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
# 资源限制
LimitNOFILE=65536
LimitMEMLOCK=infinity
# 环境变量
Environment="PATH=/opt/conda/envs/sglang/bin:/usr/local/bin:/usr/bin"
[Install]
WantedBy=multi-user.target
4.3 Nginx 反向代理配置
upstream sglang_backend {
server 127.0.0.1:30000;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name llm.example.com;
ssl_certificate /etc/ssl/certs/llm.crt;
ssl_certificate_key /etc/ssl/private/llm.key;
location / {
proxy_pass http://sglang_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# SSE 支持
proxy_set_header Accept-Encoding "";
proxy_buffering off;
proxy_cache off;
# 超时设置(生成长文本需要)
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
location /health {
proxy_pass http://sglang_backend/health;
proxy_read_timeout 5s;
}
}
五、故障排查与监控
5.1 常见问题
问题 1:启动时报 FlashInfer 版本不兼容
RuntimeError: FlashInfer version mismatch...
解决:FlashInfer 的 wheel 必须严格匹配 CUDA 和 PyTorch 版本。
# 检查当前 CUDA 版本
nvcc --version
# 检查 PyTorch CUDA 版本
python -c "import torch; print(torch.version.cuda)"
# 重新安装正确版本
pip uninstall flashinfer -y
pip install flashinfer -i https://flashinfer.ai/whl/cu124/torch2.4/
问题 2:OOM(显存不足)
通常是 --mem-fraction-static 设得太高了。
# 查看显存使用情况
nvidia-smi dmon -s u -d 1
# 降低静态显存比例
--mem-fraction-static 0.80 # 从 0.88 降到 0.80
问题 3:吞吐量上不去,GPU 利用率低
# 检查 GPU 利用率
nvidia-smi dmon -s u -d 1
# 如果 SM 利用率低,可能是 batch size 太小
--max-running-requests 512 # 尝试增加
# 检查是否有 CPU 瓶颈
top -H -p $(pgrep -f sglang)
问题 4:NCCL 通信超时
NCCL WARN Timeout at NCCL_ALLREDUCE
解决:
# 增加超时时间
export NCCL_TIMEOUT=1800
# 检查网卡状态
ibstat # 如果用 InfiniBand
ethtool eth0 # 如果用 RoCE
5.2 日志分析
SGLang 的日志里藏着丰富的性能信息:
# 实时查看性能指标
tail -f /var/log/sglang/sglang_*.log | grep -E "(throughput|latency|cache_hit)"
关键指标解读:
[INFO] Throughput: 420.5 tokens/s, Running: 128, Pending: 45
^^^^^^^^ 这是实际吞吐量
^^^^^^^ 正在处理的请求数
^^ 等待队列长度
[INFO] RadixCache hit_rate=0.73, num_nodes=15234
^^^^^^^^ 缓存命中率,越高越好
5.3 Prometheus 监控
SGLang 内置了 Prometheus metrics 端点:
curl http://localhost:30000/metrics
配置 Prometheus 抓取:
# prometheus.yml
scrape_configs:
- job_name: 'sglang'
static_configs:
- targets: ['localhost:30000']
metrics_path: /metrics
scrape_interval: 15s
关键监控指标:
| 指标名 |
含义 |
告警阈值 |
sglang_num_running_requests |
正在处理的请求数 |
> max_running_requests * 0.9 |
sglang_num_waiting_requests |
等待队列长度 |
> 100 |
sglang_token_throughput |
每秒 token 数 |
< 预期值 * 0.8 |
sglang_avg_latency_ms |
平均延迟 |
> SLA |
sglang_cache_hit_rate |
RadixCache 命中率 |
< 0.5 |
5.4 Grafana 仪表盘
我整理了一个 Grafana 仪表盘的 JSON 模板,核心 panel 包括:
{
"panels": [
{
"title": "Token Throughput",
"type": "graph",
"targets": [
{
"expr": "rate(sglang_generated_tokens_total[1m])",
"legendFormat": "tokens/s"
}
]
},
{
"title": "Request Latency (P99)",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.99, rate(sglang_request_latency_bucket[5m]))",
"legendFormat": "P99"
}
]
},
{
"title": "GPU Memory Usage",
"type": "graph",
"targets": [
{
"expr": "nvidia_gpu_memory_used_bytes / nvidia_gpu_memory_total_bytes * 100",
"legendFormat": "GPU {{gpu}}"
}
]
}
]
}
六、性能对比测试
为了验证优化效果,我做了一组对比测试。测试环境:
- 硬件:8x NVIDIA H800 80GB
- 模型:Llama-3-70B-Instruct
- Prompt 长度:512 tokens(平均)
- 生成长度:256 tokens
6.1 与 vLLM 对比
| 框架 |
配置 |
吞吐量 (tokens/s) |
P50 延迟 (ms) |
P99 延迟 (ms) |
| vLLM 0.5.x |
默认配置 |
185 |
892 |
2340 |
| vLLM 0.5.x |
调优后 |
278 |
645 |
1820 |
| SGLang |
默认配置 |
312 |
520 |
1450 |
| SGLang |
本文配置 |
425 |
380 |
980 |
SGLang 调优后的吞吐量达到了 vLLM 默认配置的 2.3 倍。
6.2 RadixAttention 效果
测试场景:100 并发的 Few-shot 请求,共享 1500 tokens 的 prompt 前缀。
| 配置 |
吞吐量 |
首 token 延迟 |
| 无 RadixAttention |
245 tokens/s |
850ms |
| 有 RadixAttention |
580 tokens/s |
180ms |
缓存命中时,首 token 延迟降低了 78%。
七、总结
从实际生产经验来看,SGLang 确实是目前 H800 上最快的开源推理框架之一。核心优化点:
- RadixAttention:对于有共享前缀的场景,这是最关键的优化
- FlashInfer 后端:充分利用 H800 的 SM90 架构特性
- FP8 KV Cache:以极小的精度代价换取翻倍的 context 长度
- 合理的批处理参数:需要根据实际负载模式动态调整
后续优化方向:
- 尝试 SGLang 的 speculative decoding 支持
- 探索 multi-node 部署方案
- 集成 LoRA 热加载能力
对于正在折腾 大模型推理 的同学,不妨在 云栈社区 里一起交流实战经验,我也常在那儿分享调优心得。
参考资料
