Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -Eeuo pipefail

export SCENARIO_TYPE=agentic-coding
source "$(dirname "${BASH_SOURCE[0]}")/../fixed_seq_len/minimaxm2.7_fp4_b200_sglang_mtp.sh"
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/usr/bin/env bash
set -Eeuo pipefail

# MiniMax-M2.7 NVFP4 on B200 with the public full-vocabulary EAGLE3 draft.
# This entrypoint serves both fixed-sequence and AgentX wrappers so the target,
# draft, speculative settings, and chat behavior cannot drift between them.

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INFERENCEX_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
source "$INFERENCEX_ROOT/benchmarks/benchmark_lib.sh"

check_env_vars \
MODEL \
TP \
EP_SIZE \
CONC \
PORT \
PRECISION \
RANDOM_RANGE_RATIO \
RESULT_FILENAME

if [[ "$PRECISION" != "fp4" || "$EP_SIZE" != "1" ]]; then
echo "MiniMax-M2.7 EAGLE3 expects NVFP4 with pure tensor parallelism, got precision=$PRECISION EP=$EP_SIZE" >&2
exit 1
fi
if [[ "$TP" != "4" && "$TP" != "8" ]]; then
echo "MiniMax-M2.7 EAGLE3 supports the configured TP4/TP8 search space, got TP=$TP" >&2
exit 1
fi

readonly TARGET_MODEL="nvidia/MiniMax-M2.7-NVFP4"
readonly TARGET_REVISION="e79701cb1f9dce8fe5395b9ed2b20170beebecde"
readonly DRAFT_MODEL="asherszhang/MiniMax-M2.7-EAGLE3-draft-vocab200k"
readonly DRAFT_REVISION="252b54f7d05a5d0db7734563ae10e2e6a81d6a7d"

if [[ "$MODEL" != "$TARGET_MODEL" && "$MODEL" != /* ]]; then
echo "Unexpected target model: $MODEL (expected $TARGET_MODEL)" >&2
exit 1
fi

if [[ -n "${MODEL_PATH:-}" ]]; then
TARGET_MODEL_PATH="$MODEL_PATH"
elif [[ "$MODEL" == /* ]]; then
TARGET_MODEL_PATH="$MODEL"
else
TARGET_MODEL_PATH="$TARGET_MODEL"
fi

if [[ "$TARGET_MODEL_PATH" == /* ]]; then
MODEL_ROOT=$(dirname "$TARGET_MODEL_PATH")
DRAFT_MODEL_PATH="${DRAFT_MODEL_PATH:-$MODEL_ROOT/MiniMax-M2.7-EAGLE3-draft-vocab200k}"
mkdir -p "$TARGET_MODEL_PATH" "$DRAFT_MODEL_PATH"
exec 8>"$MODEL_ROOT/.minimax-m2.7-eagle3-stage.lock"
flock -w 7200 8
if [[ ! -f "$TARGET_MODEL_PATH/config.json" || \
! -f "$TARGET_MODEL_PATH/model.safetensors.index.json" || \
$(find "$TARGET_MODEL_PATH" -maxdepth 1 -name 'model-*-of-00015.safetensors' | wc -l) -ne 15 ]]; then
hf download "$TARGET_MODEL" --revision "$TARGET_REVISION" --local-dir "$TARGET_MODEL_PATH"
fi
if [[ ! -f "$DRAFT_MODEL_PATH/config.json" || ! -f "$DRAFT_MODEL_PATH/model.safetensors" ]]; then
hf download "$DRAFT_MODEL" --revision "$DRAFT_REVISION" --local-dir "$DRAFT_MODEL_PATH"
fi
flock -u 8
else
hf download "$TARGET_MODEL" --revision "$TARGET_REVISION"
hf download "$DRAFT_MODEL" --revision "$DRAFT_REVISION"
DRAFT_MODEL_PATH="$DRAFT_MODEL"
fi

python3 - "$TARGET_MODEL_PATH" "$DRAFT_MODEL_PATH" <<'PY'
import json
import sys
from pathlib import Path

target_path, draft_path = map(Path, sys.argv[1:])
target = json.loads((target_path / "config.json").read_text())
draft = json.loads((draft_path / "config.json").read_text())
quant = target.get("quantization_config", {})
if quant.get("quant_algo") != "NVFP4" or quant.get("quant_method") != "modelopt":
raise SystemExit(f"Unexpected NVFP4 target configuration: {quant}")
if target.get("vocab_size") != 200064:
raise SystemExit(f"Unexpected target vocabulary: {target.get('vocab_size')}")
if draft.get("architectures") != ["LlamaForCausalLMEagle3"]:
raise SystemExit(f"Unexpected EAGLE3 architecture: {draft.get('architectures')}")
if draft.get("vocab_size") != 200064 or draft.get("num_hidden_layers") != 1:
raise SystemExit(
f"Unexpected EAGLE3 draft contract: vocab={draft.get('vocab_size')} "
Comment on lines +63 to +87

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.

🟡 In the hub-cache fallback branch (no --local-dir), TARGET_MODEL_PATH/DRAFT_MODEL_PATH are left as bare HF repo IDs (e.g. nvidia/MiniMax-M2.7-NVFP4) instead of resolved filesystem paths, so the Python preflight's (target_path / "config.json").read_text() raises FileNotFoundError before the server starts. This is a real defect in the documented hub-cache/local-invocation mode, but it's not exercised in CI since launch_b200-dgxc.sh always exports an absolute MODEL_PATH for this recipe (routing through the --local-dir branch instead); fix by capturing the resolved snapshot dir from hf download (or passing --local-dir) in that branch too.

Extended reasoning...

The new benchmark entrypoint minimaxm2.7_fp4_b200_sglang_mtp.sh supports two model-resolution modes: an absolute-path branch (used when MODEL_PATH is set or MODEL is already an absolute path) that passes --local-dir to hf download and correctly sets TARGET_MODEL_PATH/DRAFT_MODEL_PATH to real directories, and an else branch (lines 63-67) intended for hub-cache/local invocation where MODEL is the bare HF id and MODEL_PATH is unset.

In that else branch, hf download "$TARGET_MODEL" --revision "$TARGET_REVISION" is called without --local-dir, which places the downloaded files under the HF hub cache (e.g. ~/.cache/huggingface/hub/models--nvidia--MiniMax-M2.7-NVFP4/snapshots/<rev>/...), not in a directory literally named nvidia/MiniMax-M2.7-NVFP4 relative to $PWD. Despite this, the script sets TARGET_MODEL_PATH="$TARGET_MODEL" (i.e. the bare repo id string) and DRAFT_MODEL_PATH="$DRAFT_MODEL" (the bare draft repo id) rather than capturing the resolved snapshot path.

Those two variables are then fed straight into the inline Python preflight:

target_path, draft_path = map(Path, sys.argv[1:])
target = json.loads((target_path / "config.json").read_text())

With target_path = Path("nvidia/MiniMax-M2.7-NVFP4"), this resolves to the nonexistent relative path ./nvidia/MiniMax-M2.7-NVFP4/config.json, and read_text() raises FileNotFoundError. Because the script runs under set -Eeuo pipefail, this immediately aborts the run — sglang.launch_server is never even invoked, even though sglang itself would have tolerated the bare repo id and resolved it via the HF cache fine. So the preflight validation step, not the actual model loading, is what breaks this mode.

Step-by-step proof:

  1. Run the script locally/manually with MODEL=nvidia/MiniMax-M2.7-NVFP4 and MODEL_PATH unset (exactly the "local validation" scenario the PR description references).
  2. TARGET_MODEL_PATH is set to the string "nvidia/MiniMax-M2.7-NVFP4" (line ~65-67), not a filesystem path.
  3. hf download nvidia/MiniMax-M2.7-NVFP4 --revision ... (no --local-dir) populates ~/.cache/huggingface/hub/...; no ./nvidia/MiniMax-M2.7-NVFP4 directory is created in $PWD.
  4. The Python preflight executes Path("nvidia/MiniMax-M2.7-NVFP4") / "config.json", which does not exist relative to $PWD.
  5. read_text() raises FileNotFoundError, the script exits nonzero under set -e, and the server never starts.

Why this doesn't block the current CI sweep: runners/launch_b200-dgxc.sh hardcodes MODEL_PATH="/lustre/fsw/gharunners/models/MiniMax-M2.7-NVFP4" for MODEL_PREFIX=minimaxm2.7/fp4 and then does export MODEL="$MODEL_PATH", so in the actual GitHub Actions sweep TARGET_MODEL_PATH is always an absolute path and the --local-dir branch is taken instead — this else branch is dead code from CI's perspective. The PR's own validation (bash -n plus matrix generation) doesn't execute the script, so it wouldn't have caught this either. All three independent verifiers reached the same conclusion and rated it nit severity for this reason.

Suggested fix: capture the resolved snapshot directory that hf download prints/returns (or, simpler, always pass --local-dir "$TARGET_MODEL_PATH"/"$DRAFT_MODEL_PATH" pointing at a real staging directory even in the hub-cache branch, mirroring the absolute-path branch's behavior) so the preflight always receives real filesystem paths.

f"layers={draft.get('num_hidden_layers')}"
)
print(f"Validated target={target_path} draft={draft_path}")
PY

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

IS_AGENTIC=false
if [[ "${SCENARIO_TYPE:-fixed-seq-len}" == "agentic-coding" ]]; then
IS_AGENTIC=true
check_env_vars DURATION RESULT_DIR KV_OFFLOADING TOTAL_CPU_DRAM_GB
if [[ "$KV_OFFLOADING" != "none" ]]; then
echo "This low-concurrency EAGLE3 experiment requires GPU-resident KV cache" >&2
exit 1
fi
export INFMAX_CONTAINER_WORKSPACE="${INFMAX_CONTAINER_WORKSPACE:-/workspace}"
resolve_trace_source
install_agentic_deps
OUTPUT_DIR="$RESULT_DIR"
CONTEXT_LENGTH=196608
export MAX_MODEL_LEN="$CONTEXT_LENGTH"
MAX_RUNNING_REQUESTS=$((CONC * 2))
(( MAX_RUNNING_REQUESTS < 8 )) && MAX_RUNNING_REQUESTS=8
else
check_env_vars ISL OSL MAX_MODEL_LEN
OUTPUT_DIR="$PWD"
CONTEXT_LENGTH="$MAX_MODEL_LEN"
MAX_RUNNING_REQUESTS=$((CONC * 2))
fi
mkdir -p "$OUTPUT_DIR"

SERVER_LOG="$OUTPUT_DIR/server.log"
SERVER_PID=""
GPU_MONITOR_STARTED=false

cleanup() {
local exit_code=$?
trap - EXIT INT TERM
set +e
if [[ "$GPU_MONITOR_STARTED" == "true" ]]; then
stop_gpu_monitor
fi
stop_background_process_tree "$SERVER_PID" "MiniMax-M2.7 EAGLE3 server" 60
exit "$exit_code"
}
trap cleanup EXIT INT TERM

start_gpu_monitor --output "$OUTPUT_DIR/gpu_metrics.csv"
GPU_MONITOR_STARTED=true

SGLANG_CMD=(
python3 -m sglang.launch_server
--model-path "$TARGET_MODEL_PATH"
--served-model-name "$TARGET_MODEL"
--host 0.0.0.0
--port "$PORT"
--trust-remote-code
--tp "$TP"
--ep-size "$EP_SIZE"
--quantization modelopt_fp4
--dtype bfloat16
--attention-backend triton
--moe-runner-backend flashinfer_cutlass
--speculative-algorithm EAGLE3
--speculative-draft-model-path "$DRAFT_MODEL_PATH"
--speculative-num-steps 3
--speculative-eagle-topk 1
--speculative-num-draft-tokens 4
--speculative-draft-model-quantization unquant
--speculative-draft-attention-backend triton
--reasoning-parser minimax
--tool-call-parser minimax-m2
--context-length "$CONTEXT_LENGTH"
--chunked-prefill-size 16384
--weight-loader-prefetch-checkpoints
--max-running-requests "$MAX_RUNNING_REQUESTS"
--mem-fraction-static 0.85
--enable-metrics
--watchdog-timeout 1800
)

printf '%q ' "${SGLANG_CMD[@]}" | tee "$OUTPUT_DIR/sglang_command.txt"
printf '\n' | tee -a "$OUTPUT_DIR/sglang_command.txt"
"${SGLANG_CMD[@]}" > "$SERVER_LOG" 2>&1 &
SERVER_PID=$!

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

mtp_metrics=$(curl -fsS "http://127.0.0.1:$PORT/metrics")
if ! grep -m1 '^sglang:spec_accept_length' <<< "$mtp_metrics"; then
echo "EAGLE3 acceptance metrics are absent; refusing to publish a non-speculative result" >&2
exit 1
fi

if [[ "$IS_AGENTIC" == "true" ]]; then
# AgentX sends structured chat messages to /v1/chat/completions, so the
# target checkpoint's native chat template is applied by the server.
build_replay_cmd "$RESULT_DIR"
# TP4 can take several minutes to finish a long response that was already
# admitted near the end of the measurement window. The AIPerf default is
# only 30 seconds, which cancelled the sole profiling request in the TP4
# smoke while the healthy server was still decoding it. Bound the drain at
# 30 minutes without extending the measured request-admission window.
REPLAY_CMD+=" --benchmark-grace-period 1800"
REPLAY_CMD+=" --server-metrics http://localhost:$PORT/metrics"
run_agentic_replay_and_write_outputs "$RESULT_DIR"
else
# EAGLE3 was trained on chat-formatted prompts; keep the benchmark client
# on the target checkpoint's native chat template.
run_benchmark_serving \
--model "$TARGET_MODEL_PATH" \
--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 "$PWD" \
--trust-remote-code \
--use-chat-template

if [[ "${RUN_EVAL:-false}" == "true" ]]; then
run_eval --framework lm-eval --port "$PORT"
append_lm_eval_summary
fi
fi
25 changes: 25 additions & 0 deletions configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8098,3 +8098,28 @@ glm5.2-fp4-b300-sglang-agentic:
# radix cache (GPU hit 0.93->0.57 at 48->64) and thrashes on re-prefill, so it is
# strictly dominated by conc 48 on both throughput and interactivity.
- { tp: 8, ep: 8, dp-attn: true, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [48], router: { name: sglang-router, version: "0.3.2" } }

# Experimental public-weight MiniMax-M2.7 NVFP4 + EAGLE3 B200 baseline. The
# target and full-vocabulary draft revisions are pinned in the benchmark
# entrypoint; both scenarios use the same upstream SGLang runtime and 3/1/4
# speculative chain.
minimaxm2.7-fp4-b200-sglang-eagle:
image: lmsysorg/sglang:v0.5.15.post1-cu130
model: nvidia/MiniMax-M2.7-NVFP4
model-prefix: minimaxm2.7
runner: cluster:b200-dgxc
precision: fp4
framework: sglang
multinode: false
scenarios:
fixed-seq-len:
- isl: 8192
osl: 1024
search-space:
- { tp: 4, ep: 1, conc-list: [1, 2, 4, 8], spec-decoding: mtp }
- { tp: 8, ep: 1, conc-list: [1, 2, 4, 8], spec-decoding: mtp }
agentic-coding:
- dram-utilization: 0.80
search-space:
- { tp: 4, ep: 1, kv-offloading: none, conc-list: [1, 2, 4], spec-decoding: mtp }
- { tp: 8, ep: 1, kv-offloading: none, conc-list: [1, 2, 4], spec-decoding: mtp }
16 changes: 12 additions & 4 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,7 @@
- "TP=2 and TP=4, concurrency 4-256 for 1k1k and 8k1k sequence lengths"
- "Add --gpu-memory-utilization 0.9 to server launch"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/1133


- config-keys:
- dsv4-fp8-h200-vllm
Expand Down Expand Up @@ -1860,7 +1860,7 @@
- "Image pinned to lmsysorg/sglang:deepseek-v4-b300@sha256:26e116bd211e300dbb76924d56c5cbe6cc3ee5ee2fe314859cb8774f5bc070f3"
- "DP-attention path enables SGLANG_OPT_SWA_EVICT_DROP_PAGE_MARGIN=1 for better SWA eviction behavior"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/1185


- config-keys:
- dsv4-fp4-b200-sglang
Expand All @@ -1882,7 +1882,7 @@
description:
- "Update GPTOSS-120B FP4 MI355X Atom benchmark (rocm/atom:rocm7.2.2_ubuntu24.04_py3.12_pytorch_release_2.10.0_atom0.1.2.post)"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/1195


- config-keys:
- dsv4-fp4-b300-vllm
Expand Down Expand Up @@ -1990,7 +1990,7 @@
- "Add conc=8192 recipe for 1k1k: deepep mega_moe backend with cuda-graph-max-bs 1088, max-running-requests 8192, mem-fraction-static 0.80, swa-full-tokens-ratio 0.3, tokenizer-worker-num 16"
- "conc=8192 enables SGLANG_OPT_USE_ONLINE_COMPRESS=1 and --stream-interval 30"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/1209


- config-keys:
- dsv4-fp4-b300-vllm
Expand Down Expand Up @@ -5038,3 +5038,11 @@
- "Topologies: 1p4d-dep4-tep8 (conc 4/24), 1p5d-dep4-tep4 (conc 5/30/60/115/195), 1p1d-dep4-dep8 (conc 308), 2p1d-dep4-dep8 (conc 615), 3p1d-dep4-dep8 (conc 1127), 4p1d-dep4-dep8 (conc 2151)"
- "Runner updated to clone srt-slurm at sa-submission-q2-2026 and copy local recipes into recipes/trtllm/kimi-k25-nvfp4/b200-fp4/"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2249

- config-keys:
- minimaxm2.7-fp4-b200-sglang-eagle
description:
- "Experiment with MiniMax-M2.7 NVFP4 and a public full-vocabulary EAGLE3 draft on B200 using upstream SGLang v0.5.15.post1."
- "Run 8K/1K and AgentX at pure TP4/TP8 with chat templates and a 3-step, top-k-1, 4-draft-token speculative chain."
- "Pin nvidia/MiniMax-M2.7-NVFP4 and asherszhang/MiniMax-M2.7-EAGLE3-draft-vocab200k revisions in the benchmark preflight."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2289
23 changes: 18 additions & 5 deletions runners/launch_b200-dgxc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ elif [[ $MODEL_PREFIX == "minimaxm2.5" && $PRECISION == "fp8" ]]; then
elif [[ $MODEL_PREFIX == "minimaxm2.5" && $PRECISION == "fp4" ]]; then
export MODEL_PATH="/lustre/fsw/models/MiniMax-M2.5-NVFP4"
export SRT_SLURM_MODEL_PREFIX="minimax-m2.5-nvfp4"
elif [[ $MODEL_PREFIX == "minimaxm2.7" && $PRECISION == "fp4" ]]; then
# Public NVFP4 target and EAGLE3 draft are staged in the runner-writable
# shared model tree by the benchmark's revision-pinned preflight.
export MODEL_PATH="/lustre/fsw/gharunners/models/MiniMax-M2.7-NVFP4"
export SRT_SLURM_MODEL_PREFIX="minimax-m2.7-nvfp4"
# Covers the one-hour AgentX measurement plus setup and warmup while still
# allowing Slurm to backfill this experiment ahead of large reservations.
SALLOC_TIME_LIMIT="${SALLOC_TIME_LIMIT:-150}"
elif [[ $MODEL_PREFIX == "gptoss" && $PRECISION == "fp4" ]]; then
export MODEL_PATH="/lustre/fsw/models/gpt-oss-120b"
export SRT_SLURM_MODEL_PREFIX="gptoss"
Expand Down Expand Up @@ -403,6 +411,12 @@ else
# pulling from the HF hub cache. Bench scripts skip `hf download` when
# MODEL is a local path.
export MODEL="$MODEL_PATH"
MODEL_MOUNT_SPEC="$MODEL_PATH:$MODEL_PATH"
if [[ "$MODEL_PREFIX" == "minimaxm2.7" ]]; then
MODEL_ROOT=$(dirname "$MODEL_PATH")
mkdir -p "$MODEL_PATH" "$MODEL_ROOT/MiniMax-M2.7-EAGLE3-draft-vocab200k"
MODEL_MOUNT_SPEC="$MODEL_ROOT:$MODEL_ROOT"
fi
FRAMEWORK_SUFFIX=$([[ "$FRAMEWORK" == "trt" ]] && printf '_trt' || printf '')
SPEC_SUFFIX=$([[ "$SPEC_DECODING" == "mtp" ]] && printf '_mtp' || printf '')
# Prefer a framework-tagged script (e.g. dsv4_fp4_b200_vllm.sh) so models
Expand All @@ -426,10 +440,9 @@ else
CONTAINER_MOUNT_DIR=/workspace
fi

# b200-dgxc cluster was re-partitioned to gpu-1 / gpu-2; the prior gpu-10
# and gpu-15 names no longer exist. gpu-2 currently has 10 fully-idle GPU
# nodes (all of gpu-2-[0-9]); gpu-1 has 2 drained (gpu-1-4, gpu-1-8). We
# land on gpu-2 to avoid drained nodes and skip the per-node excludes.
# b200-dgxc is partitioned into gpu-1 / gpu-2; the prior gpu-10 and gpu-15
# names no longer exist. sa-shared's benchmark association currently has
# only gpu-2_qos, so idle gpu-1 nodes cannot accept these jobs.
export GPU_COUNT="${GPU_COUNT:-${TP:?TP must be set}}"

SALLOC_TIME_LIMIT="${SALLOC_TIME_LIMIT:-480}"
Expand All @@ -453,7 +466,7 @@ else

srun --jobid=$JOB_ID \
--container-image=$SQUASH_FILE \
--container-mounts=$GITHUB_WORKSPACE:$CONTAINER_MOUNT_DIR,$MODEL_PATH:$MODEL_PATH,$AIPERF_MMAP_CACHE_HOST_PATH:/aiperf_mmap_cache \
--container-mounts=$GITHUB_WORKSPACE:$CONTAINER_MOUNT_DIR,$MODEL_MOUNT_SPEC,$AIPERF_MMAP_CACHE_HOST_PATH:/aiperf_mmap_cache \
--no-container-mount-home \
--container-workdir=$CONTAINER_MOUNT_DIR \
--no-container-entrypoint --export=ALL,PORT=8888,AIPERF_DATASET_MMAP_CACHE_DIR=/aiperf_mmap_cache \
Expand Down
Loading