Skip to content

[DIAGNOSTIC] MI355X Kimi-K3 AgentX with no KV transfer config - #2635

Closed
sammshen wants to merge 47 commits into
mainfrom
kimik3-mi355x-nokv
Closed

[DIAGNOSTIC] MI355X Kimi-K3 AgentX with no KV transfer config#2635
sammshen wants to merge 47 commits into
mainfrom
kimik3-mi355x-nokv

Conversation

@sammshen

Copy link
Copy Markdown
Collaborator

DIAGNOSTIC ONLY — not for merge

Isolates whether the hipErrorIllegalAddress crashes on the MI355X Kimi-K3 AgentX runs come from the LMCache offload connector or from the node.

What this changes

Adds kimik3-fp4-mi355x-vllm-agentic-mtp-nokv, identical to kimik3-fp4-mi355x-vllm-agentic-mtp-lmcache — same image (nightly-ac7509e2b1), same script, TP8/EP1, spec-decoding: mtp, same concurrencies [4, 8, 10, 12, 16] — with one difference:

- { tp: 8, ep: 1, kv-offloading: none, conc-list: [4, 8, 10, 12, 16], spec-decoding: mtp }

kv-offloading: none means the recipe starts no LMCache MP server and passes no --kv-transfer-config. GPU-resident KV only.

Why

hipErrorIllegalAddress has appeared across:

…and every one of those landed on mia1-p01-g11, which is 0-for-8 today. Other nodes produce clean results with the identical code. That points at the node rather than the connector, but the evidence is observational — this run tests it directly.

observation node
illegal memory access ×6 g11
task-launch communication failure ×1 g11
passed

How to read the result

  • Crashes persist with no KV transfer config → not LMCache's fault; the node (or the base Kimi-K3 + DSpark stack) is at fault.
  • Crashes vanish → the connector is implicated after all, and the LMCache arm needs deeper investigation.

Either way it also yields a clean GPU-resident baseline on the current image, which is useful for the LMCache comparison in #2598.

Note

Node exclusion cannot currently keep this off g11: SALLOC_EXCLUDE is exported in benchmark-tmpl.yml but no code consumes it — neither salloc invocation passes --exclude. Until that's wired up, any point may still draw the bad node.

sammshen and others added 30 commits August 12, 2026 16:54
Add an lmcache kv-offload-backend point at TP8 conc 10 on top of the
existing DSpark MTP serving stack, mirroring the vllm-simple offload arm
for a direct backend comparison. The benchmark script gains an lmcache
case arm that installs the LMCache 0.5.4rc1 ROCm wheel (torch/ROCm stack
untouched), starts one MP server per the Kimi-K3 recipe (chunk size 768
= K3 unified block size at 8 GPUs, --separate-object-groups for the
hybrid KDA/MLA two-group KV layout, --enable-extra-logging,
--max-cpu-workers 8 --max-gpu-workers 1), and wires vLLM to it via
LMCacheMPConnector.
A separate kimik3-fp4-mi355x-vllm-agentic-mtp-lmcache key lets the
changelog select only the LMCache points instead of re-running the
resident and vllm-simple arms of the base key. The base key returns to
its upstream shape.
The LMCache MP server's L1 lives in /dev/shm and the script rejects
budgets above 90% of free shm. mi355x-amds nodes mount ~1.5 TB of shm
(cap ~1360 GB), so 0.50's 1499 GB budget failed the check in run
31644286169. 0.40 generates ~1199 GB, which fits with margin.
vLLM sizes the K3 unified attention block to 1536 tokens on the MI355X
fp8-KV TRITON_MLA path (attention page >= mamba page), and the MP
connector asserts chunk %% block == 0, so the recipe's CUDA-path 768
fails engine init (run 31644990546).
The connector requires the chunk to be a multiple of every engine KV
group's tokens_per_block. On this stack the hybrid layout registers
attention groups at 1536 and a KDA state group at 3072 (run
31645828378), so 1536 fails registration; 3072 is the minimum valid
chunk.
Auto mode loads both transfer paths; pin server-driven STORE/RETRIEVE
(as the MiniMax-M3 arm does) so the benchmark measures one
deterministic path. The L1 stays shm-backed either way, so the /dev/shm
capacity check still applies.
The default 300s read-lock TTL expires under a single GPU worker
serializing huge K3 transfers: run 31648224111 logged 57k
finish-read-on-non-read-locked-key warnings starting exactly at
warmup+300s, followed by a GPU illegal-access crash mid-profile.
Match the MiniMax-M3 arm's 7200s read TTL.
Signed-off-by: Sirra <asirra@amd.com>
Signed-off-by: Sirra <asirra@amd.com>
Signed-off-by: Sirra <asirra@amd.com>
Signed-off-by: ApostaC <yihua98@uchicago.edu>
Signed-off-by: ApostaC <yihua98@uchicago.edu>
Signed-off-by: ApostaC <yihua98@uchicago.edu>
Signed-off-by: ApostaC <yihua98@uchicago.edu>
Signed-off-by: Sirra <asirra@amd.com>
Signed-off-by: Sirra <asirra@amd.com>
Signed-off-by: Sirra <asirra@amd.com>
Signed-off-by: Sirra <asirra@amd.com>
Signed-off-by: Sirra <asirra@amd.com>
Signed-off-by: Sirra <asirra@amd.com>
ApostaC and others added 3 commits August 15, 2026 19:22
Signed-off-by: ApostaC <yihua98@uchicago.edu>
Not for merge. Adds kimik3-fp4-mi355x-vllm-agentic-mtp-nokv: identical to
the -lmcache key (same image nightly-ac7509e2b1, same script, TP8/EP1,
spec-mtp, conc 4/8/10/12/16) with kv-offloading: none, so the run starts
no LMCache server and passes no --kv-transfer-config.

Purpose: isolate the hipErrorIllegalAddress crashes. They have appeared on
mia1-p01-g11 across the LMCache arm, #2602's vllm-simple arm and two
LMCache versions -- 0-for-8 on that node -- which points at the node
rather than the connector. This runs the same points with the connector
absent to confirm that directly.

Expected: if the crashes persist with no KV transfer config, they are not
LMCache's. If they vanish, the connector is implicated after all.
@sammshen
sammshen marked this pull request as ready for review August 17, 2026 23:29
@sammshen
sammshen requested a review from a team August 17, 2026 23:29
@sammshen sammshen added agentx AgentX benchmarks, recipes, and infrastructure AMD full-sweep-enabled labels Aug 17, 2026
@sammshen
sammshen requested a review from a team August 17, 2026 23:29

@claude claude Bot left a comment

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.

Additional findings (outside current diff — PR may have been updated during review):

  • 🔴 perf-changelog.yaml:5981-5990 — This hunk overwrites the existing #2609 changelog entry (minimaxm3-fp4-gb200-dynamo-vllm-agentic-agg-mtp/-disagg-mtp) in place instead of appending after it, violating AGENTS.md's append-only invariant and permanently deleting that history. It also uses an invalid pr-link placeholder ('.../pull/PLACEHOLDER') that fails validate_perf_changelog.py's CI gate independently of the history-deletion issue — restore the deleted #2609 block, append the new entry after it, and use the 'XXX' placeholder token.

    Extended reasoning...

    perf-changelog.yaml:5981-5990 breaks two independent rules at once.

    Append-only violation / data loss. AGENTS.md states, under "Non-negotiable benchmark invariants," that perf-changelog.yaml "is append-only and byte-sensitive. Preserve all existing bytes and separator whitespace, and append only at the tail." The only hunk touching this file in the PR deletes the existing entry for config-keys: minimaxm3-fp4-gb200-dynamo-vllm-agentic-agg-mtp / -disagg-mtp (added by commit 5b9af26, pr-link .../pull/2609) — both its description lines and its pr-link — and writes the new kimik3-fp4-mi355x-vllm-agentic-mtp-nokv entry directly on top of it, including removing a separator blank line. Since this is the only change to the file, that history is not re-added elsewhere; it is simply gone from the committed changelog.

    Invalid pr-link placeholder. Independent of the deletion, the new entrys pr-link is literally https://github.com/SemiAnalysisAI/InferenceX/pull/PLACEHOLDER (confirmed by reading the file at HEAD, line 5990). utils/validate_perf_changelog.py defines PR_LINK_PLACEHOLDERS = {"XXX", ".../pull/XXX"} and CANONICAL_PR_LINK requiring .../pull/\d+. validate_added_pr_link() accepts only a link equal to .../pull/<pr_number> on a PR run (here that would be .../pull/2635) or membership in PR_LINK_PLACEHOLDERS. "PLACEHOLDER" matches neither, so it raises ChangelogValidationError: new PR entry must use .../pull/2635 or an XXX placeholder. On a main-branch validation run (pr_number=None) the canonical regex also rejects it since PLACEHOLDER is non-numeric. Either way, the changelog CI gate in run-sweep.yml goes red, and prepare_perf_changelog_merge.py would independently reject it again at merge-prep time.

    Why existing code/process does not catch this. Nothing in the diff format itself prevents an in-place overwrite — YAML has no structural append-only enforcement, so this relies entirely on author discipline plus the CI validator. The validator does catch the placeholder issue (that gate is designed for exactly this), but it has no way to detect that history for an unrelated, previously-merged config key vanished, since from its perspective the file is just newly-valid or newly-invalid YAython entries.

    Proof, step by step:

    1. Before this PR (post commit 5b9af26), lines ~5981-5990 held a valid entry: config-keys: [minimaxm3-fp4-gb200-dynamo-vllm-agentic-agg-mtp, minimaxm3-fp4-gb200-dynamo-vllm-agentic-disagg-mtp], two description lines, pr-link: .../pull/2609.
    2. This PRs only changelog hunk replaces those exact lines with the new kimik3-fp4-mi355x-vllm-agentic-mtp-nokv entry — not inserted after, but substituted in place (confirmed via sed -n 5975,5995p perf-changelog.yaml on the merged tree, which shows the #2609 config-keys/description/pr-link are simply absent).
    3. Running utils/validate_perf_changelog.py logic against this entry: pr_number=2635 (this PR), link="https://github.com/SemiAnalysisAI/InferenceX/pull/PLACEHOLDER". Expected canonical value is .../pull/2635; PLACEHOLDER is not in PR_LINK_PLACEHOLDERS = {"XXX", ".../pull/XXX"} and does not equal .../pull/2635, so validate_added_pr_link raises ChangelogValidationError.
    4. Both defects are fixable independently: re-insert the #2609 block before this new entry (restoring append-only-at-the-tail ordering) and change the pr-link to .../pull/XXX (or the real number 2635) to pass the CI gate.

    Fix: restore the deleted #2609 config-keys/description/pr-link block, place the new kimik3-fp4-mi355x-vllm-agentic-mtp-nokv entry after it (append at the tail), and change the pr-link to the XXX placeholder token (or the real PR number) instead of PLACEHOLDER.

Comment thread configs/amd-master.yaml
Comment on lines 635 to +660
agentic-coding:
- dram-utilization: 0.50
search-space:
- { tp: 8, kv-offloading: none, conc-list: [1, 4, 8] , spec-decoding: mtp}
- { tp: 8, ep: 1, kv-offloading: dram, kv-offload-backend: { name: vllm-simple }, conc-list: [10], spec-decoding: mtp }
- { tp: 8, ep: 1, kv-offloading: dram, kv-offload-backend: { name: vllm-simple }, conc-list: [1, 4, 8, 10, 12, 14, 16, 20], spec-decoding: mtp }

# LMCache MP-server DRAM offload on top of the same DSpark MTP serving stack as
# kimik3-fp4-mi355x-vllm-agentic-mtp (same image, script, and topology). A
# dedicated key so LMCache points can be selected and swept without re-running
# the resident and vllm-simple arms of the base key.
kimik3-fp4-mi355x-vllm-agentic-mtp-lmcache:
image: vllm/vllm-openai-rocm:nightly-ac7509e2b1db40fec2f03dde1ed4e9dfdc2338c9
model: moonshotai/Kimi-K3
model-prefix: kimik3
runner: cluster:mi355x-amds
precision: fp4
framework: vllm
multinode: false
scenarios:
agentic-coding:
- dram-utilization: 0.60
search-space:
- { tp: 8, ep: 1, kv-offloading: dram, kv-offload-backend: { name: lmcache, version: "0.5.4rc4" }, conc-list: [4, 8, 10, 12, 16], spec-decoding: mtp }

# DIAGNOSTIC ONLY -- not for merge. Identical to
# kimik3-fp4-mi355x-vllm-agentic-mtp-lmcache (same image, script, topology,
# concurrencies) with the KV transfer config removed entirely: no LMCache

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 base key kimik3-fp4-mi355x-vllm-agentic-mtp no longer has a GPU-resident (kv-offloading: none) arm — this diff collapses it into the single vllm-simple arm — yet the new kimik3-fp4-mi355x-vllm-agentic-mtp-lmcache key's comment still says it exists 'so LMCache points can be selected and swept without re-running the resident and vllm-simple arms of the base key.' That 'resident arm' no longer exists on the base key, so either restore it or drop 'resident' from the comment.

Extended reasoning...

Before this PR, kimik3-fp4-mi355x-vllm-agentic-mtp (configs/amd-master.yaml:635-660 region) had two arms in its search space:

- { tp: 8, kv-offloading: none, conc-list: [1, 4, 8], spec-decoding: mtp}
- { tp: 8, ep: 1, kv-offloading: dram, kv-offload-backend: { name: vllm-simple }, conc-list: [10], spec-decoding: mtp }

The first arm (kv-offloading: none) is the GPU-resident baseline. This diff deletes that arm entirely and replaces both with a single extended vllm-simple arm covering conc-list: [1, 4, 8, 10, 12, 14, 16, 20]. So the base key's GPU-resident coverage is gone.

Immediately below, the diff adds a new key, kimik3-fp4-mi355x-vllm-agentic-mtp-lmcache, whose comment reads: "A dedicated key so LMCache points can be selected and swept without re-running the resident and vllm-simple arms of the base key." That comment is describing a base key that no longer matches reality — there is no more "resident arm" on the base key to avoid re-running, since this same diff just removed it.

This is a self-inconsistency introduced entirely within this PR (not a pre-existing issue): the base key's arms and the sibling key's justifying comment are edited in the same diff, and they now contradict each other. Concretely:

  1. Diff hunk removes - { tp: 8, kv-offloading: none, conc-list: [1, 4, 8], spec-decoding: mtp} from the base key.
  2. Diff hunk adds the -lmcache key a few lines later with the comment referencing "the resident ... arms of the base key."
  3. Reading the base key as merged, there is no resident arm — only the single vllm-simple arm.
  4. The only GPU-resident (kv-offloading: none) coverage left anywhere in this file for Kimi-K3 is the diagnostic-only, explicitly-not-for-merge kimik3-fp4-mi355x-vllm-agentic-mtp-nokv key, at different concurrencies ([4, 8, 10, 12, 16] vs. the old resident arm's [1, 4, 8]).

Two plausible fixes, either is reasonable: (a) if the low-conc GPU-resident baseline was meant to be preserved (e.g. folded into vllm-simple's now-wider conc list intentionally), just fix the stale wording in the -lmcache comment to stop referencing a "resident arm"; or (b) if the removal was accidental, restore the kv-offloading: none arm on the base key.

This has no runtime impact — it's a documentation/comment accuracy issue in a diagnostic-only PR, not something that breaks CI or produces wrong benchmark results — so it doesn't block merge, but a maintainer should clean it up before the comment misleads someone editing this file later.

Comment on lines +41 to +58
_patch() {
local target="$1" marker="$2" label="$3"
if [ -z "$target" ] || [ ! -f "$target" ]; then
echo "[$label] target not found; skipping."
return 0
fi
if grep -q "$marker" "$target"; then
echo "[$label] already patched."
return 0
fi
cp -n "$target" "$target.orig" 2>/dev/null || true
if $PY - "$target" "$label"; then
return 0
else
echo "[$label] patch failed; left unchanged." >&2
return 0
fi
}

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 generic _patch() helper (lines 41-58) is dead code: none of the three real patch functions (patch_aiter_pybind11, patch_triton_mla_cudagraph, patch_kv_blockpool) call it, so each reimplements its own marker-check/backup/heredoc-dispatch logic inline instead. It also doesn't match its own documented OLD/NEW heredoc contract (it just runs $PY - "$target" "$label" with no heredoc), so it couldn't even work as-is if called. Recommend deleting it, or routing the three patchers through a shared helper to remove the triplicated boilerplate.

Extended reasoning...

apply_k3_container_patches.sh defines a generic _patch() function at lines 41-58, documented via a comment at line 39-40 as taking a heredoc body that defines OLD and NEW python strings to apply a marker-gated source patch. Grepping the file for _patch shows it appears exactly twice: once in that doc comment, once in its own definition. There is no call site anywhere else in the script.

Instead, each of the three real patch functions — patch_aiter_pybind11 (line ~71), patch_triton_mla_cudagraph, and patch_kv_blockpool — reimplements the same sequence inline: check whether a marker string is already present via grep -q, back up the target to $target.orig via cp -n, then dispatch to a $PY - "$target" <<'EOF' ... EOF heredoc that does an exact string replace and writes the file back. This is roughly six lines of boilerplate duplicated three times, and a reader encountering _patch() first has to reconcile its contract against three implementations that don't actually use it and that diverge from each other in additional ways (e.g. patch_kv_blockpool explicitly calls out needing a unique marker, unlike the shared-marker assumption implicit in _patch()'s signature).

It's also worth noting _patch() is not just unused but effectively broken relative to its own contract: the doc comment above it says callers pass OLD/NEW via a heredoc, but the function body only ever invokes $PY - "$target" "$label" with no heredoc attached to that specific call — so even if something did call it today, it would not do what the comment describes. That's consistent with it having been orphaned when the three patchers were written directly instead.

There's no functional impact: this is a diagnostic-only script (per the PR description, not for merge as-is) and the dead function is never reached at runtime, so nothing breaks. The fix is purely a maintainability one — either delete _patch() (and its doc comment) since the three patchers have already diverged enough that unifying them isn't free, or actually refactor patch_aiter_pybind11/patch_triton_mla_cudagraph/patch_kv_blockpool to route through a corrected version of _patch() to eliminate the triplicated marker-check/backup/heredoc-dispatch boilerplate.

Proof: grep -n '_patch' benchmarks/single_node/agentic/apply_k3_container_patches.sh returns only line 39 (comment) and line 41 (definition) — zero call sites. Each of patch_aiter_pybind11, patch_triton_mla_cudagraph, and patch_kv_blockpool can be inspected to confirm they each independently perform grep -q "$marker" "$target", cp -n "$target" "$target.orig", and their own $PY - "$target" <<'EOF' ... EOF block rather than calling _patch"$target" "$marker" "$label" <<EOF ... EOF.

@github-actions

Copy link
Copy Markdown
Contributor

sammshen added a commit that referenced this pull request Aug 18, 2026
The branch was cut from kimik3-mi355x-nokv, so it carried that PR's
changelog entry as well and the sweep selected both keys -- re-running the
five nokv points that already have clean results, under
AMD_SERIALIZE_KERNEL=3, on the same MI355X nodes the serialize jobs need.
Those runs would be enormously slow and their numbers meaningless.

Drop the nokv entry here so only kimik3-fp4-mi355x-vllm-agentic-mtp-serialize
sweeps. The entry stays on #2635, which owns it; this removes nothing that
exists on main.
@sammshen

Copy link
Copy Markdown
Collaborator Author

Diagnostic complete. Result: 5/5 clean (c4/c8/c10/c12/c16) with the KV transfer config removed and DSpark fully active (2.60 mean acceptance), confirming speculative decoding alone does not cause the hipErrorIllegalAddress.

Caveat recorded on the Confluence writeup: this arm removes DRAM offload, so it cannot exonerate a node that only faults under offload load. It does NOT rule out the bad-node hypothesis, which the node-placement data now supports (mia1-p01-g11: 8 failures / 8 jobs under offload).

Superseded by #2598, which reproduces the crash on the current image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentx AgentX benchmarks, recipes, and infrastructure AMD full-sweep-enabled

Projects

Development

Successfully merging this pull request may close these issues.

4 participants