【部署Qwen3.5】Ubuntu系统使用Conda+Vllm方式部署Qwen3.5-27B

Ubuntu系统使用Conda+Vllm方式部署Qwen3.5-27B

环境版本:

插件 版本号
fastapi 0.136.3
starlette 1.3.0
pydantic 2.13.4
vLLM 0.19.1
python3 3.10.20
pip3 26.1.1
NVIDIA-SMI 535.54.03
CUDA 12.2
PyTorch 2.10.0
Ubuntu 20.04.6

安装强制条件

显卡决定驱动最高版本、驱动决定cuda最高版本 cuda决定torch最高棒棒棒 torch决定vllm最高版本

显卡硬件 → 驱动最高版本 → CUDA Toolkit 最高版本 → PyTorch 最高版本 → vLLM 最高版本

复制代码
[ GPU 硬件 ]
   ↑
   |  执行所有并行计算任务(张量运算、矩阵乘、Attention等)
   |
[ 显卡驱动 (NVIDIA Driver) ]
   ↑
   |  操作系统与GPU之间的桥梁,管理GPU资源、显存分配;提供底层的CUDA Driver API
   |  版本决定了最高可支持的CUDA能力(如 nvidia-smi 显示 CUDA Version: 12.1)
   |
[ CUDA 运行时库 (CUDA Runtime) ]
   ↑
   |  封装更易用的运行时API(cudaMalloc、cudaMemcpy、核函数启动等)
   |  程序运行时动态链接到 libcudart.so,PyTorch 和 vLLM 直接调用这些库
   |
[ PyTorch (torch) ]
   ↑
   |  提供张量计算、自动微分、以及大量高性能的CUDA算子(如 cuBLAS、cuDNN)
   |  内置了特定版本的CUDA依赖(通过 +cu118 或 +cu121 标识)
   |  上层框架(vLLM / transformers)通过 PyTorch 执行实际的计算
   |
[ Transformer 模型(权重+计算图) ]
   ↑
   |  具体的神经网络结构设计:Multi-Head Attention、FFN、LayerNorm 等
   |  包含预训练好的参数(权重文件 .safetensors 或 .bin)和配置文件 config.json
   |  本身不是可运行的软件,需要被加载到 PyTorch 或 vLLM 中才能执行推理
   |
[ vLLM ]
   ↑
   |  高性能推理引擎,负责加载 Transformer 模型,并用 PagedAttention、连续批处理等
   |  技术优化显存占用与吞吐量。内部调用 PyTorch 或自己的CUDA扩展完成计算
   |  对外提供 HTTP 服务或 Python 接口,供上层应用调用
   
vllm (版本)
  └── torch (特定版本)
        ├── CUDA Toolkit 版本 (cu121 / cu124 / cu126...)
        ├── Compute Capability (sm_xx,决定支持哪些 GPU 架构)
        └── Python 版本 (cp310 / cp311 / cp312...)
		
vllm==0.x.x
  └── 要求 torch==2.x.x
        └── torch wheel 文件名:
            torch-2.x.x+cu126-cp311-cp311-linux_x86_64.whl
                   ↑         ↑      ↑
               CUDA版本   Python  架构
               (工具链)    版本
                        
            wheel 内部编译了哪些 sm_xx:
            → 这取决于 torch 官方构建时的选择

驱动全局只能一个、其他都可以有多个

dos命令

Ubuntu查询当前文件夹磁盘空间大小

df -h /xxx/xxx

查询当前文件夹下文件以及大小

du -h *

返回上一级

cd ...

新建文件夹

mkdri xxxxxx

1. 查看 NVIDIA 驱动和 CUDA 版本 nvidia-smi

NVIDIA 驱动(如果 nvidia-smi 能正常显示就跳过)

javascript 复制代码
sudo apt update
sudo apt install nvidia-driver-550
sudo reboot
text 复制代码
当前实操服务器:
当前显卡V100*8*12GB=96GB显存
显卡version是570.133.07
CUDA version 12.8

查看 Python 版本(需要 3.9+)

python3 --version

查看有没有 pip

pip3 --version

装 conda(最省心)

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh

bash Miniconda3-latest-Linux-x86_64.sh

创建环境装依赖

conda create -n vllm python=3.10 -y

conda activate vllm

退出vllm conda(如果启动vllm的话不需要执行这个)

conda activate vllm

安装 CUDA Toolkit 12.4

conda install nvidia/label/cuda-12.4.0::cuda-toolkit -y

验证

nvcc --version

安装PyTorch 版本

pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128

查看 PyTorch

pip show torch

python3 -c "import torch; print(torch.version)"

vllm的版本最高只能使用0.19,受限于显卡和CUDA版本

pip install vllm==0.19.1

查看 vllm

pip show vllm

python3 -c "import vllm; print(vllm.version)"

设置国内镜像

export HF_ENDPOINT=https://hf-mirror.com

安装依赖

pip install huggingface_hub

下载模型,该模型56GB,其中27B的B是百亿参数的意思,B参数的数越大则需要的显存越大

HF_ENDPOINT=https://hf-mirror.com hf download Qwen/Qwen3.5-27B --local-dir ./Qwen3.5-27B

用本地路径启动 其中tensor-parallel-size的8是使用8张显卡,这种只是临时启动,终端退出会停止进程

vllm serve ./Qwen3.5-27B --tensor-parallel-size 8 --pipeline-parallel-size 1 --dtype float16 --host 0.0.0.0 --port 5001 --gpu-memory-utilization 0.90 --max-model-len 16384 --max-num-seqs 64

使用nohup后台启动,首次启动编译会非常慢,需要耐心等待(模型的隐藏维度是 10240,需要能被 8 整除。tensor-parallel-size 必须能整除这个数。)

CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 nohup vllm serve ./Qwen3.5-27B --tensor-parallel-size 8 --pipeline-parallel-size 1 --dtype float16 --host 0.0.0.0 --port 5001 --gpu-memory-utilization 0.90 --max-model-len 16384 --max-num-seqs 64 > vllm.log 2>&1 &

参数类别 参数名 设定值 功能说明
环境变量 CUDA_VISIBLE_DEVICES 0,1,2,3 指定可见的GPU设备,使用4张卡
模型配置 --model /data/wh/models/Qwen3.6-27B 模型路径
--served-model-name Qwen3.6-27B API对外暴露的模型名称
--trust-remote-code - 信任并执行模型仓库中的自定义代码
--dtype float16 模型数据类型,半精度节省显存
网络配置 --host 0.0.0.0 监听所有网络接口
--port 5001 API服务端口
--api-key qweasdzxc API访问认证密钥
并行策略 --tensor-parallel-size 4 张量并行度,将模型切分到4张卡
--pipeline-parallel-size 1 流水线并行度,不启用
采样参数 --temperature 1.0 控制随机性,值越高输出越多样
--top-p 0.95 核采样,只从累积概率top-p的token中采样
--top-k 20 只从概率最高的top-k个token中采样
extraBody --min-p 0.0 0.0-1.0 只从概率最高的top-k个token中采样
--presence-penalty 1.5 惩罚已出现的token,增加话题多样性
extraBody --repetition-penalty 1.0 重复惩罚,>1.0减少重复,<1.0增加重复
frequency_penalty -2.0 ~ 2.0 根据 token 出现频率进行惩罚
显存优化 --gpu-memory-utilization 0.90 GPU显存利用率上限90%
--max-model-len 16384 最大序列长度(16K tokens)
--max-num-seqs 64 最大并发序列数
--enable-prefix-caching - 启用前缀缓存,加速重复前缀的请求
Agent功能 --enable-auto-tool-choice - 启用模型自动选择工具调用
--tool-call-parser qwen3_coder 工具调用解析器(Qwen3代码格式)
--reasoning-parser qwen3 推理解析器(Qwen3思维链格式)

查看进程是否存在

ps aux | grep "vllm serve"

2. 查看日志

tail -f vllm.log

杀死进程

pkill -9 -f vllm

测试

curl http://localhost:5001/v1/models

因为Qwen部署在异地机房,需要使用frp映射到公网

1.在github下载frp客户端到异地机房

2.frp客户端压缩包上传到异地机房服务器

tar -xzf frp_0.69.1_linux_amd64.tar.gz

3.解压frp客户端,然后进入解压文件夹

cd frp_0.69.1_linux_amd64

4.查看frp配置文件

cat frpc.toml

text 复制代码
serverAddr = "frp服务端的公网服务器的IP"
serverPort = frp服务端的bindPort端口
auth.method="token"
auth.token="frp服务端的token参数"


[[proxies]]
name = "vllm-api"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5001
remotePort = 5001

持久化启动frp

nohup ./frpc -c frpc.toml > frpc.log 2>&1 &

查看是否运行:

ps aux | grep frpc

查看日志:

tail -f frpc.log

停止:

pkill frpc

本地笔记本电脑cmd测试

curl http://frp服务端的公网服务器的IP:5001/v1/models

浏览器控制台流式输出

javascript 复制代码
  async function chat() {
    const res = await fetch("http://frp服务端的公网服务器的IP:5001/v1/chat/completions", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        model: "./Qwen3-5-27B",
        messages: [{ role: "user", content: "你好,你是谁" }],
        max_tokens: 256,
        stream: true
      })
    });
    const reader = res.body.getReader();
    const decoder = new TextDecoder();
    let result = '';
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      const chunk = decoder.decode(value);
      for (const line of chunk.split('\n')) {
        if (!line.startsWith('data: ') || line.includes('[DONE]')) continue;
        try {
          const content = JSON.parse(line.slice(6)).choices[0]?.delta?.content || '';
          result += content;
          console.clear();
          console.log(result);
        } catch(e) {}
      }
    }
  }
  chat();