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
193 changes: 193 additions & 0 deletions benchmarks/single_node/agentic/minimaxm3_fp8_mi300x_mtp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#!/usr/bin/env bash
set -eo pipefail
set -x

# MiniMax-M3 MXFP8 MI300X AgentX with EAGLE3-GQA and optional LMCache MP.

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

export EVAL_FRAMEWORK="lm-eval"

check_env_vars MODEL TP CONC KV_OFFLOADING TOTAL_CPU_DRAM_GB RESULT_DIR DURATION EP_SIZE DP_ATTENTION PORT EVAL_ONLY

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 new minimaxm3_fp8_mi300x_mtp.sh requires DP_ATTENTION via check_env_vars (line 11) but never branches on it — PARALLEL_ARGS only checks EP_SIZE. It's harmless today since every row in minimaxm3-fp8-mi300x-vllm-agentic-mtp defaults dp-attn to false, but sibling recipes with the same required var (e.g. minimaxm3_fp8_mi300x.sh, dsv4_fp4_mi355x_vllm_mtp.sh) do consume it to build DP-attention flags, so if a future dp-attn: true row is added here (as already exists for the sibling mi325x recipe), the server would silently launch plain TP/EP while the sweep still labels it DEP.

Extended reasoning...

The bug: benchmarks/single_node/agentic/minimaxm3_fp8_mi300x_mtp.sh:11 lists DP_ATTENTION in its check_env_vars call, asserting the script depends on that variable. But grepping the rest of the script shows it is never referenced again — PARALLEL_ARGS (around the EP_SIZE-gated --enable-expert-parallel block) only branches on EP_SIZE. There is no analogous branch that would add --data-parallel-size, bump the port, or start a router when DP_ATTENTION=true.

Contrast with siblings that do consume it: minimaxm3_fp8_mi300x.sh (lines 150/160/188), minimaxm3_fp8_mi325x.sh (150/165/198), and dsv4_fp4_mi355x_vllm_mtp.sh (70/365) all require the same variable and use it to switch between plain TP/EP and DP-attention serving (different parallel args, a different port, and launching vllm-router). Those scripts show what 'consuming' DP_ATTENTION is supposed to look like in this codebase, and the new MTP script has the requirement without the corresponding behavior.

Why this doesn't currently break anything: every search-space row under minimaxm3-fp8-mi300x-vllm-agentic-mtp in configs/amd-master.yaml omits dp-attn, which generate_sweep_configs.py defaults to false. With DP_ATTENTION=false, the correct behavior (plain TP/EP) is exactly what the script does anyway, so no current run is mislabeled.

Why it's still worth flagging (as a nit, not a blocker): it's a latent footgun rather than an active bug. dp-attn: true rows already exist for sibling recipes in the very same amd-master.yaml file (e.g. minimaxm3-fp8-mi325x-vllm-agentic, dsv4-fp4-mi355x-vllm-agentic-mtp). If a future row adds dp-attn: true to this recipe, the sweep would set DP_ATTENTION=true and record the point as a DEP config, but the script would silently serve plain TP/EP — corrupting the benchmark's performance-curve labeling with no error surfaced anywhere in the pipeline.

Step-by-step proof: (1) A maintainer adds { tp: 8, ep: 8, dp-attn: true, ... } to minimaxm3-fp8-mi300x-vllm-agentic-mtp in amd-master.yaml, mirroring the pattern already used for minimaxm3-fp8-mi325x-vllm-agentic. (2) generate_sweep_configs.py emits a job with DP_ATTENTION=true. (3) check_env_vars passes since the variable is set. (4) The script builds PARALLEL_ARGS=(--tensor-parallel-size "") plus --enable-expert-parallel from EP_SIZE alone — DP_ATTENTION is never read, so no --data-parallel-size flag is added and no router is started. (5) The server runs as plain TEP8, but the result is recorded and labeled as a DEP8 data point in the benchmark output, silently corrupting the recorded performance curve.

On the refutation: one verifier correctly points out that this exact required-but-unconsumed pattern already exists in glm5.2_fp8_mi325x_mtp.sh:11 and minimaxm3_fp4_mi355x_mtp.sh:16 (confirmed by inspection — both require DP_ATTENTION via check_env_vars and never branch on it, the latter only echoing it for logging). So this specific script isn't introducing a wholly novel anti-pattern into the repo, and removing DP_ATTENTION from the required-var list would arguably regress the intended uniform env-contract assertion across the agentic family. That's a fair point, and it's why this should stay a nit rather than escalate to normal — but it doesn't fully neutralize the finding: several other siblings in the same model family (minimaxm3_fp8_mi300x.sh, minimaxm3_fp8_mi325x.sh) do wire DP_ATTENTION into real behavior, and this recipe's own amd-master.yaml neighbors already ship dp-attn: true rows for other configs. The inconsistency is real even if the specific failure mode is speculative and not triggered by this PR's search space.

Suggested fix: either wire DP_ATTENTION into PARALLEL_ARGS (mirroring minimaxm3_fp8_mi300x.sh) so a future dp-attn: true row behaves correctly, or drop it from check_env_vars if DP-attention is intentionally out of scope for this MTP recipe. Either fix removes the silent-mislabeling risk.


DRAFT_MODEL="Inferact/MiniMax-M3-EAGLE3-GQA"
NUM_SPEC_TOKENS=3
SYNTHETIC_ACCEPT_LEN=2.78

if [[ -n "$SLURM_JOB_ID" ]]; then
echo "JOB $SLURM_JOB_ID running on $SLURMD_NODENAME"
fi
if [[ -n "$ROCR_VISIBLE_DEVICES" ]]; then
export HIP_VISIBLE_DEVICES="$ROCR_VISIBLE_DEVICES"
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
hf download "$DRAFT_MODEL"

rocm-smi || true
amd-smi || true
resolve_trace_source
install_agentic_deps

export AIPERF_SERVER_METRICS_URLS="http://localhost:${PORT}/metrics"
export AIPERF_REQUIRED_SERVER_METRIC_PREFIX="vllm:"

SERVER_LOG="$RESULT_DIR/server.log"
LMCACHE_LOG="$RESULT_DIR/lmcache_server.log"
mkdir -p "$RESULT_DIR"

SERVER_PID=""
LMCACHE_PIDS=()
cleanup_agentic_services() {
local exit_code=$?
trap - EXIT INT TERM
set +e
stop_background_process_tree "$SERVER_PID" "vLLM server" 60
local i
for i in "${!LMCACHE_PIDS[@]}"; do
stop_background_process_tree "${LMCACHE_PIDS[$i]}" "LMCache server $i"
done
exit "$exit_code"
}
trap cleanup_agentic_services EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

OFFLOAD_ARGS=()
case "$KV_OFFLOAD_BACKEND" in
"")
require_agentic_kv_offload_none
;;
lmcache)
require_agentic_kv_offload_backend lmcache
LMCACHE_VERSION="0.5.3"
LMCACHE_ROCM_INDEX="https://github.com/LMCache/LMCache/releases/expanded_assets/v${LMCACHE_VERSION}-rocm"
agentic_pip_install --quiet --no-cache-dir --no-deps \
"sortedcontainers==2.4.0" \
"opentelemetry-exporter-prometheus==0.61b0" \
"cupy-rocm-7-0==14.1.1" \
"lmcache==${LMCACHE_VERSION}" --find-links "$LMCACHE_ROCM_INDEX"
python3 -c \
"import cupy; import lmcache.integration.vllm.lmcache_mp_connector; import opentelemetry.exporter.prometheus" \
>/dev/null

Comment on lines +62 to +80

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.

🟡 This LMCache MP shard spin-up block (lines 62-128) is a near-verbatim duplicate (~55 lines) of the one in minimaxm3_fp4_mi355x_mtp.sh, and unlike that sibling it drops the pre-flight check that verifies LMCACHE_L1_SIZE_GB doesn't exceed 90% of free /dev/shm before launching. Worth extracting a shared start_lmcache_mp_shards helper into benchmark_lib.sh (removing the duplication) and restoring the /dev/shm guard so an oversized DRAM budget fails with a clear error instead of an opaque lmcache OOM/crash.

Extended reasoning...

What's duplicated: Lines 62-128 of minimaxm3_fp8_mi300x_mtp.sh reimplement the per-TP-rank LMCache MP server spin-up loop that already exists in benchmarks/single_node/agentic/minimaxm3_fp4_mi355x_mtp.sh (its equivalent block runs roughly lines 79-165). Both use the identical port scheme (shard_port=5555+shard, shard_http_port=8080+shard), the identical lmcache CLI flag set (--l1-init-size-gb 10 --l1-read-ttl-seconds 7200 --chunk-size 256 --max-workers 2 --eviction-policy LRU --supported-transfer-mode lmcache_driven), the same append_command/background-launch pattern, and the same wait_for_ready healthcheck loop plus the same LMCacheMPConnector OFFLOAD_ARGS kv-transfer-config construction. benchmark_lib.sh already centralizes shared agentic helpers (require_agentic_kv_offload_backend, wait_for_ready, stop_background_process_tree, append_command), so this block is a natural candidate for a start_lmcache_mp_shards helper that both scripts could call, removing roughly 55 duplicated lines.\n\nWhat's missing: the mi355x sibling has a pre-flight guard (around its lines 104-111) that computes free /dev/shm via df -BG /dev/shm and exits with a clear error if LMCACHE_L1_SIZE_GB would exceed 90% of that free space. The mi300x copy in this PR omits that check entirely — it goes straight from LMCACHE_L1_SHARD_GB=$((TOTAL_CPU_DRAM_GB / TP)) to only a -lt 1 floor check (line ~68-71 here). Since LMCache MP's L1 tier is backed by shared memory, a TOTAL_CPU_DRAM_GB/TP budget that's too large for the runner's actual /dev/shm capacity won't be caught up front; it will instead surface later as an opaque lmcache server crash or OOM in lmcache_server_*.log, which is much harder to diagnose than the sibling script's explicit pre-flight error.\n\nConcrete walk-through: suppose the mi300x runner has 512GB of /dev/shm" but the job is launched with TOTAL_CPU_DRAM_GB=600andTP=4(e.g. a copy-pasted or misconfigureddram-utilization/config value). LMCACHE_L1_SHARD_GB computes to 150GB per shard × 4 shards = 600GB total requested, exceeding the 512GB actually available. The mi355x sibling's check would catch this immediately (600 > 512 * 0.9 = 460.8) and exit with "Error: LMCache L1 size exceeds ... /dev/shm" before any process starts. In this PR's mi300x script, the -lt 1check passes fine (150 ≥ 1), so all fourlmcache serverbackground processes launch, each trying to mmap a 150GB region in the same shared/dev/shm; they will either fail to allocate, get OOM-killed, or degrade in ways that only show up in the per-shard logs after the fact, all while the outer wait_for_ready loop times out after up to 600 seconds — a much slower and more confusing failure than the sibling's instant, actionable error.\n\n**Why this matters but isn't blocking:** this LMCache configuration is exercised by exactly one isolated gate arm (tp: 8, conc-list: [16]) in the new minimaxm3-fp8-mi300x-vllm-agentic-mtpmatrix entry, so the blast radius if it does misconfigure is a single functional-gate job failing loudly (if diagnosed) rather than a broader correctness or data-loss issue. It's a code-quality / parity gap relative to the sibling recipe, not a bug that breaks the documented behavior of this PR.\n\n**Fix:** extract the LMCache MP spin-up loop (including the/dev/shmpre-flight check) from the mi355x script into a sharedstart_lmcache_mp_shardsfunction inbenchmark_lib.sh, and have both minimaxm3_fp8_mi300x_mtp.shandminimaxm3_fp4_mi355x_mtp.sh` call it. This removes the ~55-line duplication and restores the missing capacity check in one change.

LMCACHE_L1_SHARD_GB=$((TOTAL_CPU_DRAM_GB / TP))
if [ "$LMCACHE_L1_SHARD_GB" -lt 1 ]; then
echo "Error: LMCache DRAM budget is less than 1 GB per TP rank." >&2
exit 1
fi

LMCACHE_SERVER_URLS=()
LMCACHE_HTTP_PORTS=()
LMCACHE_LOGS=()
: > "$RESULT_DIR/lmcache_command.txt"
for shard in $(seq 0 $((TP - 1))); do
shard_port=$((5555 + shard))
shard_http_port=$((8080 + shard))
shard_log="${LMCACHE_LOG%.log}_${shard}.log"
LMCACHE_CMD=(
lmcache server
--host 127.0.0.1
--port "$shard_port"
--http-host 127.0.0.1
--http-port "$shard_http_port"
--l1-size-gb "$LMCACHE_L1_SHARD_GB"
--l1-init-size-gb 10
--l1-read-ttl-seconds 7200
--chunk-size 256
--max-workers 2
--eviction-policy LRU
--supported-transfer-mode lmcache_driven
)
append_command "$RESULT_DIR/lmcache_command.txt" "${LMCACHE_CMD[@]}"
"${LMCACHE_CMD[@]}" > "$shard_log" 2>&1 &
LMCACHE_PIDS+=($!)
LMCACHE_HTTP_PORTS+=("$shard_http_port")
LMCACHE_LOGS+=("$shard_log")
LMCACHE_SERVER_URLS+=("tcp://127.0.0.1:${shard_port}")
done
for shard in "${!LMCACHE_PIDS[@]}"; do
wait_for_ready \
--endpoint "http://127.0.0.1:${LMCACHE_HTTP_PORTS[$shard]}/healthcheck" \
--log "${LMCACHE_LOGS[$shard]}" \
--pid "${LMCACHE_PIDS[$shard]}" \
--sleep-interval 1 \
--timeout 600
done
LMCACHE_SERVER_URLS_CSV=$(IFS=,; echo "${LMCACHE_SERVER_URLS[*]}")
OFFLOAD_ARGS=(
--kv-transfer-config
"{\"kv_connector\":\"LMCacheMPConnector\",\"kv_connector_module_path\":\"lmcache.integration.vllm.lmcache_mp_connector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{\"lmcache.mp.server_urls\":\"$LMCACHE_SERVER_URLS_CSV\",\"lmcache.mp.mq_timeout\":6000.0}}"
)
;;
*)
echo "Unsupported KV_OFFLOAD_BACKEND: $KV_OFFLOAD_BACKEND" >&2
exit 1
;;
esac

PARALLEL_ARGS=(--tensor-parallel-size "$TP")
if [ "$EP_SIZE" -gt 1 ]; then
PARALLEL_ARGS+=(--enable-expert-parallel)
fi

if [ "$EVAL_ONLY" = "true" ]; then
SPEC_CONFIG="{\"method\":\"eagle3\",\"model\":\"$DRAFT_MODEL\",\"num_speculative_tokens\":$NUM_SPEC_TOKENS,\"attention_backend\":\"TRITON_ATTN\"}"
else
SPEC_CONFIG="{\"method\":\"eagle3\",\"model\":\"$DRAFT_MODEL\",\"num_speculative_tokens\":$NUM_SPEC_TOKENS,\"attention_backend\":\"TRITON_ATTN\",\"rejection_sample_method\":\"synthetic\",\"synthetic_acceptance_length\":$SYNTHETIC_ACCEPT_LEN}"
fi

export PYTHONNOUSERSITE=1
export VLLM_ENGINE_READY_TIMEOUT_S=3600
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1800
export VLLM_USE_BREAKABLE_CUDAGRAPH=0
export VLLM_ROCM_USE_AITER=1
export VLLM_ROCM_USE_AITER_MHA=0
export TORCH_BLAS_PREFER_HIPBLASLT=1
export NCCL_MIN_NCHANNELS=112
export GPU_MAX_HW_QUEUES=2

VLLM_CMD=(
vllm serve "$MODEL_PATH"
--served-model-name "$MODEL"
--host 0.0.0.0
--port "$PORT"
"${PARALLEL_ARGS[@]}"
--trust-remote-code
--block-size 128
--gpu-memory-utilization 0.90
--enable-chunked-prefill
--max-num-batched-tokens 16384
--language-model-only
--enable-prefix-caching
--attention-backend TRITON_ATTN
--kv-cache-dtype fp8
--tool-call-parser minimax_m3
--reasoning-parser minimax_m3
--enable-auto-tool-choice
--default-chat-template-kwargs '{"thinking_mode":"enabled"}'
--max-num-seqs "$((2 * CONC))"
--stream-interval 20
--speculative-config "$SPEC_CONFIG"
"${OFFLOAD_ARGS[@]}"
)
write_command "$RESULT_DIR/server_command.txt" "${VLLM_CMD[@]}"
"${VLLM_CMD[@]}" > "$SERVER_LOG" 2>&1 &
SERVER_PID=$!

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

if [ "$EVAL_ONLY" = "true" ]; then
run_eval --port "$PORT"
else
build_replay_cmd "$RESULT_DIR"
REPLAY_CMD+=" --apply-chat-template"
run_agentic_replay_and_write_outputs "$RESULT_DIR"
fi
15 changes: 15 additions & 0 deletions configs/amd-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,21 @@ minimaxm3-fp8-mi300x-vllm-agentic:
- { tp: 8, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "0.3.11.post1" }, conc-list: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20] }
- { tp: 8, ep: 8, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "0.3.11.post1" }, conc-list: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20] }

minimaxm3-fp8-mi300x-vllm-agentic-mtp:
image: vllm/vllm-openai-rocm:v0.27.1
model: MiniMaxAI/MiniMax-M3-MXFP8
model-prefix: minimaxm3
runner: cluster:mi300x-amds
precision: fp8
framework: vllm
multinode: false
scenarios:
agentic-coding:
- dram-utilization: 0.80
search-space:
- { tp: 8, spec-decoding: mtp, kv-offloading: none, conc-list: [2, 4, 6, 8, 10] }
- { tp: 8, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: lmcache, version: "0.5.3" }, conc-list: [16] }

# GLM-5.2 FP8 full-context AgentX refresh on MI325X. This preserves the TP8
# GPU-resident-KV c1/c2/c3/c4/c5/c6/c8 curve from Actions run 29657732517
# and enables EAGLE MTP with the committed thinking-on golden AL.
Expand Down
8 changes: 8 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5918,3 +5918,11 @@
- "Add a TEP2 arm (tp 2, ep 2) to the qwen3.5-fp4-b200-sglang-mtp 8k/1k sweep at concurrency 16, 32, and 64"
- "Rides on the NVFP4-V2 checkpoint switch from #2205"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2550

- config-keys:
- minimaxm3-fp8-mi300x-vllm-agentic-mtp
scenario-type:
- agentic-coding
description:
- "Add MI300X MiniMax-M3 MXFP8 AgentX with vLLM EAGLE3-GQA, measured resident TP8 c2-c10, and an LMCache MP DRAM-offload c16 point."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2580
Loading