🎯 模块十:多模态与生产 Capstone(MUSA)

视觉-语言模型部署(Qwen3-VL / OCR 模型 on MUSA)

高级·⏱ 90分钟·📅 2026-11-14
本课说明(2026-11-14 新编)
适用范围:vLLM-MUSA v0.28.0(2026-11-03 发布,基于 vLLM 0.28.0、vllm-musa 0.1.28),MUSA Driver 5.2.0,MTT S5000。 本课依据官方文档与正式 Release 整理,未在真实 MUSA GPU 上实测;标注“官方”的命令与数值来自原文,标注“本站补充”的脚本由本站编写用于检查与教学,使用前请在自己的环境中验证。
关于 InternVL3
本课原定标题包含 InternVL3。截至本课编写时,vLLM-MUSA v0.28.0 Release 与 vLLM-MUSA v0.28.0 Qwen cookbook 中没有 InternVL3 的官方 recipe,因此 2026-11-14 起标题改为“视觉-语言模型部署(Qwen3-VL / OCR 模型 on MUSA)”。本课只讲有官方配置的 Qwen3-VL 与 OCR 模型,不提供 InternVL3 的部署参数;官方发布 InternVL3 配置后再评估补充。

一、官方支持矩阵

整理自 vLLM-MUSA v0.28.0 Release 与各模型 recipe:

模型精度硬件 / 并行最大上下文 / 并发序列
Qwen3-VL-8B-InstructBF16 / FP81× S5000,TP18,192 / 256
Qwen3-VL-30B-A3B-Instruct(MoE)BF16 / FP84× S5000,TP48,192 / 64
Qwen3-VL-32B-InstructBF16 / FP84× S5000,TP48,192 / 64
DeepSeek-OCR、MinerU2.5-Pro-2605-1.2B、OvisOCR2、PaddleOCR-VL-1.6BF161× S5000,TP1见各 OCR recipe

本课后文的启动参数与差异表均取自 BF16 recipe。8B 与 30B-A3B 的 FP8 recipe 与 BF16 基本一致;32B 的 FP8 recipe 差异较大(例如 --gpu-memory-utilization 0.90、--generation-config vllm、FULL_AND_PIECEWISE 图模式与不同的捕获尺寸),请以 Qwen3-VL-32B-Instruct-FP8 recipe 原文为准。

所有 Qwen3-VL recipe 均关闭推测解码。这些配置按约 4K 输入 / 1K 输出的负载调优,官方要求整体照搬,换负载时重新调参;FP8、MTP 与图捕获路径对模型版本敏感,recipe 中固定了 ModelScope 上的权重提交号。

二、准备环境

  1. 宿主机按第 2 模块装好 Driver 5.2.0,并配置 MUSA 容器运行时(第 2 模块第 4 课)。
  2. 拉取发布镜像(vLLM-MUSA v0.28.0 docker/README):
bash
export VLLM_MUSA_IMAGE=registry.mthreads.com/mcconline/inference/vllm/vllm-openai:v0.28.0
docker pull "${VLLM_MUSA_IMAGE}"
镜像版本提示
官方说明:仓库镜像标签与代码分支独立发布,可能带有不同的依赖版本。做性能测试前先在容器内核对包版本;需要与分支完全一致时,用 docker/build_image.sh 从源码构建。另外,默认分支 v0.32.0-dev 的 README 提到了 v0.32.0 镜像,但 GitHub 上还没有对应的 Release / tag;本课仍以有 Release 的 v0.28.0 为准。
bash
pip install modelscope
modelscope download --model Qwen/Qwen3-VL-8B-Instruct --local_dir /models/Qwen3-VL-8B-Instruct
# 严格复现时,按 recipe 中记录的提交号下载对应版本

三、启动 Qwen3-VL-8B-Instruct(单卡)

以下命令与 Qwen3-VL-8B-Instruct recipe 一致,在已安装 vllm-musa 的环境(如容器内的 shell)中执行:

bash
export VLLM_PLUGINS=musa,musa_custom_ops
export VLLM_WORKER_MULTIPROC_METHOD=spawn
export PYTHONUNBUFFERED=1
export VLLM_MUSA_QWEN3_VL_VISION_CUDAGRAPH=1
export SAFETENSORS_FAST_GPU=1

CAPTURE_SIZES="$(seq -s, 1 256)"

vllm serve /models/Qwen3-VL-8B-Instruct \
  --trust-remote-code \
  --tensor-parallel-size 1 \
  --served-model-name Qwen3-VL-8B-Instruct \
  --max-model-len 8192 \
  --max-num-seqs 256 \
  --max-num-batched-tokens 8192 \
  --gpu-memory-utilization 0.9 \
  --mamba-ssm-cache-dtype float32 \
  --mm-processor-cache-gb 0 \
  --limit-mm-per-prompt '{"image":1,"video":0}' \
  --no-enable-prefix-caching \
  --no-enable-chunked-prefill \
  --attention-backend FLASH_ATTN \
  --mm-encoder-attn-backend FLASH_ATTN \
  --compilation-config "{\"mode\":\"NONE\",\"cudagraph_mode\":\"FULL_DECODE_ONLY\",\"cudagraph_capture_sizes\":[${CAPTURE_SIZES}]}"

用 Docker 直接启动时,发布镜像的入口是 vllm serve,把模型路径和参数接在镜像名后面,环境变量用 --env 传入(本站整理,基于 docker/README 的示例形式):

bash
MODEL_PATH=/models/Qwen3-VL-8B-Instruct
CAPTURE_SIZES="$(seq -s, 1 256)"
docker run --rm --privileged --ipc=host --network=host \
  --env MUSA_VISIBLE_DEVICES=0 --env MTHREADS_VISIBLE_DEVICES=0 \
  --env VLLM_PLUGINS=musa,musa_custom_ops \
  --env VLLM_WORKER_MULTIPROC_METHOD=spawn \
  --env VLLM_MUSA_QWEN3_VL_VISION_CUDAGRAPH=1 \
  --env SAFETENSORS_FAST_GPU=1 \
  -v "${MODEL_PATH}:${MODEL_PATH}:ro" \
  "${VLLM_MUSA_IMAGE}" "${MODEL_PATH}" --host 0.0.0.0 \
  --trust-remote-code --tensor-parallel-size 1 \
  --served-model-name Qwen3-VL-8B-Instruct \
  --max-model-len 8192 --max-num-seqs 256 --max-num-batched-tokens 8192 \
  --gpu-memory-utilization 0.9 --mamba-ssm-cache-dtype float32 \
  --mm-processor-cache-gb 0 --limit-mm-per-prompt '{"image":1,"video":0}' \
  --no-enable-prefix-caching --no-enable-chunked-prefill \
  --attention-backend FLASH_ATTN --mm-encoder-attn-backend FLASH_ATTN \
  --compilation-config "{\"mode\":\"NONE\",\"cudagraph_mode\":\"FULL_DECODE_ONLY\",\"cudagraph_capture_sizes\":[${CAPTURE_SIZES}]}"
关于 --privileged
官方说明 --privileged 只用于隔离的测试主机。生产环境请改用平台的最小权限 MUSA 设备策略;可见设备变量只限定 vLLM 看到的逻辑设备,不会降低 Docker 权限。对 vLLM-MUSA 来说,MUSA_VISIBLE_DEVICES 是决定可见设备的变量。

关键参数说明

参数作用
--limit-mm-per-prompt '{"image":1,"video":0}'每个请求最多 1 张图片、0 个视频(8B 与 30B-A3B recipe 显式设置)
--mm-encoder-attn-backend FLASH_ATTN视觉编码器走 FlashAttention(v0.28.0 新增的 Qwen3-VL 路由)
VLLM_MUSA_QWEN3_VL_VISION_CUDAGRAPH=1可选的视觉模块图捕获(8B recipe 中开启)
--mm-processor-cache-gb 0关闭多模态预处理缓存,不影响视觉编码器和图捕获
--no-enable-prefix-caching / --no-enable-chunked-prefill8B / 30B-A3B recipe 中都关闭
--gpu-memory-utilization 0.98B 为 0.9;30B-A3B 与 32B 为 0.85

四、30B-A3B 与 32B(4 卡 TP4)的差异

项目Qwen3-VL-30B-A3B-InstructQwen3-VL-32B-Instruct
--tensor-parallel-size44
--max-num-seqs6464
--gpu-memory-utilization0.850.85
Chunked prefill关闭开启(--enable-chunked-prefill)
注意力后端 / 编译配置FLASH_ATTN;图捕获尺寸 1–32 与 64recipe 未显式指定
--max-num-batched-tokens8192recipe 未设置
--limit-mm-per-prompt{"image":1,"video":0}recipe 未设置

请分别按两份 recipe 原样复制,不要把 8B 的参数套到大模型上。

五、发送图片请求

服务提供 OpenAI 兼容的 chat completions 接口。下面的写法参考官方 PaddleOCR-VL recipe 的客户端示例,改为调用 Qwen3-VL(本站整理):

python
import base64, mimetypes
from pathlib import Path
from openai import OpenAI

image_path = Path("demo.jpg")
mime = mimetypes.guess_type(image_path.name)[0] or "image/jpeg"
data = base64.b64encode(image_path.read_bytes()).decode("ascii")

client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1", timeout=600)
resp = client.chat.completions.create(
    model="Qwen3-VL-8B-Instruct",
    messages=[{
        "role": "user",
        "content": [
            {"type": "image_url", "image_url": {"url": f"data:{mime};base64,{data}"}},
            {"type": "text", "text": "请描述这张图片,并列出图中的文字。"},
        ],
    }],
    temperature=0.0,
    max_tokens=512,
)
print(resp.choices[0].message.content)

六、OCR 类模型

vLLM-MUSA v0.28.0 OCR cookbook 提供四个单卡 BF16 配置。以 PaddleOCR-VL-1.6 recipe 为例:

bash
export MODEL_PATH="<MODEL_PATH>"
export VLLM_WORKER_MULTIPROC_METHOD=spawn
vllm serve "${MODEL_PATH}" \
  --trust-remote-code --tensor-parallel-size 1 \
  --served-model-name PaddleOCR-VL-1.6-0.9B \
  --host 0.0.0.0 --port 8000 --dtype bfloat16 \
  --max-model-len 8192 --max-num-seqs 512 --max-num-batched-tokens 16384 \
  --gpu-memory-utilization 0.80 --no-enable-prefix-caching --mm-processor-cache-gb 0
  • 任务通过提示词选择:OCR:、Table Recognition:、Formula Recognition:、Chart Recognition:、Spotting:、Seal Recognition:。
  • 该 recipe 要求保持默认编译与图捕获设置,不要添加 --enforce-eager;ViT 路径会自动选择 FLASH_ATTN。
  • 版面分析与页面拼装由客户端负责。
  • 该 recipe 的前置条件写的是“vLLM-MUSA v0.28.0-dev”,与发布镜像 v0.28.0 不完全对应,使用发布镜像时请先核对包版本。
  • MinerU2.5-Pro 需要在推理引擎与客户端两侧都安装 mineru-vl-utils。

七、验证

bash
# 1. 服务是否就绪
curl -s http://localhost:8000/v1/models
# 2. 单图语义检查:用答案明确的图片(如含固定文字的截图)对比输出
# 3. 小并发冒烟:并发发送 8–16 个请求,观察是否出现超时或显存错误

正式压测请参照 recipe 中的负载形态(2K/1K 到 4K/1.5K、每组 500 条),并记录驱动、镜像与权重提交号,方便对比。

八、已知限制

  • 8B 与 30B-A3B recipe 显式限制为每请求 1 张图、0 个视频;32B recipe 未设该限制,但调优负载同样以单图为主。官方未给出视频输入的调优参数。
  • 没有 InternVL3 的官方配置。
  • FP8 与图捕获路径对模型版本敏感,请固定权重版本。
  • Recipe 以约 4K 输入 / 1K 输出调优,长图文混合场景需要重新调参。

九、回滚

服务无状态,回滚只需换回上一版镜像标签和对应的启动参数。升级前保留旧的启动脚本与镜像,并记录 pip list 中 vllm 与 vllm-musa 的版本。

参考来源