[AMD][AgentX] MINIMAX-M3 FP4 MI355X agentX vLLM#2129
Conversation
Signed-off-by: ajith-sirra-amd <ajith.sirra@amd.com>
Signed-off-by: ajith-sirra-amd <ajith.sirra@amd.com>
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🔴
perf-changelog.yaml:4607—perf-changelog.yamlis missing its trailing newline afterpr-link: To-Be-Added(the diff footer shows\ No newline at end of file).utils/validate_perf_changelog.pyexplicitly rejects this:parse_changelog(line 89) andvalidate_matrix_compatible_change(line 352-354) both raiseChangelogValidationError("{path} does not end with a newline")— so the changelog-validation job will fail and block merge. Also worth updatingpr-link: To-Be-Addedto the actual PR URL before merge; every other entry uses a real link.Extended reasoning...
What the bug is
The new entry appended to
perf-changelog.yamlterminates without an LF byte. Byte-level inspection (od -c) confirms the file ends with the raw bytesT o - B e - A d d e d— no trailing\n. The diff itself makes this explicit with the\ No newline at end of filemarker afterpr-link: To-Be-Added.Why existing code doesn't tolerate it
utils/validate_perf_changelog.pyhas two independent hard checks for this exact condition:parse_changelog(line 88-90):if not raw.endswith(b"\n"): raise ChangelogValidationError(f"{label} does not end with a newline")validate_matrix_compatible_change(line 350-354) re-runs the same check on the head-ref bytes:if not head_raw.endswith(b"\n"): raise ChangelogValidationError(f"{path} at {head_ref} does not end with a newline")
AGENTS.md line 176 explicitly warns about this file: "Never delete or modify whitespace in perf-changelog.yaml — CI depends on exact whitespace (including trailing spaces on blank separator lines). Altering it breaks CI."
Impact
The changelog-validation job will refuse this PR — this is a merge-blocking CI failure, not a cosmetic issue. It's also the failure mode AGENTS.md explicitly warns about: a future append to this file (via
>>or a non-POSIX editor that doesn't insert an LF before writing) would physically concatenate the next- config-keys:onto the same line asTo-Be-Added, silently corrupting the YAML.Step-by-step proof
git show HEAD -- perf-changelog.yaml | tail -3shows:pr-link: To-Be-Addedfollowed by\ No newline at end of file.tail -c 30 perf-changelog.yaml | od -creturns...9 2 d 5 c 1 " \n p r - l i n k : T o - B e - A d d e d— the final byte isd, not\n.- Running validation:
python utils/validate_perf_changelog.py(or its equivalent CI entrypoint) entersparse_changelog, which reads the file as bytes and immediately hitsif not raw.endswith(b"\n")on line 89 → raisesChangelogValidationError: perf-changelog.yaml does not end with a newline. - Every other entry in the file terminates with
\n\nbefore the next- config-keys:— this is the only entry that breaks the invariant.
Fix
Append a single
\nbyte to the end of the file (e.g.,printf '\n' >> perf-changelog.yamlor ensure the editor's "insert final newline" setting is on). Also updatepr-link: To-Be-Addedtohttps://github.com/SemiAnalysisAI/InferenceX/pull/2129to match every other entry's convention before merge.
| lmcache) | ||
| unset VLLM_USE_SIMPLE_KV_OFFLOAD | ||
| git clone https://github.com/LMCache/LMCache.git | ||
| cd LMCache | ||
| pip install -r requirements/build.txt | ||
| CXX=hipcc BUILD_WITH_HIP=1 pip install -e . --no-build-isolation | ||
| cd .. |
There was a problem hiding this comment.
🟡 The LMCache install at minimaxm3_fp4_mi355x.sh:93 clones https://github.com/LMCache/LMCache.git with no pinned tag/SHA (--branch/--depth), so each benchmark run installs whatever HEAD of the LMCache default branch happens to be at that moment — silently invalidating cross-run perf comparability, which is the whole point of this recipe. The clone is also non-idempotent under set -euo pipefail: a retried run in the same workspace dies at git clone with 'destination path already exists'. Prefer a versioned pip install (as in kimik2.5_fp4_b200.sh:99: agentic_pip_install --quiet --no-cache-dir lmcache), or pin the clone (git clone --depth 1 --branch <tag> ...) with an rm -rf LMCache guard first (as in minimaxm3_fp8_mi325x.sh:87 for mooncake).
Extended reasoning...
What the bug is
At benchmarks/single_node/agentic/minimaxm3_fp4_mi355x.sh:91-97, the lmcache branch of the offload-backend case builds LMCache from source like this:
lmcache)
unset VLLM_USE_SIMPLE_KV_OFFLOAD
git clone https://github.com/LMCache/LMCache.git
cd LMCache
pip install -r requirements/build.txt
CXX=hipcc BUILD_WITH_HIP=1 pip install -e . --no-build-isolation
cd ..There is no --branch <tag>, no --depth 1, no post-clone git checkout <sha> — the clone resolves to whatever HEAD of the LMCache default branch (main) is at run time.
Impact — reproducibility
The entire purpose of a recipe in this repo is to produce numerically comparable perf points that get ingested into perf-changelog.yaml and rolled up across time. The single search-space row in configs/amd-master.yaml for this recipe is:
- { tp: 4, kv-offloading: dram, kv-offload-backend: lmcache, conc-list: [1, 4, 8, 16, 32] }i.e. every configuration this recipe measures goes through the lmcache branch and therefore through the LMCache library. LMCache is an active project — LMCache/main moves. Two runs of this recipe on different days will install different LMCache revisions and can produce different tokens/s, TTFT and cache-hit numbers without any change to this repository. That is the exact anti-pattern the codebase avoids everywhere else: configs/amd-master.yaml pins every vLLM/SGLang image to a digest-suffixed nightly tag (see the dsv4-fp4-mi355x-vllm block comment that explicitly warns "pin to a digest-suffixed nightly tag rather than the floating :nightly"), and the sibling Mooncake source builds in minimaxm3_fp8_mi325x.sh:87 and minimaxm3_fp8_mi300x.sh:87 use git clone --depth 1 --branch "$mooncake_tag" ... with mooncake_tag='v0.3.11.post1'.
Impact — idempotency
The script begins with set -euo pipefail at line 2. git clone <url> into an existing non-empty directory fails with fatal: destination path 'LMCache' already exists and is not an empty directory, exit code 128. There is no rm -rf LMCache guard or [[ -d LMCache ]] || ... check. If a prior run in the same workspace didn't clean up (retried run, workspace reuse across configs in the sweep, or actions/checkout not pruning untracked directories), the second attempt aborts before the LMCache server ever starts.
Regression from the pre-existing pattern
The sibling NVIDIA recipe benchmarks/single_node/agentic/kimik2.5_fp4_b200.sh:99 uses a versioned PyPI install through the isolated agentic venv:
agentic_pip_install --quiet --no-cache-dir lmcacheThis was the established pattern for installing LMCache in an agentic recipe. Switching to an unpinned HEAD clone is a strict regression. (The MI355X build does need a HIP source build — no ROCm wheel on PyPI — so falling back to a git clone is fine, but it must be pinned.)
Step-by-step proof of the non-idempotency failure
- Sweep dispatches the config
{ tp: 4, kv-offloading: dram, kv-offload-backend: lmcache, conc: 1 }; the script cds into the workspace, runsgit clone https://github.com/LMCache/LMCache.git, builds, benchmarks, exits 0.LMCache/remains in the workspace. - Sweep dispatches the next config
{ tp: 4, ..., conc: 4 }; the script cds into the same workspace again, hitsgit cloneat line 93. - Git prints
fatal: destination path 'LMCache' already exists and is not an empty directoryand returns exit 128. set -eimmediately aborts the script with exit 128 —wait_for_lmcache_ready,vllm serve, andrun_agentic_replay_and_write_outputsnever run. The config is recorded as failed.
(Whether the workspace persists across configs depends on the launcher; the current launch_mi355x-amds.sh mounts $GITHUB_WORKSPACE at /workspace, so in practice fresh CI containers usually paper over this — but any local re-run or workspace-reuse path trips the failure.)
Fix
A one-line change, either
agentic_pip_install --quiet --no-cache-dir lmcache==<version>or, keeping the HIP source build:
rm -rf LMCache
git clone --depth 1 --branch <tag-or-sha> https://github.com/LMCache/LMCache.gitSeverity
Nit — a clean container run in CI passes, the numerical result is some valid measurement, and there is no user-visible crash on the green path. But this is directly identified as an anti-pattern relative to the sibling recipes in the same directory and it silently erodes benchmark comparability over time, so worth calling out before merge.
| # | ||
| # Required env vars: | ||
| # MODEL, TP, CONC, OFFLOADING, TOTAL_CPU_DRAM_GB, RESULT_DIR | ||
| # | ||
| # OFFLOADING values: | ||
| # none - vLLM GPU KV only. | ||
| # cpu - vLLM native CPU offload. | ||
| # lmcache - LMCache MP server + vLLM LMCacheMPConnector. | ||
| 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 | ||
| echo "MODEL=$MODEL TP=$TP CONC=$CONC KV_OFFLOADING=$KV_OFFLOADING TOTAL_CPU_DRAM_GB=$TOTAL_CPU_DRAM_GB RESULT_DIR=$RESULT_DIR DURATION=$DURATION EP_SIZE=$EP_SIZE DP_ATTENTION=$DP_ATTENTION" | ||
| PORT=${PORT:-8888} | ||
| DURATION=${DURATION:-1800} | ||
| EP_SIZE=${EP_SIZE:-1} | ||
| if [[ -n "${SLURM_JOB_ID:-}" ]]; then | ||
| echo "JOB $SLURM_JOB_ID running on ${SLURMD_NODENAME:-unknown}" |
There was a problem hiding this comment.
🟡 The docstring, check_env_vars call, and variable defaults in the new script are internally inconsistent: (1) the header lists OFFLOADING but check_env_vars actually requires KV_OFFLOADING; (2) the header omits DURATION, EP_SIZE, DP_ATTENTION, and KV_OFFLOAD_BACKEND, all of which check_env_vars enforces; (3) the DURATION=${DURATION:-1800} and EP_SIZE=${EP_SIZE:-1} defaults at lines 19-20 are dead code (check_env_vars has already exited if either was empty); and (4) the header enumerates none / cpu / lmcache as OFFLOADING values but the case statement switches on $KV_OFFLOAD_BACKEND and only implements the lmcache arm — cpu and none silently fall through to --no-enable-prefix-caching. The launcher passes the correct vars so CI is unaffected, but the header will actively mislead anyone reading or running this stand-alone. Fix by syncing the docstring with the actual required vars and either dropping the unreachable defaults or removing DURATION/EP_SIZE from check_env_vars.
Extended reasoning...
The script header at lines 5-20 sets up a runtime interface that contradicts itself on four related points. This is doc/dead-code inconsistency in a brand-new file, so nothing breaks in the CI-driven flow — but this recipe is very likely to be copy-pasted (it is the first vLLM+LMCache MI355X agentic entry), and future readers will be misled.
(1) Wrong variable name in the required-vars list. Line 7 documents OFFLOADING, but line 14 calls check_env_vars MODEL TP CONC KV_OFFLOADING TOTAL_CPU_DRAM_GB RESULT_DIR DURATION EP_SIZE DP_ATTENTION. Sibling recipes (dsv4_fp4_b300_sglang.sh:7, kimik2.5_fp4_b300.sh:10, qwen3.5_fp8_h100.sh:15, minimaxm3_fp8_mi325x.sh) all consistently use the KV_OFFLOADING name — the header here is out of sync with the codebase convention.
(2) Missing required vars. The header lists 6 vars; check_env_vars enforces 9. DURATION, EP_SIZE, and DP_ATTENTION are all required at line 14 but never mentioned in the header. A user reading only the header, exporting the 6 documented vars, and running the script will hit Error: The following required environment variables are not set: DURATION EP_SIZE DP_ATTENTION.
(3) Dead-code defaults. Lines 19-20 set DURATION=${DURATION:-1800} and EP_SIZE=${EP_SIZE:-1}. But check_env_vars (benchmarks/benchmark_lib.sh:234) uses [[ -z "${!var_name:-}" ]] to detect unset OR empty and exits 1 if any listed var is missing. So by the time execution reaches line 19, DURATION and EP_SIZE are guaranteed non-empty — the :- fallbacks are unreachable. Compare minimaxm3_fp8_mi325x.sh:7, which requires the same vars in check_env_vars and correctly does NOT repeat the defaults.
(4) Values list documents a third variable name. Lines 9-12 enumerate none / cpu / lmcache as OFFLOADING values. The case statement at line 90 switches on $KV_OFFLOAD_BACKEND — a third, undocumented name — and only implements the lmcache arm. Setting KV_OFFLOAD_BACKEND=cpu (or =none) falls through to the initial OFFLOAD_ARGS=(--no-enable-prefix-caching) from line 47, which is neither vLLM native CPU offload nor GPU-KV-only in any meaningful sense.
Step-by-step proof. Take the header at face value and run: MODEL=amd/MiniMax-M3-MXFP4 TP=4 CONC=8 OFFLOADING=lmcache TOTAL_CPU_DRAM_GB=3000 RESULT_DIR=/tmp/x ./minimaxm3_fp4_mi355x.sh. set -u is on, so line 14 checks KV_OFFLOADING (which the user did not set because the header said OFFLOADING); check_env_vars reports 4 missing vars: KV_OFFLOADING, DURATION, EP_SIZE, DP_ATTENTION. Even if the user then also exports KV_OFFLOADING=lmcache DURATION=1800 EP_SIZE=1 DP_ATTENTION=false, the case at line 90 needs KV_OFFLOAD_BACKEND=lmcache — still-undocumented — otherwise LMCache never starts and vLLM runs with --no-enable-prefix-caching only.
Fix. Two clean options: (a) sync the header — replace OFFLOADING with KV_OFFLOADING, add KV_OFFLOAD_BACKEND, DURATION, EP_SIZE, DP_ATTENTION to the required list, drop the invented cpu/none values (or implement them), and delete lines 19-20; or (b) treat DURATION/EP_SIZE as optional-with-defaults — remove them from check_env_vars, keep the defaults, and add them to the header as optional. The sibling minimaxm3_fp8_mi325x.sh follows option (a); either is fine so long as the docstring and code match. Non-blocking (nit) — CI passes because configs/amd-master.yaml supplies the right variables via the launcher, so this only bites stand-alone users and future readers.
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=29024988092 |
…l File. Signed-off-by: ajith-sirra-amd <ajith.sirra@amd.com>
…om/SemiAnalysisAI/InferenceX into amd/agentx-minimax-m3-vllm-lmcache
…l File. Signed-off-by: ajith-sirra-amd <ajith.sirra@amd.com>
…l File. Signed-off-by: ajith-sirra-amd <ajith.sirra@amd.com>
…l File. Signed-off-by: ajith-sirra-amd <ajith.sirra@amd.com>
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=29032224772 |
|
/reuse-sweep-run |
|
@ajith-sirra-amd |
There was a problem hiding this comment.
thanks for the contribution @seungrokj @ajith-sirra-amd
can u please update recipes.vllm.ai with ur new configs?
vllm recipes doesn't currently support offloading tho the inferact guys said they are open to contributions to support mooncake store, lmcache, vllm native offloading support in vllm recipes
as in sglang cookbook recipes , they also have support for cpu offloading recipes
Summary
benchmarks/single_node/agentic/minimaxm3_fp4_mi355x.shwithlmcache(LMCache MP server + LMCacheMPConnector)minimaxm3-fp4-mi355x-vllm-agenticwith TP4 search spacevllm/vllm-openai-rocm:nightly-69715823df89b11ee684b84066390cbb9092d5c1amd/MiniMax-M3-MXFP4