-
Notifications
You must be signed in to change notification settings - Fork 243
[Klaud Cold] minimaxm3-fp8-h100-vllm-agentic-mtp: MiniMax-M3 H100 AgentX EAGLE3 recipe with golden-AL synthetic acceptance / MiniMax-M3 H100 AgentX EAGLE3 配方(黄金 AL 合成接受) #2423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
functionstackx
wants to merge
4
commits into
main
Choose a base branch
from
feat/minimaxm3-fp8-h100-agentic-mtp
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+249
−0
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
d6544c8
feat(minimaxm3-h100-agentic-mtp): add EAGLE3 AgentX recipe with golde…
functionstackx c80a2aa
chore: fill perf-changelog pr-link for #2423
functionstackx c90e8a3
fix(minimaxm3-h100-agentic-mtp): drop gpu-memory-utilization to 0.90 …
functionstackx c8b3cef
fix: AgentX MTP conc grid — steps >=2, hard stop at conc 16
functionstackx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
211 changes: 211 additions & 0 deletions
211
benchmarks/single_node/agentic/minimaxm3_fp8_h100_mtp.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| #!/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. | ||
| # | ||
| # One deliberate exception: --gpu-memory-utilization is 0.90, not the non-MTP | ||
| # sibling's 0.95. That matches fixed_seq_len/minimaxm3_fp8_h100_mtp.sh, the | ||
| # proven MTP configuration on this SKU. At 0.95 the EAGLE3 head and its KV leave | ||
| # no runtime headroom on an 80 GB H100 and the engine core dies mid-warmup with | ||
| # torch.OutOfMemoryError (96 MiB short on every rank; run 30515793863, TP8/EP8 c9). | ||
| # | ||
| # Speculative config: Inferact/MiniMax-M3-EAGLE3 draft head, 3 speculative | ||
| # tokens — the same draft/level every merged MiniMax-M3 MTP recipe uses | ||
| # (fixed_seq_len/minimaxm3_fp8_{h100,h200,mi300x,mi325x}_mtp.sh). | ||
| # | ||
| # 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" | ||
|
|
||
| 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 | ||
| if require_agentic_kv_offload_backend mooncake; then | ||
| 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}}' | ||
| ) | ||
| fi | ||
|
|
||
| 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 | ||
|
|
||
| # 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 (golden_al_distribution/README.md). 2.83 is the MiniMax-M3 EAGLE3 | ||
| # curve at num_speculative_tokens=3, thinking_on | ||
| # (golden_al_distribution/minimaxm3_eagle3.yaml). The separate | ||
| # minimaxm3_eagle3_gqa.yaml curve belongs to the Inferact/MiniMax-M3-EAGLE3-GQA | ||
| # draft and is not mixed in here. | ||
| # | ||
| # 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.83 | ||
| 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, so keep | ||
| # the non-MTP recipe's 2x scheduler headroom for subagent fan-out. | ||
| MAX_NUM_SEQS=$((2 * CONC)) | ||
| # 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 \ | ||
| --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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Lines 175-176 pass both
--tool-call-parser minimax_m3and--reasoning-parser minimax_m3, butminimaxm3_fp4_mi355x.sh(merged the day before, PR #2118) intentionally omits the reasoning parser for this exact agentic-coding scenario, with a detailed comment explaining that MiniMax-M3's<mm:think>block must round-trip inmessage.contentevery turn or multi-turn quality collapses into repeated commands / empty patches. This is inherited fromminimaxm3_fp8_h100.shand its h200/mi300x/mi325x siblings, but this PR copies the same known-bad flag pair into brand-new code, and it hits hardest on theEVAL_ONLYreal-verification path (lines 195-196), which would score near zero from the resulting degeneration. Fix: drop--reasoning-parser minimax_m3here as was already done in the mi355x recipe.Extended reasoning...
The bug:
minimaxm3_fp8_h100_mtp.sh:175-176passes both--tool-call-parser minimax_m3and--reasoning-parser minimax_m3to the vLLM server. MiniMax-M3 is an interleaved-thinking model that emits an<mm:think>...</mm:think>block as part of its response. When--reasoning-parser minimax_m3is enabled, vLLM strips that block out ofmessage.contentand surfaces it instead as a separate, response-onlyreasoning_contentfield on the OpenAI-compatible completion object.Why that breaks multi-turn agentic runs: the agentic-coding harness this script drives (
run_agentic_replay_and_write_outputs, mini-swe-agent + litellm's OpenAI client) only round-tripsmessage.contentback into the conversation on the next turn — it does not resendreasoning_content. So with the reasoning parser enabled, the model's think block silently disappears from its own conversation history every turn. Without visibility into its prior plan, the model degenerates: it repeats the same command turn after turn until the step limit is hit, producing an empty patch.Why nothing here prevents it: this exact failure mode and fix are already documented in the repo.
benchmarks/single_node/agentic/minimaxm3_fp4_mi355x.sh:119-127(PR #2118, merged one day before this recipe's non-MTP sibling) keeps--tool-call-parser minimax_m3but explicitly omits--reasoning-parser minimax_m3, with an inline comment spelling out precisely this mechanism. That fix was never backported tominimaxm3_fp8_h100.sh(nor its h200/mi300x/mi325x siblings), and this new MTP recipe copies the non-MTP h100 sibling's server flags verbatim (including both parsers), propagating the known-bad configuration into new code rather than following the already-established fix.Impact: the failure is model/harness-level, not hardware- or spec-decode-specific, so it applies identically on this H100 EAGLE3 recipe. The throughput sweep runs use
rejection_sample_method: syntheticwith a forced golden acceptance length, so the actual generated text there is synthetic/garbage by design and the parser bug doesn't change the throughput numbers. But theEVAL_ONLYpath (lines 195-196) drops synthetic acceptance specifically to do real target verification and score genuine agentic quality — this is exactly the path that would collapse into empty-patch degeneration from the stripped think block, defeating the purpose of carving outEVAL_ONLYin the first place.Step-by-step proof of the mechanism:
--reasoning-parser minimax_m3(line 176), so every completion response has its<mm:think>...</mm:think>content removed frommessage.contentand placed only inmessage.reasoning_content.message.content(the tool-call/action portion) —reasoning_contentis not part of the OpenAI chat schema the client resends, so it's dropped.<mm:think>reasoning/plan.EVAL_ONLYreal-verification path would score at or near zero, even though the server and harness are otherwise functioning correctly.Fix: drop
--reasoning-parser minimax_m3from thevllm serveinvocation (keep--tool-call-parser minimax_m3), matching whatminimaxm3_fp4_mi355x.shalready does for this identical scenario.