Cherish's Notebook
切换到深色模式
菜单
Wiki--浏览次数--访问次数--跳出率--平均停留

Qwen3.8-27B-AWQ-INT4 双 RTX A4000 部署教程

Qwen3.8-27B-AWQ-INT4 双 RTX A4000 部署教程

Docker + vLLM + 多 GPU + 长上下文 + 多模态 + OpenAI API + Cherry Studio

本教程完整记录在 2 × NVIDIA RTX A4000 16GB 服务器上,通过 Docker + vLLM 部署 Qwen3.8-27B-AWQ-INT4 的全过程。

最终实现:

  • 2 张 A4000 共同运行一个 27B 模型
  • Tensor Parallel = 2
  • 最大上下文 112K
  • max-num-seqs = 4
  • 支持文本和图片
  • 提供 OpenAI 兼容 API
  • 可供 Cherry Studio、curl、OpenAI SDK 等客户端访问

1. 先理解整套部署架构

本次系统分成 5 层:

CODE
Cherry Studio / curl / OpenAI SDK
              │
              │ HTTP / OpenAI API
              ▼
        vLLM API Server
              │
              ▼
 Qwen3.8-27B-AWQ-INT4
              │
       Tensor Parallel = 2
          ┌──────┴──────┐
          ▼             ▼
     RTX A4000      RTX A4000
       16GB            16GB

再往下看运行环境:

CODE
NVIDIA GPU
   ↓
NVIDIA Driver
   ↓
NVIDIA Container Runtime
   ↓
Docker
   ↓
vLLM
   ↓
Qwen3.8
   ↓
OpenAI API
   ↓
Cherry Studio

最重要的一点:

Docker、vLLM、模型、客户端不是一回事。


2. Docker、vLLM、Ollama、ONNX 分别是什么

2.1 Docker:负责运行环境

Docker 不负责模型推理。

它主要负责把这些依赖封装起来:

CODE
Python
PyTorch
CUDA 运行库
vLLM
其他依赖

例如:

CODE
# 下载已经预装好 vLLM 的 Docker 镜像
docker pull vllm/vllm-openai:latest

可以简单理解:

CODE
Docker = 运行环境
vLLM   = 推理引擎
Qwen   = 被加载的模型

2.2 vLLM:负责高性能大模型服务端推理

vLLM 特别适合:

  • NVIDIA GPU 服务器
  • 多 GPU
  • 高并发
  • 长上下文
  • OpenAI API
  • 多用户访问
  • KV Cache 管理
  • Continuous Batching

vLLM 的基本工作流:

CODE
客户端发请求
    ↓
vLLM 接收
    ↓
调度请求
    ↓
管理 KV Cache
    ↓
调用 GPU
    ↓
返回 OpenAI 兼容结果

Continuous Batching

vLLM 可以动态调度不同时间到达的请求:

CODE
请求 A 先到
请求 B 后到
请求 C 再到
      ↓
vLLM 动态加入/移出 batch
      ↓
尽量保持 GPU 忙碌

因此它非常适合服务器 API 场景。


2.3 Ollama:更适合个人本地快速运行

Ollama 更强调:

CODE
安装简单
模型管理方便
命令简单
适合本地快速体验

典型命令:

CODE
# 下载模型
ollama pull <模型名>

# 运行模型
ollama run <模型名>

更适合:

CODE
Mac
Windows
Linux 桌面
个人电脑
单用户
快速测试模型

2.4 ONNX / ONNX Runtime

ONNX 是一种模型交换格式。

典型流程:

CODE
PyTorch / TensorFlow
        ↓
      导出
        ↓
      ONNX
        ↓
 ONNX Runtime
        ↓
CPU / CUDA / TensorRT

ONNX Runtime 更偏向:

CODE
把模型嵌入自己的软件

例如:

  • C++
  • C#
  • Python
  • 工业软件
  • 桌面软件
  • 边缘端
  • 跨平台应用

可以简单记:

CODE
Ollama
→ 本地快速运行模型

vLLM
→ GPU 服务器提供大模型 API

ONNX Runtime
→ 把模型嵌入自己的应用程序

3. 本次服务器和模型信息

3.1 GPU

服务器:

CODE
GPU 0:NVIDIA RTX A4000 16GB
GPU 1:NVIDIA RTX A4000 16GB

总显存约:

CODE
32GB

查看 GPU:

CODE
# 查看 GPU 型号、显存、温度、利用率和进程
nvidia-smi

实时监控:

CODE
# 每 1 秒刷新一次 GPU 状态
watch -n 1 nvidia-smi

3.2 模型

本次准备了:

CODE
/data/qwen/models/Qwen3.8-27B-AWQ-BF16-INT4
/data/qwen/models/Qwen3.8-27B-AWQ-INT4

最终使用:

CODE
/data/qwen/models/Qwen3.8-27B-AWQ-INT4

原因:

CODE
模型权重占用相对更小
↓
留给 KV Cache 的空间更多
↓
更适合长上下文和多模态

4. 理解模型名称:27B、AWQ、INT4

模型名:

CODE
Qwen3.8-27B-AWQ-INT4

可拆成:

CODE
Qwen3.8
├── 27B
├── AWQ
└── INT4

4.1 27B

CODE
B = Billion

所以:

CODE
27B ≈ 270 亿参数

INT4 不会让 27B 变成 7B,参数规模仍是 27B。

4.2 AWQ

AWQ 是一种权重量化方案。

CODE
高精度权重
   ↓
AWQ 量化
   ↓
低 bit 权重
   ↓
降低显存和存储压力

4.3 INT4

INT4 表示模型权重主要采用 4 bit 表示。

粗略对比:

CODE
BF16 → 16 bit
INT8 →  8 bit
INT4 →  4 bit

本次选择 INT4 的核心目的:

CODE
减少权重显存
↓
给 KV Cache 留更多空间

5. GPU、驱动、CUDA、Docker Runtime 的关系

完整关系:

CODE
NVIDIA GPU
   ↓
NVIDIA Driver
   ↓
CUDA Runtime
   ↓
NVIDIA Container Toolkit
   ↓
Docker
   ↓
PyTorch / vLLM

NVIDIA Driver

CODE
# 能正常显示 GPU,说明宿主机驱动基本正常
nvidia-smi

CUDA

vLLM 和 PyTorch 通过 CUDA 使用 NVIDIA GPU。

注意:

CODE
nvidia-smi 里的 CUDA Version

更接近“当前驱动支持的最高 CUDA Runtime 版本”,不等于系统一定安装了同版本 CUDA Toolkit。

NVIDIA Container Toolkit

它负责把宿主机 GPU 暴露给 Docker 容器。


6. 配置 NVIDIA Container Toolkit

CODE
# 查看 nvidia-ctk 是否已经安装
which nvidia-ctk

# 查看版本
nvidia-ctk --version

配置 Docker:

CODE
# 把 NVIDIA Runtime 注册到 Docker
sudo nvidia-ctk runtime configure --runtime=docker

# 重新加载 Docker 配置
# 本机还有其他重要容器,因此优先 reload
sudo systemctl reload docker

检查:

CODE
# 确认 Docker 已经识别 nvidia runtime
docker info | grep -i runtime

7. 检查 Docker 是否能使用 GPU

本机标准的:

CODE
--gpus all

曾出现兼容问题,因此最终使用:

CODE
--runtime=nvidia
-e NVIDIA_VISIBLE_DEVICES=all

测试:

CODE
# --rm:
# 容器退出后自动删除,只适合一次性测试
#
# --runtime=nvidia:
# 使用 NVIDIA Container Runtime
#
# NVIDIA_VISIBLE_DEVICES=all:
# 让容器看到所有 NVIDIA GPU
docker run --rm \
  --runtime=nvidia \
  -e NVIDIA_VISIBLE_DEVICES=all \
  nvidia/cuda:12.8.0-base-ubuntu22.04 \
  nvidia-smi

容器里能看到两张 A4000,就说明 Docker → GPU 链路正常。


8. 磁盘检查与目录准备

查看磁盘:

CODE
# 查看根分区和 /data 使用情况
df -h

查看 Docker 占用:

CODE
# 查看 Docker 镜像、容器、volume、build cache 占用
docker system df

清理未使用 Build Cache:

CODE
# -a:清理全部未使用 build cache
# -f:不再确认
docker builder prune -af

不要在有重要数据库 volume 的服务器上盲目执行:

CODE
docker system prune -a --volumes

准备目录:

CODE
# 模型目录
mkdir -p /data/qwen/models

# vLLM / Hugging Face 缓存
mkdir -p /data/qwen/vllm-cache

# 临时目录
mkdir -p /data/qwen/tmp

9. 从 Hugging Face 下载 Qwen3.8 模型

在使用 vLLM 启动 Qwen3.8 之前,首先要把模型文件准备好。

本教程采用的方式是:

CODE
Hugging Face
    ↓
hf download
    ↓
/data/qwen/models/
    ↓
Docker 只读挂载
    ↓
vLLM 加载本地模型

这样做的优点是模型文件位置固定,Docker 容器删除或重建以后也不需要重新下载模型。


9.1 Hugging Face 和 hf 命令分别是什么

Hugging Face 是常用的模型、数据集和机器学习资源托管平台。

模型仓库通常写成:

CODE
用户名或组织名/模型名

例如本次使用:

CODE
cyankiwi/Qwen3.8-27B-AWQ-INT4

其中:

CODE
cyankiwi
→ Hugging Face 用户 / 组织

Qwen3.8-27B-AWQ-INT4
→ 模型仓库名称

hf 是 Hugging Face Hub 官方命令行工具,可以完成:

CODE
登录
下载模型
上传文件
查看缓存
管理仓库

9.2 安装 Hugging Face CLI

hf 命令由 Python 包 huggingface_hub 提供。

建议在专门用于 Qwen 部署的 Conda 环境中安装:

CODE
# 进入自己的 Qwen Python / Conda 环境
# 如果已经进入目标环境,可以跳过这一步
conda activate /data/conda_envs/qwen

# 安装或升级 huggingface_hub
# -U:upgrade,升级到较新的版本
python -m pip install -U huggingface_hub

安装后检查:

CODE
# 查看 hf 命令位于哪里
which hf

# 查看 hf CLI 帮助,能正常输出说明安装成功
hf --help

# 查看 huggingface_hub Python 包版本
python -m pip show huggingface_hub

为什么不再推荐 huggingface-cli

旧教程经常使用:

CODE
huggingface-cli download ...

较新的 Hugging Face Hub 已经统一推荐:

CODE
hf download ...

如果终端提示:

CODE
huggingface-cli is deprecated

直接改用 hf 即可。


9.3 是否需要登录 Hugging Face

如果模型仓库是公开的,很多情况下可以直接下载。

如果仓库需要授权、访问受限,或者匿名下载受到限制,则需要登录。

交互式登录:

CODE
# 登录 Hugging Face
# 执行后终端会提示输入 Access Token
hf auth login

检查当前账号:

CODE
# 查看当前登录的 Hugging Face 用户
hf auth whoami

也可以使用环境变量:

CODE
# 把 Hugging Face Token 保存到当前 Shell
# 不要把真实 Token 提交到 Git 或公开教程
export HF_TOKEN='你的_HuggingFace_Access_Token'

Hugging Face Token 和后面 vLLM 的 --api-key 不是同一个东西:

CODE
HF_TOKEN
→ 用来访问 Hugging Face 下载模型

VLLM_API_KEY
→ 用来保护自己部署的 vLLM API

9.4 创建模型存储目录

本次不把模型下载到系统根分区,而是统一放到容量更大的 /data:

CODE
# 创建模型根目录
# -p:父目录不存在时一起创建;目录已经存在也不会报错
mkdir -p /data/qwen/models

推荐目录结构:

CODE
/data/qwen/
├── models/
│   ├── Qwen3.8-27B-AWQ-INT4/
│   └── Qwen3.8-27B-AWQ-BF16-INT4/
├── vllm-cache/
└── tmp/

9.5 下载本次最终使用的 AWQ-INT4 模型

本次最终部署的是:

CODE
cyankiwi/Qwen3.8-27B-AWQ-INT4

推荐命令:

CODE
# hf download:
# 下载 Hugging Face 仓库中的模型文件
#
# cyankiwi/Qwen3.8-27B-AWQ-INT4:
# Hugging Face 仓库 ID
#
# --local-dir:
# 指定模型最终保存到哪个本地目录
hf download \
  cyankiwi/Qwen3.8-27B-AWQ-INT4 \
  --local-dir /data/qwen/models/Qwen3.8-27B-AWQ-INT4

一行版:

CODE
hf download cyankiwi/Qwen3.8-27B-AWQ-INT4 --local-dir /data/qwen/models/Qwen3.8-27B-AWQ-INT4

下载完成以后,宿主机模型路径就是:

CODE
/data/qwen/models/Qwen3.8-27B-AWQ-INT4

9.6 下载另一个 AWQ-BF16-INT4 版本

本次还准备过:

CODE
cyankiwi/Qwen3.8-27B-AWQ-BF16-INT4

下载:

CODE
# 下载另一个显存压力更大的量化版本
hf download \
  cyankiwi/Qwen3.8-27B-AWQ-BF16-INT4 \
  --local-dir /data/qwen/models/Qwen3.8-27B-AWQ-BF16-INT4

一行版:

CODE
hf download cyankiwi/Qwen3.8-27B-AWQ-BF16-INT4 --local-dir /data/qwen/models/Qwen3.8-27B-AWQ-BF16-INT4

两者都属于 27B 模型,但本次最终选择 AWQ-INT4,因为它能给 KV Cache 留出更多显存。


9.7 下载中断后怎么办

下载几十 GB 的模型时,中途断网并不罕见。

一般情况下,重新执行同一条:

CODE
hf download cyankiwi/Qwen3.8-27B-AWQ-INT4 --local-dir /data/qwen/models/Qwen3.8-27B-AWQ-INT4

Hugging Face Hub 会重新检查已有文件,并继续处理缺失或未完成的内容。

因此不需要因为一次网络中断就删除整个模型目录重新下载。


9.8 下载完成后检查模型文件

先看目录:

CODE
# 查看模型目录中的文件
ls -lh /data/qwen/models/Qwen3.8-27B-AWQ-INT4

查看总大小:

CODE
# -s:只显示总大小
# -h:使用 GB / MB 等人类可读单位
# du:查看目录占用空间

du -sh /data/qwen/models/Qwen3.8-27B-AWQ-INT4

通常会看到一些重要文件:

CODE
config.json
生成配置相关 JSON
Tokenizer 相关文件
*.safetensors
模型索引文件
Processor / 多模态相关配置

具体文件名取决于仓库内容。


9.9 检查 config.json

config.json 是模型最重要的配置文件之一。

vLLM 会从中读取:

CODE
模型架构
隐藏层配置
最大上下文配置
量化信息
部分多模态配置

查看前 40 行:

CODE
# 查看 config.json 前 40 行
head -n 40 /data/qwen/models/Qwen3.8-27B-AWQ-INT4/config.json

搜索架构:

CODE
# -n:显示匹配行号
# 搜索 architectures 字段
grep -n '"architectures"' /data/qwen/models/Qwen3.8-27B-AWQ-INT4/config.json

如果想查最大位置 / 上下文相关配置,也可以:

CODE
# 搜索常见上下文长度字段;不同模型字段名称可能不同
grep -n -E 'max_position|max_seq|rope' /data/qwen/models/Qwen3.8-27B-AWQ-INT4/config.json

9.10 为什么本教程选择“先下载到本地”

vLLM 也可以在某些情况下直接使用 Hugging Face 仓库 ID,让运行时自动下载模型。

本次没有采用这种方式,而是:

CODE
先 hf download
↓
固定模型目录
↓
Docker 只读挂载
↓
vLLM 加载本地路径

原因:

CODE
模型位置明确
容器删除后模型仍然存在
重新创建容器不需要重新下载
方便比较不同量化版本
方便检查模型大小和完整性
减少 Docker 层和模型文件混在一起

Docker 中使用:

CODE
# 宿主机 /data/qwen/models
# 映射成容器里的 /models
# :ro 表示 read-only,只读
-v /data/qwen/models:/models:ro

所以宿主机:

CODE
/data/qwen/models/Qwen3.8-27B-AWQ-INT4

在容器中就变成:

CODE
/models/Qwen3.8-27B-AWQ-INT4

这也解释了最终 docker run 为什么写:

CODE
/models/Qwen3.8-27B-AWQ-INT4

而不是宿主机完整路径。


9.11 Hugging Face 下载常见错误

hf: command not found

说明当前 Python 环境没有安装 CLI,或者 PATH 没找到它:

CODE
# 在当前 Python 环境安装 / 升级
python -m pip install -U huggingface_hub

# 再检查
which hf
hf --help

huggingface-cli is deprecated

不要继续使用旧命令:

CODE
huggingface-cli download

改成:

CODE
hf download

Hugging Face 无法连接

先测试网络:

CODE
# -I:只获取 HTTP 响应头,不下载完整网页
curl -I https://huggingface.co

如果服务器必须通过代理联网,可在当前 Shell 临时设置:

CODE
# HTTP 代理;端口根据自己的代理实际配置填写
export http_proxy=http://127.0.0.1:代理端口

# HTTPS 代理
export https_proxy=http://127.0.0.1:代理端口

再执行:

CODE
hf download cyankiwi/Qwen3.8-27B-AWQ-INT4 --local-dir /data/qwen/models/Qwen3.8-27B-AWQ-INT4

如果代理只运行在另一台电脑上,需要保证服务器能真正访问那个代理地址,不能把服务器自己的 127.0.0.1 和另一台电脑的 127.0.0.1 混淆。


10. 拉取 vLLM 镜像

CODE
# 下载 vLLM OpenAI-Compatible Server 镜像
docker pull vllm/vllm-openai:latest

确认:

CODE
# 查看镜像
docker images | grep vllm

11. API Key、OpenSSL 和 Bearer

11.1 为什么需要 API Key

服务如果开放到:

CODE
http://服务器IP:8000

就应该加鉴权,避免任意用户直接调用。

vLLM 使用:

CODE
--api-key <KEY>

开启 API Key 验证。


11.2 OpenSSL 是什么

OpenSSL 是一个常用的密码学和 TLS 工具集,常见能力包括:

CODE
生成随机数
计算哈希
生成密钥
处理证书
TLS / HTTPS

本次部署只用到了它的:

CODE
安全随机数生成

命令:

CODE
# openssl:
# 调用 OpenSSL
#
# rand:
# 生成密码学安全随机数据
#
# -hex:
# 以十六进制字符串输出
#
# 32:
# 生成 32 字节随机数据
# 32 字节 = 256 bit
openssl rand -hex 32

由于:

CODE
1 字节 = 2 个十六进制字符

所以会得到:

CODE
64 个十六进制字符

这里的 OpenSSL:

只是用来生成一个难以猜测的 API Key,并不是在给模型本身加密。


11.3 用环境变量保存 API Key

CODE
# 把 API Key 保存到当前 Shell 的环境变量
export VLLM_API_KEY='你的API_KEY'

检查长度:

CODE
# 只查看长度,不直接打印 Key
echo ${#VLLM_API_KEY}

注意:

CODE
export

通常只对当前 Shell 和它启动的子进程生效。

重新 SSH 或新开终端后,可能需要重新设置。


11.4 Bearer 是什么

请求中:

CODE
-H "Authorization: Bearer $VLLM_API_KEY"

可以拆成:

CODE
Authorization
→ HTTP 身份认证请求头

Bearer
→ 使用 Token 作为凭证的认证格式

$VLLM_API_KEY
→ 真正的 API Key

例如:

CODE
Authorization: Bearer abc123

其中 Bearer 不是 API Key 的一部分。


12. 最终部署命令

12.1 教学版

CODE
docker run -d \
  \
  # 容器名称
  --name qwen38-int4 \
  \
  # Docker / 服务器重启后自动恢复
  --restart unless-stopped \
  \
  # 使用 NVIDIA Container Runtime
  --runtime=nvidia \
  \
  # 让容器看到全部 NVIDIA GPU
  -e NVIDIA_VISIBLE_DEVICES=all \
  \
  # vLLM 临时目录
  -e TMPDIR=/tmp/vllm \
  \
  # 使用宿主机 IPC;
  # 多 GPU / PyTorch multiprocessing / NCCL 更友好
  --ipc=host \
  \
  # 宿主机 8000 → 容器 8000
  -p 8000:8000 \
  \
  # 模型目录只读挂载
  -v /data/qwen/models:/models:ro \
  \
  # 缓存放到 /data,避免继续占根分区
  -v /data/qwen/vllm-cache:/root/.cache \
  \
  # 临时目录放到 /data
  -v /data/qwen/tmp:/tmp/vllm \
  \
  # vLLM 官方 OpenAI Server 镜像
  vllm/vllm-openai:latest \
  \
  # 实际加载的模型路径
  /models/Qwen3.8-27B-AWQ-INT4 \
  \
  # API 对外暴露的模型 ID
  --served-model-name qwen38 \
  \
  # 两张 GPU 共同运行同一个模型
  --tensor-parallel-size 2 \
  \
  # vLLM 规划使用约 96% GPU 显存
  --gpu-memory-utilization 0.96 \
  \
  # 单条 sequence 最大上下文
  # 114688 = 112 × 1024 = 112K
  --max-model-len 114688 \
  \
  # 最大活跃 sequence 数
  --max-num-seqs 4 \
  \
  # 强制 eager mode
  # 更偏稳定和兼容,可能牺牲部分速度
  --enforce-eager \
  \
  # 解析 Qwen reasoning / thinking
  --reasoning-parser qwen3 \
  \
  # 开启 API Key 鉴权
  --api-key "$VLLM_API_KEY"

12.2 一行版

CODE
docker run -d --name qwen38-int4 --restart unless-stopped --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all -e TMPDIR=/tmp/vllm --ipc=host -p 8000:8000 -v /data/qwen/models:/models:ro -v /data/qwen/vllm-cache:/root/.cache -v /data/qwen/tmp:/tmp/vllm vllm/vllm-openai:latest /models/Qwen3.8-27B-AWQ-INT4 --served-model-name qwen38 --tensor-parallel-size 2 --gpu-memory-utilization 0.96 --max-model-len 114688 --max-num-seqs 4 --enforce-eager --reasoning-parser qwen3 --api-key "$VLLM_API_KEY"

为了支持图片,最终命令中没有加入 --language-model-only。


13. vLLM 核心参数

--served-model-name qwen38

这是 API 对外显示的模型 ID。

请求中:

CODE
{
  "model": "qwen38"
}

必须匹配。

--tensor-parallel-size 2

CODE
一个模型
↓
拆到 2 张 GPU
↓
两张卡协同完成推理

--gpu-memory-utilization 0.96

表示 vLLM 在规划显存时,目标使用比例约 96%。

它会综合考虑:

CODE
模型权重
KV Cache
CUDA Runtime
Vision 模块
工作空间

--max-model-len 114688

控制单条 sequence 的最大上下文。

这里:

CODE
114688 = 112K

上下文包含:

CODE
System Prompt
历史对话
当前输入
图片视觉 token
输出 token

--max-num-seqs 4

最多允许 4 条活跃 sequence 参与调度。

不代表 4 条请求都可以同时各自占满 112K,因为它们共享有限 KV Cache。

--enforce-eager

强制 eager mode。

优点:

CODE
兼容性较好
显存行为更直观

缺点:

CODE
可能比 CUDA Graph 路径慢

为什么没有 --language-model-only

如果加入:

CODE
--language-model-only

会:

CODE
关闭视觉模块
↓
不能传图片
↓
节省显存

本次需要多模态,所以没有加。


14. 启动后检查

查看容器:

CODE
# 查看 qwen 容器
docker ps -a | grep qwen

查看日志:

CODE
# 实时日志
docker logs -f qwen38-int4

最近 100 行:

CODE
# 查看最近 100 行
docker logs --tail 100 qwen38-int4

查看状态:

CODE
# 查看是否 running、是否反复重启、退出码
docker inspect qwen38-int4 \
  --format 'Status={{.State.Status}} Restarting={{.State.Restarting}} RestartCount={{.RestartCount}} ExitCode={{.State.ExitCode}}'

查看真实启动参数:

CODE
# 查看当前容器实际使用的全部 vLLM 参数
docker inspect qwen38-int4 --format '{{json .Args}}'

查看 GPU:

CODE
# 每秒刷新一次
watch -n 1 nvidia-smi

15. 查询真正的模型 ID

不要猜模型名。

CODE
# 查询 vLLM 当前注册的模型 ID
curl http://127.0.0.1:8000/v1/models \
  -H "Authorization: Bearer $VLLM_API_KEY"

返回中:

CODE
{
  "data": [
    {
      "id": "qwen38"
    }
  ]
}

这里的 id 才是 API 请求应使用的模型名。


16. curl 纯文本测试

CODE
# time:
# 统计整个请求耗时
#
# curl:
# 命令行 HTTP 客户端
#
# -H:
# 添加 HTTP Header
#
# -d:
# 发送 JSON 请求体
time curl http://127.0.0.1:8000/v1/chat/completions \
  -H "Authorization: Bearer $VLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model":"qwen38",
    "messages":[
      {
        "role":"user",
        "content":"1+1等于多少?只回答结果"
      }
    ],
    "max_tokens":20
  }'

max_tokens 只控制本次最多生成多少 token,不等于 max-model-len。


17. curl 图片多模态测试

17.1 Base64 是什么

JSON 是文本格式,不能直接把 JPG 二进制原样放进去。

所以常见做法:

CODE
JPG / PNG
↓
Base64
↓
文本字符串
↓
放进 JSON

17.2 转换图片

假设:

CODE
/data/qwen/test.jpg

执行:

CODE
# base64:
# 把二进制图片编码成 Base64 文本
#
# -w0:
# 不自动换行
#
# IMG=:
# 把结果保存到 Shell 变量 IMG
IMG=$(base64 -w0 /data/qwen/test.jpg)

17.3 发送图片

CODE
curl http://127.0.0.1:8000/v1/chat/completions \
  -H "Authorization: Bearer $VLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"qwen38\",
    \"messages\": [
      {
        \"role\": \"user\",
        \"content\": [
          {
            \"type\": \"text\",
            \"text\": \"请描述这张图片中的内容\"
          },
          {
            \"type\": \"image_url\",
            \"image_url\": {
              \"url\": \"data:image/jpeg;base64,$IMG\"
            }
          }
        ]
      }
    ],
    \"max_tokens\": 512
  }"

本次实际测试已经成功识别图片。


18. Cherry Studio 接入

Base URL:

CODE
http://服务器IP:8000/v1

例如:

CODE
http://192.168.10.102:8000/v1

配置:

CODE
API 地址:
http://服务器IP:8000/v1

API Key:
与 --api-key 一致

模型:
使用 /v1/models 返回的 id

如果 curl 图片正常但 Cherry Studio 不能上传图片,优先检查客户端是否识别该模型的 Vision 能力。


19. 上下文、KV Cache 和并发

19.1 上下文

上下文就是模型当前需要保留和处理的 token 总体。

包括:

CODE
系统提示词
历史聊天
当前输入
代码
图片视觉 token
输出

19.2 KV Cache

Transformer Attention 会产生:

CODE
K = Key
V = Value

为了避免每生成一个 token 都重新计算全部历史,模型会把历史 K/V 缓存在 GPU 中。

这就是:

CODE
KV Cache

可以把显存粗略理解为:

CODE
GPU 显存
├── 模型权重
├── CUDA / vLLM Runtime
├── Vision 模块
├── 工作空间
└── KV Cache

19.3 为什么长上下文更吃显存

CODE
token 越多
↓
需要保存的 K/V 越多
↓
KV Cache 越大
↓
显存越紧张

19.4 为什么并发也吃 KV Cache

每条活跃 sequence 都有自己的 K/V 状态。

所以:

CODE
长上下文
+
高并发

会同时竞争显存。


20. 为什么 256K、128K 失败,而 112K 成功

256K

CODE
262144 tokens

当时日志显示:

CODE
所需 KV Cache ≈ 8.09 GiB
可用 KV Cache ≈ 3.7 GiB

所以失败。

128K

CODE
131072 tokens

日志显示:

CODE
所需 KV Cache ≈ 4.09 GiB
可用 KV Cache ≈ 3.7 GiB
估计最大长度 ≈ 118384 tokens

所以:

CODE
131072 > 118384

仍然失败。

112K

CODE
114688 tokens

而:

CODE
114688 < 118384

因此成功启动。


21. 为什么图片模式更慢

纯文本:

CODE
Text
↓
Tokenizer
↓
LLM
↓
Output

图片:

CODE
Image
↓
Decode / Resize / Processor
↓
Vision Encoder
↓
视觉特征
↓
LLM
↓
Reasoning
↓
Output

图片比纯文本多了一整段视觉编码,所以更慢是正常现象。

另外:

CODE
--reasoning-parser qwen3

意味着模型可能先生成 reasoning token,再生成最终答案。

如果客户端没有及时显示 reasoning,体感上会像“转了很久”。


22. 常见错误

Unauthorized

表示 API Key 不匹配。

CODE
# 查看当前 Shell 是否存在 API Key
echo ${#VLLM_API_KEY}

重新设置:

CODE
export VLLM_API_KEY='你的API_KEY'

model does not exist

表示鉴权已经通过,但模型 ID 不对。

CODE
# 查询真实模型 ID
curl http://127.0.0.1:8000/v1/models \
  -H "Authorization: Bearer $VLLM_API_KEY"

KV Cache 不足

典型:

CODE
KV cache is needed
larger than available KV cache memory

优先降低:

CODE
--max-model-len

Connection reset by peer

检查:

CODE
# 看容器状态
docker ps -a | grep qwen

# 看错误日志
docker logs --tail 100 qwen38-int4

图片不能上传

CODE
# 查看真实启动参数
docker inspect qwen38-int4 --format '{{json .Args}}'

确认没有:

CODE
--language-model-only

然后先用 curl 验证图片后端,再排查 Cherry Studio。


23. 常用 Docker 运维命令

CODE
# 查看容器
docker ps -a | grep qwen

# 停止,不删除
docker stop qwen38-int4

# 启动已有容器
docker start qwen38-int4

# 重启
docker restart qwen38-int4

# 强制停止并删除容器
# 不会删除 /data/qwen/models 中的模型文件
docker rm -f qwen38-int4

只有修改这些启动参数时,才需要重新 docker run:

CODE
模型路径
端口
上下文
并发
Tensor Parallel
Volume
API Key
served-model-name

24. vLLM、Ollama、ONNX 的选择建议

项目vLLMOllamaONNX Runtime
主要定位大模型服务器本地模型运行通用模型执行
上手难度中高低中高
多 GPU LLM强封装更多取决于实现
高并发强一般取决于应用
OpenAI API核心能力支持兼容接口一般自己实现
模型管理自己管理很方便自己管理
C++ 集成非主要用途非主要用途很适合
长上下文调参灵活封装更多取决于实现
适合本次是不优先不优先

可以直接记:

CODE
个人快速跑模型
→ Ollama

GPU 服务器模型服务
→ vLLM

软件内嵌模型
→ ONNX Runtime

25. 后续性能优化方向

模型稳定运行以后,再研究性能。

重要指标:

CODE
TTFT = Time To First Token
首 token 延迟

TPS = Tokens Per Second
每秒生成 token 数

建议分别对比:

CODE
上下文:
32K
64K
112K

并发:
1
2
4

一次只改一个变量。

后续还可以研究:

CODE
FP8 KV Cache
Prefix Cache
CUDA Graph
移除 --enforce-eager
Nginx / Caddy
HTTPS
监控
Benchmark

26. 本次最终配置

CODE
模型:
Qwen3.8-27B-AWQ-INT4

GPU:
2 × RTX A4000 16GB

推理框架:
vLLM

部署方式:
Docker

Tensor Parallel:
2

GPU Memory Utilization:
0.96

最大上下文:
114688 tokens
= 112K

最大活跃 sequence:
4

多模态:
开启

Reasoning Parser:
qwen3

Eager:
开启

API:
OpenAI-Compatible API

端口:
8000

最终链路:

CODE
2 × RTX A4000
      ↓
NVIDIA Container Runtime
      ↓
Docker
      ↓
vLLM
      ↓
Qwen3.8-27B-AWQ-INT4
      ↓
112K Context
+ 4 Active Sequences
+ Vision
      ↓
OpenAI-Compatible API
      ↓
Cherry Studio / curl / SDK

27. 最后应掌握的核心术语

CODE
Docker
→ 容器运行环境

vLLM
→ 大模型服务端推理框架

Ollama
→ 本地模型管理与运行工具

ONNX Runtime
→ 通用模型执行运行时

27B
→ 参数规模

AWQ
→ 权重量化方案

INT4
→ 4-bit 权重量化

Tensor Parallel
→ 多 GPU 共同运行一个模型

KV Cache
→ 保存历史 Attention K/V 的显存缓存

max-model-len
→ 单条 sequence 最大上下文

max-num-seqs
→ 最大活跃 sequence 数

served-model-name
→ API 对外暴露的模型 ID

OpenSSL
→ 本次用于生成安全随机 API Key

Bearer
→ HTTP Token 鉴权格式

Base64
→ 把图片二进制编码成文本以便放进 JSON

Cherry Studio
→ 客户端,真正推理发生在服务器 vLLM