跳转至

MiniMax-M3

1 简介

MiniMax-M3 是一个支持文本、图像和视频输入的多模态大语言模型。在昇腾上,它支持 BF16 和 W8A8 部署、思考模式、推理解析、工具调用解析以及多模态输入。

本文档涵盖支持的特性、环境和模型准备、单节点部署、多节点部署、思考与解析器配置、功能验证、精度评估以及故障排查。

本文档基于最新的 vLLM-Ascend 版本编写。该模型在 main 分支上受支持。

2 支持的特性

模型支持矩阵请参考支持的模型

特性配置说明请参考特性指南

3 前提条件

3.1 模型权重

MiniMax-M3 BF16 模型需要 16 × 64 GB NPU 芯片。下载模型权重。 我们还提供 W8A8 量化模型,至少需要 8 x 64G NPU 芯片。下载模型权重。 建议将模型权重放置在共享缓存目录中。

3.2 验证多节点通信(可选)

对于多节点部署,请按照验证多节点通信环境验证通信环境。

4 安装

4.1 Docker 镜像安装

您可以使用官方一体化 Docker 镜像。有关可用的镜像标签和已发布版本,请参考使用 Docker

  • 步骤 1:下载最新的 Docker 镜像

    docker pull quay.io/ascend/vllm-ascend:{tag}
    

  • 步骤 2:启动 Docker 容器

    # Set the vLLM Ascend image name.
    export IMAGE=quay.io/ascend/vllm-ascend:{tag}
    export NAME=minimax-m3-dev
    
    # Start the container with the variables defined above.
    # Update --device for your hardware (Atlas A3: /dev/davinci[0-15]; Atlas A2: /dev/davinci[0-7]).
    # If you use a Docker bridge network, open the ports required for multi-node communication in advance.
    docker run --rm \
    --name $NAME \
    --net=host \
    --shm-size=100g \
    --device /dev/davinci0 \
    --device /dev/davinci1 \
    --device /dev/davinci2 \
    --device /dev/davinci3 \
    --device /dev/davinci4 \
    --device /dev/davinci5 \
    --device /dev/davinci6 \
    --device /dev/davinci7 \
    --device /dev/davinci8 \
    --device /dev/davinci9 \
    --device /dev/davinci10 \
    --device /dev/davinci11 \
    --device /dev/davinci12 \
    --device /dev/davinci13 \
    --device /dev/davinci14 \
    --device /dev/davinci15 \
    --device /dev/davinci_manager \
    --device /dev/devmm_svm \
    --device /dev/hisi_hdc \
    -v /usr/local/dcmi:/usr/local/dcmi \
    -v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
    -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
    -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
    -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
    -v /etc/ascend_install.info:/etc/ascend_install.info \
    -v /root/.cache:/root/.cache \
    -it $IMAGE bash
    

  • 步骤 3:编译 Rust 前端

    cd /vllm-workspace/vllm
    
    # Install _rust_tool_parser for the Rust frontend.
    pip install setuptools-rust
    ./build_rust.sh
    

  • 步骤 4:安装验证:

启动容器后,运行以下命令验证安装:

docker ps | grep vllm-ascend-env

预期结果:容器以状态 Up 列出。您还可以在容器内验证 vllm-ascend 版本:

pip show vllm-ascend

预期结果:显示版本信息,与拉取的镜像版本一致。

5 在线服务部署

使用以下命令启动在线推理服务:

有关部署示例中使用的标准 vllm serve 参数的说明,请参考 vLLM Serving 参数文档。有关通过 --additional-config 传递的昇腾特定选项,请参考附加配置。有关昇腾特定的环境变量,请参考环境变量

5.1 单节点部署

单节点部署在同一节点内完成 Prefill 和 Decode。BF16 模型和量化模型都可以部署在 1 台 Atlas 800 A3(64GB × 16)上。量化模型可以部署在 1 台 Atlas 800 A2(64GB × 8)上。

export PYTORCH_NPU_ALLOC_CONF="expandable_segments:True"
export HCCL_OP_EXPANSION_MODE="AIV"
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD

vllm serve ${WEIGHT_PATH} \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --max-model-len 43008 \
  --tensor-parallel-size 16 \
  --enable-expert-parallel \
  --max-num-seqs 16 \
  --distributed_executor_backend "mp" \
  --gpu-memory-utilization 0.92 \
  --reasoning-parser minimax_m3 \
  --limit-mm-per-prompt '{"image":1}' \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --additional-config '{
      "enable_cpu_binding": true,
      "ascend_compilation_config": {
      "enable_static_kernel": true,
      "fuse_norm_quant": false
      },
      "multistream_overlap_shared_expert": true,
      "weight_nz_mode": 2,
      "enable_flashcomm1": true,
      "enable_reduce_sample": true
  }' \
  --port 11223 > ${LOG_PATH} 2>&1 &
export PYTORCH_NPU_ALLOC_CONF="expandable_segments:True"
export HCCL_OP_EXPANSION_MODE="AIV"
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD

vllm serve ${WEIGHT_PATH} \
--served-model-name minimax-m3 \
--trust-remote-code \
--max-model-len 131072 \
--tensor-parallel-size 4 \
--data-parallel-size 4 --api_server_count 1 \
--max-num-batched-tokens 32768 \
--long-prefill-token-threshold 4096 \
--enable-expert-parallel \
--max-num-seqs 32 \
--distributed_executor_backend "mp" \
--gpu-memory-utilization 0.92 \
--reasoning-parser minimax_m3 \
--limit-mm-per-prompt '{"image":1}' \
--speculative-config '{"model":"${EAGLE3_WEIGHT_PATH}", "method":"eagle3", "num_speculative_tokens":3}' \
--compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
--additional-config '{
    "enable_cpu_binding": true,
    "ascend_compilation_config": {
      "enable_static_kernel": true,
      "fuse_norm_quant": false
    },
    "multistream_overlap_shared_expert": true,
    "enable_shared_expert_dp": true,
    "weight_nz_mode": 2,
    "enable_flashcomm1": true,
    "enable_reduce_sample": true
}' \
--port 11223 > ${LOG_PATH} 2>&1 &

注意:在上述脚本中,max-num-seqs 设置为 16,表示调度器在单次迭代中可处理的最大序列数。请根据实际业务动态调整 max-num-seqs 参数。

对于纯文本部署,可以省略 --limit-mm-per-prompt。对于多模态部署,请根据实际请求形态配置此参数。例如,对于双图像请求,使用 --limit-mm-per-prompt '{"image":2}';对于单视频请求,使用 --limit-mm-per-prompt '{"video":1}'

5.2 多节点部署

在昇腾 A2 服务器上部署 BF16 模型至少需要两个节点。不建议在没有 prefill-decode 分离的情况下在 A3 服务器上进行多节点部署。请根据实际环境更新 WEIGHT_PATHEAGLE3_WEIGHT_PATHLOG_PATHlocal_ipnode0_ipIFNAME

在节点 0 上运行以下命令:

local_ip="${NODE0_IP}"
node0_ip="${NODE0_IP}"

export HCCL_IF_IP=$local_ip
export IFNAME="${NETWORK_INTERFACE}"
export GLOO_SOCKET_IFNAME="$IFNAME"
export TP_SOCKET_IFNAME="$IFNAME"
export HCCL_SOCKET_IFNAME="$IFNAME"
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export VLLM_ENGINE_READY_TIMEOUT_S=3600
export HCCL_CONNECT_TIMEOUT=7200
export ASCEND_CONNECT_TIMEOUT=10000
export ASCEND_TRANSFER_TIMEOUT=10000
export VLLM_RPC_TIMEOUT=1800000
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export HCCL_OP_EXPANSION_MODE="AIV"
export TASK_QUEUE_ENABLE=1
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD

vllm serve ${WEIGHT_PATH} \
  --host 0.0.0.0 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --max-model-len 40960 \
  --tensor-parallel-size 8 \
  --enable-expert-parallel \
  --max-num-seqs 8 \
  --data-parallel-size 2 \
  --data-parallel-size-local 1 \
  --data-parallel-start-rank 0 \
  --data-parallel-address $node0_ip \
  --distributed_executor_backend "mp" \
  --gpu-memory-utilization 0.94 \
  --reasoning-parser minimax_m3 \
  --limit-mm-per-prompt '{"image":1}' \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --additional-config '{"enable_cpu_binding":true, "ascend_compilation_config":{"fuse_norm_quant":false}, "multistream_overlap_shared_expert": true, "weight_nz_mode": 2}' \
  --port 11223 > ${LOG_PATH} 2>&1 &

在节点 1 上运行以下命令:

local_ip="${NODE1_IP}"
node0_ip="${NODE0_IP}"

export HCCL_IF_IP=$local_ip
export IFNAME="${NETWORK_INTERFACE}"
export GLOO_SOCKET_IFNAME="$IFNAME"
export TP_SOCKET_IFNAME="$IFNAME"
export HCCL_SOCKET_IFNAME="$IFNAME"
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export VLLM_ENGINE_READY_TIMEOUT_S=3600
export HCCL_CONNECT_TIMEOUT=7200
export ASCEND_CONNECT_TIMEOUT=10000
export ASCEND_TRANSFER_TIMEOUT=10000
export VLLM_RPC_TIMEOUT=1800000
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export HCCL_OP_EXPANSION_MODE="AIV"
export TASK_QUEUE_ENABLE=1
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD

vllm serve ${WEIGHT_PATH} \
  --host 0.0.0.0 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --headless \
  --max-model-len 40960 \
  --tensor-parallel-size 8 \
  --enable-expert-parallel \
  --max-num-seqs 8 \
  --data-parallel-size 2 \
  --data-parallel-size-local 1 \
  --data-parallel-start-rank 1 \
  --data-parallel-address $node0_ip \
  --distributed_executor_backend "mp" \
  --gpu-memory-utilization 0.94 \
  --reasoning-parser minimax_m3 \
  --limit-mm-per-prompt '{"image":1}' \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --additional-config '{"enable_cpu_binding":true, "ascend_compilation_config":{"fuse_norm_quant":false}, "multistream_overlap_shared_expert": true, "weight_nz_mode": 2}' \
  --port 11223 > ${LOG_PATH} 2>&1 &

在节点 0 上运行以下命令:

local_ip="${NODE0_IP}"
node0_ip="${NODE0_IP}"

export HCCL_IF_IP=$local_ip
export IFNAME="${NETWORK_INTERFACE}"
export GLOO_SOCKET_IFNAME="$IFNAME"
export TP_SOCKET_IFNAME="$IFNAME"
export HCCL_SOCKET_IFNAME="$IFNAME"
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export VLLM_ENGINE_READY_TIMEOUT_S=3600
export HCCL_CONNECT_TIMEOUT=7200
export ASCEND_CONNECT_TIMEOUT=10000
export ASCEND_TRANSFER_TIMEOUT=10000
export VLLM_RPC_TIMEOUT=1800000
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=30000
export PYTORCH_NPU_ALLOC_CONF="expandable_segments:True"
export HCCL_OP_EXPANSION_MODE="AIV"
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD

vllm serve ${WEIGHT_PATH} \
  --host 0.0.0.0 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --max-model-len 131072 \
  --tensor-parallel-size 8 \
  --enable-expert-parallel \
  --max-num-seqs 8 \
  --data-parallel-size 2 \
  --data-parallel-size-local 1 \
  --data-parallel-start-rank 0 \
  --data-parallel-address $node0_ip \
  --distributed_executor_backend "mp" \
  --gpu-memory-utilization 0.92 \
  --reasoning-parser minimax_m3 \
  --limit-mm-per-prompt '{"image":1}' \
  --speculative-config '{"model":"${EAGLE3_WEIGHT_PATH}", "method":"eagle3", "num_speculative_tokens":3}' \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --additional-config '{"enable_cpu_binding":true, "ascend_compilation_config":{"fuse_norm_quant":false}, "multistream_overlap_shared_expert": false, "weight_nz_mode": 2, "enable_flashcomm1": true}' \
  --port 11223 > ${LOG_PATH} 2>&1 &

在节点 1 上运行以下命令:

local_ip="${NODE1_IP}"
node0_ip="${NODE0_IP}"

export HCCL_IF_IP=$local_ip
export IFNAME="${NETWORK_INTERFACE}"
export GLOO_SOCKET_IFNAME="$IFNAME"
export TP_SOCKET_IFNAME="$IFNAME"
export HCCL_SOCKET_IFNAME="$IFNAME"
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export VLLM_ENGINE_READY_TIMEOUT_S=3600
export HCCL_CONNECT_TIMEOUT=7200
export ASCEND_CONNECT_TIMEOUT=10000
export ASCEND_TRANSFER_TIMEOUT=10000
export VLLM_RPC_TIMEOUT=1800000
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=30000
export PYTORCH_NPU_ALLOC_CONF="expandable_segments:True"
export HCCL_OP_EXPANSION_MODE="AIV"
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD

vllm serve ${WEIGHT_PATH} \
  --host 0.0.0.0 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --headless \
  --max-model-len 131072 \
  --tensor-parallel-size 8 \
  --enable-expert-parallel \
  --max-num-seqs 8 \
  --data-parallel-size 2 \
  --data-parallel-size-local 1 \
  --data-parallel-start-rank 1 \
  --data-parallel-address $node0_ip \
  --distributed_executor_backend "mp" \
  --gpu-memory-utilization 0.92 \
  --reasoning-parser minimax_m3 \
  --limit-mm-per-prompt '{"image":1}' \
  --speculative-config '{"model":"${EAGLE3_WEIGHT_PATH}", "method":"eagle3", "num_speculative_tokens":3}' \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --additional-config '{"enable_cpu_binding":true, "ascend_compilation_config":{"fuse_norm_quant":false}, "multistream_overlap_shared_expert": false, "weight_nz_mode": 2, "enable_flashcomm1": true}' \
  --port 11223 > ${LOG_PATH} 2>&1 &

5.3 多模态和 ViT DP(可选)

MiniMax-M3 在昇腾上支持图像和视频输入。上述部署示例将 --limit-mm-per-prompt '{"image":1}' 作为默认的多模态容量假设,因为其他推理参数是针对单图像路径调优的。

对于 ViT / 多模态编码器部分,支持数据并行执行,可以通过以下方式启用:

--mm-encoder-tp-mode data

默认部署示例中未启用此选项,因为它可能会增加每卡内存使用量。启用 ViT DP 时,请针对目标工作负载重新评估内存相关参数,例如 --max-model-len--max-num-seqs--gpu-memory-utilization

对于视频或图像-视频混合请求,请根据实际请求形态调整多模态限制,而不是盲目更改默认模板:

# one video
--limit-mm-per-prompt '{"video":1}'

# one image and one video
--limit-mm-per-prompt '{"image":1, "video":1}'

当在请求中使用本地媒体路径(例如 file:///path/to/video.mp4)时,请添加显式的白名单路径:

--allowed-local-media-path /

如果未指定采样的视频帧数,vLLM 将使用其默认的视频采样策略,默认采样 32 帧。对于快速功能冒烟测试,可以在请求或评估配置中设置较小的帧数,例如 8 或 16。对于基准测试运行,请遵循数据集协议。

对于 MiniMax-M3 服务,不应同时启用 FLASHCOMM1 和仅语言模型模式。FLASHCOMM1 通过 additional_config.enable_flashcomm1 启用,而仅语言模型模式通过 --language-model-only 启用。

# Enable FLASHCOMM1.
--additional-config '{"enable_flashcomm1": true}'

# Enable language-model-only mode.
--language-model-only

VLLM_ASCEND_ENABLE_FLASHCOMM1=1 保留用于兼容性,但更推荐使用 additional_config.enable_flashcomm1

6 思考与解析器配置

6.1 思考模式

MiniMax-M3 支持三种思考模式,通过 chat_template_kwargs 中的 thinking_mode 控制:

模式 行为 使用场景
enabled 模型在每次响应前进行思考,包括在工具结果之后 复杂推理、智能体
disabled 不进行思考;模型直接回答 对延迟敏感的轮次
adaptive 模型根据任务决定是否思考(未设置时的默认值) 一般用途

6.1.1 请求示例

禁用思考(curl):

curl http://{ip}:{port}/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m3",
    "messages": [{"role": "user", "content": "who are you?"}],
    "max_tokens": 100,
    "stream": false,
    "top_p": 0.95,
    "top_k": 40,
    "temperature": 1.0,
    "chat_template_kwargs": {"thinking_mode": "disabled"}
  }'

根据需要将 "thinking_mode" 更改为 "enabled""adaptive"。已弃用的 enable_thinking 参数(等同于 thinking_mode: "enabled")也受支持。

启用思考(Python SDK):

from openai import OpenAI

client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1")

response = client.chat.completions.create(
    model="minimax-m3",
    messages=[{"role": "user", "content": "Prove there are infinitely many primes."}],
    extra_body={"chat_template_kwargs": {"thinking_mode": "enabled"}},
)
msg = response.choices[0].message
print(getattr(msg, "reasoning", None))  # the <mm:think> block
print(msg.content)                       # the final answer

6.2 推理解析器

MiniMax-M3 推理解析器(--reasoning-parser minimax_m3)从模型输出中提取思考块 <mm:think>...</mm:think>,并将其作为 reasoning 字段暴露。其余文本作为 content 返回。

6.2.1 服务器配置

--reasoning-parser minimax_m3 标志启用 MiniMax-M3 推理解析器,该解析器使用 <mm:think>...</mm:think> 分隔符将模型输出拆分为推理和内容:

vllm serve ${WEIGHT_PATH} \
  --reasoning-parser minimax_m3 \
  ...

6.2.2 输出格式

MiniMax-M3 使用显式的思考分隔符:

<mm:think>reasoning process...</mm:think>final answer

6.2.3 解析器行为

  • thinking_mode="enabled":聊天模板在提示中预填充 <mm:think>。生成的文本从推理块内部开始,并在 </mm:think> 之后过渡到内容。
  • thinking_mode="disabled" 或默认:模型输出被视为纯内容。如果出现 <mm:think>,解析器将根据分隔符进行拆分。
  • 流式输出:推理和内容通过 DeltaMessage.reasoningDeltaMessage.content 逐 token 增量流式传输。
  • Token 计数<mm:think> 块内的推理 token 会被正确计数。

6.3 工具调用解析器

MiniMax-M3 使用命名空间分隔的 XML 格式进行工具调用。通过 --tool-parser minimax_m3 启用。

6.3.1 服务器配置

当同时指定 --reasoning-parser minimax_m3--tool-call-parser minimax_m3 时,解析器会自动协同工作,处理同时包含推理块和工具调用的响应:

vllm serve ${WEIGHT_PATH} \
  --reasoning-parser minimax_m3 \
  --enable-auto-tool-choice \
  --tool-call-parser minimax_m3 \
  ...

6.3.2 工具调用格式

每个结构标签前都有 ]<]minimax[>[ 命名空间标记:

]<]minimax[>[<tool_call>
]<]minimax[>[<invoke name="create_order">
]<]minimax[>[<user_id>42]<]minimax[>[</user_id>
]<]minimax[>[<shipping>
]<]minimax[>[<city>Singapore]<]minimax[>[</city>
]<]minimax[>[<zip>018956]<]minimax[>[</zip>
]<]minimax[>[</shipping>
]<]minimax[>[</invoke>
]<]minimax[>[</tool_call>

6.3.3 主要特性

  • 递归参数解析:支持嵌套对象和数组(例如,包含 city/zipshipping)。
  • 模式感知类型转换:根据函数的 JSON Schema 定义,字符串参数值会自动转换为正确的类型(整数、布尔值、对象、数组)。
  • 多次调用:单个 <tool_call> 块可以包含多个 <invoke> 块。
  • 流式输出:工具名称和参数片段在接收 <invoke> 块时增量流式传输。

6.3.4 请求示例(curl)

curl http://{ip}:{port}/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m3",
    "messages": [{"role": "user", "content": "What's the weather like in Shanghai?"}],
    "max_tokens": 300,
    "stream": false,
    "tool_choice": "auto",
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "Get current weather for a city",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "City or country name"
                        }
                    },
                    "required": ["location"],
                    "additionalProperties": false
                }
            }
        }
    ],
    "chat_template_kwargs": {"thinking_mode": "disabled"}
  }'

7 功能验证

7.1 文本

curl http://{ip}:{port}/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "model": "minimax-m3",
  "messages": [
    {
      "role": "user",
      "content": "Answer the following multiple choice question. The last line of your response should be of the following format: 'Answer: LETTER' (without quotes) where LETTER is one of ABCD. Think step by step before answering.\n\nA student regrets that he fell asleep during a lecture in electrochemistry, facing the following incomplete statement in a test:\nThermodynamically, oxygen is a …… oxidant in basic solutions. Kinetically, oxygen reacts …… in acidic solutions.\nWhich combination of weaker/stronger and faster/slower is correct?\n\nA) weaker – faster\nB) stronger – faster\nC) weaker - slower\nD) stronger – slower"
    }
  ],
  "max_tokens": 8000,
  "temperature": 1.0
}
EOF

预期结果:答案为 C。

7.2 单张图片

启动服务时启用图像输入,例如 --limit-mm-per-prompt '{"image":1}'。在客户端将 ${IMAGE_PATH} 替换为本地图像路径。

IMAGE_PATH=/path/to/image.jpg
IMAGE_BASE64="$(base64 -w 0 "${IMAGE_PATH}")"

curl http://{ip}:{port}/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "model": "minimax-m3",
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,${IMAGE_BASE64}"}},
        {"type": "text", "text": "Briefly describe this image."}
      ]
    }
  ],
  "max_tokens": 512,
  "temperature": 0
}
EOF

7.3 单个视频

启动服务时启用视频输入,例如 --limit-mm-per-prompt '{"video":1}'。如果请求使用 file:// 本地视频路径,还需添加 --allowed-local-media-path / 或更窄的允许目录。如果未指定 media_io_kwargs.video.num_frames,vLLM 默认采样 32 帧。

curl http://{ip}:{port}/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m3",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "video_url",
            "video_url": {
              "url": "file:///path/to/video.mp4"
            }
          },
          {
            "type": "text",
            "text": "Briefly describe the main content of this video."
          }
        ]
      }
    ],
    "max_tokens": 512,
    "temperature": 0
  }'

7.4 图像和视频混合请求

启动服务时同时启用图像和视频输入。对于以下请求,使用 --limit-mm-per-prompt '{"image":1,"video":1}'。如果请求使用 file:// 本地视频路径,还需添加 --allowed-local-media-path / 或更窄的允许目录。

IMAGE_BASE64="$(base64 -w 0 /path/to/image.jpg)"

curl http://{ip}:{port}/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "model": "minimax-m3",
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,${IMAGE_BASE64}"}},
        {"type": "video_url", "video_url": {"url": "file:///path/to/video.mp4"}},
        {"type": "text", "text": "Describe the image and video separately, and explain whether they are related."}
      ]
    }
  ],
  "max_tokens": 512,
  "temperature": 0
}
EOF

8 精度评估

8.1 使用 AISBench

详细说明请参阅 使用 AISBench 进行精度评估

8.2 文本评估

数据集 硬件 分数 max-model-len max-num-seqs max_out_len batch_size generation_kwargs
GSM8K GPU 96.72 65536 16 49152 16 temperature=1.0, top_p=0.95
GSM8K NPU 96.36 10240 16 9500 20 temperature=1.0, top_p=0.95
AIME2025 GPU 95@repeat4 - - - - -
AIME2025 NPU 93.3@repeat2 131072 32 65536 8 temperature=1.0, top_p=0.95
GPQA-Diamond GPU 92.42 81920 64 75776 8 temperature=0.6, top_p=0.95
GPQA-Diamond NPU 92.42 131072 32 65536 8 temperature=0.6, top_p=0.95

8.3 多模态评估

MiniMax-M3 多模态精度使用 AISBench 进行评估。ViT DP 路径是可选的,可通过在服务命令中添加 --mm-encoder-tp-mode data 来启用,但并非所有多模态精度测试都需要该路径。对于视频评估,如果请求或评估配置中未指定帧数,vLLM 默认采样 32 帧。

以下 Video-MME 结果是在 chunk1 和 chunk2 上测得的,并非完整数据集。

对于 Video-MME 评估,请运行启用视频输入的 vLLM OpenAI 兼容服务,并使用 AISBench 发送 Video-MME 请求。官方 AISBench 指南可能未将 Video-MME 列为内置示例,因此此处使用的关键 MiniMax-M3 设置如下:

  • 使用 --limit-mm-per-prompt '{"video":1}' 提供服务;
  • 不设置 media_io_kwargs.video.num_frames,以便 vLLM 使用默认的 32 个采样帧;
  • 使用 max-model-len=90112max_out_len=8192
  • 评估 Video-MME 的 chunk1 和 chunk2,而非完整数据集。

用于 Video-MME chunk1+chunk2 评估的 AISBench 命令如下:

ais_bench \
  --models vllm_api_general_chat \
  --datasets videomme_subset_1_2.py \
  --mode all \
  --dump-eval-details \
  --merge-ds

videomme_subset_1_2.py 是一个本地 AISBench 数据集配置,源自原始 Video-MME 配置(如 videomme_gen.py)。它将 path 指向根据本地可用的 chunk1/chunk2 视频从完整 Video-MME 元数据中筛选出的 parquet 文件,并将 video_path 指向提取的 chunk1/chunk2 .mp4 目录。这样既保持了评估的轻量性,又保留了标准的 Video-MME 请求和评分流程。

Dataset Modality Tool Hardware ViT DP max-model-len max_out_len Input Config generation_kwargs Score
TextVQA Image AISBench GPU disabled 65536 512 --limit-mm-per-prompt '{"image":1}' temperature=1.0, top_p=0.95 70.82
TextVQA Image AISBench NPU disabled 65536 512 --limit-mm-per-prompt '{"image":1}' temperature=1.0, top_p=0.95 72.75
Video-MME chunk1+chunk2 Video AISBench GPU - 90112 8192 --limit-mm-per-prompt '{"video":1}', default 32 frames temperature=1.0, top_p=0.95 73.41
Video-MME chunk1+chunk2 Video AISBench NPU - 90112 8192 --limit-mm-per-prompt '{"video":1}', default 32 frames temperature=1.0, top_p=0.95 74.21

9 性能调优

注意:以下配置在特定测试环境中经过验证,仅供参考。最佳配置取决于最大输入/输出长度、前缀缓存命中率、精度要求以及部署机器比例等因素。建议根据实际情况参考第 9.2 节进行调优。

9.1 推荐配置

推荐配置与第 5 章“在线服务部署”中指定的配置相同。

9.2 调优指南

9.2.1 通用调优参考

有关通用调优方法,请参阅公共性能调优文档

有关详细的功能描述,请参阅功能指南

10 常见问题

  • 问:如何重新安装 vLLM Ascend?

答:使用以下命令重新安装 vLLM Ascend,并使用当前 Python 环境中的依赖进行构建:

pip install -v --no-build-isolation -e . -i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com
  • 问:当未设置 media_io_kwargs.video.num_frames 时,视频请求变慢或超时该怎么办?

答:默认情况下,vLLM 在读取视频时会采样 32 帧。MiniMax-M3 每帧会产生大量视觉 token,因此 32 帧的视频会显著增加预填充计算量。如果请求变慢或超时,请显式将 media_io_kwargs.video.num_frames 设置为较小的值,例如 8 或 16 帧:

{
  "media_io_kwargs": {
    "video": {
      "num_frames": 8
    }
  }
}