前言
随着前两篇文章的推进生产环境H200部署DeepSeek 671B 满血版全流程实战(一):系统初始化 生产环境H200部署DeepSeek 671B 满血版全流程实战(二):vLLM 安装详解,我们已经成功地在H200服务器上完成了DeepSeek 671B满血版的系统初始化以及vLLM的安装配置工作,整个部署架构正逐渐变得丰富和完善。但为了进一步挖掘模型的潜力,实现更加高效、精准的推理服务,SGLang的安装变得至关重要。
SGLang作为一种专门针对大型语言模型(LLM)的推理引擎,它具备独特的优化机制,能够在资源利用率和推理速度方面带来显著提升,是整个DeepSeek 671B模型部署生态中不可或缺的一环。在本篇文章中,我们将聚焦于SGLang的安装过程。
一、初始化系统环境
请参考生产环境H200部署DeepSeek 671B 满血版全流程实战(一):系统初始化
二、安装SGlang
1.1 创建虚拟环境
为避免依赖冲突,建议使用Conda创建独立环境:
ini
conda create -n sglang python=3.10
conda activate sglang
1.2 安装SGLang及依赖
升级pip并安装SGLang核心组件:
bash
pip install --upgrade pip
# 安装SGLang内核
pip install sgl-kernel --force-reinstall --no-deps
# 安装完整依赖(含GPU加速内核)
pip install "sglang[all]>=0.4.3.post2" --find-links https://flashinfer.ai/whl/cu124/torch2.5/flashinfer-python
1.3. 兼容性调整
由于SGLang对Transformers版本有特定要求,需降级至兼容版本:
ini
pip uninstall transformers -y
pip install transformers==4.48.3 --force-reinstall --no-deps
验证安装:
sql
pip show sglang
二、启动SGLang服务
2.1 激活 sglang 环境
确保在 sglang 的 conda 环境中:
conda activate sglang
2.2 启动服务
通过以下命令启动DeepSeek 671B模型的推理服务:
css
python -m sglang.launch_server --model /data/DeepSeek-R1 --trust-remote-code --tp 8 --mem-fraction-static 0.9 --host 0.0.0.0 --port 8102
参数详解:
- --model /data/DeepSeek-R1:指定模型路径,需与训练权重目录一致。
- --trust-remote-code:允许SGLang加载并执行模型相关的自定义代码,在启动DeepSeek 671B服务时,模型目录中包含自定义的模型实现代码(modeling_deepseek.py),需添加参数以允许SGLang解析并执行这些代码。
- --tp 8:使用8块GPU进行张量并行(Tensor Parallelism),需与服务器GPU数量匹配。
- --mem-fraction-static 0.9:预留90%显存供模型计算,剩余10%用于动态分配,避免显存碎片。
2.3 启动日志
三、验证服务可用性
发送API请求测试服务是否正常运行:
vbnet
curl -X POST "http://localhost:8102/v1/chat/completions" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-r1",
"messages": [{"role": "user", "content": "写个100字的散文"}]
}'
预期结果:返回模型生成的文本内容,表示服务部署成功。
四、SGLang 自带压测工具
SGLang提供内置压测工具,可评估模型在高并发场景下的表现。
4.1 压测命令
css
python -m sglang.bench_serving --backend sglang --host 0.0.0.0 --port 8102 --model /data/DeepSeek-R1 --dataset-name random --random-input-len 1024 --random-output-len 2048 --max-concurrency 64 --num-prompts 128
参数详解:
- --backend:指定使用的推理后端框架
- --model:指定加载的模型路径
- --dataset-name:选择测试数据集类型
- --num-prompts :定义总请求数量
- --max-concurrency: 控制并发请求数
- --random-input-len :定义每个请求的输入文本长度(单位:token)
- --random-output-len :定义每个请求的输出文本长度(单位:token)
4.2 SGLang压测性能
-
吞吐量趋势:
- 最优性能点:在 512并发 时,总Token吞吐量达到 2136 token/s,为系统性能峰值。
- 瓶颈显现:当并发增至 768 时,吞吐量下降至 1975 token/s,表明显存或计算资源(如GPU核心)达到极限,可能因显存碎片化或调度争用导致效率下降。
-
延迟分析:
- 端到端延迟:随着并发数增加,延迟呈指数级增长(64并发为77秒,768并发达476秒),表明系统在高并发下需排队处理请求,资源竞争激烈。
- 首Token时间:在 512并发 时激增至 11.9秒,反映GPU计算单元超载,调度效率降低,用户感知的响应速度显著下降。
- 每Token生成时间:从64并发的 73ms 增至768并发的 389ms,说明模型生成效率随负载增加而下降。
4.3 压测详细数据
4.4 压测结果字段详解
头部信息:
- Backend: sglang: 指明本次测试使用的推理后端框架是 SGLang。
- Traffic request rate: inf: 流量请求速率:无限 (inf)。 这意味着压测工具尽可能快地发送请求,不限制请求发送速率,以测试系统的最大处理能力。
- Max reqeuest concurrency:: 最大请求并发数。
- Successful requests:: 成功请求数。
- Benchmark duration (s):: 基准测试持续时间 (秒)。
- Total input tokens:: 总输入 tokens 数。 指所有请求中,客户端总共发送给模型的输入 token 数量。
- Total generated tokens:: 总生成 tokens 数。 指模型在所有请求中,总共生成的输出 token 数量。
- Total generated tokens (retokenized):: 总生成 tokens 数 (重新分词后)。
- Request throughput (req/s):: 请求吞吐量 (每秒请求数)。 指服务器每秒能够处理的请求数量。 越高越好。
- Input token throughput (tok/s):: 输入 token 吞吐量 (每秒输入 token 数)。 指服务器每秒能够处理的输入 token 数量。 越高越好。
- Output token throughput (tok/s):: 输出 token 吞吐量 (每秒输出 token 数)。 指服务器每秒能够生成的输出 token 数量。
- Total token throughput (tok/s):: 总 token 吞吐量 (每秒总 token 数)。 指服务器每秒能够处理的总 token 数量 (输入 + 输出)。 越高越好,综合反映了系统的 token 处理能力。
- Concurrency:: 实际并发数。 指在压测期间,平均的实际并发请求数量。
End-to-End Latency (端到端延迟) :
- Mean E2E Latency (ms):: 平均端到端延迟 (毫秒)。 指从客户端发送请求到接收到完整响应的平均时间。 越低越好,反映了请求的平均响应速度。
- Median E2E Latency (ms):: 中位数端到端延迟 (毫秒)。 指端到端延迟的中位数,比平均值更稳健,更能反映延迟的典型水平,受极端值影响较小。 越低越好。
Time to First Token (TTFT,首 token 延迟) :
- Mean TTFT (ms):: 平均首 token 延迟 (毫秒)。 指从客户端发送请求到服务器返回第一个 token 的平均时间。 越低越好,反映了模型响应的启动速度。
- Median TTFT (ms):: 中位数首 token 延迟 (毫秒)。 指首 token 延迟的中位数。越低越好。
- P99 TTFT (ms):: P99 首 token 延迟 (毫秒)。 指 99% 的请求的首 token 延迟都低于这个值。 反映了在较高百分位下的首 token 延迟情况,越高说明延迟波动越大。越低越好。
Time per Output Token (TPOT,每输出 token 时间) :
- Mean TPOT (ms):: 平均每输出 token 时间 (毫秒)。 指生成除第一个 token 之外的每个后续 token 的平均时间。 。
- Median TPOT (ms):: 中位数每输出 token 时间 (毫秒)。 指每输出 token 时间的中位数。
- P99 TPOT (ms):: P99 每输出 token 时间 (毫秒)。 指 99% 的请求的每输出 token 时间都低于这个值。
Inter-token Latency (ITL,token 间延迟) :
- Mean ITL (ms):: 平均 token 间延迟 (毫秒)。 指模型生成连续两个 token 之间的平均时间间隔。 越低越好,反映了模型生成 token 的流畅度。
- Median ITL (ms):: 中位数 token 间延迟 (毫秒)。 指 token 间延迟的中位数。越低越好。
- P99 ITL (ms):: P99 token 间延迟 (毫秒)。 指 99% 的请求的 token 间延迟都低于这个值。越低越好。
4.4. 压测系统监控
五、SGLang服务控制脚本
bash
#!/bin/bash
# DeepSeek-R1 671B 控制脚本,依据实际情况修改路径脚本
# 删除USE_SUDO变量
PID_FILE="/data/deepseek.pid"
CONDA_PATH="/home/turboai/miniconda3"
CONDA_ENV="sglang"
LOG_FILE="/data/deepseek.log"
SERVED_MODEL="/data/DeepSeek-R1"
PORT=8102
# 日志函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# 检查服务是否运行
check_service() {
# 检查所有相关的sglang进程,包括主进程和子进程
if pgrep -f "python.*sglang.*--port $PORT" > /dev/null || \
pgrep -f "python.*-m.*sglang.*--port $PORT" > /dev/null || \
pgrep -f "sglang::scheduler" > /dev/null || \
pgrep -f "sglang::detokenizer" > /dev/null || \
pgrep -f "multiprocessing.*sglang" > /dev/null || \
pgrep -f "python.*-c.*multiprocessing.*resource_tracker" > /dev/null; then
return 0 # 服务正在运行
else
return 1 # 服务未运行
fi
}
# 获取服务PID
get_service_pid() {
# 获取主进程和所有子进程的PID
(pgrep -f "python.*sglang.*--port $PORT"
pgrep -f "python.*-m.*sglang.*--port $PORT"
pgrep -f "sglang::scheduler"
pgrep -f "sglang::detokenizer"
pgrep -f "multiprocessing.*sglang"
pgrep -f "python.*-c.*multiprocessing.*resource_tracker") 2>/dev/null || echo ""
}
# 获取所有相关进程的父进程
get_parent_pids() {
local child_pids=$(get_service_pid)
local parent_pids=""
for pid in $child_pids; do
# 获取父进程ID
local ppid=$(ps -o ppid= -p $pid 2>/dev/null)
if [ -n "$ppid" ]; then
parent_pids="$parent_pids $ppid"
fi
done
echo $parent_pids
}
stop() {
# 检查服务是否在运行
if ! check_service; then
log_message "DeepSeek服务未运行"
rm -f "$PID_FILE" 2>/dev/null
return 0
fi
log_message "正在停止DeepSeek服务..."
# 获取所有相关进程的PID(包括父进程)
PIDS=$(get_service_pid)
PARENT_PIDS=$(get_parent_pids)
ALL_PIDS="$PIDS $PARENT_PIDS"
# 先尝试正常终止所有进程
# 修改进程终止部分
for PID in $ALL_PIDS; do
if kill -0 $PID 2>/dev/null; then
log_message "正在停止进程 PID: $PID"
kill $PID 2>/dev/null
fi
done
# 等待进程结束
TIMEOUT=30
while [ $TIMEOUT -gt 0 ] && check_service; do
sleep 1
TIMEOUT=$((TIMEOUT-1))
done
# 如果进程仍在运行,强制终止
if check_service; then
log_message "进程未响应,强制终止..."
# 重新获取进程列表,因为可能有些已经终止
PIDS=$(get_service_pid)
PARENT_PIDS=$(get_parent_pids)
ALL_PIDS="$PIDS $PARENT_PIDS"
# 修改强制终止部分
for PID in $ALL_PIDS; do
if kill -0 $PID 2>/dev/null; then
log_message "强制终止进程 PID: $PID"
kill -9 $PID 2>/dev/null
fi
done
sleep 2
# 修改pkill命令
pkill -9 -f "sglang::scheduler" 2>/dev/null
pkill -9 -f "sglang::detokenizer" 2>/dev/null
pkill -9 -f "multiprocessing.*sglang" 2>/dev/null
pkill -9 -f "python.*-c.*multiprocessing.*resource_tracker" 2>/dev/null
fi
# 最终检查
if check_service; then
log_message "错误: 无法停止所有DeepSeek服务进程"
return 1
else
rm -f "$PID_FILE" 2>/dev/null
log_message "DeepSeek服务已停止"
return 0
fi
}
# 初始化conda环境
init_conda() {
# 确保conda命令可用
if [ ! -f "$CONDA_PATH/etc/profile.d/conda.sh" ]; then
log_message "错误: Conda初始化脚本不存在: $CONDA_PATH/etc/profile.d/conda.sh"
return 1
fi
# 测试conda环境
if ! bash -c "source "$CONDA_PATH/etc/profile.d/conda.sh" && conda env list | grep -q "$CONDA_ENV""; then
log_message "错误: Conda环境 '$CONDA_ENV' 不存在"
return 1
fi
return 0
}
start() {
# 检查服务是否已在运行
if check_service; then
SERVICE_PID=$(get_service_pid)
log_message "DeepSeek服务已在运行 (PID: $SERVICE_PID)"
echo $SERVICE_PID > "$PID_FILE" # 更新PID文件
return 1
fi
# 检查conda环境
if ! init_conda; then
return 1
fi
log_message "正在启动DeepSeek服务(sglang)..."
# 启动服务 - 使用sglang的启动命令
# 修改启动命令,移除sudo相关判断
bash -c "
source "$CONDA_PATH/etc/profile.d/conda.sh" &&
conda activate $CONDA_ENV &&
nohup python -m sglang.launch_server \
--model "$SERVED_MODEL" \
--trust-remote-code \
--tp 8 \
--mem-fraction-static 0.9 \
--host 0.0.0.0 \
--port $PORT >> "$LOG_FILE" 2>&1 &"
# 等待服务启动
sleep 10 # 增加等待时间,确保所有子进程都启动
# 检查服务是否成功启动
if check_service; then
SERVICE_PID=$(get_service_pid | head -1) # 获取第一个PID作为主进程
echo $SERVICE_PID > "$PID_FILE"
log_message "DeepSeek服务(sglang)启动成功! 主进程PID: $SERVICE_PID"
log_message "服务端口: $PORT"
log_message "日志文件: $LOG_FILE"
else
log_message "错误: DeepSeek服务启动失败"
return 1
fi
}
restart() {
log_message "正在重启DeepSeek服务..."
stop
sleep 5 # 增加等待时间,确保完全停止
start
}
status() {
if check_service; then
SERVICE_PIDS=$(get_service_pid)
MAIN_PID=$(echo "$SERVICE_PIDS" | head -1)
SCHEDULER_COUNT=$(pgrep -f "sglang::scheduler" | wc -l)
log_message "DeepSeek服务正在运行"
log_message "主进程PID: $MAIN_PID"
log_message "调度器进程数: $SCHEDULER_COUNT"
# 更新PID文件
echo $MAIN_PID > "$PID_FILE"
# 显示GPU使用情况
if command -v nvidia-smi &> /dev/null; then
log_message "GPU使用情况:"
nvidia-smi --query-compute-apps=pid,used_memory,gpu_uuid --format=csv | grep -E "$(echo $SERVICE_PIDS | tr ' ' '|')" || echo "未找到相关GPU进程"
fi
# 检查端口是否在监听
if command -v ss &> /dev/null; then
log_message "端口状态:"
ss -tulpn | grep ":$PORT " || echo "端口 $PORT 未在监听"
elif command -v netstat &> /dev/null; then
log_message "端口状态:"
netstat -tulpn 2>/dev/null | grep ":$PORT " || echo "端口 $PORT 未在监听"
fi
return 0
else
log_message "DeepSeek服务未运行"
rm -f "$PID_FILE" 2>/dev/null # 清理可能存在的PID文件
return 1
fi
}
# 根据参数执行相应操作
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
*)
echo "用法: $0 {start|stop|restart|status}"
echo " start - 启动DeepSeek服务"
echo " stop - 停止DeepSeek服务"
echo " restart - 重启DeepSeek服务"
echo " status - 查看服务状态"
exit 1
esac
exit $?