Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11878,3 +11878,64 @@ minimaxm3-fp8-b300-vllm-mtp:
- { tp: 8, ep: 8, conc-start: 1, conc-end: 256, spec-decoding: mtp }
- { tp: 4, conc-start: 1, conc-end: 64, spec-decoding: mtp }
- { tp: 8, ep: 8, dp-attn: true, conc-start: 128, conc-end: 256, spec-decoding: mtp }

# EAGLE3 speculative-decoding (spec-decoding: mtp) variant of
# minimaxm3-fp8-h200-vllm (PR #1731), pairing MiniMaxAI/MiniMax-M3-MXFP8 with
# the Inferact/MiniMax-M3-EAGLE3 draft head (3 speculative tokens, drafter
# pinned to FLASH_ATTN since the head is MHA and FlashInfer needs GQA/MQA at
# page size 128). Search space mirrors the non-MTP entry trimmed at the
# extreme-concurrency end, per the dsv4-fp4-b300-vllm-mtp / minimaxm3 b300-mtp
# precedent: spec decode pays off at low/mid concurrency while acceptance
# dilutes in big batches, and the draft weights + draft KV shave headroom.
minimaxm3-fp8-h200-vllm-mtp:
image: vllm/vllm-openai:minimax-m3
model: MiniMaxAI/MiniMax-M3-MXFP8
model-prefix: minimaxm3
runner: h200
precision: fp8
framework: vllm
multinode: false
scenarios:
fixed-seq-len:
- isl: 1024
osl: 1024
search-space:
- { tp: 4, conc-start: 4, conc-end: 64, spec-decoding: mtp }
- { tp: 4, ep: 4, conc-start: 128, conc-end: 256, spec-decoding: mtp }
- { tp: 8, conc-start: 4, conc-end: 128, spec-decoding: mtp }
- { tp: 8, ep: 8, conc-start: 256, conc-end: 256, spec-decoding: mtp }
- { tp: 8, ep: 8, dp-attn: true, conc-start: 256, conc-end: 512, spec-decoding: mtp }
- isl: 8192
osl: 1024
search-space:
- { tp: 4, conc-start: 4, conc-end: 32, spec-decoding: mtp }
- { tp: 8, conc-start: 4, conc-end: 128, spec-decoding: mtp }
- { tp: 8, ep: 8, conc-start: 256, conc-end: 256, spec-decoding: mtp }
- { tp: 8, ep: 8, dp-attn: true, conc-start: 256, conc-end: 256, spec-decoding: mtp }

# EAGLE3 speculative-decoding (spec-decoding: mtp) variant of
# minimaxm3-fp8-h100-vllm (PR #1731). Same TP8-only layout (H100 80 GB has no
# room below TP8 once ~56 GB of weights land per GPU) plus the
# Inferact/MiniMax-M3-EAGLE3 draft head (3 speculative tokens, FLASH_ATTN
# drafter). DEP stays omitted as on the non-MTP entry — KV-cache init already
# failed at high conc, and the draft head + draft KV only tighten it further.
minimaxm3-fp8-h100-vllm-mtp:
image: vllm/vllm-openai:minimax-m3
model: MiniMaxAI/MiniMax-M3-MXFP8
model-prefix: minimaxm3
runner: h100
precision: fp8
framework: vllm
multinode: false
scenarios:
fixed-seq-len:
- isl: 1024
osl: 1024
search-space:
- { tp: 8, conc-start: 4, conc-end: 128, spec-decoding: mtp }
- { tp: 8, ep: 8, conc-start: 256, conc-end: 256, spec-decoding: mtp }
- isl: 8192
osl: 1024
search-space:
- { tp: 8, conc-start: 4, conc-end: 64, spec-decoding: mtp }
- { tp: 8, ep: 8, conc-start: 128, conc-end: 256, spec-decoding: mtp }
157 changes: 157 additions & 0 deletions benchmarks/single_node/fixed_seq_len/minimaxm3_fp8_h100_mtp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env bash

# MiniMax-M3 MXFP8 H100 single-node vLLM recipe with EAGLE3 speculative
# decoding — the repo's spec-decoding=mtp variant of minimaxm3_fp8_h100.sh
# (https://recipes.vllm.ai/MiniMaxAI/MiniMax-M3). Adds the
# Inferact/MiniMax-M3-EAGLE3 draft head via --speculative-config with 3
# speculative tokens. Everything else keeps the non-MTP serve shape:
# --block-size 128 is mandatory (MSA sparse_block_size is 128), the benchmark
# is text-only so --language-model-only frees the vision encoder's VRAM, and
# the MXFP8 MoE runs through vLLM's Hopper-compatible backends. H100 is
# TP8-only (~56 GB of weights per 80 GB GPU below TP8 leaves no room).
#
# The drafter is pinned to FLASH_ATTN: the EAGLE3 head is MHA, and FlashInfer
# only supports page size 128 through its trtllm-gen kernel, which requires
# GQA/MQA — engine init dies in FlashInferMetadataBuilder otherwise (hit on
# the B300 MTP canary). FLASH_ATTN takes any multiple-of-16 block size, so
# the mandatory 128 is fine for the draft.

source "$(dirname "$0")/../../benchmark_lib.sh"

check_env_vars \
MODEL \
TP \
EP_SIZE \
DP_ATTENTION \
CONC \
ISL \
OSL \
MAX_MODEL_LEN \
RANDOM_RANGE_RATIO \
RESULT_FILENAME

DRAFT_MODEL="Inferact/MiniMax-M3-EAGLE3"

if [[ -n "$SLURM_JOB_ID" ]]; then
echo "JOB $SLURM_JOB_ID running on $SLURMD_NODENAME"
fi

nvidia-smi

# The shared HF cache lives on a network FS; concurrent day-zero downloads of
# the same ~444 GB checkpoint from sibling nodes hit huggingface_hub's
# WeakFileLock "[Errno 116] Stale file handle" race. Retry the download (it
# resumes), then serve with HF_HUB_OFFLINE=1 so vllm's snapshot_download does
# a lock-free local-cache read instead of re-contending the lock files. The
# EAGLE3 draft is fetched the same way so the offline serve finds it cached.
SERVE_OFFLINE=()
if [[ "$MODEL" != /* ]]; then
for attempt in 1 2 3 4 5; do
hf download "$MODEL" && break
if [ "$attempt" = 5 ]; then echo "hf download failed after $attempt attempts" >&2; exit 1; fi
echo "hf download attempt $attempt failed; retrying in 60s" >&2
sleep 60
done
for attempt in 1 2 3 4 5; do
hf download "$DRAFT_MODEL" && break
if [ "$attempt" = 5 ]; then echo "hf download failed after $attempt attempts" >&2; exit 1; fi
echo "hf download attempt $attempt failed; retrying in 60s" >&2
sleep 60
done
SERVE_OFFLINE=(env HF_HUB_OFFLINE=1)
fi

SERVER_LOG=/workspace/server.log

export PYTHONNOUSERSITE=1
# ~444 GB of MXFP8 weights off shared FS; engine startup can exceed the
# default 600s readiness window.
export VLLM_ENGINE_READY_TIMEOUT_S=3600

if [ "${DP_ATTENTION}" = "true" ]; then
PARALLEL_ARGS="--tensor-parallel-size=1 --data-parallel-size=$TP --enable-expert-parallel"
elif [ "$EP_SIZE" -gt 1 ]; then
PARALLEL_ARGS="--tensor-parallel-size=$TP --enable-expert-parallel"
else
PARALLEL_ARGS="--tensor-parallel-size=$TP"
fi

# use 3 speculative tokens for all configs for now
NUM_SPEC_TOKENS=3

# Fixed-seq-len runs don't need graphs past the decode step's token count:
# with spec decoding every running request contributes 1 + NUM_SPEC_TOKENS
# tokens per step, so capture up to the next power of two >=
# CONC * (1 + NUM_SPEC_TOKENS), capped at vLLM's 2048 ceiling.
CAPTURE_SIZE=4
while (( CAPTURE_SIZE < CONC * (1 + NUM_SPEC_TOKENS) )); do CAPTURE_SIZE=$((CAPTURE_SIZE * 2)); done
(( CAPTURE_SIZE > 2048 )) && CAPTURE_SIZE=2048

# H100 DEP is weights-bound: every DP rank replicates the ~20 GB
# BF16-dequantized attention/dense/embedding weights next to its ~52 GB
# expert shard, and at gmu 0.90 KV-cache init fails outright at high conc
# (sweep 27441767143, conc 256: "No available memory for the cache blocks").
# Claw back headroom: higher gpu-memory-utilization, and decode graphs
# capped at 2x the per-rank batch share (spec-token-scaled) instead of the
# full CONC bound.
GMU=0.90
if [ "${DP_ATTENTION}" = "true" ]; then
GMU=0.94
PER_RANK_BOUND=$(( 2 * ((CONC + TP - 1) / TP) * (1 + NUM_SPEC_TOKENS) ))
CAPTURE_SIZE=4
while (( CAPTURE_SIZE < PER_RANK_BOUND )); do CAPTURE_SIZE=$((CAPTURE_SIZE * 2)); done
(( CAPTURE_SIZE > 2048 )) && CAPTURE_SIZE=2048
fi

if [ "${EVAL_ONLY}" = "true" ]; then
setup_eval_context
MAX_MODEL_LEN="$EVAL_MAX_MODEL_LEN"
fi
# Start GPU monitoring (power, temperature, clocks every second)
start_gpu_monitor

set -x
"${SERVE_OFFLINE[@]}" vllm serve $MODEL --port $PORT \
$PARALLEL_ARGS \
--gpu-memory-utilization $GMU \
--max-model-len $MAX_MODEL_LEN \
--block-size 128 \
--language-model-only \
--max-cudagraph-capture-size $CAPTURE_SIZE \
--max-num-batched-tokens "$((ISL * 2 ))" \
--speculative-config "{\"method\": \"eagle3\", \"model\": \"$DRAFT_MODEL\", \"num_speculative_tokens\": $NUM_SPEC_TOKENS, \"attention_backend\": \"FLASH_ATTN\"}" \
--stream-interval 20 --no-enable-prefix-caching \
--trust-remote-code > $SERVER_LOG 2>&1 &

SERVER_PID=$!

# Wait for server to be ready
wait_for_server_ready --port "$PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID"

pip install -q datasets pandas

# Spec-decode acceptance rate degrades on raw random tokens; route prompts
# through the chat template as the other MTP recipes do.
run_benchmark_serving \
--model "$MODEL" \
--port "$PORT" \
--backend vllm \
--input-len "$ISL" \
--output-len "$OSL" \
--random-range-ratio "$RANDOM_RANGE_RATIO" \
--num-prompts "$((CONC * 10))" \
--max-concurrency "$CONC" \
--result-filename "$RESULT_FILENAME" \
--result-dir /workspace/ \
--trust-remote-code \
--use-chat-template

# After throughput, run evaluation only if RUN_EVAL is true
if [ "${RUN_EVAL}" = "true" ]; then
run_eval --framework lm-eval --port "$PORT"
append_lm_eval_summary
fi

# Stop GPU monitoring
stop_gpu_monitor
set +x
140 changes: 140 additions & 0 deletions benchmarks/single_node/fixed_seq_len/minimaxm3_fp8_h200_mtp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/usr/bin/env bash

# MiniMax-M3 MXFP8 H200 single-node vLLM recipe with EAGLE3 speculative
# decoding — the repo's spec-decoding=mtp variant of minimaxm3_fp8_h200.sh
# (https://recipes.vllm.ai/MiniMaxAI/MiniMax-M3). Adds the
# Inferact/MiniMax-M3-EAGLE3 draft head via --speculative-config with 3
# speculative tokens. Everything else keeps the non-MTP serve shape:
# --block-size 128 is mandatory (MSA sparse_block_size is 128), the benchmark
# is text-only so --language-model-only frees the vision encoder's VRAM, and
# the MXFP8 MoE runs through vLLM's Hopper-compatible backends.
#
# The drafter is pinned to FLASH_ATTN: the EAGLE3 head is MHA, and FlashInfer
# only supports page size 128 through its trtllm-gen kernel, which requires
# GQA/MQA — engine init dies in FlashInferMetadataBuilder otherwise (hit on
# the B300 MTP canary). FLASH_ATTN takes any multiple-of-16 block size, so
# the mandatory 128 is fine for the draft.

source "$(dirname "$0")/../../benchmark_lib.sh"

check_env_vars \
MODEL \
TP \
EP_SIZE \
DP_ATTENTION \
CONC \
ISL \
OSL \
MAX_MODEL_LEN \
RANDOM_RANGE_RATIO \
RESULT_FILENAME

DRAFT_MODEL="Inferact/MiniMax-M3-EAGLE3"

if [[ -n "$SLURM_JOB_ID" ]]; then
echo "JOB $SLURM_JOB_ID running on $SLURMD_NODENAME"
fi

nvidia-smi

# The shared HF cache lives on a network FS; concurrent day-zero downloads of
# the same ~444 GB checkpoint from sibling nodes hit huggingface_hub's
# WeakFileLock "[Errno 116] Stale file handle" race. Retry the download (it
# resumes), then serve with HF_HUB_OFFLINE=1 so vllm's snapshot_download does
# a lock-free local-cache read instead of re-contending the lock files. The
# EAGLE3 draft is fetched the same way so the offline serve finds it cached.
SERVE_OFFLINE=()
if [[ "$MODEL" != /* ]]; then
for attempt in 1 2 3 4 5; do
hf download "$MODEL" && break
if [ "$attempt" = 5 ]; then echo "hf download failed after $attempt attempts" >&2; exit 1; fi
echo "hf download attempt $attempt failed; retrying in 60s" >&2
sleep 60
done
for attempt in 1 2 3 4 5; do
hf download "$DRAFT_MODEL" && break
if [ "$attempt" = 5 ]; then echo "hf download failed after $attempt attempts" >&2; exit 1; fi
echo "hf download attempt $attempt failed; retrying in 60s" >&2
sleep 60
done
SERVE_OFFLINE=(env HF_HUB_OFFLINE=1)
fi

SERVER_LOG=/workspace/server.log

export PYTHONNOUSERSITE=1
# ~444 GB of MXFP8 weights off shared FS; engine startup can exceed the
# default 600s readiness window.
export VLLM_ENGINE_READY_TIMEOUT_S=3600

if [ "${DP_ATTENTION}" = "true" ]; then
PARALLEL_ARGS="--tensor-parallel-size=1 --data-parallel-size=$TP --enable-expert-parallel"
elif [ "$EP_SIZE" -gt 1 ]; then
PARALLEL_ARGS="--tensor-parallel-size=$TP --enable-expert-parallel"
else
PARALLEL_ARGS="--tensor-parallel-size=$TP"
fi

# use 3 speculative tokens for all configs for now
NUM_SPEC_TOKENS=3

# Fixed-seq-len runs don't need graphs past the decode step's token count:
# with spec decoding every running request contributes 1 + NUM_SPEC_TOKENS
# tokens per step, so capture up to the next power of two >=
# CONC * (1 + NUM_SPEC_TOKENS), capped at vLLM's 2048 ceiling.
CAPTURE_SIZE=4
while (( CAPTURE_SIZE < CONC * (1 + NUM_SPEC_TOKENS) )); do CAPTURE_SIZE=$((CAPTURE_SIZE * 2)); done
(( CAPTURE_SIZE > 2048 )) && CAPTURE_SIZE=2048

if [ "${EVAL_ONLY}" = "true" ]; then
setup_eval_context
MAX_MODEL_LEN="$EVAL_MAX_MODEL_LEN"
fi
# Start GPU monitoring (power, temperature, clocks every second)
start_gpu_monitor

set -x
"${SERVE_OFFLINE[@]}" vllm serve $MODEL --port $PORT \
$PARALLEL_ARGS \
--gpu-memory-utilization 0.90 \
--max-model-len $MAX_MODEL_LEN \
--block-size 128 \
--language-model-only \
--max-cudagraph-capture-size $CAPTURE_SIZE \
--max-num-batched-tokens "$((ISL * 2 ))" \
--speculative-config "{\"method\": \"eagle3\", \"model\": \"$DRAFT_MODEL\", \"num_speculative_tokens\": $NUM_SPEC_TOKENS, \"attention_backend\": \"FLASH_ATTN\"}" \
--stream-interval 20 --no-enable-prefix-caching \
--trust-remote-code > $SERVER_LOG 2>&1 &

SERVER_PID=$!

# Wait for server to be ready
wait_for_server_ready --port "$PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID"

pip install -q datasets pandas

# Spec-decode acceptance rate degrades on raw random tokens; route prompts
# through the chat template as the other MTP recipes do.
run_benchmark_serving \
--model "$MODEL" \
--port "$PORT" \
--backend vllm \
--input-len "$ISL" \
--output-len "$OSL" \
--random-range-ratio "$RANDOM_RANGE_RATIO" \
--num-prompts "$((CONC * 10))" \
--max-concurrency "$CONC" \
--result-filename "$RESULT_FILENAME" \
--result-dir /workspace/ \
--trust-remote-code \
--use-chat-template

# After throughput, run evaluation only if RUN_EVAL is true
if [ "${RUN_EVAL}" = "true" ]; then
run_eval --framework lm-eval --port "$PORT"
append_lm_eval_summary
fi

# Stop GPU monitoring
stop_gpu_monitor
set +x
12 changes: 12 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3684,3 +3684,15 @@
- "Add MiniMax-M2.5 NVFP4 B200 TensorRT-LLM single-node benchmark (1k1k and 8k1k)"
- "Image: nvcr.io#nvidia/tensorrt-llm/release:1.3.0rc18"
pr-link: https://github.com/NVIDIA/InferenceMAX/pull/1722

- config-keys:
- minimaxm3-fp8-h200-vllm-mtp
- minimaxm3-fp8-h100-vllm-mtp
description:
- "Initial submission: MiniMax-M3 MXFP8 H200 + H100 vLLM benchmarks with EAGLE3 speculative decoding (target: MiniMaxAI/MiniMax-M3-MXFP8, draft: Inferact/MiniMax-M3-EAGLE3, 3 speculative tokens) — spec-decoding=mtp variants of the day-zero H200/H100 recipes (PR #1731)"
- "Image: vllm/vllm-openai:minimax-m3 (same m3_release-branch build as the non-MTP entries)"
- "Serve shape follows the non-MTP H200/H100 scripts (--block-size 128, --language-model-only, HF_HUB_OFFLINE serve to dodge the shared-FS download-lock race); cudagraph capture scaled to CONC * (1 + spec tokens); prompts routed through the chat template for realistic acceptance"
- "Drafter pinned to FLASH_ATTN via speculative-config attention_backend: the EAGLE3 head is MHA and FlashInfer only supports the mandatory page size 128 through its GQA-only trtllm-gen kernel"
- "H200 layouts: TP4 / TP8 (latency), TP4+EP4 / TP8+EP8 (TEP), TP8+EP8 dp-attn (DEP) across 1k1k and 8k1k — non-MTP search space trimmed at the extreme-concurrency end. H100 is TP8-only (no room below TP8 at ~56 GB weights/GPU); DEP omitted (KV-cache init fails at high conc, draft only tightens it)"
- "Adds SPEC_SUFFIX to the three H100 launchers (cw, cr, dgxc-slurm) so spec-decoding=mtp routes to the _mtp script — they hardcoded _h100.sh and never gained the _mtp routing the H200 launchers have had since #392"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/1739
6 changes: 5 additions & 1 deletion runners/launch_h100-cr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ PORT=8888

server_name="bmk-server"

# Route spec-decoding=mtp configs to the _mtp benchmark script (parity with
# the h200 launchers, which have carried SPEC_SUFFIX since #392).
SPEC_SUFFIX=$([[ "$SPEC_DECODING" == "mtp" ]] && printf '_mtp' || printf '')

set -x
docker run --rm --network=host --name=$server_name \
--runtime=nvidia --gpus=all --ipc=host --privileged --shm-size=16g --ulimit memlock=-1 --ulimit stack=67108864 \
Expand All @@ -15,4 +19,4 @@ docker run --rm --network=host --name=$server_name \
-e PYTHONPYCACHEPREFIX=/tmp/pycache/ -e TORCH_CUDA_ARCH_LIST="9.0" -e CUDA_DEVICE_ORDER=PCI_BUS_ID -e CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7" \
--entrypoint=/bin/bash \
$IMAGE \
benchmarks/single_node/${SCENARIO_SUBDIR}"${EXP_NAME%%_*}_${PRECISION}_h100.sh"
benchmarks/single_node/${SCENARIO_SUBDIR}"${EXP_NAME%%_*}_${PRECISION}_h100${SPEC_SUFFIX}.sh"
Loading
Loading