生产环境H200部署DeepSeek 671B 满血版全流程实战(三):SGLang 安装详解

前言

随着前两篇文章的推进生产环境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 $?
相关推荐
AskHarries3 分钟前
Spring Boot对接twilio发送邮件信息
后端
李长渊哦18 分钟前
引入其他 YML 配置源 —— Spring Boot 中的 `import` 功能
数据库·spring boot·后端
姜太小白18 分钟前
【Linux】centos配置可用的yum源
linux·运维·centos
高建伟-joe18 分钟前
Spring Boot Tomcat 漏洞修复
java·spring boot·后端·网络安全·tomcat
shix .24 分钟前
爬虫一些基础知识的备忘录(需要自取)
c++·爬虫·python
uhakadotcom1 小时前
Python 缓存利器:`cachetools`
后端·面试·github
tan180°1 小时前
版本控制器Git(4)
linux·c++·git·后端·vim
龙雨LongYu121 小时前
Go执行当前package下的所有方法
开发语言·后端·golang
程序员小刚1 小时前
基于springboot + vue 的实验室(预约)管理系统
vue.js·spring boot·后端
IT 古月方源1 小时前
linux centos 忘记root密码拯救
linux·运维·centos