一、背景
进入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