跳转至

MiniMax-M3

1 简介

Note

本文档中提到的“PD 分离部署”仅作为示例/参考设计,用于演示以分离模式部署 Prefill 和 Decode 的基本流程。不建议在生产环境中使用。

对于客户侧的生产环境,推荐使用 MindIE Motor + vLLM-Ascend。

MiniMax-M3 is a multimodal large language model that supports text, image, and video inputs. On Ascend, it supports BF16 and W8A8 on A2/A3, Prefill-Decode disaggregation on Atlas 800I A3 (W8A8) and 950DT products (MXFP8), thinking mode, reasoning parsing, tool-call parsing, and multimodal inputs.

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

This document is written based on the vLLM Ascend main branch. This model is supported on this branch.

2 支持的特性

请参阅支持特性列表了解模型支持矩阵。

请参阅特性指南获取特性配置说明。

3 前提条件

3.1 模型权重

权重版本 硬件要求 下载链接 说明
MiniMax-M3 (BF16) 2 Atlas 800I A3 nodes (64GB × 16 per node) ModelScope /
MiniMax-M3-w8a8 (W8A8) 1 Atlas 800I A3 node (64GB × 16) or 1 Atlas 800 A2 node (64GB × 8) ModelScope /
MiniMax-M3-MXFP8 (MXFP8) 1 个 950DT 节点(96GB × 8) ModelScope 支持 W8A8C8 配置:C8 为动态 KV-cache 量化。在服务命令中设置 --kv-cache-dtype fp8 以启用 FP8 KV cache。
MiniMax-M3-EAGLE3-GQA / ModelScope 用于 EAGLE3 投机解码的草稿模型;使用分组查询注意力(GQA)。

建议将模型权重放置在共享缓存目录中。

Record the downloaded model directories for use in the deployment commands. Set WEIGHT_PATH (or MODEL_PATH in the PD templates) to the target-weight directory, for example /models/MiniMax-M3-w8a8, and EAGLE3_WEIGHT_PATH to the downloaded EAGLE3 draft directory when speculative decoding is enabled. Replace the weight-path placeholders below with these actual paths and ensure they are accessible inside the container. LOG_PATH is a writable log-file path; the single-node examples use files in the current directory.

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

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

4 安装

4.1 Docker 镜像安装

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

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

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

步骤 2: 启动 Docker 容器

根据您的硬件平台选择 docker run 命令:

# 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.
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
# Set the vLLM Ascend image name.
export IMAGE=quay.io/ascend/vllm-ascend:{tag}
export NAME=minimax-m3-dev

# 950DT products have 8 NPUs and use Device UB.
docker run --rm \
--name $NAME \
--net=host \
--privileged=true \
--shm-size=60g \
--device /dev/davinci_manager \
--device /dev/hisi_hdc \
--device /dev/ummu \
--device /dev/uburma \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci4 \
--device /dev/davinci5 \
--device /dev/davinci6 \
--device /dev/davinci7 \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /etc/hixlep:/etc/hixlep \
-v /etc/hccn.conf:/etc/hccn.conf \
-v /var/log/npu/:/usr/slog \
-v /root/.cache:/root/.cache \
-it $IMAGE bash

请根据您的环境调整数据卷挂载(例如模型权重和数据集)。在 950DT 产品上,请勿添加主机上不存在的 Atlas A3 挂载,例如 /dev/devmm_svm。

In another terminal on the same host, check the container status (replace minimax-m3-dev if you changed NAME):

docker ps --filter name=minimax-m3-dev

Expected result: The container is listed with status Up. In the original container terminal, verify the vLLM Ascend version:

pip show vllm-ascend

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

5 在线服务部署

Start the online serving service with the deployment commands in the following sections. Select the tab for your hardware platform and weight format.

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

--speculative-config 示例使用单引号 JSON。'"${EAGLE3_WEIGHT_PATH}"' 片段会在该 JSON 中展开草稿权重路径。

5.1 单节点部署

单节点部署在同一节点内完成 Prefill 和 Decode。MiniMax-M3(BF16)模型可部署在 1 个 Atlas 800I A3(64GB × 16)上,但 A3 系列上的 BF16 推荐双节点部署;不建议单节点部署。MiniMax-M3-w8a8(W8A8)量化模型推荐单节点部署在 1 个 Atlas 800I A3(64GB × 16)或 1 个 Atlas 800 A2(64GB × 8)上。MiniMax-M3-MXFP8(MXFP8)量化模型可部署在 1 个 Ascend 950DT系列产品(96GB × 8)上。

Note

--quantization 参数仅量化权重需要,且必须与权重变体匹配:

  • BF16 权重(MiniMax-M3):不要添加 --quantization;模型将以浮点方式加载。
  • W8A8 权重(MiniMax-M3-w8a8,ModelSlim 格式):使用 --quantization ascend。vLLM Ascend 可以从检查点文件中自动检测到这一点,但为了清晰起见,建议显式指定。
  • MXFP8 权重(MiniMax-M3-MXFP8):使用 --quantization mxfp8。

添加错误的 --quantization 值(或对 BF16 权重添加该参数)会导致检查点被误读并加载失败。

WEIGHT_PATH="<MINIMAX_M3_BF16_PATH>"
LOG_PATH="./minimax-m3-bf16.log"

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_OP_EXPANSION_MODE="AIV"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

vllm serve "${WEIGHT_PATH}" \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --host 0.0.0.0 \
  --port 11223 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --max-model-len 133000 \
  --tensor-parallel-size 8 \
  --data-parallel-size 2 \
  --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,"video":0}' \
  --enable-chunked-prefill \
  --enable-prefix-caching \
  --additional-config '{
      "enable_cpu_binding": true,
      "enable_flashcomm1": true,
      "ascend_compilation_config": {
      "fuse_norm_quant": false
      },
      "multistream_overlap_shared_expert": true,
      "weight_nz_mode": 2
  }' > "${LOG_PATH}" 2>&1 &
WEIGHT_PATH="<MINIMAX_M3_W8A8_PATH>"
EAGLE3_WEIGHT_PATH="<MINIMAX_M3_EAGLE3_GQA_PATH>"
LOG_PATH="./minimax-m3-w8a8.log"

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_OP_EXPANSION_MODE="AIV"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

vllm serve "${WEIGHT_PATH}" \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --host 0.0.0.0 \
  --port 11223 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --quantization ascend \
  --max-model-len 133000 \
  --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,"video":0}' \
  --enable-chunked-prefill \
  --enable-prefix-caching \
  --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3}' \
  --additional-config '{
      "enable_cpu_binding": true,
      "enable_flashcomm1": true,
      "ascend_compilation_config": {
        "fuse_norm_quant": false
          },
      "multistream_overlap_shared_expert": true,
      "enable_shared_expert_dp": true,
      "weight_nz_mode": 2
  }' > "${LOG_PATH}" 2>&1 &
WEIGHT_PATH="<MINIMAX_M3_MXFP8_PATH>"
EAGLE3_WEIGHT_PATH="<MINIMAX_M3_EAGLE3_GQA_PATH>"
LOG_PATH="./minimax-m3-mxfp8.log"

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=512
export HCCL_OP_EXPANSION_MODE="AIV"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

vllm serve "${WEIGHT_PATH}" \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --host 0.0.0.0 \
  --port 11223 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --distributed-executor-backend mp \
  --tensor-parallel-size 4 \
  --data-parallel-size 2 \
  --enable-expert-parallel \
  --dtype bfloat16 \
  --quantization mxfp8 \
  --max-model-len 133000 \
  --max-num-batched-tokens 16384 \
  --kv-cache-dtype fp8 \
  --max-num-seqs 500 \
  --enable-chunked-prefill \
  --enable-prefix-caching \
  --async-scheduling \
  --reasoning-parser minimax_m3 \
  --limit-mm-per-prompt '{"image":1,"video":0}' \
  --gpu-memory-utilization 0.92 \
  --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_qknorm_rope":false,"fuse_norm_quant":false},"multistream_overlap_shared_expert":true,"enable_shared_expert_dp":true,"enable_flashcomm1":true}' \
  --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3, "kv_cache_dtype":"bfloat16"}' \
  --safetensors-load-strategy prefetch > "${LOG_PATH}" 2>&1 &

Note: In the scripts above, max-num-seqs limits the number of sequences scheduled in one iteration. Tune this limit using the expected input/output lengths and request concurrency, and measure throughput and latency after each change. Increase it only when additional sequences fit in the available KV cache; a higher limit does not guarantee higher throughput and can increase latency or KV-cache preemption.

Text requests do not require --limit-mm-per-prompt, but omitting this flag does not disable multimodal support or its memory planning. For a text-only service that disables multimodal inputs, use --language-model-only, as described in Section 5.5. For multimodal deployment, configure the limits according to the actual request shape. For example, use --limit-mm-per-prompt '{"image":2,"video":0}' for two-image requests, and use --limit-mm-per-prompt '{"image":0,"video":1}' for one-video requests.

5.2 多节点部署

Deploying the BF16 model on Ascend A2 servers requires at least two nodes. Multi-node deployment on A3 servers without prefill–decode disaggregation is not recommended. Update WEIGHT_PATH, EAGLE3_WEIGHT_PATH, LOG_PATH, local_ip, node0_ip, and NETWORK_INTERFACE based on the actual environment. Set NETWORK_INTERFACE to the local communication NIC name (for example, eth0); HCCL and Gloo use this value directly.

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

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

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_IF_IP=$local_ip
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME="${NETWORK_INTERFACE}"
export GLOO_SOCKET_IFNAME="${NETWORK_INTERFACE}"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

vllm serve ${WEIGHT_PATH} \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --host 0.0.0.0 \
  --port 11223 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --max-model-len 133000 \
  --tensor-parallel-size 8 \
  --enable-expert-parallel \
  --max-num-seqs 32 \
  --data-parallel-size 4 \
  --data-parallel-size-local 2 \
  --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,"video":0}' \
  --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3}' \
  --enable-chunked-prefill \
  --enable-prefix-caching \
  --additional-config '{"enable_cpu_binding":true, "ascend_compilation_config":{"fuse_norm_quant":false}, "enable_shared_expert_dp":true,"multistream_overlap_shared_expert": true, "weight_nz_mode": 2,"enable_flashcomm1":true}' > ${LOG_PATH} 2>&1 &

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

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

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_IF_IP=$local_ip
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME="${NETWORK_INTERFACE}"
export GLOO_SOCKET_IFNAME="${NETWORK_INTERFACE}"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

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

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

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

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_IF_IP=$local_ip
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME="${NETWORK_INTERFACE}"
export GLOO_SOCKET_IFNAME="${NETWORK_INTERFACE}"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

vllm serve ${WEIGHT_PATH} \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --host 0.0.0.0 \
  --port 11223 \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --quantization ascend \
  --max-model-len 133000 \
  --tensor-parallel-size 4 \
  --enable-expert-parallel \
  --max-num-seqs 32 \
  --data-parallel-size 8 \
  --data-parallel-size-local 4 \
  --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,"video":0}' \
  --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3}' \
  --enable-chunked-prefill \
  --enable-prefix-caching \
  --additional-config '{"enable_cpu_binding":true, "ascend_compilation_config":{"fuse_norm_quant":false}, "enable_shared_expert_dp":true,"multistream_overlap_shared_expert": true, "weight_nz_mode": 2,"enable_flashcomm1":true}' > ${LOG_PATH} 2>&1 &

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

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

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_IF_IP=$local_ip
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME="${NETWORK_INTERFACE}"
export GLOO_SOCKET_IFNAME="${NETWORK_INTERFACE}"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

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

Start both nodes before waiting for initialization to complete, and inspect the log file configured by LOG_PATH on each node for errors. Node 1 uses --headless and does not expose an HTTP API. After node 0 logs Application startup complete, run the following checks inside the node 0 container:

curl --noproxy '*' -fsS -i http://127.0.0.1:11223/health
curl --noproxy '*' -fsS http://127.0.0.1:11223/v1/models

Expected result: /health returns HTTP 200, and /v1/models lists minimax-m3. If startup fails or either check fails, inspect both nodes' logs before sending inference requests.

5.3 低于 1M Token 上下文部署

以下 PD 示例涵盖低于 1M token 的上下文,并使用 --max-model-len 133000。可选择普通 PD 或添加具有相同服务拓扑的 KV cache 池。关于单独的 1M 上下文配置,请参见第 5.4 节。

5.3.1 Prefill-Decode 分离部署

本节将 MiniMax-M3 PD 分离部署为 1P1D:在 Atlas 800I A3 上跨三个节点(一个 Prefill 节点和两个 Decode 节点);在 950DT 上运行于两个节点,下面的 950DT 示例还描述了将相同的 1P1D 部署扩展到三个节点。此处 1P1D 表示一个 Prefill 服务组和一个 Decode 服务组。Decode 组可跨两台机器,同时仍为一个全局 DP 组。

PD 分离将 Prefill 和 Decode 分离到不同的服务组中。Prefill 节点处理大型提示块,Decode 节点负责 token 生成,代理在两者之间转发请求。使用 Mooncake 进行 KV 缓存传输。通用 PD 分离工作流程请参考 Mooncake。

启动模式为:在每个节点上准备 launch_online_dp.py 和特定角色的 run_dp_template.sh,然后在每个引擎打印 Application startup complete 后启动负载均衡代理。下面的启动器在仓库示例的基础上扩展了 --pp-size:在 A3 上,Prefill 使用 DP=1、TP=4 和流水线并行(PP=4),将 60 个 transformer 层按 16,15,15,14 拆分;而 950DT 产品的 MXFP8 启动在两个角色上均使用 PP=1 和 DP=2。每个 DP rank 占用 tp_size * pp_size 个 NPU。

常见问题提示: 对于 KV 传输超时或 Mooncake 连接错误等 PD 分离问题,请参考公共 FAQ。对于 MiniMax 特有问题,请参考第 10 章 FAQ。

开始之前,请在每个节点上准备脚本 launch_online_dp.py:

import argparse
import multiprocessing
import os
import subprocess
import sys


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--dp-size",
        type=int,
        required=True,
        help="Data parallel size."
    )
    parser.add_argument(
        "--tp-size",
        type=int,
        default=1,
        help="Tensor parallel size."
    )
    parser.add_argument(
        "--pp-size",
        type=int,
        default=1,
        help="Pipeline parallel size."
    )
    parser.add_argument(
        "--dp-size-local",
        type=int,
        default=-1,
        help="Local data parallel size."
    )
    parser.add_argument(
        "--dp-rank-start",
        type=int,
        default=0,
        help="Starting rank for data parallel."
    )
    parser.add_argument(
        "--dp-address",
        type=str,
        required=True,
        help="IP address for data parallel master node."
    )
    parser.add_argument(
        "--dp-rpc-port",
        type=str,
        default="12321",
        help="Port for data parallel master node."
    )
    parser.add_argument(
        "--vllm-start-port",
        type=int,
        default=8000,
        help="Starting port for the engine."
    )
    return parser.parse_args()


args = parse_args()
dp_size = args.dp_size
tp_size = args.tp_size
pp_size = args.pp_size
dp_size_local = args.dp_size_local
if dp_size_local == -1:
    dp_size_local = dp_size
dp_rank_start = args.dp_rank_start
dp_address = args.dp_address
dp_rpc_port = args.dp_rpc_port
vllm_start_port = args.vllm_start_port
gpus_per_dp_rank = tp_size * pp_size


def run_command(visible_devices, dp_rank, vllm_engine_port):
    env = os.environ.copy()
    env.update(
        VISIBLE_DEVICES=visible_devices,
        SERVICE_PORT=str(vllm_engine_port),
        DP_SIZE=str(dp_size),
        DP_RANK=str(dp_rank),
        DP_ADDRESS=dp_address,
        DP_RPC_PORT=dp_rpc_port,
        TP_SIZE=str(tp_size),
        PP_SIZE=str(pp_size),
    )
    subprocess.run(["bash", "./run_dp_template.sh"], env=env, check=True)


if __name__ == "__main__":
    template_path = "./run_dp_template.sh"
    if not os.path.exists(template_path):
        print(f"Template file {template_path} does not exist.")
        sys.exit(1)

    processes = []
    for i in range(dp_size_local):
        dp_rank = dp_rank_start + i
        vllm_engine_port = vllm_start_port + i
        visible_devices = ",".join(
            str(x) for x in range(i * gpus_per_dp_rank, (i + 1) * gpus_per_dp_rank)
        )
        process = multiprocessing.Process(
            target=run_command,
            args=(visible_devices, dp_rank, vllm_engine_port),
        )
        processes.append(process)
        process.start()

    for process in processes:
        process.join()

模板使用命名环境变量而非位置 shell 参数。请替换每个模板中的 <...> IP、网卡和权重路径占位符,或在启动前在 shell 中导出这些变量。下面的数值默认值描述单个引擎;launch_online_dp.py 在启动所有本地引擎时会覆盖每个 rank 的值。您无需为每个 rank 计算不同的端口或设备列表。使用启动器时,请编辑节点特定启动块中的 export DP_SIZE=...、export TP_SIZE=...、export PP_SIZE=... 及其他值;这些值优先于模板中的 ${VARIABLE:-default} 值。

变量 含义 设置方式
LOCAL_IP 当前节点的通信 IP 在每个节点上分别填写 <CURRENT_NODE_IP>。
NIC_NAME 与 LOCAL_IP 关联的网卡 填写 <CURRENT_NODE_NIC>。
MODEL_PATH / EAGLE3_WEIGHT_PATH 目标 / 草稿权重目录 在每个节点上使用相同的挂载路径。
DP_SIZE / TP_SIZE / PP_SIZE 全局 DP 大小 / 单个 rank 内的 TP 和 PP 大小 数值在下面的每个启动命令中导出。
LOCAL_DP_SIZE 当前节点上的引擎数量 作为 --dp-size-local 传递给启动器。
DP_RANK_START 此节点上的第一个全局 DP rank 作为 --dp-rank-start 传递;两个 Decode 节点之间不同。
DP_ADDRESS 此角色的 DP 协调器 IP P 使用 Prefill 的 IP;每个 D 节点使用第一个 Decode 节点的 IP。
DP_RPC_PORT DP 协调器端口 同一角色中的所有节点使用相同的值。
SERVICE_PORT 启动 shell 中的第一个本地 API 端口 启动器为每个引擎加上本地 rank 索引。
DP_RANK 引擎的全局 DP rank 启动器设置 DP_RANK_START + local_rank。
VISIBLE_DEVICES 单个引擎的 NPU ID 启动器为每个本地 rank 分配 TP_SIZE × PP_SIZE 个连续 ID。

显式指定 --data-parallel-rank 可启用外部 DP 负载均衡,vLLM 要求此时 DP_SIZE > 1。下面的模板根据实际的 DP_SIZE 构建 DP_RANK_ARGS:DP1 时省略 rank 参数,DP 大于 1 时传入启动器分配的 rank。请保留 DP_RANK 供各 rank 的连接器端口使用,DP1 时也是如此。

将 Prefill 模板保存为 P 节点上的 run_dp_template.sh,并将 Decode 模板以该名称保存到每个 D 节点上。从包含匹配模板的目录运行启动器。如果使用共享文件系统,请将 P 和 D 模板放在不同目录中。更改 TP/DP/PP 还需要更新两个角色的 --kv-transfer-config 中匹配的 prefill 和 decode 布局。

以下数值与第 5.3.1 节和第 5.3.2 节中的配置相匹配。P/D 节点数指机器数量;DP_SIZE 是该角色的全局 rank 数量。

章节 / 平台 角色 / 节点 DP_SIZE TP_SIZE PP_SIZE LOCAL_DP_SIZE DP_RANK_START 首个 SERVICE_PORT
5.3.1 A3,三节点 P 1 4 4 1 0 31050
5.3.1 A3,三节点 第一个 D 8 4 1 4 0 31260
5.3.1 A3,三节点 第二个 D 8 4 1 4 4 31260
5.3.1 950DT,两节点 P 2 4 1 2 0 31050
5.3.1 950DT,两节点 D 2 4 1 2 0 31060
5.3.1 950DT,三节点 P 2 4 1 2 0 31050
5.3.1 950DT,三节点 第一个 D 4 4 1 2 0 31060
5.3.1 950DT,三节点 第二个 D 4 4 1 2 2 31062
5.3.2 A3,三节点 P 1 4 4 1 0 31050
5.3.2 A3,三节点 第一个 D 8 4 1 4 0 31260
5.3.2 A3,三节点 第二个 D 8 4 1 4 4 31260
5.3.2 950DT,两节点 P 2 4 1 2 0 31050
5.3.2 950DT,两节点 D 2 4 1 2 0 31060

Prefill-Decode 分离部署可在 3 台 Atlas 800I A3(64GB × 24)上部署 MiniMax-M3-w8a8(W8A8),并搭配 MiniMax-M3-EAGLE3-GQA。

1. Prefill 节点

unset http_proxy https_proxy ftp_proxy

# Replace IP, NIC and weight-path placeholders for this node.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_W8A8_PATH>}"
export EAGLE3_WEIGHT_PATH="${EAGLE3_WEIGHT_PATH:-<MINIMAX_M3_EAGLE3_GQA_PATH>}"

# Role defaults; the launcher overrides these for each engine.
export DP_SIZE=${DP_SIZE:-1}
export TP_SIZE=${TP_SIZE:-4}
export PP_SIZE=${PP_SIZE:-4}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-$LOCAL_IP}"
export DP_RPC_PORT=${DP_RPC_PORT:-6884}
export SERVICE_PORT=${SERVICE_PORT:-31050}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}"

export VLLM_PP_LAYER_PARTITION="16,15,15,14"
export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=1024
export HCCL_IF_IP=$LOCAL_IP
export HCCL_SOCKET_IFNAME=$NIC_NAME
export ASCEND_RT_VISIBLE_DEVICES=$VISIBLE_DEVICES
# If Mooncake is installed in a non-standard path, set this before startup.
if [ -n "${MOONCAKE_LIB_DIRS:-}" ]; then
    export LD_LIBRARY_PATH="${MOONCAKE_LIB_DIRS}:${LD_LIBRARY_PATH:-}"
fi
export GLOO_SOCKET_IFNAME=$NIC_NAME
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

# For better prefill performance, keep max_num_batched_tokens at 8192 for input
# lengths >= 8192 tokens; for shorter inputs, set it to the input token length.
vllm serve "$MODEL_PATH" \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    "${DP_RANK_ARGS[@]}" \
    --data-parallel-address "$DP_ADDRESS" \
    --data-parallel-rpc-port "$DP_RPC_PORT" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --enforce-eager \
    --distributed-executor-backend mp \
    --served-model-name minimax-m3 \
    --enable-expert-parallel \
    --seed 1024 \
    --max-model-len 133000 \
    --max-num-seqs 32 \
    --max-num-batched-tokens 8192 \
    --enable-chunked-prefill \
    --enable-prefix-caching \
    --trust-remote-code \
    --quantization ascend \
    --gpu-memory-utilization 0.92 \
    --limit-mm-per-prompt '{"image":1,"video":0}' \
    --reasoning-parser minimax_m3 \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_norm_quant":false},"multistream_overlap_shared_expert":true,"weight_nz_mode":2,"enable_shared_expert_dp":true,"enable_flashcomm1":true}' \
    --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3}' \
    --kv-transfer-config \
    '{
        "kv_connector":"MooncakeConnectorV1",
        "kv_role":"kv_producer",
        "kv_port":"36000",
        "kv_connector_extra_config":{
            "use_ascend_direct":true,
            "prefill":{"dp_size":1,"tp_size":4,"pp_size":4,"pp_layer_partition":"16,15,15,14"},
            "decode":{"dp_size":8,"tp_size":4,"pp_size":1}
        }
    }'

2. Decode 节点

unset http_proxy https_proxy ftp_proxy all_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY

# Replace IP, NIC and weight-path placeholders for this node.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_W8A8_PATH>}"
export EAGLE3_WEIGHT_PATH="${EAGLE3_WEIGHT_PATH:-<MINIMAX_M3_EAGLE3_GQA_PATH>}"

# Role defaults; the launcher overrides these for each engine.
export DP_SIZE=${DP_SIZE:-8}
export TP_SIZE=${TP_SIZE:-4}
export PP_SIZE=${PP_SIZE:-1}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-<DECODE_COORDINATOR_IP>}"
export DP_RPC_PORT=${DP_RPC_PORT:-31422}
export SERVICE_PORT=${SERVICE_PORT:-31260}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3}"

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=2048
export HCCL_IF_IP=$LOCAL_IP
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME=$NIC_NAME
export ASCEND_RT_VISIBLE_DEVICES=$VISIBLE_DEVICES
# If Mooncake is installed in a non-standard path, set this before startup.
if [ -n "${MOONCAKE_LIB_DIRS:-}" ]; then
    export LD_LIBRARY_PATH="${MOONCAKE_LIB_DIRS}:${LD_LIBRARY_PATH:-}"
fi
export GLOO_SOCKET_IFNAME=$NIC_NAME
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

vllm serve "$MODEL_PATH" \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    "${DP_RANK_ARGS[@]}" \
    --data-parallel-address "$DP_ADDRESS" \
    --data-parallel-rpc-port "$DP_RPC_PORT" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --enable-expert-parallel \
    --enable-chunked-prefill \
    --enable-prefix-caching \
    --seed 1024 \
    --served-model-name minimax-m3 \
    --reasoning-parser minimax_m3 \
    --distributed-executor-backend mp \
    --max-model-len 133000 \
    --max-num-batched-tokens 32768 \
    --trust-remote-code \
    --quantization ascend \
    --max-num-seqs 128 \
    --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
    --gpu-memory-utilization 0.92 \
    --limit-mm-per-prompt '{"image":1,"video":0}' \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_norm_quant":false},"multistream_overlap_shared_expert":true,"weight_nz_mode":2,"enable_shared_expert_dp":true,"enable_fused_mc2":0,"enable_flashcomm1":true}' \
    --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3}' \
    --kv-transfer-config \
    '{
        "kv_connector":"MooncakeConnectorV1",
        "kv_buffer_device":"npu",
        "kv_role":"kv_consumer",
        "kv_port":"36100",
        "kv_connector_extra_config":{
            "use_ascend_direct":true,
            "prefill":{"dp_size":1,"tp_size":4,"pp_size":4,"pp_layer_partition":"16,15,15,14"},
            "decode":{"dp_size":8,"tp_size":4,"pp_size":1}
        }
    }'

对于短输入负载,请参考第 9.2 节调整 --max-num-batched-tokens。 准备完成后,在每个节点上使用以下命令启动服务:

1. Prefill 节点

export PREFILL_IP="<PREFILL_NODE_IP>"
export LOCAL_IP="$PREFILL_IP"
export DP_SIZE=1
export TP_SIZE=4
export PP_SIZE=4
export LOCAL_DP_SIZE=1
export DP_RANK_START=0
export DP_ADDRESS="$PREFILL_IP"
export DP_RPC_PORT=6884
export SERVICE_PORT=31050

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

这会在端口 31050 上启动一个 Prefill API 服务器。等待其打印 Application startup complete。

2. 第一个 Decode 节点(DECODE0_IP,全局 rank 0–3)

export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
export LOCAL_IP="$DECODE0_IP"
export DP_SIZE=8
export TP_SIZE=4
export PP_SIZE=1
export LOCAL_DP_SIZE=4
export DP_RANK_START=0
export DP_ADDRESS="$DECODE0_IP"
export DP_RPC_PORT=31422
export SERVICE_PORT=31260

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

3. 第二个 Decode 节点(DECODE1_IP,全局 rank 4–7)

export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
export DECODE1_IP="<SECOND_DECODE_NODE_IP>"
export LOCAL_IP="$DECODE1_IP"
export DP_SIZE=8
export TP_SIZE=4
export PP_SIZE=1
export LOCAL_DP_SIZE=4
export DP_RANK_START=4
export DP_ADDRESS="$DECODE0_IP"
export DP_RPC_PORT=31422
export SERVICE_PORT=31260

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

请分别为每个节点在 Decode 模板中设置 LOCAL_IP 和 NIC_NAME。两个 Decode 节点均使用 DECODE0_IP 作为 DP 协调器地址。每个节点对外暴露 API 端口 31260 至 31263;请等待全部八个 DP rank 打印 Application startup complete。

要设置请求转发,请在一台能够访问所有 Prefill 和 Decode API 端口的节点上运行以下脚本。代理程序可在仓库的 examples 目录中获取:load_balance_proxy_server_example.py。对于 A3 1P1D,代理会将请求转发到一个 Prefill 节点以及跨三个节点的八个 DP rank。

export PREFILL_IP="<PREFILL_NODE_IP>"
export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
export DECODE1_IP="<SECOND_DECODE_NODE_IP>"

unset http_proxy https_proxy ftp_proxy all_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY

python load_balance_proxy_server_example.py \
--port 8009 \
--host $PREFILL_IP \
--prefiller-hosts \
    $PREFILL_IP \
--prefiller-ports \
    31050 \
--decoder-hosts \
    $DECODE0_IP $DECODE0_IP $DECODE0_IP $DECODE0_IP \
    $DECODE1_IP $DECODE1_IP $DECODE1_IP $DECODE1_IP \
--decoder-ports \
    31260 31261 31262 31263 31260 31261 31262 31263

The service is then accessible at <proxy_ip>:8009. For PD disaggregation, use this proxy endpoint in Section 7.

To use MindIE PyMotor instead of the example proxy, follow Deploy vLLM Ascend with MindIE-Motor and register the same one Prefill and eight Decode endpoints. Send benchmark requests to the Motor inference endpoint. Changing the request router does not require changing the P/D parallel sizes or scheduler limits above.

Prefill-Decode 分离部署可在两台 950DT 节点(96GB × 8,1P1D)上部署 MiniMax-M3-MXFP8,并搭配 MiniMax-M3-EAGLE3-GQA,且可扩展至三节点同时保持 1P1D(一个 Prefill 服务组和一个 Decode 服务组;见下文)。在 SuperPod 系统(UB 网络)上,请按下文模板导出 ASCEND_LOCAL_COMM_RES='{"version":"1.3"}';挂载 /etc/hixlep/ 则适用于通过 RoCE 连接的 950DT。示例中显式启用了 Model Runner V2。

将 PREFILL_IP 和 DECODE_IP 设置为 Prefill 和 Decode 的通信 IP。对于三节点变体,还需设置 DECODE0_IP 和 DECODE1_IP。请在每个节点的模板中分别设置 LOCAL_IP 和 NIC_NAME。

1. Prefill 节点

unset ftp_proxy https_proxy http_proxy all_proxy
unset FTP_PROXY HTTPS_PROXY HTTP_PROXY ALL_PROXY

# Replace IP, NIC and weight-path placeholders for this node.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_MXFP8_PATH>}"
export EAGLE3_WEIGHT_PATH="${EAGLE3_WEIGHT_PATH:-<MINIMAX_M3_EAGLE3_GQA_PATH>}"

# Role defaults; the launcher overrides these for each engine.
export DP_SIZE=${DP_SIZE:-2}
export TP_SIZE=${TP_SIZE:-4}
export PP_SIZE=${PP_SIZE:-1}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-$LOCAL_IP}"
export DP_RPC_PORT=${DP_RPC_PORT:-6884}
export SERVICE_PORT=${SERVICE_PORT:-31050}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3}"

# SuperPod (UB fabric): Mooncake auto-negotiates local endpoints. On RoCE-attached
# 950DT products, mount /etc/hixlep and use ascend_local_comm_res_path instead.
export ASCEND_LOCAL_COMM_RES='{"version":"1.3"}'

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=256
export HCCL_IF_IP=$LOCAL_IP
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME=$NIC_NAME
export ASCEND_RT_VISIBLE_DEVICES=$VISIBLE_DEVICES
export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/lib64:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages/mooncake:$LD_LIBRARY_PATH
export GLOO_SOCKET_IFNAME=$NIC_NAME
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

vllm serve "$MODEL_PATH" \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    "${DP_RANK_ARGS[@]}" \
    --data-parallel-address "$DP_ADDRESS" \
    --data-parallel-rpc-port "$DP_RPC_PORT" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --served-model-name minimax-m3 \
    --trust-remote-code \
    --dtype bfloat16 \
    --max-num-seqs 128 \
    --max-num-batched-tokens 32768 \
    --max-model-len 133000 \
    --enable-expert-parallel \
    --quantization mxfp8 \
    --gpu-memory-utilization 0.92 \
    --distributed-executor-backend mp \
    --kv-cache-dtype fp8 \
    --reasoning-parser minimax_m3 \
    --safetensors-load-strategy prefetch \
    --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3, "kv_cache_dtype":"bfloat16"}' \
    --enforce-eager \
    --enable-chunked-prefill \
    --enable-prefix-caching \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_qknorm_rope":true,"fuse_norm_quant":true},"multistream_overlap_shared_expert":true,"enable_shared_expert_dp":true,"enable_flashcomm1":true}' \
    --kv-transfer-config \
    '{
        "kv_connector":"MooncakeConnectorV1",
        "kv_role":"kv_producer",
        "kv_port":"30000",
        "kv_connector_extra_config":{
            "use_ascend_direct":true,
            "prefill":{"dp_size":2,"tp_size":4,"pp_size":1},
            "decode":{"dp_size":2,"tp_size":4,"pp_size":1}
        }
    }'

2. Decode 节点

unset ftp_proxy https_proxy http_proxy all_proxy
unset FTP_PROXY HTTPS_PROXY HTTP_PROXY ALL_PROXY

# Replace IP, NIC and weight-path placeholders for this node.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_MXFP8_PATH>}"
export EAGLE3_WEIGHT_PATH="${EAGLE3_WEIGHT_PATH:-<MINIMAX_M3_EAGLE3_GQA_PATH>}"

# Role defaults; the launcher overrides these for each engine.
export DP_SIZE=${DP_SIZE:-2}
export TP_SIZE=${TP_SIZE:-4}
export PP_SIZE=${PP_SIZE:-1}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-<DECODE_COORDINATOR_IP>}"
export DP_RPC_PORT=${DP_RPC_PORT:-5964}
export SERVICE_PORT=${SERVICE_PORT:-31060}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3}"

# SuperPod (UB fabric): Mooncake auto-negotiates local endpoints. On RoCE-attached
# 950DT products, mount /etc/hixlep and use ascend_local_comm_res_path instead.
export ASCEND_LOCAL_COMM_RES='{"version":"1.3"}'

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=2048
export HCCL_IF_IP=$LOCAL_IP
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME=$NIC_NAME
export ASCEND_RT_VISIBLE_DEVICES=$VISIBLE_DEVICES
export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/lib64:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages/mooncake:$LD_LIBRARY_PATH
export GLOO_SOCKET_IFNAME=$NIC_NAME
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

vllm serve "$MODEL_PATH" \
    --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    "${DP_RANK_ARGS[@]}" \
    --data-parallel-address "$DP_ADDRESS" \
    --data-parallel-rpc-port "$DP_RPC_PORT" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --enable-expert-parallel \
    --seed 1024 \
    --served-model-name minimax-m3 \
    --reasoning-parser minimax_m3 \
    --safetensors-load-strategy prefetch \
    --distributed-executor-backend mp \
    --max-model-len 133000 \
    --max-num-batched-tokens 32768 \
    --trust-remote-code \
    --max-num-seqs 256 \
    --gpu-memory-utilization 0.92 \
    --dtype bfloat16 \
    --quantization mxfp8 \
    --kv-cache-dtype fp8 \
    --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3, "kv_cache_dtype":"bfloat16"}' \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_norm_quant":false},"multistream_overlap_shared_expert":true,"enable_shared_expert_dp":true,"enable_flashcomm1":true}' \
    --kv-transfer-config \
    '{
        "kv_connector":"MooncakeConnectorV1",
        "kv_role":"kv_consumer",
        "kv_port":"26900",
        "kv_connector_extra_config":{
            "use_ascend_direct":true,
            "prefill":{"dp_size":2,"tp_size":4,"pp_size":1},
            "decode":{"dp_size":2,"tp_size":4,"pp_size":1}
        }
    }'

准备完成后,在每个节点上使用以下命令启动服务:

1. Prefill 节点

export PREFILL_IP="<PREFILL_NODE_IP>"
export LOCAL_IP="$PREFILL_IP"
export DP_SIZE=2
export TP_SIZE=4
export PP_SIZE=1
export LOCAL_DP_SIZE=2
export DP_RANK_START=0
export DP_ADDRESS="$PREFILL_IP"
export DP_RPC_PORT=6884
export SERVICE_PORT=31050

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

这将在端口 31050 和 31051 上启动两个 Prefill API 服务。等待两个 rank 均打印 Application startup complete。

2. Decode 节点

export DECODE_IP="<DECODE_NODE_IP>"
export LOCAL_IP="$DECODE_IP"
export DP_SIZE=2
export TP_SIZE=4
export PP_SIZE=1
export LOCAL_DP_SIZE=2
export DP_RANK_START=0
export DP_ADDRESS="$DECODE_IP"
export DP_RPC_PORT=5964
export SERVICE_PORT=31060

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

这将在端口 31060 和 31061 上启动两个 Decode API 服务。

要设置请求转发,请在一台能够访问所有 Prefill 和 Decode API 端口的节点上运行以下脚本。代理程序可在仓库的 examples 目录中获取:load_balance_proxy_server_example.py。对于 950DT 产品 1P1D,代理会将请求转发到 2 个 Prefill rank 和 2 个 DP rank。

export PREFILL_IP="<PREFILL_NODE_IP>"
export DECODE_IP="<DECODE_NODE_IP>"

unset ftp_proxy
unset https_proxy
unset http_proxy

python load_balance_proxy_server_example.py \
--port 8009 \
--host $PREFILL_IP \
--prefiller-hosts \
    $PREFILL_IP $PREFILL_IP \
--prefiller-ports \
    31050 31051 \
--decoder-hosts \
    $DECODE_IP $DECODE_IP \
--decoder-ports \
    31060 31061

The service is then accessible at <proxy_ip>:8009. For PD disaggregation, use this proxy endpoint in Section 7.

扩展至三节点(仍为 1P1D)

上述部署可扩展至三节点:一个 Prefill 节点(DP2 × TP4)加上两个 Decode 节点组成一个全局 DP4 组(每个 Decode 节点有两个本地 DP rank,各为 TP4;共 24 个 NPU)。只需修改一处模板:在 Prefill 和 Decode 的 run_dp_template.sh 两者中,更新 Mooncake 额外配置以描述新的 Decode 组 —— "decode":{"dp_size":4,"tp_size":4,"pp_size":1}(Prefill 保持 dp_size: 2)。同时将每个节点模板中的 LOCAL_IP 设置为该节点的计算网络 IP。

完全按照两节点示例启动 Prefill 节点。然后启动第一个 Decode 节点(DECODE0_IP,全局 DP rank 0–1),该节点同时充当 DP 组主节点:

export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
export LOCAL_IP="$DECODE0_IP"
export DP_SIZE=4
export TP_SIZE=4
export PP_SIZE=1
export LOCAL_DP_SIZE=2
export DP_RANK_START=0
export DP_ADDRESS="$DECODE0_IP"
export DP_RPC_PORT=5964
export SERVICE_PORT=31060

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

等待 rank 0 打印 Started DP Coordinator process。在另一个 shell 中,启动第二个 Decode 节点(DECODE1_IP,全局 DP rank 2–3),该节点将加入同一个 DP 组:

export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
export DECODE1_IP="<SECOND_DECODE_NODE_IP>"
export LOCAL_IP="$DECODE1_IP"
export DP_SIZE=4
export TP_SIZE=4
export PP_SIZE=1
export LOCAL_DP_SIZE=2
export DP_RANK_START=2
export DP_ADDRESS="$DECODE0_IP"
export DP_RPC_PORT=5964
export SERVICE_PORT=31062

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

第一个 Decode 节点提供 API 端口 31060/31061,第二个提供 31062/31063。请等待全部四个 DP rank 打印 Application startup complete。模板基础 kv_port(26900)可以在两个 Decode 节点上保持一致。请确保全局 DP rank 互不相同(0–1 和 2–3);Mooncake 使用全局 DP rank 偏移量推导握手端口。请在所有六个引擎启动完成后再启动代理(示例代理对每个后端只等待几秒),并注册两个 Prefill rank 和四个 DP rank:

export PREFILL_IP="<PREFILL_NODE_IP>"
export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
export DECODE1_IP="<SECOND_DECODE_NODE_IP>"

python load_balance_proxy_server_example.py \
--port 8009 \
--host $PREFILL_IP \
--prefiller-hosts \
    $PREFILL_IP $PREFILL_IP \
--prefiller-ports \
    31050 31051 \
--decoder-hosts \
    $DECODE0_IP $DECODE0_IP $DECODE1_IP $DECODE1_IP \
--decoder-ports \
    31060 31061 31062 31063

在贡献者验证的纯文本运行中,使用此三节点布局并启用 MXFP8、EAGLE3、fp8 KV cache 以及可选的 --language-model-only 标志,全部 232 个请求在 128k 输入 / 1k 输出、并发 58 的情况下均成功完成。平均 TPOT 为 27.3 ms,总输出吞吐量约为 1840 tokens/s。在该设置中,启用 Decode FlashComm1 将输出吞吐量从 1807.9 提升至 1840.7 tokens/s(约 1.8%)。这些测量结果描述的是该工作负载和配置;启动模板将 --language-model-only 保留为可选。

关键参数说明:

launch_online_dp.py 参数:

参数 类型 是否必填 默认值 说明
--dp-size int 是 - 数据并行大小(所有节点上 DP rank 的总数)。
--tp-size int 否 1 每个 DP rank 内的张量并行大小。
--pp-size int 否 1 每个 DP rank 内的流水线并行大小。每个 rank 占用 tp_size * pp_size 个 NPU。
--dp-size-local int 否 (与 --dp-size 相同) 当前节点上的 DP rank 数量。
--dp-rank-start int 否 0 此节点上数据并行 rank 的起始 rank 偏移量。
--dp-address str 是 - 数据并行主节点的 IP 地址。
--dp-rpc-port str 否 12321 数据并行主节点通信的 RPC 端口。
--vllm-start-port int 否 8000 此节点上每个 vLLM 引擎实例的起始端口。每个 DP rank 的引擎端口 = vllm_start_port + 本地 rank 索引。

Prefill 节点专属配置:

  • --language-model-only (optional): For text-only tests, add this flag as needed based on the available KV cache capacity to avoid reserving memory for multimodal processing. This flag can be used together with FLASHCOMM1 for text-only serving.
  • --pipeline-parallel-size (A3 Prefill: 4): Splits the 60 MiniMax-M3 layers across four pipeline stages. A3 sets VLLM_PP_LAYER_PARTITION=16,15,15,14 and also writes pp_layer_partition into the Mooncake extra config. With TP=4, use DP=1 to fit all four stages on one 16-NPU node. The 950DT products launch uses --pp-size 1 on both Prefill and Decode (no pipeline parallel), so no layer partition is needed.
  • --max-num-batched-tokens (A3 Prefill: 8192): For better prefill performance, use 8192 when the input length is at least 8192 tokens; for shorter inputs, set it to the input token length. This recommendation also applies to A3 Prefill with KV Cache Pool in Section 5.3.2.
  • --enforce-eager: Prefill nodes retain eager execution without graph capture.
  • --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}': The ordinary-context Decode examples and the single-node examples in Section 5.4.1 use full graph capture for decode-only batches. Section 5.4.2 uses eager execution for its 1M-context PD layout.
  • --speculative-config '{"method":"eagle3", ...}': Enables the MiniMax-M3-EAGLE3-GQA draft model.

Decode 节点专属配置:

  • --language-model-only(可选):对于纯文本测试,根据可用的 KV cache 容量按需添加此标志,以避免为多模态处理预留内存。
  • "max_cudagraph_capture_size"(可选,默认省略):限制 ACL 图捕获所覆盖的最大解码批次大小以及为其预留的图内存;程序默认值为 512。使用 EAGLE3 投机解码时,每个请求在一个解码步骤中扩展为 1 + num_speculative_tokens 个 tokens(当 num_speculative_tokens=3 时为 4 个 tokens)。由于 DP 按 rank 分发请求,每个 rank 的批次大小很重要:例如,使用 DP2 和 --max-num-seqs 256 时,每个 DP rank 处理 128 个请求,每步产生 4 × 128 = 512 个 tokens——恰好处于默认限制。如果并发升至 257,一个 DP rank 处理 129 个请求,得到 4 × 129 = 516 > 512;超过 512 的批次会跳过图捕获并回退到 eager 执行,从而降低解码吞吐量。在这种情况下,在 --compilation-config 中设置 "max_cudagraph_capture_size":1024(例如 --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY","max_cudagraph_capture_size":1024}')。由于更大的捕获大小会预留额外的 NPU 内存,本教程中的配置保持默认值;仅当目标并发需要时才添加此选项。
  • --max-num-seqs 256:经验证的 Ascend 950DT系列产品 1P1D 启动所使用的 Decode 并发数。A3 使用 128。

Mooncake KV 传输配置(--kv-transfer-config):

  • "kv_connector": "MooncakeConnectorV1":使用 Mooncake 作为 prefill 和 decode 节点之间的 KV cache 传输连接器。
  • "kv_role": "kv_producer" / "kv_consumer":prefill 节点上为 kv_producer,decode 节点上为 kv_consumer。
  • "kv_port":Mooncake KV 传输端口。prefill 和 decode 使用不同的端口。经验证的值是 A3 为 36000/36100,Ascend 950DT系列产品为 30000/26900。
  • "use_ascend_direct": true:为 KV cache 启用昇腾直接传输。
  • "ascend_local_comm_res_path": "/etc/hixlep"(仅 Ascend 950DT系列产品):用于挂载了 /etc/hixlep/ 的 RoCE 连接的 950DT。在 SuperPod UB 网络上,省略此选项并改为导出 ASCEND_LOCAL_COMM_RES='{"version":"1.3"}',如第 5.3.1 节模板所示。第 5.3.2 节的 950DT KV-pool 模板保留了静态路径设置;使用 UB 网络时,以相同方式调整 MultiConnector 内的 Mooncake 连接器。
  • "prefill" / "decode" 部分:dp_size、tp_size 和 pp_size 必须与每个节点上的实际全局布局匹配。A3 在一个节点上使用 prefill: dp1 tp4 pp4,在两个节点上使用 decode: dp8 tp4 pp1(每个 Decode 节点有四个本地 DP rank)。Ascend 950DT系列产品在两个节点上使用 prefill: dp2 tp4 pp1 和 decode: dp2 tp4 pp1,或在三个节点上使用 decode: dp4 tp4 pp1。

请求转发(代理):

  • 在启动代理之前,等待 Prefill 节点和 DP rank 打印 Application startup complete。
  • 代理将每个 prefill 引擎端点和每个 decode 引擎端点映射到端口 8009 上的单一入口点。
  • 如果请求到达代理但没有返回输出,请检查代理主机列表是否包含每个健康的 Prefill 节点和 DP rank,以及所有节点在之前的运行后是否仍有空闲的 NPU 内存。

关于上述环境变量的进一步说明和限制,请参阅 envs.py。

5.3.2 使用 KV Cache Pool 的 Prefill-Decode 分离

本节建立在第 5.3.1 节的基础上。A3 示例使用与第 5.3.1 节相同的三节点 1P1D 拓扑、launch_online_dp.py、代理映射和 MiniMax vllm serve 设置(一个 P 节点加两个 D 节点)。950DT pool 示例保留其两节点 1P1D 拓扑。Prefill 和 Decode 切换到 MultiConnector,以便实时 P→D KV 传输和 Mooncake KV Cache Pool 协同工作:

  • MooncakeConnectorV1 将 KV 从 Prefill 实时传输到 Decode(与 5.3.1 节中的作用相同)。
  • AscendStoreConnector 将 KV 存储到 Mooncake KV Cache Pool 中,使后续具有相同前缀的 Prefill 可以命中该池而无需重新计算。
  • 默认情况下,Prefill 节点执行池查找、加载和写入。

以下示例启用三层 KV 存储:活跃的 NPU KV 缓存、Mooncake 内存池以及 SSD 卸载。

For backend selection, mooncake.json, Mooncake Master, eviction, tenant options, and hardware-specific pooling exports, refer to the KV Cache Pool Deployment Guide. For MiniMax-specific issues, refer to Chapter 10 FAQ.

与 5.3.1 节中的普通 PD 相比,请特别注意以下仅池化模式才有的要求:

项目 普通 PD(5.3.1) 池化 PD(本节)
连接器 单一 MooncakeConnectorV1 MultiConnector 包装 MooncakeConnectorV1 + AscendStoreConnector
Mooncake Master 不需要 在启动 Decode / Prefill 之前启动 mooncake_master --enable_offload=true
mooncake.json 不需要 每个 rank 都需要;A3 的 P 和两个 D 节点使用 15GB;950DT 的 P 和 D 使用 64GB;在所有节点上启用 SSD 卸载
lookup_rpc_port 未使用 每个 DP rank 必须唯一(37000/37100 + DP_RANK),以避免 IPC 端点冲突
额外环境变量 仅 5.3.1 节的 HCCL_* 保留 5.3.1 节的环境变量,然后从 KV Cache Pool 部署指南 中添加池化 fabric/UB 相关导出
容器挂载 950DT 需要 /etc/hixlep/ 还需挂载 /etc/hccn.conf 和本地 SSD 卸载目录;在 950DT 上保留 /etc/hixlep/
启动顺序 Decode → Prefill → Proxy Mooncake Master → Decode → Prefill → Proxy
验证 仅 P→D KV 传输 还需在重复前缀预热后检查 Prefill 池的 lookup/get/put 命中情况
5.3.2.1 前提条件

将主机 HCCN 配置挂载到每个参与池化的容器中:

-v /etc/hccn.conf:/etc/hccn.conf:ro

在 Ascend 950DT系列产品上,还需保留 5.3.1 节中的 /etc/hixlep/ 挂载,用于昇腾直接 KV 传输。

在每个 Prefill 和 Decode 主机上使用本地 SSD 目录来存储从内存池卸载的 KV cache 数据。/nvme/mooncake_offload 是容器内的示例目录,并非必需的 SSD 挂载点,也不是每台主机上都已存在的目录。

在每台主机上,首先识别本地 SSD 及其挂载点:

lsblk -o NAME,TYPE,SIZE,ROTA,FSTYPE,MOUNTPOINTS,MODEL
df -hT

使用 MODEL 识别 SSD,使用 MOUNTPOINTS 查找其挂载的文件系统。ROTA=0 表示非旋转设备;请通过机器的磁盘信息确认存储类型,尤其是 RAID 或虚拟磁盘。选择一个已存在且具有足够可用空间的本地 SSD 文件系统。共享网络文件系统不属于本地 SSD 层级。

例如,如果 SSD 挂载在 /data/nvme,请在启动容器前在该处创建卸载目录。将下面的 /data/nvme 替换为实际的 SSD 挂载点:

mkdir -p /data/nvme/mooncake_offload
findmnt -T /data/nvme/mooncake_offload
df -hT /data/nvme/mooncake_offload

确认 findmnt 报告的是所选 SSD 文件系统,并且 df 显示有足够的可用空间。仅创建一个名为 /nvme 的目录并不会挂载 SSD,也不能确保数据存储在其上。

在创建每个模型容器时添加此绑定挂载:

-v /data/nvme/mooncake_offload:/nvme/mooncake_offload

左侧是该主机 SSD 上的目录;右侧是容器内可见的目录。使用此映射时,请在 mooncake.json 中保留 "ssd_offload_path": "/nvme/mooncake_offload"。如果容器路径发生变化,请更新 ssd_offload_path 以保持一致。请使用存在且 vLLM 进程可写的绝对路径;不要使用符号链接。在 P 和每个 D 主机上重复此设置:相同的路径字符串在每台主机上指向各自独立的本地 SSD 存储。后端会在其下创建各 rank 的子目录。已安装的 Mooncake 包必须支持 MooncakeDistributedStore.setup() 中的 enable_ssd_offload 和 ssd_offload_path 参数;请使用 KV Cache Pool 部署指南 中的依赖项。

在每个节点上将 mooncake.json 放在 run_dp_template.sh 旁边。在 P 和 D 上使用相同的可达 Mooncake Master 地址和 tenant_id。global_segment_size 按 worker rank 分配,而不是按节点分配;非零值必须按 1GB 对齐。如果已导出 MOONCAKE_GLOBAL_SEGMENT_SIZE,请将其更新为预期大小,或在启动前取消设置,因为它会覆盖 JSON 中的值。

A3 上 P、第一个 D 和第二个 D 的 mooncake.json(将 <MOONCAKE_MASTER_IP> 替换为实际的 Master IP):

{
  "metadata_server": "P2PHANDSHAKE",
  "protocol": "ascend",
  "device_name": "",
  "master_server_address": "<MOONCAKE_MASTER_IP>:50088",
  "global_segment_size": "15GB",
  "preferred_segment": false,
  "prefer_alloc_in_same_node": true,
  "enable_ssd_offload": true,
  "ssd_offload_path": "/nvme/mooncake_offload",
  "tenant_id": "default"
}

A3 示例在 P 和两个 D 节点上为每个 worker rank 使用 15GB。在为 vLLM 和其他进程预留内存后,请根据每台机器的可用可分配内存和碎片情况调整此值。为 SSD I/O 缓冲区和分配余量预留额外空间,并考虑节点上所有参与贡献的 worker rank。

950DT Prefill 的 mooncake.json(替换 <MOONCAKE_MASTER_IP>):

{
  "metadata_server": "P2PHANDSHAKE",
  "protocol": "ascend",
  "device_name": "",
  "master_server_address": "<MOONCAKE_MASTER_IP>:50088",
  "global_segment_size": "64GB",
  "preferred_segment": false,
  "prefer_alloc_in_same_node": true,
  "enable_ssd_offload": true,
  "ssd_offload_path": "/nvme/mooncake_offload",
  "tenant_id": "default"
}

950DT Decode 的 mooncake.json(相同的 Master 和 tenant_id;P 和 D 均贡献内存并启用 SSD 卸载):

{
  "metadata_server": "P2PHANDSHAKE",
  "protocol": "ascend",
  "device_name": "",
  "master_server_address": "<MOONCAKE_MASTER_IP>:50088",
  "global_segment_size": "64GB",
  "preferred_segment": true,
  "prefer_alloc_in_same_node": true,
  "enable_ssd_offload": true,
  "ssd_offload_path": "/nvme/mooncake_offload",
  "tenant_id": "default"
}

950DT 示例在 P 和 D 上为每个 worker rank 使用 64GB。请根据每台机器的实际可用内存和碎片情况调整大小,而不要将 64GB 视为固定要求。为节点上的所有 worker rank、SSD I/O 缓冲区和其他分配预留预算;如果机器无法注册所请求的内存,请减小 segment 大小。

5.3.2.2 环境变量

Start from the Section 5.3.1 environment block (HCCL_IF_IP, HCCL/GLOO socket IFNAME, HCCL_BUFFSIZE, PYTORCH_NPU_ALLOC_CONF, ASCEND_RT_VISIBLE_DEVICES, and A3 Prefill VLLM_PP_LAYER_PARTITION="16,15,15,14"). Then add the pooling-required variables below. Choose the hardware/communication exports from the Mooncake backend startup examples according to your machine and link type.

Prefill 节点和 DP rank 上常用的池化导出:

# Optional: only if Mooncake is installed in a non-standard path (same as Section 5.3.1).
if [ -n "${MOONCAKE_LIB_DIRS:-}" ]; then
    export LD_LIBRARY_PATH="${MOONCAKE_LIB_DIRS}:${LD_LIBRARY_PATH:-}"
fi
export MOONCAKE_CONFIG_PATH=./mooncake.json
export PYTHONHASHSEED=0
# SSD I/O buffer per worker: 1GB, aligned for A3 fabric memory.
export MOONCAKE_OFFLOAD_LOCAL_BUFFER_SIZE_BYTES=1073741824
# SSD disk budget per NPU worker rank: 10 GiB.
export MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY=lru

SSD I/O 缓冲区与 global_segment_size 分开分配。上面的 1GB 值是一个对齐的起始值;请根据 KV 读写批次确定其大小,并将其计入每个 rank 的内存预算。

对于此三层(NPU、主机内存和 SSD)示例,建议在每个 Prefill 和 Decode 节点上为每个 NPU worker rank 预留 10 GiB 的 SSD 起始预算。这两个容量限制描述的是同一份 SSD 预算;它们不会累加为 20 GiB:

  • MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES 设置上报给 Mooncake Master 的每个 worker 的 SSD 容量。其默认值为每个 worker 2199023255552 字节(2 TiB);这是对外通告的容量,并非立即分配的磁盘空间。
  • MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE 设置本地 bucket 大小限制。其默认值为 0,表示物理磁盘容量的 90%。请结合上报容量显式设置该项。
  • MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY=lru enables least-recently-used eviction, removing old SSD cache entries to make room for new writes within the configured bucket limit.

请根据工作负载和可用 SSD 空间调整这两个值,需考虑共享该磁盘的所有 worker,并为其他数据留出空间。请按 TP 和 PP 的 worker rank 数量计算,而不仅仅是 DP rank 或物理卡数量:一台拥有 16 个逻辑 NPU worker 的 A3 节点,按每个 worker 10 GiB 计算,合计预算为 160 GiB。这些限制控制 SSD 使用量;它们不会改变主机内存池或 SSD I/O 缓冲区大小。详情请参见 Mooncake SSD Offload 参数调优。

硬件 依赖项 所需的池化导出 说明
800I/T A3(HCCS,推荐) HDK >= 26.0,或 HDK >= 25.5 且 mooncake >= v0.3.11;CANN >= 9.0.0 export ASCEND_ENABLE_USE_FABRIC_MEM=1 保留 5.3.1 节的 HCCL_IF_IP / socket IFNAME 导出。
800I/T A3(RoCE)或 800I/T A2 A2:推荐 HDK >= 25.5 export HCCL_INTRA_ROCE_ENABLE=1,加上 HCCL_IF_IP / socket IFNAME,以及 nr_hugepages=200000 使用 KV Cache 池指南中的 RoCE 路径。
950PR/DT(Device UB) HDK >= 25.6 且 mooncake >= v0.3.11;CANN >= 9.1.0 export ASCEND_LOCAL_COMM_RES_PATH=/etc/hixlep/ 和 export ASCEND_LOCAL_COMM_RES='{"version":"1.3"}';unset ASCEND_GLOBAL_RESOURCE_CONFIG 挂载 /etc/hixlep/。若使用 UBOE,请按 KV Cache 池指南中的说明使用 ASCEND_GLOBAL_RESOURCE_CONFIG。
5.3.2.3 Prefill / Decode 脚本

复用 5.3.1 节的 launch_online_dp.py。将每个角色的 run_dp_template.sh 替换为下方的池化版本。保持 5.3.1 节的 vllm serve 参数风格;仅将 --kv-transfer-config 切换为 MultiConnector。根据 DP_RANK 推导 lookup_rpc_port,使每个 rank 使用不同的 IPC 端点。

在 3 台 Atlas 800I A3(64GB × 16)上,使用 KV Cache Pool 为 MiniMax-M3-w8a8(W8A8)配合 MiniMax-M3-EAGLE3-GQA 进行 Prefill-Decode 分离部署。

一个 Prefill 节点使用 DP1 × TP4 × PP4;两个 Decode 节点组成全局 DP8 × TP4 × PP1 组,每个节点有四个本地 rank。将相同的 Decode 模板复制到两个 D 节点,并使用下方针对各节点的启动配置块。服务设置与第 5.3.1 节一致;MultiConnector 增加了 KV 池。--language-model-only 选项仍保留在现有的可选指导中,而不在启动命令中。

1. Prefill 节点

unset http_proxy https_proxy ftp_proxy

# Replace IP, NIC and weight-path placeholders for this node.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_W8A8_PATH>}"
export EAGLE3_WEIGHT_PATH="${EAGLE3_WEIGHT_PATH:-<MINIMAX_M3_EAGLE3_GQA_PATH>}"

# Role defaults; the launcher overrides these for each engine.
export DP_SIZE=${DP_SIZE:-1}
export TP_SIZE=${TP_SIZE:-4}
export PP_SIZE=${PP_SIZE:-4}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-$LOCAL_IP}"
export DP_RPC_PORT=${DP_RPC_PORT:-6884}
export SERVICE_PORT=${SERVICE_PORT:-31050}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}"

# Pooling extras: all three A3 nodes use the 15GB mooncake.json in Section 5.3.2.1.
export VLLM_PP_LAYER_PARTITION="16,15,15,14"
export VLLM_SERVER_DEV_MODE=1
export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=1024
export HCCL_IF_IP=$LOCAL_IP
export HCCL_SOCKET_IFNAME=$NIC_NAME
export ASCEND_ENABLE_USE_FABRIC_MEM=1
export ASCEND_RT_VISIBLE_DEVICES=$VISIBLE_DEVICES
# If Mooncake is installed in a non-standard path, set this before startup.
if [ -n "${MOONCAKE_LIB_DIRS:-}" ]; then
    export LD_LIBRARY_PATH="${MOONCAKE_LIB_DIRS}:${LD_LIBRARY_PATH:-}"
fi
export GLOO_SOCKET_IFNAME=$NIC_NAME
export MOONCAKE_CONFIG_PATH=./mooncake.json
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export PYTHONHASHSEED=0
export MOONCAKE_OFFLOAD_LOCAL_BUFFER_SIZE_BYTES=1073741824
# SSD disk budget per NPU worker rank: 10 GiB.
export MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY=lru

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

# For better prefill performance, keep max_num_batched_tokens at 8192 for input
# lengths >= 8192 tokens; for shorter inputs, set it to the input token length.
vllm serve "$MODEL_PATH" \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    "${DP_RANK_ARGS[@]}" \
    --data-parallel-address "$DP_ADDRESS" \
    --data-parallel-rpc-port "$DP_RPC_PORT" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --enforce-eager \
    --distributed-executor-backend mp \
    --served-model-name minimax-m3 \
    --enable-expert-parallel \
    --seed 1024 \
    --max-model-len 133000 \
    --max-num-seqs 32 \
    --max-num-batched-tokens 8192 \
    --enable-chunked-prefill \
    --enable-prefix-caching \
    --trust-remote-code \
    --quantization ascend \
    --gpu-memory-utilization 0.92 \
    --limit-mm-per-prompt '{"image":1,"video":0}' \
    --reasoning-parser minimax_m3 \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_norm_quant":false},"multistream_overlap_shared_expert":true,"weight_nz_mode":2,"enable_shared_expert_dp":true,"enable_flashcomm1":true}' \
    --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3}' \
    --kv-transfer-config \
    '{
        "kv_connector":"MultiConnector",
        "kv_role":"kv_producer",
        "kv_connector_extra_config":{
            "connectors":[
                {
                    "kv_connector":"MooncakeConnectorV1",
                    "kv_buffer_device":"npu",
                    "kv_role":"kv_producer",
                    "kv_port":"36000",
                    "kv_connector_extra_config":{
                        "use_ascend_direct":true,
                        "prefill":{"dp_size":1,"tp_size":4,"pp_size":4,"pp_layer_partition":"16,15,15,14"},
                        "decode":{"dp_size":8,"tp_size":4,"pp_size":1}
                    }
                },
                {
                    "kv_connector":"AscendStoreConnector",
                    "kv_role":"kv_producer",
                    "kv_connector_extra_config":{
                        "backend":"mooncake",
                        "lookup_rpc_port":'$((37000 + $DP_RANK))'
                    }
                }
            ]
        }
    }'

2. Decode 节点

unset http_proxy https_proxy ftp_proxy all_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY

# Replace IP, NIC and weight-path placeholders for this node.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_W8A8_PATH>}"
export EAGLE3_WEIGHT_PATH="${EAGLE3_WEIGHT_PATH:-<MINIMAX_M3_EAGLE3_GQA_PATH>}"

# Role defaults; the launcher overrides these for each engine.
export DP_SIZE=${DP_SIZE:-8}
export TP_SIZE=${TP_SIZE:-4}
export PP_SIZE=${PP_SIZE:-1}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-<DECODE_COORDINATOR_IP>}"
export DP_RPC_PORT=${DP_RPC_PORT:-31422}
export SERVICE_PORT=${SERVICE_PORT:-31260}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3}"

# Pooling extras: all three A3 nodes use the 15GB mooncake.json in Section 5.3.2.1.
export VLLM_SERVER_DEV_MODE=1
export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=2048
export HCCL_IF_IP=$LOCAL_IP
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME=$NIC_NAME
export ASCEND_ENABLE_USE_FABRIC_MEM=1
export ASCEND_RT_VISIBLE_DEVICES=$VISIBLE_DEVICES
# If Mooncake is installed in a non-standard path, set this before startup.
if [ -n "${MOONCAKE_LIB_DIRS:-}" ]; then
    export LD_LIBRARY_PATH="${MOONCAKE_LIB_DIRS}:${LD_LIBRARY_PATH:-}"
fi
export GLOO_SOCKET_IFNAME=$NIC_NAME
export MOONCAKE_CONFIG_PATH=./mooncake.json
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export PYTHONHASHSEED=0
export MOONCAKE_OFFLOAD_LOCAL_BUFFER_SIZE_BYTES=1073741824
# SSD disk budget per NPU worker rank: 10 GiB.
export MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY=lru

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

vllm serve "$MODEL_PATH" \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    "${DP_RANK_ARGS[@]}" \
    --data-parallel-address "$DP_ADDRESS" \
    --data-parallel-rpc-port "$DP_RPC_PORT" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --enable-expert-parallel \
    --enable-chunked-prefill \
    --enable-prefix-caching \
    --seed 1024 \
    --served-model-name minimax-m3 \
    --reasoning-parser minimax_m3 \
    --distributed-executor-backend mp \
    --max-model-len 133000 \
    --max-num-batched-tokens 32768 \
    --trust-remote-code \
    --quantization ascend \
    --max-num-seqs 128 \
    --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
    --gpu-memory-utilization 0.92 \
    --limit-mm-per-prompt '{"image":1,"video":0}' \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_norm_quant":false},"multistream_overlap_shared_expert":true,"weight_nz_mode":2,"enable_shared_expert_dp":true,"enable_fused_mc2":0,"enable_flashcomm1":true}' \
    --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3}' \
    --kv-transfer-config \
    '{
        "kv_connector":"MultiConnector",
        "kv_role":"kv_consumer",
        "kv_connector_extra_config":{
            "connectors":[
                {
                    "kv_connector":"MooncakeConnectorV1",
                    "kv_buffer_device":"npu",
                    "kv_role":"kv_consumer",
                    "kv_port":"36100",
                    "kv_connector_extra_config":{
                        "use_ascend_direct":true,
                        "prefill":{"dp_size":1,"tp_size":4,"pp_size":4,"pp_layer_partition":"16,15,15,14"},
                        "decode":{"dp_size":8,"tp_size":4,"pp_size":1}
                    }
                },
                {
                    "kv_connector":"AscendStoreConnector",
                    "kv_role":"kv_consumer",
                    "kv_connector_extra_config":{
                        "backend":"mooncake",
                        "lookup_rpc_port":'$((37100 + $DP_RANK))'
                    }
                }
            ]
        }
    }'

A3 上的启动顺序:Mooncake Master → 两个 Decode 节点 → Prefill → Proxy。使用第 5.3.2.4 节中的三节点命令,DP/TP/PP 大小和 API 端口与第 5.3.1 节 A3 一致。在启动 proxy 之前,单个 P API 和全部八个 D API 必须处于健康状态。

在 2 台 Ascend 950DT系列产品(96GB × 8)上为 MiniMax-M3-MXFP8 搭配 MiniMax-M3-EAGLE3-GQA 使用 KV Cache Pool 进行 Prefill-Decode 分离部署。挂载 /etc/hixlep/、/etc/hccn.conf 以及第 5.3.2.1 节中的本地 SSD 目录。将第 5.3.2.1 节中的 Prefill / Decode mooncake.json 放到 run_dp_template.sh 旁边(两个角色的 global_segment_size 均为 64GB,并启用 SSD 卸载)。在两个节点上将 master_server_address 设置为同一个可访问的 Mooncake Master。

1. Prefill 节点

unset ftp_proxy https_proxy http_proxy all_proxy
unset FTP_PROXY HTTPS_PROXY HTTP_PROXY ALL_PROXY

# Replace IP, NIC and weight-path placeholders for this node.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_MXFP8_PATH>}"
export EAGLE3_WEIGHT_PATH="${EAGLE3_WEIGHT_PATH:-<MINIMAX_M3_EAGLE3_GQA_PATH>}"

# Role defaults; the launcher overrides these for each engine.
export DP_SIZE=${DP_SIZE:-2}
export TP_SIZE=${TP_SIZE:-4}
export PP_SIZE=${PP_SIZE:-1}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-$LOCAL_IP}"
export DP_RPC_PORT=${DP_RPC_PORT:-6884}
export SERVICE_PORT=${SERVICE_PORT:-31050}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3}"

# Runtime environment and pooling extras (Device UB).
export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=256
export HCCL_IF_IP=$LOCAL_IP
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME=$NIC_NAME
export ASCEND_RT_VISIBLE_DEVICES=$VISIBLE_DEVICES
export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/lib64:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages/mooncake:$LD_LIBRARY_PATH
export GLOO_SOCKET_IFNAME=$NIC_NAME
export MOONCAKE_CONFIG_PATH=./mooncake.json
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export PYTHONHASHSEED=0
export MOONCAKE_OFFLOAD_LOCAL_BUFFER_SIZE_BYTES=1073741824
# SSD disk budget per NPU worker rank: 10 GiB.
export MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY=lru
export ASCEND_LOCAL_COMM_RES_PATH=/etc/hixlep/
export ASCEND_LOCAL_COMM_RES='{"version":"1.3"}'
unset ASCEND_GLOBAL_RESOURCE_CONFIG

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

vllm serve "$MODEL_PATH" \
  --host 0.0.0.0 \
  --port "$SERVICE_PORT" \
  --data-parallel-size "$DP_SIZE" \
  "${DP_RANK_ARGS[@]}" \
  --data-parallel-address "$DP_ADDRESS" \
  --data-parallel-rpc-port "$DP_RPC_PORT" \
  --tensor-parallel-size "$TP_SIZE" \
  --pipeline-parallel-size "$PP_SIZE" \
  --served-model-name minimax-m3 \
  --trust-remote-code \
  --dtype bfloat16 \
  --max-num-seqs 128 \
  --max-num-batched-tokens 32768 \
  --max-model-len 133000 \
  --enable-expert-parallel \
  --quantization mxfp8 \
  --gpu-memory-utilization 0.92 \
  --distributed-executor-backend mp \
  --kv-cache-dtype fp8 \
  --reasoning-parser minimax_m3 \
  --safetensors-load-strategy prefetch \
  --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3, "kv_cache_dtype":"bfloat16"}' \
  --enforce-eager \
  --enable-chunked-prefill \
  --enable-prefix-caching \
  --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_qknorm_rope":true,"fuse_norm_quant":true},"multistream_overlap_shared_expert":true,"enable_shared_expert_dp":true,"enable_flashcomm1":true}' \
  --kv-transfer-config \
  '{
      "kv_connector":"MultiConnector",
      "kv_role":"kv_producer",
      "kv_connector_extra_config":{
          "connectors":[
              {
                  "kv_connector":"MooncakeConnectorV1",
                  "kv_buffer_device":"npu",
                  "kv_role":"kv_producer",
                  "kv_port":"30000",
                  "kv_connector_extra_config":{
                      "use_ascend_direct":true,
                      "ascend_local_comm_res_path":"/etc/hixlep",
                      "prefill":{"dp_size":2,"tp_size":4,"pp_size":1},
                      "decode":{"dp_size":2,"tp_size":4,"pp_size":1}
                  }
              },
              {
                  "kv_connector":"AscendStoreConnector",
                  "kv_role":"kv_producer",
                  "kv_connector_extra_config":{
                      "backend":"mooncake",
                      "lookup_rpc_port":'$((37000 + $DP_RANK))'
                  }
              }
          ]
      }
  }'

2. Decode 节点

unset ftp_proxy https_proxy http_proxy all_proxy
unset FTP_PROXY HTTPS_PROXY HTTP_PROXY ALL_PROXY

# Replace IP, NIC and weight-path placeholders for this node.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_MXFP8_PATH>}"
export EAGLE3_WEIGHT_PATH="${EAGLE3_WEIGHT_PATH:-<MINIMAX_M3_EAGLE3_GQA_PATH>}"

# Role defaults; the launcher overrides these for each engine.
export DP_SIZE=${DP_SIZE:-2}
export TP_SIZE=${TP_SIZE:-4}
export PP_SIZE=${PP_SIZE:-1}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-<DECODE_COORDINATOR_IP>}"
export DP_RPC_PORT=${DP_RPC_PORT:-5964}
export SERVICE_PORT=${SERVICE_PORT:-31060}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3}"

# Runtime environment and pooling extras (Device UB).
export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=2048
export HCCL_IF_IP=$LOCAL_IP
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME=$NIC_NAME
export ASCEND_RT_VISIBLE_DEVICES=$VISIBLE_DEVICES
export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/lib64:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages/mooncake:$LD_LIBRARY_PATH
export GLOO_SOCKET_IFNAME=$NIC_NAME
export MOONCAKE_CONFIG_PATH=./mooncake.json
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export PYTHONHASHSEED=0
export MOONCAKE_OFFLOAD_LOCAL_BUFFER_SIZE_BYTES=1073741824
# SSD disk budget per NPU worker rank: 10 GiB.
export MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE=$((10 * 1024 * 1024 * 1024))
export MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY=lru
export ASCEND_LOCAL_COMM_RES_PATH=/etc/hixlep/
export ASCEND_LOCAL_COMM_RES='{"version":"1.3"}'
unset ASCEND_GLOBAL_RESOURCE_CONFIG

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

vllm serve "$MODEL_PATH" \
    --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    "${DP_RANK_ARGS[@]}" \
    --data-parallel-address "$DP_ADDRESS" \
    --data-parallel-rpc-port "$DP_RPC_PORT" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --enable-expert-parallel \
    --seed 1024 \
    --served-model-name minimax-m3 \
    --reasoning-parser minimax_m3 \
    --safetensors-load-strategy prefetch \
    --distributed-executor-backend mp \
    --max-model-len 133000 \
    --max-num-batched-tokens 32768 \
    --trust-remote-code \
    --max-num-seqs 256 \
    --gpu-memory-utilization 0.92 \
    --dtype bfloat16 \
    --quantization mxfp8 \
    --kv-cache-dtype fp8 \
    --speculative-config '{"model":"'"${EAGLE3_WEIGHT_PATH}"'", "method":"eagle3", "num_speculative_tokens":3, "kv_cache_dtype":"bfloat16"}' \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"fuse_norm_quant":false},"multistream_overlap_shared_expert":true,"enable_shared_expert_dp":true}' \
    --kv-transfer-config \
    '{
        "kv_connector":"MultiConnector",
        "kv_role":"kv_consumer",
        "kv_connector_extra_config":{
            "connectors":[
                {
                    "kv_connector":"MooncakeConnectorV1",
                    "kv_buffer_device":"npu",
                    "kv_role":"kv_consumer",
                    "kv_port":"26900",
                    "kv_connector_extra_config":{
                        "use_ascend_direct":true,
                        "ascend_local_comm_res_path":"/etc/hixlep",
                        "prefill":{"dp_size":2,"tp_size":4,"pp_size":1},
                        "decode":{"dp_size":2,"tp_size":4,"pp_size":1}
                    }
                },
                {
                    "kv_connector":"AscendStoreConnector",
                    "kv_role":"kv_consumer",
                    "kv_connector_extra_config":{
                        "backend":"mooncake",
                        "lookup_rpc_port":'$((37100 + $DP_RANK))'
                    }
                }
            ]
        }
    }'

950DT 上的启动顺序:Mooncake Master → Decode → Prefill → Proxy。使用第 5.3.1 节 950DT 双节点命令(两个角色均为 DP2,proxy 监听 8009),并配合上述池化模板;此双节点示例不要使用三节点扩展命令。

5.3.2.4 启动服务

按以下顺序启动:

Mooncake Master
    ↓
First Decode node → Second Decode node (A3 only)
    ↓
Prefill
    ↓
Proxy (:8009)
  1. 在 Prefill 节点或其他可访问的主机上启动 mooncake_master,并在每个 mooncake.json 中使用其实际 IP。确认所有模型节点都能访问端口 50088:

    mooncake_master \
    --port 50088 \
    --eviction_high_watermark_ratio 0.9 \
    --eviction_ratio 0.1 \
    --default_kv_lease_ttl 11000 \
    --enable_offload=true \
    --client_ttl=120
    

    These are example Master settings: default_kv_lease_ttl=11000 is in milliseconds, while client_ttl=120 is in seconds. The eviction ratios are dimensionless; see the Mooncake Master parameter table for their meanings and supported ranges. Keep enable_offload=true for this SSD-offload deployment.

  2. 启动第一个 A3 Decode 节点(全局 DP rank 0–3)。使用池化 Decode 模板,而不是普通 PD 模板:

    export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
    export LOCAL_IP="$DECODE0_IP"
    export DP_SIZE=8
    export TP_SIZE=4
    export PP_SIZE=1
    export LOCAL_DP_SIZE=4
    export DP_RANK_START=0
    export DP_ADDRESS="$DECODE0_IP"
    export DP_RPC_PORT=31422
    export SERVICE_PORT=31260
    
    python launch_online_dp.py \
        --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
        --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
        --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
        --vllm-start-port "$SERVICE_PORT"
    

    等待 rank 0 打印 Started DP Coordinator process。保持此 shell 运行,同时从另一个 shell 启动第二个节点;初始化需要两个 D 节点。在 950DT 上,使用第 5.3.1 节的双节点 Decode 启动命令并配合 950DT 池化模板,然后跳过步骤 3。

  3. 启动第二个 A3 Decode 节点(全局 DP rank 4–7)。LOCAL_IP 是第二个节点的 IP,但 DP_ADDRESS 仍为第一个 Decode 节点的 IP:

    export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
    export DECODE1_IP="<SECOND_DECODE_NODE_IP>"
    export LOCAL_IP="$DECODE1_IP"
    export DP_SIZE=8
    export TP_SIZE=4
    export PP_SIZE=1
    export LOCAL_DP_SIZE=4
    export DP_RANK_START=4
    export DP_ADDRESS="$DECODE0_IP"
    export DP_RPC_PORT=31422
    export SERVICE_PORT=31260
    
    python launch_online_dp.py \
        --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
        --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
        --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
        --vllm-start-port "$SERVICE_PORT"
    

    两个 Decode 节点均暴露 API 端口 31260–31263。等待全部八个 DP rank 打印 Application startup complete。

  4. 启动 Prefill。在 A3 上,使用池化 P 模板:

    export PREFILL_IP="<PREFILL_NODE_IP>"
    export LOCAL_IP="$PREFILL_IP"
    export DP_SIZE=1
    export TP_SIZE=4
    export PP_SIZE=4
    export LOCAL_DP_SIZE=1
    export DP_RANK_START=0
    export DP_ADDRESS="$PREFILL_IP"
    export DP_RPC_PORT=6884
    export SERVICE_PORT=31050
    
    python launch_online_dp.py \
        --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
        --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
        --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
        --vllm-start-port "$SERVICE_PORT"
    

    在 950DT 上,使用第 5.3.1 节的双节点 Prefill 命令并配合其池化模板。等待每个 Prefill rank 打印 Application startup complete。

  5. 启动 proxy。A3 proxy 注册一个 Prefill 端点和八个 Decode 端点:

    export PREFILL_IP="<PREFILL_NODE_IP>"
    export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
    export DECODE1_IP="<SECOND_DECODE_NODE_IP>"
    
    unset http_proxy https_proxy ftp_proxy all_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY
    
    python load_balance_proxy_server_example.py \
    --port 8009 \
    --host $PREFILL_IP \
    --prefiller-hosts \
        $PREFILL_IP \
    --prefiller-ports \
        31050 \
    --decoder-hosts \
        $DECODE0_IP $DECODE0_IP $DECODE0_IP $DECODE0_IP \
        $DECODE1_IP $DECODE1_IP $DECODE1_IP $DECODE1_IP \
    --decoder-ports \
        31260 31261 31262 31263 31260 31261 31262 31263
    

    On 950DT, use the Section 5.3.1 two-node proxy command (two P and two D endpoints). The service is accessible at <proxy_ip>:8009; use this endpoint in Section 7.

5.3.2.5 验证
  1. 确认 Mooncake Master 端口 50088 可访问,Master 以 --enable_offload=true 运行,且每个节点使用相同的 Master 地址和 tenant_id。对于 A3,确认 P 和两个 D 节点均加载 global_segment_size="15GB";对于 950DT,确认 P 和 D 均加载 "64GB"。检查 worker 日志以确认 segment 注册成功。
  2. 确认每个 Decode 引擎端口就绪,然后确认每个 Prefill 引擎端口就绪。A3 有九个 API(一个 P 加八个 D);950DT 有四个(两个 P 加两个 D)。
  3. 仅向端口 8009 上的 proxy 发送请求。
  4. 使用重复前缀请求进行预热,然后再次发送,并检查 Prefill 日志中的 KV Pool 查找/get/put 及命中信息。
  5. 确认 Decode 日志仍显示正常的 Mooncake P→D KV 传输。
  6. 确认每个参与 worker 的 SSD 存储在 ssd_offload_path 下完成初始化。使用大于内存池的工作集来验证 SSD 卸载及后续读取;仅命中内存池并不能测试 SSD 层级。

5.4 1M Token 上下文部署

MiniMax-M3 支持 1M token 上下文,可在 Atlas 800I A3(W8A8)和 950DT(MXFP8)上单节点部署,也可采用 5.4.2 节中的三节点 A3 Prefill-Decode 布局。此处 1M 指 1,048,576 个 token。--max-model-len 限制输入与生成 token 的总量,因此在准备请求时需预留输出空间。

5.4.1 单节点部署

以下单节点示例使用此前已验证的长上下文部署布局,并以 FULL_DECODE_ONLY 作为图模式。

平台 / 权重 每节点 NPU 数 TP / PP / DP max-model-len max-num-batched-tokens KV cache dtype
Atlas 800I A3 / W8A8 16 × 64GB 4 / 4 / 1 1049600 8192 auto (default)
950DT / MXFP8 8 × 96GB 8 / 1 / 1 1048576 2048 fp8, excluding layers 0–2
950DT / MXFP8 8 × 96GB 4 / 2 / 1 1048576 8192 fp8, excluding layers 0–2

所有三种配置均使用 Model Runner V2、max-num-seqs=1、FULL_DECODE_ONLY graph 模式以及分块 prefill。前缀缓存、异步调度和静态 kernel 均已禁用;不加载投机模型。较小的 batched-token 预算控制的是每个 prefill 分块的大小,而不是总上下文长度。

按照第 3 节和第 4 节所述准备容器和权重。将 <MODEL_PATH>(A3)或 <WEIGHT_PATH>(950DT)替换为本地权重目录。将 HCCL_SOCKET_IFNAME 和 GLOO_SOCKET_IFNAME 中的 <NIC_NAME> 替换为同一个本地通信接口。命令使用部署配置中的 CANN 环境脚本路径;请将这些路径调整为已安装的 CANN 目录。950DT 示例还需要匹配的 experimental_950_transformer 厂商包,然后才能 source 其环境脚本。

使用一台 16-NPU 节点,配置为 TP4/PP4/DP1。此长上下文配置将 60 个 transformer 层均匀划分为 15,15,15,15。1049600 的上下文限制为 1,048,576 个输入 Token 和最多 1024 个生成 Token 留出空间。当配置的限制超过 checkpoint 声明的最大值时,VLLM_ALLOW_LONG_MAX_MODEL_LEN=1 允许使用该限制。

MODEL_PATH="<MODEL_PATH>"  # MiniMax-M3 W8A8 weights

source /usr/local/Ascend/cann-9.1.0/set_env.sh

export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=7200
export VLLM_PP_LAYER_PARTITION="15,15,15,15"
export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=256
export HCCL_SOCKET_IFNAME="<NIC_NAME>"
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
export GLOO_SOCKET_IFNAME="<NIC_NAME>"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

vllm serve "$MODEL_PATH" \
    --host 0.0.0.0 \
    --port 8000 \
    --served-model-name minimax-m3 \
    --trust-remote-code \
    --language-model-only \
    --reasoning-parser minimax_m3 \
    --distributed-executor-backend mp \
    --tensor-parallel-size 4 \
    --pipeline-parallel-size 4 \
    --data-parallel-size 1 \
    --enable-expert-parallel \
    --quantization ascend \
    --block-size 128 \
    --max-model-len 1049600 \
    --max-num-batched-tokens 8192 \
    --long-prefill-token-threshold 8192 \
    --max-num-seqs 1 \
    --enable-chunked-prefill \
    --no-enable-prefix-caching \
    --no-async-scheduling \
    --gpu-memory-utilization 0.95 \
    --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
    --additional-config '{
        "enable_cpu_binding": true,
        "ascend_compilation_config": {
            "enable_static_kernel": false,
            "fuse_norm_quant": false
        },
        "multistream_overlap_shared_expert": true,
        "enable_shared_expert_dp": false,
        "weight_nz_mode": 2,
        "enable_flashcomm1": true,
        "enable_reduce_sample": false
    }' > ./minimax-m3-1m-a3.log 2>&1 &

--long-prefill-token-threshold 8192 设置与分块预填充配合使用的长预填充阈值。调度器使用该阈值限制单次迭代中为长请求调度的预填充 token 数量,而 --max-num-batched-tokens 8192 则限定该次迭代的总 token 预算。在这个单请求示例中,两个值均为 8192,因此 1M 的提示词会在该预算内分块处理。该参数不会降低由 --max-model-len 配置的总上下文限制。详情请参阅 vLLM 调度器参数。

在八个 NPU 上选择以下任意一种单节点部署方案。两者均使用 FP8 KV 缓存,同时将第 0、1、2 层保持为默认的 KV 数据类型。输入与生成的 token 总数必须控制在 1,048,576 个 token 以内;例如,通过将输入限制为最多 1,047,552 个 token 来预留 1024 个输出 token。

export WEIGHT_PATH="<WEIGHT_PATH>"  # MiniMax-M3 MXFP8 weights

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=256
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME="<NIC_NAME>"
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export GLOO_SOCKET_IFNAME="<NIC_NAME>"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

source /usr/local/Ascend/cann-9.1.0/opp/vendors/experimental_950_transformer/bin/set_env.bash

vllm serve "$WEIGHT_PATH" \
    --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
    --host 0.0.0.0 \
    --port 31050 \
    --served-model-name minimax-m3 \
    --trust-remote-code \
    --distributed-executor-backend mp \
    --tensor-parallel-size 8 \
    --pipeline-parallel-size 1 \
    --data-parallel-size 1 \
    --dtype bfloat16 \
    --quantization mxfp8 \
    --max-model-len 1048576 \
    --max-num-seqs 1 \
    --max-num-batched-tokens 2048 \
    --gpu-memory-utilization 0.92 \
    --kv-cache-dtype fp8 \
    --kv-cache-dtype-skip-layers 0 1 2 \
    --limit-mm-per-prompt '{"image":0,"video":0}' \
    --enable-chunked-prefill \
    --no-enable-prefix-caching \
    --no-async-scheduling \
    --reasoning-parser minimax_m3 \
    --safetensors-load-strategy prefetch \
    --additional-config '{
        "enable_cpu_binding": true,
        "ascend_compilation_config": {
            "fuse_qknorm_rope": false,
            "fuse_norm_quant": false,
            "enable_static_kernel": false
        },
        "multistream_overlap_shared_expert": false,
        "enable_shared_expert_dp": false,
        "enable_flashcomm1": false
    }' > ./minimax-m3-1m-950dt-tp8.log 2>&1 &
export WEIGHT_PATH="<WEIGHT_PATH>"  # MiniMax-M3 MXFP8 weights

export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=256
export HCCL_SOCKET_IFNAME="<NIC_NAME>"
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export GLOO_SOCKET_IFNAME="<NIC_NAME>"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

source /usr/local/Ascend/cann-9.1.0/opp/vendors/experimental_950_transformer/bin/set_env.bash

vllm serve "$WEIGHT_PATH" \
    --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
    --host 0.0.0.0 \
    --port 31050 \
    --served-model-name minimax-m3 \
    --trust-remote-code \
    --distributed-executor-backend mp \
    --tensor-parallel-size 4 \
    --pipeline-parallel-size 2 \
    --data-parallel-size 1 \
    --dtype bfloat16 \
    --quantization mxfp8 \
    --max-model-len 1048576 \
    --max-num-seqs 1 \
    --max-num-batched-tokens 8192 \
    --gpu-memory-utilization 0.92 \
    --kv-cache-dtype fp8 \
    --kv-cache-dtype-skip-layers 0 1 2 \
    --enable-chunked-prefill \
    --no-enable-prefix-caching \
    --no-async-scheduling \
    --reasoning-parser minimax_m3 \
    --safetensors-load-strategy prefetch \
    --additional-config '{
        "enable_cpu_binding": true,
        "ascend_compilation_config": {
            "fuse_qknorm_rope": false,
            "fuse_norm_quant": false,
            "enable_static_kernel": false
        },
        "multistream_overlap_shared_expert": false,
        "enable_shared_expert_dp": false,
        "enable_flashcomm1": false
    }' > ./minimax-m3-1m-950dt-tp4pp2.log 2>&1 &

在发送请求之前,请等待所选命令末尾的日志文件中出现 Application startup complete。将报告的 KV 缓存容量与配置的上下文长度进行核对。这些示例展示了已验证的长上下文部署形态;请针对目标工作负载单独调优并发度和性能。

5.4.2 Prefill-Decode 分离部署

本示例使用三台 Atlas 800I A3 节点(每节点 16 × 64GB NPU)和 W8A8 权重:一个 Prefill 服务和一个 Decode 服务分布在两台节点上。它使用 MooncakeConnectorV1 进行直接的 P→D KV 传输。下方的 1M 上下文 PD 配置针对 A3 提供。

角色 全局 DP / TP / PP 本地 DP rank API 端口 max-num-batched-tokens long-prefill-token-threshold
Prefill 1 / 8 / 2 0 31050 8192 8192
First Decode node 4 / 8 / 1 0–1 31260, 31261 8 8
Second Decode node 4 / 8 / 1 2–3 31260, 31261 8 8

两个角色均使用 Model Runner V2、--max-model-len 1049600、--max-num-seqs 1、--kv-cache-dtype auto、FLASHCOMM1、chunked prefill 以及纯文本服务。前缀缓存、异步调度、shared-expert DP、静态 kernel 和 EAGLE3 均已禁用。Prefill 将 60 层拆分为 30,30 并启用 shared-expert 多流重叠;Decode 禁用该重叠。

在此 PD 配置中,P 和 D 均使用 --enforce-eager 并设置 cudagraph_mode=NONE。此前使用 FULL_DECODE_ONLY 和自动调整 KV cache 大小的 Decode 候选方案在测试环境中分配阶段失败。成功的配置使用 Decode token 预算和 long-prefill 阈值 8,并为每个 worker 显式设置 47620449894 字节(约 44.35 GiB)的 KV cache 预算。这是一个可用的长上下文部署示例;性能需另行调优。

5.4.2.1 准备工作

按照第 3、4 和 5.3.1 节的说明准备模型容器、W8A8 权重、跨节点通信和 Mooncake 依赖。在准备好的容器内运行以下命令。将 IP/网卡占位符替换为各主机的通信网络 IP 及匹配的网卡接口,并设置在每个容器内均可见的权重目录。如果安装路径不同,请调整 CANN 环境脚本路径。在多网卡主机上,请检查启动日志,确保通告的主机地址使用预期的通信网络;仅设置 HCCL/GLOO 接口并不能控制所有组件的地址选择。

复用 5.3.1 节的 launch_online_dp.py。在每台相关主机上创建独立的角色目录:

mkdir -p ~/minimax-m3-1m-pd/prefill ~/minimax-m3-1m-pd/decode

将 launch_online_dp.py 的副本保存到每个将要使用的角色目录中。在 P 上将下方的 Prefill 模板保存为 ~/minimax-m3-1m-pd/prefill/run_dp_template.sh,在两台 D 节点上将 Decode 模板保存为 ~/minimax-m3-1m-pd/decode/run_dp_template.sh。即使使用共享存储,也要保持角色目录相互独立:启动器会从其工作目录读取 ./run_dp_template.sh。

四个 Decode 引擎共享一个全局 DP 组。每个进程通过 --data-parallel-size-local 1 服务一个 DP rank;启动器的 --dp-size-local 2 会在每台 D 主机上启动两个这样的进程。两台 D 主机都必须使用第一台 D 主机的 IP 作为 DP_ADDRESS,并使用不同的全局 DP rank 范围。如果更改 TP/PP/DP,请同时更新两个角色的 kv_connector_extra_config 以及所有启动映射。

5.4.2.2 服务脚本

1. Prefill 节点:prefill/run_dp_template.sh

#!/usr/bin/env bash
set -eo pipefail

unset http_proxy https_proxy ftp_proxy all_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY
source /usr/local/Ascend/ascend-toolkit/set_env.sh

# Set this host's communication IP, NIC and W8A8 weight path.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_W8A8_PATH>}"

# The Section 5.3.1 launcher supplies these values for the Prefill engine.
export DP_SIZE=${DP_SIZE:-1}
export TP_SIZE=${TP_SIZE:-8}
export PP_SIZE=${PP_SIZE:-2}
export SERVICE_PORT=${SERVICE_PORT:-31050}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}"

export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=7200
export VLLM_PP_LAYER_PARTITION="30,30"
export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=256
export HCCL_IF_IP="$LOCAL_IP"
export HCCL_SOCKET_IFNAME="$NIC_NAME"
export ASCEND_ENABLE_USE_FABRIC_MEM=1
export ASCEND_RT_VISIBLE_DEVICES="$VISIBLE_DEVICES"
if [ -n "${MOONCAKE_LIB_DIRS:-}" ]; then
    export LD_LIBRARY_PATH="${MOONCAKE_LIB_DIRS}:${LD_LIBRARY_PATH:-}"
fi
export GLOO_SOCKET_IFNAME="$NIC_NAME"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

vllm serve "$MODEL_PATH" \
    --enforce-eager \
    --compilation-config '{"cudagraph_mode":"NONE"}' \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --distributed-executor-backend mp \
    --served-model-name minimax-m3 \
    --enable-expert-parallel \
    --seed 1024 \
    --max-model-len 1049600 \
    --max-num-seqs 1 \
    --max-num-batched-tokens 8192 \
    --long-prefill-token-threshold 8192 \
    --enable-chunked-prefill \
    --no-enable-prefix-caching \
    --no-async-scheduling \
    --trust-remote-code \
    --quantization ascend \
    --gpu-memory-utilization 0.95 \
    --kv-cache-dtype auto \
    --language-model-only \
    --reasoning-parser minimax_m3 \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"enable_static_kernel":false,"fuse_norm_quant":false},"multistream_overlap_shared_expert":true,"weight_nz_mode":2,"enable_shared_expert_dp":false,"enable_flashcomm1":true}' \
    --kv-transfer-config '{
    "kv_connector": "MooncakeConnectorV1",
    "kv_buffer_device": "npu",
    "kv_role": "kv_producer",
    "kv_port": "36000",
    "kv_connector_extra_config": {
        "use_ascend_direct": true,
        "prefill": {
            "dp_size": 1,
            "tp_size": 8,
            "pp_size": 2,
            "pp_layer_partition": "30,30"
        },
        "decode": {
            "dp_size": 4,
            "tp_size": 8,
            "pp_size": 1
        }
    }
}'

2. Decode 节点:decode/run_dp_template.sh

#!/usr/bin/env bash
set -eo pipefail

unset http_proxy https_proxy ftp_proxy all_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY
source /usr/local/Ascend/ascend-toolkit/set_env.sh

# Set this host's communication IP, NIC and W8A8 weight path.
export LOCAL_IP="${LOCAL_IP:-<CURRENT_NODE_IP>}"
export NIC_NAME="${NIC_NAME:-<CURRENT_NODE_NIC>}"
export MODEL_PATH="${MODEL_PATH:-<MINIMAX_M3_W8A8_PATH>}"

# Both D hosts use the first D host's IP as the global DP coordinator.
export DP_SIZE=${DP_SIZE:-4}
export TP_SIZE=${TP_SIZE:-8}
export PP_SIZE=${PP_SIZE:-1}
export DP_RANK=${DP_RANK:-0}
export DP_ADDRESS="${DP_ADDRESS:-<FIRST_DECODE_NODE_IP>}"
export DP_RPC_PORT=${DP_RPC_PORT:-31422}
export SERVICE_PORT=${SERVICE_PORT:-31260}
export VISIBLE_DEVICES="${VISIBLE_DEVICES:-0,1,2,3,4,5,6,7}"

unset VLLM_PP_LAYER_PARTITION

export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=7200
export VLLM_USE_V2_MODEL_RUNNER=1
export HCCL_BUFFSIZE=64
export HCCL_IF_IP="$LOCAL_IP"
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_SOCKET_IFNAME="$NIC_NAME"
export ASCEND_ENABLE_USE_FABRIC_MEM=1
export ASCEND_RT_VISIBLE_DEVICES="$VISIBLE_DEVICES"
if [ -n "${MOONCAKE_LIB_DIRS:-}" ]; then
    export LD_LIBRARY_PATH="${MOONCAKE_LIB_DIRS}:${LD_LIBRARY_PATH:-}"
fi
export GLOO_SOCKET_IFNAME="$NIC_NAME"
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

# Explicit DP ranks enable external load balancing, which requires DP > 1.
DP_RANK_ARGS=()
if [ "$DP_SIZE" -gt 1 ]; then
    DP_RANK_ARGS=(--data-parallel-rank "$DP_RANK")
fi

vllm serve "$MODEL_PATH" \
    "${DP_RANK_ARGS[@]}" \
    --data-parallel-size-local 1 \
    --data-parallel-address "$DP_ADDRESS" \
    --data-parallel-rpc-port "$DP_RPC_PORT" \
    --enforce-eager \
    --compilation-config '{"cudagraph_mode":"NONE"}' \
    --host 0.0.0.0 \
    --port "$SERVICE_PORT" \
    --data-parallel-size "$DP_SIZE" \
    --tensor-parallel-size "$TP_SIZE" \
    --pipeline-parallel-size "$PP_SIZE" \
    --distributed-executor-backend mp \
    --served-model-name minimax-m3 \
    --enable-expert-parallel \
    --seed 1024 \
    --max-model-len 1049600 \
    --max-num-seqs 1 \
    --max-num-batched-tokens 8 \
    --long-prefill-token-threshold 8 \
    --enable-chunked-prefill \
    --no-enable-prefix-caching \
    --no-async-scheduling \
    --trust-remote-code \
    --quantization ascend \
    --gpu-memory-utilization 0.99 \
    --kv-cache-memory-bytes 47620449894 \
    --kv-cache-dtype auto \
    --language-model-only \
    --reasoning-parser minimax_m3 \
    --additional-config '{"enable_cpu_binding":true,"ascend_compilation_config":{"enable_static_kernel":false,"fuse_norm_quant":false},"multistream_overlap_shared_expert":false,"weight_nz_mode":2,"enable_shared_expert_dp":false,"enable_flashcomm1":true}' \
    --kv-transfer-config '{
    "kv_connector": "MooncakeConnectorV1",
    "kv_buffer_device": "npu",
    "kv_role": "kv_consumer",
    "kv_port": "36100",
    "kv_connector_extra_config": {
        "use_ascend_direct": true,
        "prefill": {
            "dp_size": 1,
            "tp_size": 8,
            "pp_size": 2,
            "pp_layer_partition": "30,30"
        },
        "decode": {
            "dp_size": 4,
            "tp_size": 8,
            "pp_size": 1
        }
    }
}'

上述命令保留了经过测试的 --kv-cache-memory-bytes 47620449894 值。如果针对其他环境进行调整,请参考以下指导:

  • Meaning and units: --kv-cache-memory-bytes specifies the KV cache budget per NPU worker, in bytes (1 GiB = 1,073,741,824 bytes). 47620449894 (about 44.35 GiB) is the tested example for this layout, not a fixed MiniMax-M3 requirement. When set explicitly, it overrides --gpu-memory-utilization for KV cache sizing; the two values are not added together.
  • Choosing a value: Keep the same value in both Decode templates. Size it for the least available NPU memory across the Decode workers after accounting for model weights, communication buffers, activations, and operator workspaces, with runtime headroom. Recheck this budget when changing hardware, TP/PP, KV dtype, or software versions.
  • Checking capacity: After startup, confirm the reported KV token capacity can support --max-model-len 1049600, then verify a 1M-token request. A value that is too large can cause an out-of-memory error; a value that is too small cannot hold the required context. If the required KV cache cannot fit, revise the parallel layout rather than assuming a smaller allocation still supports 1M tokens.

HCCL_BUFFSIZE is 256 on P and 64 on D.

5.4.2.3 Start the Services

1. Prefill 节点

cd ~/minimax-m3-1m-pd/prefill

export LOCAL_IP="<PREFILL_NODE_IP>"
export NIC_NAME="<PREFILL_NODE_NIC>"
export MODEL_PATH="<MINIMAX_M3_W8A8_PATH>"
export DP_SIZE=1
export TP_SIZE=8
export PP_SIZE=2
export LOCAL_DP_SIZE=1
export DP_RANK_START=0
export DP_ADDRESS="$LOCAL_IP"
export DP_RPC_PORT=6884
export SERVICE_PORT=31050

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

2. First Decode node: global DP ranks 0–1

cd ~/minimax-m3-1m-pd/decode

export LOCAL_IP="<FIRST_DECODE_NODE_IP>"
export NIC_NAME="<FIRST_DECODE_NODE_NIC>"
export MODEL_PATH="<MINIMAX_M3_W8A8_PATH>"
export DP_SIZE=4
export TP_SIZE=8
export PP_SIZE=1
export LOCAL_DP_SIZE=2
export DP_RANK_START=0
export DP_ADDRESS="$LOCAL_IP"
export DP_RPC_PORT=31422
export SERVICE_PORT=31260

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

This starts the DP coordinator and the first two engines. Keep this command running and start the second D host from another shell; do not wait for the first D host to become fully healthy before starting the second.

3. Second Decode node: global DP ranks 2–3

cd ~/minimax-m3-1m-pd/decode

export LOCAL_IP="<SECOND_DECODE_NODE_IP>"
export NIC_NAME="<SECOND_DECODE_NODE_NIC>"
export MODEL_PATH="<MINIMAX_M3_W8A8_PATH>"
export DP_SIZE=4
export TP_SIZE=8
export PP_SIZE=1
export LOCAL_DP_SIZE=2
export DP_RANK_START=2
export DP_ADDRESS="<FIRST_DECODE_NODE_IP>"
export DP_RPC_PORT=31422
export SERVICE_PORT=31260

python launch_online_dp.py \
    --dp-size "$DP_SIZE" --tp-size "$TP_SIZE" --pp-size "$PP_SIZE" \
    --dp-size-local "$LOCAL_DP_SIZE" --dp-rank-start "$DP_RANK_START" \
    --dp-address "$DP_ADDRESS" --dp-rpc-port "$DP_RPC_PORT" \
    --vllm-start-port "$SERVICE_PORT"

The launcher's device assignment is:

Host / DP rank Local logical NPUs API port
Prefill / 0 0–15 31050
First D / 0 0–7 31260
First D / 1 8–15 31261
Second D / 2 0–7 31260
Second D / 3 8–15 31261

Wait for all five APIs to report Application startup complete and return HTTP 200 from /health. Run the two D launch commands concurrently until their shared initialization completes.

4. Proxy: one Prefill endpoint and four Decode endpoints

Use load_balance_proxy_server_example.py from Section 5.3.1. Run it in a container that can reach all five engine APIs, for example on P. Replace all three IP placeholders. This topology has four D endpoints; use the mapping below rather than the eight-endpoint mapping for the ordinary-context A3 example.

export PREFILL_IP="<PREFILL_NODE_IP>"
export DECODE0_IP="<FIRST_DECODE_NODE_IP>"
export DECODE1_IP="<SECOND_DECODE_NODE_IP>"

unset http_proxy https_proxy all_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY

python load_balance_proxy_server_example.py \
    --port 18919 \
    --host 0.0.0.0 \
    --prefiller-hosts "$PREFILL_IP" \
    --prefiller-ports 31050 \
    --decoder-hosts \
        "$DECODE0_IP" "$DECODE0_IP" \
        "$DECODE1_IP" "$DECODE1_IP" \
    --decoder-ports \
        31260 31261 31260 31261

Send client requests to http://<PROXY_HOST_IP>:18919/v1/chat/completions. The 0.0.0.0 value is the proxy's listen address.

5.4.2.4 Verification
  1. Check all five /health endpoints and each engine's reported KV token capacity before sending a long request.
  2. Send a short text request through the proxy and verify the generated answer.
  3. Prepare a prompt containing exactly 1,048,576 tokens using the deployed model's tokenizer. Keep input plus requested output within 1049600; the configuration reserves up to 1024 output tokens for a full 1M-token input.
  4. Verify the API's input-token count and the requested retrieval answers. Check the Prefill prompt-token counter and the receiving Decode rank's external-prefix-cache hit counter to confirm the long prefix was transferred. Decode should not recompute the complete 1M prefill.

The recorded A3 run used vLLM 0.30.0+empty and vLLM Ascend 0.30.0rc2.dev166+g7672a5b50. All five engines were healthy, and one real request with 1,048,576 input tokens and 33 output tokens correctly retrieved three random values placed across the context. P processed 1,048,576 prompt tokens; the receiving D rank reported the same transferred-prefix hit increment and 33 iteration tokens, with no preemption. This validates one 1M-context PD request. It does not establish four simultaneous 1M requests or an AISBench performance result. The templates retain that run's serving parameters and KV topology, with environment exports normalized to this guide. These normalized snippets have not been rerun on NPUs, and the existing validation container was not fully audited for pre-existing changes. Recheck compatibility when reproducing on a different build.

5.5 多模态与 ViT DP(可选)

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

MiniMax-M3 的图像和视频输入共享同一个 Vision Tower。如果服务只需要一种模态,请将未使用的模态显式设置为 0;例如,仅图像服务使用 {"image":1,"video":0},仅视频服务使用 {"image":0,"video":1}。只要图像或视频任一保持启用,共享的 Vision Tower 就会被保留。将未使用的模态设置为 0 比省略它更清晰,因为省略的模态仍可能参与多模态容量和性能分析规划。

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

--mm-encoder-tp-mode data

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

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

# one video
--limit-mm-per-prompt '{"image":0,"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。对于基准测试运行,请遵循数据集协议。

FLASHCOMM1 与 --language-model-only 可以同时启用以用于纯文本服务,包括 第 5.4 节 中的 A3 1M 上下文示例。通过 additional_config.enable_flashcomm1 启用 FLASHCOMM1。--language-model-only 标志为可选项,对于多模态输入必须省略。

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

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

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):

Set base_url to the deployed API address, including /v1. The example below uses the Section 5.1 service port; for PD disaggregation, use the proxy address and port instead.

from openai import OpenAI

client = OpenAI(api_key="EMPTY", base_url="http://localhost:11223/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> 分隔符将模型输出拆分为推理和内容:

export VLLM_USE_V2_MODEL_RUNNER=1

vllm serve ${WEIGHT_PATH} \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --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.reasoning 和 DeltaMessage.content 逐 token 增量流式传输。
  • Token 计数:<mm:think> 块内的推理 token 会被正确计数。

6.3 工具调用解析器

MiniMax-M3 uses a namespace-delimited XML format for tool calls. Enable it with --tool-call-parser minimax_m3.

6.3.1 服务器配置

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

export VLLM_USE_V2_MODEL_RUNNER=1

vllm serve ${WEIGHT_PATH} \
  --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
  --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/zip 的 shipping)。
  • 模式感知类型转换:根据函数的 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 is 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,"video":0}'。在客户端将 ${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

预期结果:HTTP 200 响应,JSON 响应体包含非空的 choices 以及描述该图像的生成文本。

7.3 单个视频

以启用视频输入的方式启动服务,例如 --limit-mm-per-prompt '{"image":0,"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
  }'

预期结果:HTTP 200 响应,JSON 响应体包含非空的 choices 以及描述该视频内容的生成文本。

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

预期结果:HTTP 200 响应,JSON 响应体包含非空的 choices 以及分别描述图像和视频并说明二者是否相关的生成文本。

8 精度评估

下表中的上下文限制条目使用统一的服务设置 max-model-len=133000。精度分数沿用现有评测结果,未随本次文档更新重新测量。

8.1 使用 AISBench

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

8.2 文本评估

数据集 硬件 分数 max-model-len max-num-seqs max_out_len batch_size generation_kwargs
GSM8K 8 H20 (96GB × 8) 96.72 133000 16 49152 16 temperature=1.0, top_p=0.95
GSM8K 8 Atlas 800I A3 (64GB × 16) 96.36 133000 16 9500 20 temperature=1.0, top_p=0.95
AIME2025 8 H20 (96GB × 8) 95@repeat4 - - - - -
AIME2025 8 Atlas 800I A3 (64GB × 16) 93.3@repeat2 133000 32 65536 8 temperature=1.0, top_p=0.95
GPQA-Diamond 8 H20 (96GB × 8) 92.42 133000 64 75776 8 temperature=0.6, top_p=0.95
GPQA-Diamond 8 Atlas 800I A3 (64GB × 16) 92.42 263000 32 131072 64 temperature=1.0, top_p=0.95
GPQA-Diamond 8 950DT products (96GB × 8) 92.9 133000 128 131072 128 temperature=1.0, top_p=0.95
MMMU-pro 8 950DT products (96GB × 8) 78.9 133000 128 131072 50 temperature=1.0, top_p=0.95

8.3 多模态评估

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

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

Obtain the video files and question/answer annotations from the Video-MME dataset, linked by the official Video-MME repository. Prepare the chunk1/chunk2 videos locally and set the dataset config's path and video_path to the corresponding metadata and extracted video directories, as described below.

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

  • 使用 --limit-mm-per-prompt '{"image":0,"video":1}' 启动服务;
  • 不要设置 media_io_kwargs.video.num_frames,这样 vLLM 会使用默认的 32 个采样帧;
  • 使用 max-model-len=133000 和 max_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 133000 512 --limit-mm-per-prompt '{"image":1,"video":0}' temperature=1.0, top_p=0.95 70.82
TextVQA Image AISBench NPU disabled 133000 512 --limit-mm-per-prompt '{"image":1,"video":0}' temperature=1.0, top_p=0.95 72.75
Video-MME chunk1+chunk2 Video AISBench GPU - 133000 8192 --limit-mm-per-prompt '{"image":0,"video":1}', default 32 frames temperature=1.0, top_p=0.95 73.41
Video-MME chunk1+chunk2 Video AISBench NPU - 133000 8192 --limit-mm-per-prompt '{"image":0,"video":1}', default 32 frames temperature=1.0, top_p=0.95 74.21

9 性能调优

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

9.1 推荐配置

The recommended configurations are the same as those specified in Chapter 5 Online Service Deployment.

9.2 调优指南

9.2.1 通用调优参考

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

请参阅特性矩阵获取详细的特性描述。

9.2.2 针对特定工作负载的调优

可将以下调整作为目标工作负载的起点。每次更改后请重新检查内存使用、延迟和吞吐量。

场景 参数 调整示例 调优目标
受设备内存限制、KV 缓存容量制约并发度的工作负载 HCCL_BUFFSIZE 从 2048 降低到 1024(MB)。 减少 HCCL 通信缓冲区内存占用,为 KV 缓存腾出更多设备内存,从而支持更高的并发度。
短输入工作负载,例如 4K 输入 token --max-num-batched-tokens 从 32768 降低到 8192。 使用更小的单次迭代 token 预算以降低内存压力,并调优短序列性能。

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
    }
  }
}