使用vLLM在EC2 GPU上部署Qwen3-VL模型

一、背景

进入2025年,大语言模型LLM的发展已经经过了几轮迭代,大量国产开源模型涌现出来,并在文本生成、多模态图像理解、Embedding等多个场景中证明了自己的优秀能力。其中,Qwen-VL系列模型被广泛使用。尤其是参数量较小的模型,GPU硬件配置要求低,便于私有化部署。同时,小参数量的模型还便于用户自己发起Fine-tune微调,只需要花费较低的训练成本就能满足特定业务要求。本文介绍在AWS云上海外区域,基于Nvidia L4 GPU的EC2 G6系列机型,使用vLLM部署Qwen3-VL系列模型。

二、模型版本选择

1、模型体积和运行精度

从Huggingface上能看到的Qwen3-VL系列模型:

https://huggingface.co/collections/Qwen/qwen3-vl

选型时,由于MoE架构(激活部分参数)的模型性能好于Dense稠密架构(激活所有参数)的模型,推理性能更好。因此就不再列出Dense模型。从Huggingface可以看到如下几个版本是主要候选版本(下表中X表示属于本项的意思)

参数量 名称 FP16/BF16 FP8量化 模型体积
235B Qwen3-VL-235B-A22B-Thinking X 476.7 GB
235B Qwen3-VL-235B-A22B-Thinking-FP8 X 237.7GB
30B Qwen3-VL-30B-A3B-Thinking X 62.1GB
30B Qwen3-VL-30B-A3B-Thinking-FP8 X 32.2GB
8B Qwen3-VL-8B-Thinking X 17GB
8B Qwen3-VL-8B-Thinking-FP8 X 10.6GB
4B Qwen3-VL-4B-Thinking X 8.3GB
2B Qwen3-VL-2B-Thinking X 4.0GB

2、EC2虚拟机GPU显存对模型的适配建议

GPU标称的显存通常整数,例如L4 GPU是标称24GB,但考虑到1000进制和1024进制,启动操作系统后,实际可用显存在22GB多。以8B模型为例,在FP16/BF16条件下,8B x 2= 16GB显存用于加载模型。KV Cache需要1~3GB(根据序列长度)、激活参数需要1~2GB,pyTorch需要约1GB,总计22GB非常紧凑,基本上无法满足8B参数模型以FP16/BF16精度运行。所以在L4这种24GB显存级别的GPU上,降低到8bit量化版本是安全稳定的,或者干脆换用L40s等更大显存48GB机型。

训练型GPU机型可用显存如下表格。

EC2机型 GPU 单卡可用显存 FP16/BF16推理 FP8或FP4量化
p4 单卡或者8卡Nvidia A100 40GB或80GB 30B参数模型(单卡)
p5 单卡或者8卡Nvidia H100 80GB 30B参数模型(单卡)
p5en 8卡Nvidia H200 141GB 235B参数模型(多卡) 235B参数模型(多卡)
p6-b200 8卡Nvidia B200 179GB 235B参数模型(多卡) 235B参数模型(多卡)
p6e-gb200 8卡Nvidia GB200 185GB 235B参数模型(多卡) 235B参数模型(多卡)
p6-b300 8卡Nvidia B300 268GB 235B参数模型(多卡) 235B参数模型(多卡)

推理型GPU机型可用显存如下表格。

EC2机型 GPU 单卡可用显存 FP16/BF16推理 FP8量化
g4dn 1卡/4卡/8卡 Nvidia T4 16GB 4B参数模型 4B参数模型
g5 1卡/4卡/8卡 Nvidia A10G 22GB 4B参数模型 8B参数模型
g6 1卡/4卡/8卡 Nvidia L4 22GB 4B参数模型 8B参数模型
g6e 1卡/4卡/8卡 Nvidia L40s 44GB 8B参数模型 30B参数模型

3、结论

建议在具有24GB显存、实际可用22GB显存的Nvidia L4 GPU上,运行FP8量化的8B参数模型,以确保一定的显存空余量。最终下载部署 Qwen3-VL-8B-Thinking-FP8 模型。

三、创建EC2

1、创建EC2

创建EC2的操作过程这里不展开叙述,但是一些配置关键点说明如下:

  • 区域选择us-west-2俄勒冈
  • 架构选择x86_64
  • 选择操作系统位置点击浏览更多AMI,搜索关键字Deep Learning,并找到Ubuntu系统的Deep Learning OSS Nvidia Driver AMI GPU PyTorch 2.8 (Ubuntu 24.04)
  • GPU机型选择g6.xlarge(1张L4 GPU,24GB显存)
  • 安全组开放22端口,填写入站地址来源是pl-047d464325e7bf465,这个是us-west-2俄勒冈的EC2 Instance Connect的入站IP,如果您部署在别的region,请自行查询
  • 安全组放行8080端口,入站范围是0.0.0.0/0,允许来自远程互联网的访问,您也可以限制到本VPC特定范围
  • 登陆EC2的OS密钥自行配置
  • 将EC2部署到允许向外发起网络连接的VPC,有去外网0.0.0.0/0路由表的子网(需要从外网下Python包)
  • 磁盘选择gp3类型,容量建议200GB,最好适当提升IOPS从默认的3000提升到6000或者8000,吞吐从默认的125MB/s提升到200MB/s

按以上参数创建EC2。

2、连接到EC2

创建完毕后等待3~5分钟,然后登陆EC2。在控制台上选中这个EC2,然后点击Connect连接按钮。如下截图。

选择第一个标签页EC2 Instance Connect,默认用户名是Ubuntu不用修改,点击右下角连接按钮。如下截图。

连接成功。这里可以看到由于选择的是Deep Learning AMI,所以CUDA驱动已经就绪。如下截图。

下边可以部署模型了。

四、使用vLLM部署模型

1、环境准备

首先登陆EC2,执行如下命令安装依赖。

sudo apt update
sudo apt upgrade -y
sudo apt install pipx net-tools -y
pipx install huggingface_hub
pipx ensurepath
sudo reboot

2、从Huggingface下载模型

执行如下命令从huggingface下载模型:

cd /home/ubuntu/
hf download Qwen/Qwen3-VL-8B-Thinking-FP8 --local-dir ./Qwen3-VL-8B-Thinking-FP8

3、安装vLLM

安装uv包管理工具,并通过uv安装vLLM:

cd /home/ubuntu/
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv --python 3.12 --clear --seed
source .venv/bin/activate
uv pip install vllm bitsandbytes qwen-vl-utils --torch-backend=auto

安装过程下载软件包一般要3~5分钟,注意网络不要断线。

4、启动vLLM服务

为了确保后台进程不中断,可以使用tmux虚拟终端。执行tmux即可启动。

执行如下命令启动服务:

uv run vllm serve /home/ubuntu/Qwen3-VL-8B-Thinking-FP8 \
    --served-model-name Qwen3-VL-8B \
    --api-key qwen-by-vllm-api-key-2025-11 \
    --gpu-memory-utilization 0.9 \
    --async-scheduling \
    --mm-encoder-tp-mode data \
    --max-model-len 4096 \
    --max-num-seqs 8 \
    --host 0.0.0.0 \
    --port 8080

第一次加载模型时间会比较长,可能需要3-5分钟。后续再次启动时候加载速度会较快。启动成功后会显示Application startup complete。下来就可以开始API测试。

如果刚才窗口SSH远程连接中断,希望再回到刚才的虚拟终端,可执行tmux attach即可返回。

5、配置vLLM为系统服务随OS自动启动(可选)

以上方法为按需启动vLLM服务。第一次加载模型耗时较长。如果希望将其配置为操作系统后台服务自动启动,那么可以执行如下命令完成配置(注意替换里边的API Key、模型目录等参数):

# vLLM 系统服务自动配置脚本
echo "正在配置 vLLM 系统服务..."

# 创建 systemd 服务文件
sudo tee /etc/systemd/system/vllm.service > /dev/null <<EOF
[Unit]
Description=vLLM Inference Server
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu
Environment="PATH=/home/ubuntu/.venv/bin:/home/ubuntu/.cargo/bin:/usr/local/bin:/usr/bin"
ExecStart=/home/ubuntu/.cargo/bin/uv run vllm serve /home/ubuntu/Qwen3-VL-8B-Thinking-FP8 \
  --served-model-name Qwen3-VL-8B \
  --api-key qwen-by-vllm-api-key-2025-11 \
  --gpu-memory-utilization 0.9 \
  --async-scheduling \
  --mm-encoder-tp-mode data \
  --max-model-len 4096 \
  --max-num-seqs 8 \
  --host 0.0.0.0 \
  --port 8080
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

echo "服务文件已创建: /etc/systemd/system/vllm.service"

# 重载 systemd 配置
sudo systemctl daemon-reload
echo "systemd 配置已重载"

# 启用开机自启
sudo systemctl enable vllm
echo "已启用开机自启动"

# 重启服务
sudo systemctl restart vllm
echo "服务已重启"

# 等待 5 秒
sleep 5

# 显示服务状态
echo ""
echo "========== 服务状态 =========="
sudo systemctl status vllm --no-pager

echo ""
echo "========== 配置完成 =========="
echo "服务地址: http://0.0.0.0:8080"
echo "模型名称: Qwen3-VL-8B"
echo ""
echo "常用命令:"
echo "  查看状态: sudo systemctl status vllm"
echo "  查看日志: sudo journalctl -u vllm -f"
echo "  停止服务: sudo systemctl stop vllm"
echo "  重启服务: sudo systemctl restart vllm"
echo "  禁用自启: sudo systemctl disable vllm"

执行以上命令,即可将vLLM添加为系统服务。

注意:以上脚本使用了特定的模型路径、模型名称、API密钥、端口等,如果需要更换模型,请编辑服务对应的配置文件/etc/systemd/system/vllm.service修改里边的参数。修改完毕后,还需要重新加载配置文件sudo systemctl daemon-reload才可正常工作。

6、调用API测试

以上操作为在AWS云上的EC2上部署。以下测试可以在开发者本机进行,请确保开发者本机可以无障碍的连接到位于海外网络的AWS云的EC2虚拟机。

建议使用uv来做环境管理。在对应环境上安装Python的openai的sdk。

curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv --python 3.13
source .venv/bin/activate
uv pip install openai

(1) 文字生成任务

准备Python如下代码。注意替换代码中的Endpoint的IP地址为上一步的EC2 Public IP,替换模型ID为上一步启动vLLM服务时候指定的模型名称。

from openai import OpenAI

client = OpenAI(
    base_url="http://44.247.29.89:8080/v1", 
    # api_key="xxxxxxxxxxxxxx" # 上一步在配置文件中指定的API KEY,这里不推荐hard-code方式,最好是设置OS环境变量
)

stream = client.chat.completions.create(
    model="Qwen3-VL-8B",
    messages=[
        {
            "role": "user",
            "content": "你是什么模型,你有内置的tool能力吗。"
        }
    ],
    stream=True
)

for chunk in stream:
    if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()

即可对接口发起测试。注意如果返回request timeout,注意检查网络连通性。

(2) 文字生成任务(Image-to-text)

现在本地保存图片,通过OpenAI的接口规范提交图片。在当前目录下准备图片文件image.jpg,然后准备如下python代码。

import base64
from openai import OpenAI

# 读取图片并转换为base64
def encode_image(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')
# 编码图片
image_base64 = encode_image("image.jpg")

client = OpenAI(
    base_url="http://44.247.29.89:8080/v1", 
    # api_key="xxxxxxxxxxxxxx" # 上一步在配置文件中指定的API KEY,这里不推荐hard-code方式,最好是设置OS环境变量
)

stream = client.chat.completions.create(
    model="Qwen3-VL-8B",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "告诉我图中发生了什么"
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image_base64}"
                    }
                }
            ]
        }
    ],
    stream=True
)

for chunk in stream:
    if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()

即可对接口发起测试。注意如果返回request timeout,注意检查网络连通性。

五、vLLM推理性能测试

vLLM的测试分离线测试和在线测试测试。离线测试是不需要启动vLLM服务的,直接有python脚本调用模型加载。在线服务是先启动在线API的后台进程,然后再从网络调用API,存在网络开销。因此这两种场景可分别用于测试GPU的理论值,和实际API服务值。本人为了考察GPU的推理能力,不考虑网络开销,选择离线测试的方式。

1、离线测试吞吐(文本输入)

本文的测试使用24GB显存的L4 GPU的g6.2xlarge完成,然后更换为使用48GB显存的L40s GPU的g6e.2xlarge进行测试。首先确认后台服务已经关闭,vLLM已经退出,显存完全释放。

针对吞吐量的离线测试,执行如下命令:

uv run vllm bench throughput \
    --model /home/ubuntu/Qwen3-VL-8B-Thinking-FP8 \
    --gpu-memory-utilization 0.9 \
    --max-model-len 4096 \
    --max-num-seqs 32 \
    --num-prompts 50 \
    --input-len 512 \
    --output-len 256

此外另外打开一个shell,通过nvidia-smi命令可观察GPU的显存使用。

Every 1.0s: nvidia-smi                                                         ip-172-31-1-204: Fri Nov 28 10:36:56 2025

Fri Nov 28 10:36:56 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.95.05              Driver Version: 580.95.05      CUDA Version: 13.0     |
+-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA L4                      On  |   00000000:31:00.0 Off |                    0 |
| N/A   68C    P0             71W /   72W |   14954MiB /  23034MiB |     97%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|    0   N/A  N/A           14831      C   VLLM::EngineCore                      14946MiB |
+-----------------------------------------------------------------------------------------+

在测试中可看到显存使用情况,

--max-num-seqs参数从8调整到12、16、32、64,只要没有触发 OOM(Out-of-memory),那么就可以继续施压。直到OOM无法完成测试、或者效率下降。

--max-num-seqs=32时候结果如下:

参数 数值 说明
模型 Qwen3-VL-8B-Thinking-FP8 8B参数的FP8量化模型
GPU Nvidia L4 24GB显存,22GB实际可用
并发数 32 16线程并发,每线程2个sequence
输入长度 512 tokens 一个prompt的token数量
输出长度 256 tokens 一个response的token数量
总请求数 50 测试总共执行50个请求
吞吐量 1.60 req/s 每秒完成1.6个完整请求
总token速度 1232.03 tok/s 每秒处理1232个token
输出速度 410.68 tok/s 每秒输出大概205个中文字
耗时 31秒 一共31秒完成50个请求
扩展效率 51.6% 相比单线程的提升比例

--max-num-seqs=64时候,汇总了之前所有结果:

指标 1线程 8线程 12线程 16线程 20线程 32线程 64线程 增长趋势
吞吐量 0.10 0.62 0.81 0.99 1.21 1.60 1.52 📉 下降
总token速度 74.61 472.98 624.91 758.56 932.34 1232.03 1165.62 📉 下降
输出速度 24.87 157.66 208.30 252.85 310.78 410.68 388.54 📉 下降
扩展效率 - 79.2% 69.8% 63.6% 60.5% 51.6% 23.8% 📉 暴跌

因此可看到L4这块显卡在22GB可用显存的场景下,运行qwen3-VL 8B模型的FP8量化版本,有着不错的并发能力,当32线程时候的输出效率最高,但是再提高到64线程,只能提升很少不能按预期增长,因此64线程不被建议。建议线程数在32效率最高。

我们将EC2机型从使用24GB显存的L4 GPU的g6.2xlarge更换为使用48GB显存的L40s GPU的g6e.2xlarge。G6e的L40s GPU使用32线程的测试,效果如下:

Processed prompts: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:12<00:00,  4.15it/s, est. speed input: 2127.26 toks/s, output: 1063.63 toks/s]
[rank0]:[W1129 06:04:03.381717598 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())
Throughput: 4.12 requests/s, 3165.21 total tokens/s, 1055.07 output tokens/s
Total num prompt tokens:  25600

G6e的L40s GPU使用64线程的测试效果如下:

Processed prompts: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:07<00:00,  6.44it/s, est. speed input: 3298.99 toks/s, output: 1649.49 toks/s]
[rank0]:[W1129 06:00:29.273824291 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())
Throughput: 6.36 requests/s, 4883.75 total tokens/s, 1627.92 output tokens/s
Total num prompt tokens:  25600
Total num output tokens:  12800
Processed prompts: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:07<00:00,  6.44it/s, est. speed input: 3297.59 toks/s, output: 1648.79 toks/s]
[rank0]:[W1129 07:15:44.201402217 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())
Throughput: 6.37 requests/s, 4890.88 total tokens/s, 1630.29 output tokens/s
Total num prompt tokens:  25600
Total num output tokens:  12800

由此可以看到,L40s的性能较上一代L4有相当大幅度的提升,在32线程下,output token从410.68 token/s涨到1055 token/s。在64线程下,output token从388.54 token/s涨到1627.92 token/s,体现出了相当好的性能正常,即L40s可承受64线程的压力。

继续将L40s环境的压力提升到96线程和128线程,但output tokens/s性能仅轻微浮动而没有提升,因此可看到48线程是最终比较理想的结果。

2、离线测试延迟

本文的测试使用24GB显存的L4 GPU的g6.2xlarge完成,然后更换为使用48GB显存的L40s GPU的g6e.2xlarge进行测试。首先确认后台服务已经关闭,vLLM已经退出,显存完全释放。

执行命令如下:

uv run vllm bench latency \
    --model /home/ubuntu/Qwen3-VL-8B-Thinking-FP8 \
    --gpu-memory-utilization 0.9 \
    --max-model-len 4096 \
    --max-num-seqs 32 \
    --input-len 512 \
    --output-len 256

测试完毕,返回结果如下:

Avg latency: 11.907208129666635 seconds
10% percentile latency: 11.855494655300095 seconds
25% percentile latency: 11.87158773074998 seconds
50% percentile latency: 11.905295175499987 seconds
75% percentile latency: 11.951438236249999 seconds
90% percentile latency: 11.96355620340007 seconds
99% percentile latency: 11.977924459430096 seconds

以上测试结果表示512token输入和256token输出的场景下,完成一次完整的交互需要12秒左右。这对于256 token而言,大概等于200汉字,折算1秒20字,因此时间上还是合理的。

再次修改--max-num-seqs为8,再做一次测试,可获取测试结果也是12秒左右。这说明生成token的能力限制是GPU算力,在8并发和32并发下都可以稳定输出。如果需要缩短这个生成时间,只能换算力更强的更高端GPU。

我们将EC2机型从使用24GB显存的L4 GPU的g6.2xlarge更换为使用48GB显存的L40s GPU的g6e.2xlarge,使用32并发运行同样的测试,效果如下:

Avg latency: 4.861004937666606 seconds
10% percentile latency: 4.853756664999674 seconds
25% percentile latency: 4.854472121999947 seconds
50% percentile latency: 4.8599380749999455 seconds
75% percentile latency: 4.86551012974985 seconds
90% percentile latency: 4.870080923500245 seconds
99% percentile latency: 4.877819421329982 seconds
[rank0]:[W1129 05:46:08.955282032 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())

可看到推理性能提升到了4.8秒,提升了近2.5倍。表明更换更高的GPU后,延迟显著降低,推理能力大幅提升。

离线测试完毕。

3、小结

以上测试结果可以看出,8B模型的FP8量化版本在24GB显存的L4上运行良好。如果要运行8B模型的FP16版本,则必须要使用48GB显存以上的L40s。如果要运行30B模型,则必须要使用48GB显存以上的L40s(并以FP8量化方式运行)。当并发达到一定程度后,瓶颈是在GPU本身的算力,更换更强力的GPU、更大显存的GPU,推理性能会显著提升。基于这个测试结果L40s GPU是L4 GPU的2倍,能很好的满足8B模型的FP8量化版本的运行。

六、参考文档

vLLM Github 官网:

https://github.com/vllm-project/vllm/


最后修改于 2025-11-30