Skip to content
Open
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
225 changes: 225 additions & 0 deletions benchmarks/single_node/agentic/minimaxm3_fp8_h100_mtp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#!/usr/bin/env bash
set -euo pipefail
set -x

# MiniMax-M3 MXFP8 H100 AgentX (agentic-coding) recipe with EAGLE3 speculative
# decoding — the spec-decoding=mtp variant of agentic/minimaxm3_fp8_h100.sh.
# Everything outside the speculative block mirrors the non-MTP agentic sibling
# (Mooncake host-DRAM KV offload, --block-size 128, --language-model-only,
# --kv-cache-dtype fp8, TRITON_ATTN, minimax_m3 parsers, vllm-router for
# DP-attention), so the spec-decode delta is readable at equal concurrency.
#
# Speculative config: the current Inferact/MiniMax-M3-EAGLE3-GQA draft head
# with three speculative tokens and the committed thinking-on golden AL.
#
# The drafter is pinned to FLASH_ATTN, as on every CUDA MiniMax-M3 MTP recipe:
# the EAGLE3 head is MHA and FlashInfer only serves page size 128 through its
# trtllm-gen kernel, which requires GQA/MQA. FLASH_ATTN accepts any
# multiple-of-16 block size, so the mandatory 128 is fine for the draft. (The
# ROCm recipes need no pin because their server runs TRITON_ATTN throughout.)
#
# Throughput runs pin synthetic acceptance to the committed golden AL; the
# EVAL_ONLY accuracy run keeps real target verification. See SYNTHETIC_ACCEPT_LEN.

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

check_env_vars MODEL TP CONC KV_OFFLOADING TOTAL_CPU_DRAM_GB RESULT_DIR DURATION EP_SIZE DP_ATTENTION

DRAFT_MODEL="Inferact/MiniMax-M3-EAGLE3-GQA"

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

if [[ -n "${MODEL_PATH:-}" ]]; then
if [[ ! -d "$MODEL_PATH" || -z "$(ls -A "$MODEL_PATH" 2>/dev/null)" ]]; then
hf download "$MODEL" --local-dir "$MODEL_PATH"
fi
else
hf download "$MODEL"
export MODEL_PATH="$MODEL"
fi

# The EAGLE3 draft is never pre-staged next to the target checkpoint; fetch it
# into the shared HF cache. That cache is a network FS where concurrent
# day-zero downloads hit huggingface_hub's WeakFileLock "[Errno 116] Stale file
# handle" race, so retry (the download resumes) as the fixed-seq-len MTP
# recipes do.
for attempt in 1 2 3 4 5; do
hf download "$DRAFT_MODEL" && break
if [ "$attempt" = 5 ]; then echo "hf download of $DRAFT_MODEL failed after $attempt attempts" >&2; exit 1; fi
echo "hf download attempt $attempt failed; retrying in 60s" >&2
sleep 60
done
nvidia-smi

export WEKA_LOADER_OVERRIDE=semianalysis_cc_traces_weka_062126
resolve_trace_source
install_agentic_deps

export VLLM_ENGINE_READY_TIMEOUT_S=3600
export PYTHONNOUSERSITE=1

SERVER_LOG="$RESULT_DIR/server.log"
ROUTER_LOG="$RESULT_DIR/router.log"
MOONCAKE_MASTER_LOG="$RESULT_DIR/mooncake_master.log"
mkdir -p "$RESULT_DIR"

OFFLOAD_ARGS=()
MODEL_CPU_OFFLOAD_GB=26
MODEL_CHECKPOINT_PAGE_CACHE_GIB=414
MOONCAKE_LOCAL_BUFFER_GIB=4
case "${KV_OFFLOAD_BACKEND:-}" in
"")
require_agentic_kv_offload_none
;;
mooncake)
require_agentic_kv_offload_backend mooncake
TOTAL_CPU_DRAM_GIB=$((TOTAL_CPU_DRAM_GB * 1000000000 / 1073741824))
PER_RANK_GIB=$(((TOTAL_CPU_DRAM_GIB - MODEL_CHECKPOINT_PAGE_CACHE_GIB) / TP - MODEL_CPU_OFFLOAD_GB - MOONCAKE_LOCAL_BUFFER_GIB))
if (( PER_RANK_GIB <= 0 )); then
echo "Error: CPU DRAM budget is too small for checkpoint cache, model, and KV offload" >&2
exit 1
fi
MOONCAKE_VERSION=0.3.11.post1
agentic_pip_install --quiet --no-cache-dir --no-deps \
--force-reinstall "mooncake-transfer-engine-cuda13==$MOONCAKE_VERSION"
python3 -c "from mooncake.store import MooncakeDistributedStore" >/dev/null
MOONCAKE_MASTER_PORT=$((PORT + 12000))
MOONCAKE_CONFIG_PATH="$RESULT_DIR/mooncake_config.json"
cat > "$MOONCAKE_CONFIG_PATH" <<EOF
{
"mode": "embedded",
"metadata_server": "P2PHANDSHAKE",
"master_server_address": "127.0.0.1:$MOONCAKE_MASTER_PORT",
"global_segment_size": "${PER_RANK_GIB}GB",
"local_buffer_size": "${MOONCAKE_LOCAL_BUFFER_GIB}GB",
"protocol": "rdma",
"device_name": "",
"enable_offload": false
}
EOF
export MOONCAKE_CONFIG_PATH PYTHONHASHSEED=0 MC_SLICE_SIZE=1048576 MC_WORKERS_PER_CTX=4
export MC_ENABLE_DEST_DEVICE_AFFINITY=1
mooncake_master --port "$MOONCAKE_MASTER_PORT" \
--eviction_high_watermark_ratio=0.80 \
--eviction_ratio=0.10 > "$MOONCAKE_MASTER_LOG" 2>&1 &
MOONCAKE_MASTER_PID=$!
sleep 2
kill -0 "$MOONCAKE_MASTER_PID"
OFFLOAD_ARGS=(
--kv-transfer-config
'{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_connector_extra_config":{"load_async":true}}'
)
;;
*)
echo "Error: unsupported KV_OFFLOAD_BACKEND='$KV_OFFLOAD_BACKEND'" >&2
exit 1
;;
esac

PARALLEL_ARGS=(--tensor-parallel-size "$TP" --data-parallel-size 1)
if [[ "$DP_ATTENTION" == "true" ]]; then
PARALLEL_ARGS=(--tensor-parallel-size 1 --data-parallel-size "$TP")
fi

EP_ARGS=()
if (( EP_SIZE > 1 )); then
EP_ARGS=(--enable-expert-parallel)
fi

VLLM_BACKEND_PORT="$PORT"
if [[ "$DP_ATTENTION" == "true" ]]; then
VLLM_BACKEND_PORT=$((PORT + 1))
export AIPERF_HTTP_X_SESSION_ID_FROM_CORRELATION_ID=1
agentic_pip_install --quiet 'vllm-router==0.1.14'
fi

# The public endpoint is the router for DEP, so explicitly scrape the engine
# endpoint. Pure TP/TEP deduplicates this URL against the automatic scrape.
export AIPERF_SERVER_METRICS_URLS="http://localhost:${VLLM_BACKEND_PORT}/metrics"
export AIPERF_REQUIRED_SERVER_METRIC_PREFIX="vllm:"

# use 3 speculative tokens for all configs, matching the MiniMax-M3 MTP recipes
NUM_SPEC_TOKENS=3
TOKENS_PER_SEQ=$((1 + NUM_SPEC_TOKENS))

# AgentX pins acceptance to the committed golden AL so submissions are compared
# on system performance at a fixed acceptance target rather than on draft-head
# quality. 2.78 is minimaxm3_eagle3_gqa.yaml thinking_on[3].
#
# EVAL_ONLY switches back to real verification: synthetic acceptance commits
# drafted tokens regardless of the target logits, so generated text is wrong and
# the eval would score ~0 (same split as dsv4_fp4_b*_vllm_mtp.sh).
SYNTHETIC_ACCEPT_LEN=2.78
if [ "${EVAL_ONLY:-false}" = "true" ]; then
SPEC_CONFIG="{\"method\": \"eagle3\", \"model\": \"$DRAFT_MODEL\", \"num_speculative_tokens\": $NUM_SPEC_TOKENS, \"attention_backend\": \"FLASH_ATTN\"}"
else
SPEC_CONFIG="{\"method\": \"eagle3\", \"model\": \"$DRAFT_MODEL\", \"num_speculative_tokens\": $NUM_SPEC_TOKENS, \"attention_backend\": \"FLASH_ATTN\", \"rejection_sample_method\": \"synthetic\", \"synthetic_acceptance_length\": $SYNTHETIC_ACCEPT_LEN}"
fi

# AgentX concurrency counts live session trees, not individual requests. DEP
# splits those trees across eight data-parallel ranks; TP/TEP keeps them local.
if [[ "$DP_ATTENTION" == "true" ]]; then
if (( 2 * CONC % TP != 0 )); then
echo "DEP requires 2*CONC divisible by TP (CONC=$CONC TP=$TP)" >&2
exit 1
fi
MAX_NUM_SEQS=$((2 * CONC / TP))
else
MAX_NUM_SEQS=$((2 * CONC))
fi
# Cudagraph capture sizes are in TOKENS: a decode batch of S sequences verifies
# S*(1+NUM_SPEC_TOKENS) tokens, so cap capture at MAX_NUM_SEQS*(1+N) or the
# FULL_DECODE_ONLY ladder tops out at MAX_NUM_SEQS/(1+N) sequences and the
# largest decode batches fall back to eager.
MAX_CUDAGRAPH_CAPTURE_SIZE=$((MAX_NUM_SEQS * TOKENS_PER_SEQ))

vllm serve "$MODEL_PATH" --served-model-name "$MODEL" \
--host 0.0.0.0 \
--port "$VLLM_BACKEND_PORT" \
"${PARALLEL_ARGS[@]}" \
"${EP_ARGS[@]}" \
--gpu-memory-utilization 0.90 \
--cpu-offload-gb "$MODEL_CPU_OFFLOAD_GB" \
--kv-cache-dtype fp8 \
--attention-backend TRITON_ATTN \
--block-size 128 \
--language-model-only \
--enable-prefix-caching \
--enable-prompt-tokens-details \
--default-chat-template-kwargs '{"thinking_mode":"enabled"}' \
--max-num-seqs "$MAX_NUM_SEQS" \
--max-cudagraph-capture-size "$MAX_CUDAGRAPH_CAPTURE_SIZE" \
--speculative-config "$SPEC_CONFIG" \
--tool-call-parser minimax_m3 \
--reasoning-parser minimax_m3 \
--enable-auto-tool-choice \
--safetensors-load-strategy lazy \
--trust-remote-code \
"${OFFLOAD_ARGS[@]}" > "$SERVER_LOG" 2>&1 &
SERVER_PID=$!

wait_for_server_ready --port "$VLLM_BACKEND_PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID"

if [[ "$DP_ATTENTION" == "true" ]]; then
vllm-router \
--worker-urls "http://localhost:$VLLM_BACKEND_PORT" \
--policy consistent_hash \
--intra-node-data-parallel-size "$TP" \
--host 0.0.0.0 \
--port "$PORT" \
--prometheus-host 127.0.0.1 \
--prometheus-port "$((PORT + 10000))" \
--request-timeout-secs 14400 \
--disable-retries > "$ROUTER_LOG" 2>&1 &
ROUTER_PID=$!
wait_for_server_ready --port "$PORT" --server-log "$ROUTER_LOG" --server-pid "$ROUTER_PID"
fi

if [ "${EVAL_ONLY}" = "true" ]; then
run_eval --port "$PORT"
else
build_replay_cmd "$RESULT_DIR"
run_agentic_replay_and_write_outputs "$RESULT_DIR"
fi
29 changes: 29 additions & 0 deletions configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7141,6 +7141,35 @@ minimaxm3-fp8-h100-vllm-agentic:
- { tp: 8, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "0.3.11.post1" }, conc-list: [3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16] }
- { tp: 8, ep: 8, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "0.3.11.post1" }, conc-list: [3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16] }

# EAGLE3 speculative-decoding (spec-decoding: mtp) variant of
# minimaxm3-fp8-h100-vllm-agentic, pairing MiniMaxAI/MiniMax-M3-MXFP8 with the
# Inferact/MiniMax-M3-EAGLE3-GQA draft head (3 speculative tokens, FLASH_ATTN
# drafter) and pinning synthetic acceptance to the golden AL 2.78
# (golden_al_distribution/minimaxm3_eagle3_gqa.yaml, thinking_on, K=3). Same TP8-only
# layout and KV arms as the non-MTP entry so the spec-decode delta is readable at
# equal concurrency, trimmed at the extreme-conc end: the draft head plus its KV
# eat into the same HBM budget that already puts the GPU-resident cliff near
Comment on lines 7143 to +7151

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The comment block above the new minimaxm3-fp8-h100-vllm-agentic-mtp entry (configs/nvidia-master.yaml:7108-7116) names the wrong draft head and golden AL: it says Inferact/MiniMax-M3-EAGLE3 and AL 2.83 from minimaxm3_eagle3.yaml, but the script it drives (minimaxm3_fp8_h100_mtp.sh) actually uses Inferact/MiniMax-M3-EAGLE3-GQA with AL 2.78 from minimaxm3_eagle3_gqa.yaml.

Extended reasoning...

The new minimaxm3-fp8-h100-vllm-agentic-mtp config entry at configs/nvidia-master.yaml:7108-7116 carries a doc comment describing the recipe's speculative-decoding setup. It states the recipe pairs the target with the Inferact/MiniMax-M3-EAGLE3 draft head and pins synthetic acceptance to golden AL 2.83 from golden_al_distribution/minimaxm3_eagle3.yaml (thinking_on, K=3).

That doesn't match the script this config entry actually drives, benchmarks/single_node/agentic/minimaxm3_fp8_h100_mtp.sh. Line 27 sets DRAFT_MODEL="Inferact/MiniMax-M3-EAGLE3-GQA" (the GQA variant, not the plain one named in the comment), and line 144 sets SYNTHETIC_ACCEPT_LEN=2.78, with the script's own adjacent comment correctly attributing that value to minimaxm3_eagle3_gqa.yaml thinking_on[3].

Step-by-step proof:

  1. golden_al_distribution/minimaxm3_eagle3.yamlminimax-m3.thinking_on[3] = 2.83 (non-GQA head).
  2. golden_al_distribution/minimaxm3_eagle3_gqa.yamlminimax-m3.thinking_on[3] = 2.78 (GQA head).
  3. The script sets DRAFT_MODEL=Inferact/MiniMax-M3-EAGLE3-GQA and SYNTHETIC_ACCEPT_LEN=2.78 — this is the GQA value from file (2), not the 2.83 from file (1).
  4. perf-changelog.yaml's new entry for this PR independently confirms this, stating "EAGLE3-GQA synthetic golden AL 2.78".
  5. Yet the nvidia-master.yaml comment cites the non-GQA model name, the non-GQA file, and the non-GQA AL value (2.83) — all three details are inconsistent with what the script and changelog actually implement.

The two golden_al_distribution files hold distinct measured acceptance-length values for genuinely different draft heads (GQA vs non-GQA attention in the EAGLE3 head), so this isn't a rounding or trivial wording slip — it names a different model checkpoint and cites a different measurement entirely. Nothing in the YAML schema or config loader cross-checks free-text comments against the script's runtime constants, so there's no existing mechanism that would have caught this drift, and it will keep misleading anyone reading the config to reconcile it with what's actually benchmarked.

There is no runtime impact: the comment is inert documentation and the script's own hardcoded DRAFT_MODEL/SYNTHETIC_ACCEPT_LEN values are what actually execute. The fix is simply to update the comment at configs/nvidia-master.yaml:7108-7116 to say Inferact/MiniMax-M3-EAGLE3-GQA, cite golden_al_distribution/minimaxm3_eagle3_gqa.yaml, and use golden AL 2.78, matching both the script and the new perf-changelog entry.

# conc 6 on 80 GB H100s.
Comment on lines +7144 to +7152

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 WARNING: This comment block contradicts what the recipe actually runs, in three places:

  1. The script pins DRAFT_MODEL="Inferact/MiniMax-M3-EAGLE3-GQA" (benchmarks/single_node/agentic/minimaxm3_fp8_h100_mtp.sh:28), not Inferact/MiniMax-M3-EAGLE3.
  2. The script pins SYNTHETIC_ACCEPT_LEN=2.78, which is golden_al_distribution/minimaxm3_eagle3_gqa.yaml thinking_on[3] — not 2.83 from minimaxm3_eagle3.yaml (that's the non-GQA head's table). The perf-changelog entry in this PR also says 2.78.
  3. "Same TP8-only layout and KV arms as the non-MTP entry" — this entry adds two DEP8 (dp-attn: true) arms that the non-MTP entry doesn't have.

Why it matters: this comment is what reviewers and future submitters use to check which acceptance target a submission is pinned to; citing 2.83/minimaxm3_eagle3.yaml invites someone to "fix" the script to the wrong golden AL for the GQA draft.

Fix:

Suggested change
# EAGLE3 speculative-decoding (spec-decoding: mtp) variant of
# minimaxm3-fp8-h100-vllm-agentic, pairing MiniMaxAI/MiniMax-M3-MXFP8 with the
# Inferact/MiniMax-M3-EAGLE3 draft head (3 speculative tokens, FLASH_ATTN
# drafter) and pinning synthetic acceptance to the golden AL 2.83
# (golden_al_distribution/minimaxm3_eagle3.yaml, thinking_on, K=3). Same TP8-only
# layout and KV arms as the non-MTP entry so the spec-decode delta is readable at
# equal concurrency, trimmed at the extreme-conc end: the draft head plus its KV
# eat into the same HBM budget that already puts the GPU-resident cliff near
# conc 6 on 80 GB H100s.
# EAGLE3 speculative-decoding (spec-decoding: mtp) variant of
# minimaxm3-fp8-h100-vllm-agentic, pairing MiniMaxAI/MiniMax-M3-MXFP8 with the
# Inferact/MiniMax-M3-EAGLE3-GQA draft head (3 speculative tokens, FLASH_ATTN
# drafter) and pinning synthetic acceptance to the golden AL 2.78
# (golden_al_distribution/minimaxm3_eagle3_gqa.yaml, thinking_on, K=3). Keeps the
# non-MTP entry's TP8/TEP8 layout and KV arms (plus DEP8 dp-attn arms) so the
# spec-decode delta is readable at equal concurrency, trimmed at the
# extreme-conc end: the draft head plus its KV eat into the same HBM budget
# that already puts the GPU-resident cliff near conc 6 on 80 GB H100s.

minimaxm3-fp8-h100-vllm-agentic-mtp:
image: vllm/vllm-openai:v0.27.1
model: MiniMaxAI/MiniMax-M3-MXFP8
model-prefix: minimaxm3
runner: cluster:h100-dgxc
precision: fp8
framework: vllm
multinode: false
scenarios:
agentic-coding:
# The complete fast sweep places the GPU-resident cliff between c5 and c6.
# Retain the resident latency/knee curve through c5, then use Mooncake at
# c6/c8 to extend throughput without crossing that HBM cliff. TEP was
# dominated, DEP could not allocate its 1M-token KV cache on 80 GB H100s,
# and the vLLM-simple function screen did not justify a frontier row.
- dram-utilization: 0.80
search-space:
- { tp: 8, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 2, 3, 4, 5] }
- { tp: 8, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "0.3.11.post1" }, conc-list: [6, 8] }

minimaxm3-fp8-h200-vllm-agentic:
image: vllm/vllm-openai:nightly-04c2a8deac44fdb1ca3e2b5ec3e6bf16f3f6a914
model: MiniMaxAI/MiniMax-M3-MXFP8
Expand Down
10 changes: 10 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5818,3 +5818,13 @@
description:
- "Extend the SimpleCPUOffloadConnector grid to c8/c12/c16/c20/c24/c28/c32/c48/c64 to locate its crossover against the resident curve"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2475

- config-keys:
- minimaxm3-fp8-h100-vllm-agentic-mtp
scenario-type:
- agentic-coding
description:
- "Refresh H100 MiniMax-M3 MXFP8 AgentX with vLLM v0.27.1 and EAGLE3-GQA synthetic golden AL 2.78."
- "Use the complete fast sweep to retain TP8 c1-c5 and Mooncake TP8 c6/c8 for the strict Pareto sweep; TEP is dominated, DEP is infeasible at its required KV allocation, and vLLM-simple is not retained after its function screen."
- "Require vLLM server metrics and keep strict request and profile validation."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2564
Loading