Skip to content
21 changes: 21 additions & 0 deletions .github/configs/amd-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,27 @@ kimik2.5-int4-mi355x-vllm:
osl: 1024
search-space:
- { tp: 8, conc-start: 4, conc-end: 64 }

kimik2.5-fp4-mi355x-vllm-eagle3-fp8:
image: vllm/vllm-openai-rocm:nightly-fb1ac806c55a6dc96fe92261b80c8550e9c39d2f
model: amd/Kimi-K2.5-MXFP4
model-prefix: kimik2.5
runner: mi355x
precision: fp4
framework: vllm
multinode: false
scenarios:
fixed-seq-len:
- isl: 1024
osl: 1024
search-space:
- { tp: 8, spec-decoding: eagle3_fp8, conc-start: 4, conc-end: 64 }
- { tp: 4, spec-decoding: eagle3_fp8, conc-start: 4, conc-end: 64 }
- isl: 8192
osl: 1024
search-space:
- { tp: 8, spec-decoding: eagle3_fp8, conc-start: 4, conc-end: 64 }
- { tp: 4, spec-decoding: eagle3_fp8, conc-start: 4, conc-end: 64 }

kimik2.5-int4-mi325x-vllm:
image: vllm/vllm-openai-rocm:v0.21.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ The corresponding `SingleNodeMatrixEntry` enforces these same fields with approp

2. **`extra='forbid'`**: Unknown fields are rejected, preventing typos or deprecated fields from slipping through.

3. **Strict typing**: Fields like `spec-decoding` use `Literal["mtp", "draft_model", "none"]` to restrict values to known options.
3. **Strict typing**: Fields like `spec-decoding` use a `Literal` type to restrict values to a fixed set of known options (see `utils/matrix_logic/validation.py` for the current set).

4. **Concurrency validation**: The system ensures either `conc-list` OR `conc-start`/`conc-end` is provided, but not both.

Expand Down
116 changes: 116 additions & 0 deletions benchmarks/single_node/kimik2.5_fp4_mi355x_eagle3_fp8.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env bash

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

check_env_vars \
MODEL \
TP \
CONC \
ISL \
OSL \
MAX_MODEL_LEN \
RANDOM_RANGE_RATIO \
RESULT_FILENAME

if [[ -n "$SLURM_JOB_ID" ]]; then
echo "JOB $SLURM_JOB_ID running on $SLURMD_NODENAME"
fi

if [[ "$MODEL" != /* ]]; then hf download "$MODEL"; fi

# Set HIP_VISIBLE_DEVICES to match ROCR_VISIBLE_DEVICES for Ray compatibility in vLLM 0.14+
if [ -n "$ROCR_VISIBLE_DEVICES" ]; then
export HIP_VISIBLE_DEVICES="$ROCR_VISIBLE_DEVICES"
fi

SERVER_LOG=/workspace/server.log
PORT=${PORT:-8888}

if [ "${EVAL_ONLY}" = "true" ]; then
setup_eval_context
MAX_MODEL_LEN="$EVAL_MAX_MODEL_LEN"
fi

# If the machine runs a MEC FW older than 177, RCCL
# cannot reclaim some memory.
# Disable that features to avoid crashes.
# This is related to the changes in the driver at:
# https://rocm.docs.amd.com/en/docs-6.4.3/about/release-notes.html#amdgpu-driver-updates
version=`rocm-smi --showfw | grep MEC | head -n 1 | awk '{print $NF}'`
if [[ "$version" == "" || $version -lt 177 ]]; then
export HSA_NO_SCRATCH_RECLAIM=1
fi

export VLLM_ROCM_USE_AITER=1
# FP8 Eagle3 quantizes the draft model; the target model remains MXFP4.
export VLLM_ROCM_USE_AITER_FP4_ASM_GEMM=1
export VLLM_ROCM_QUICK_REDUCE_QUANTIZATION=INT4

# Disable AITER RMSNorm for TP < 8 due to accuracy issues
if [ "${TP}" -lt 8 ]; then
export VLLM_ROCM_USE_AITER_RMSNORM=0
fi

if [ "${EP_SIZE:-0}" -gt 1 ]; then
EP=" --enable-expert-parallel"
else
EP=" "
fi

SPEC_DRAFT_MODEL=${SPEC_DRAFT_MODEL:-amd/kimi-k2.5-eagle3-fp8}
SPEC_NUM_TOKENS=${SPEC_NUM_TOKENS:-6}
SPEC_DRAFT_TP=${SPEC_DRAFT_TP:-1}
if [[ "$SPEC_DRAFT_MODEL" != /* ]]; then hf download "$SPEC_DRAFT_MODEL"; fi
SPEC_CONFIG="{\"model\":\"${SPEC_DRAFT_MODEL}\",\"method\":\"eagle3\",\"num_speculative_tokens\":${SPEC_NUM_TOKENS},\"draft_tensor_parallel_size\":${SPEC_DRAFT_TP}}"

PREFILL_ARGS=()
if [[ "${ISL}" -ge 8192 ]]; then
PREFILL_ARGS+=(
--long-prefill-token-threshold 8192
--max-num-batched-tokens 16384
)
fi

# Start GPU monitoring (power, temperature, clocks every second)
start_gpu_monitor

set -x
vllm serve $MODEL --port $PORT \
--tensor-parallel-size=$TP \
$EP \
--gpu-memory-utilization 0.90 \
--max-model-len $MAX_MODEL_LEN \
"${PREFILL_ARGS[@]}" \
--no-enable-prefix-caching \
--trust-remote-code \
--mm-encoder-tp-mode data \
--speculative-config "$SPEC_CONFIG" > $SERVER_LOG 2>&1 &

SERVER_PID=$!

# Wait for server to be ready
wait_for_server_ready --port "$PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID"

run_benchmark_serving \
--model "$MODEL" \
--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 /workspace/ \
--use-chat-template \
--trust-remote-code

# After throughput, run evaluation only if RUN_EVAL is true
if [ "${RUN_EVAL}" = "true" ]; then
run_eval --framework lm-eval --port "$PORT"
append_lm_eval_summary
fi

# Stop GPU monitoring
stop_gpu_monitor
set +x
9 changes: 9 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2901,3 +2901,12 @@
description:
- "Add MI355X config: qwen3.5-fp4-sglang-mtp using lmsysorg/sglang-rocm:v0.5.12-rocm720-mi35x-20260517"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/1445

- config-keys:
- kimik2.5-fp4-mi355x-vllm-eagle3-fp8
description:
- "Add Kimi-K2.5 MXFP4 vLLM Eagle3 speculative decoding config with AMD Quark FP8 draft model on MI355X"
- "Image: vllm/vllm-openai-rocm:nightly-fb1ac806c55a6dc96fe92261b80c8550e9c39d2f"
- "Model: amd/Kimi-K2.5-MXFP4"
- "Draft model: amd/kimi-k2.5-eagle3-fp8 (num_speculative_tokens=6, draft_tp=1)"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/1515
6 changes: 5 additions & 1 deletion runners/launch_mi355x-amds.sh
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ else
export PORT_OFFSET=${RUNNER_NAME: -1}
export PORT=$(( 8888 + ${PORT_OFFSET} ))
FRAMEWORK_SUFFIX=$([[ "$FRAMEWORK" == "atom" ]] && printf '_atom' || printf '')
SPEC_SUFFIX=$([[ "$SPEC_DECODING" == "mtp" ]] && printf '_mtp' || printf '')
case "$SPEC_DECODING" in
mtp) SPEC_SUFFIX='_mtp' ;;
eagle3_fp8) SPEC_SUFFIX='_eagle3_fp8' ;;
*) SPEC_SUFFIX='' ;;
esac
Comment on lines +183 to +187
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 case statement on lines 184-188 unconditionally reassigns SPEC_SUFFIX for every value of $SPEC_DECODING (mtp / eagle3_fp8 / *), which makes the prior ternary on line 183 dead code. The diff was clearly meant to replace the ternary with the case block — please delete line 183.

Extended reasoning...

What: In runners/launch_mi355x-amds.sh, lines 183-188 of the post-diff file read:

SPEC_SUFFIX=$([[ "$SPEC_DECODING" == "mtp" ]] && printf '_mtp' || printf '')
case "$SPEC_DECODING" in
    mtp)        SPEC_SUFFIX='_mtp' ;;
    eagle3_fp8) SPEC_SUFFIX='_eagle3_fp8' ;;
    *)          SPEC_SUFFIX='' ;;
esac

The line-183 assignment via the ternary is dead: the case statement on the immediately following lines unconditionally reassigns SPEC_SUFFIX for every possible value of $SPEC_DECODING (the * arm makes it total). There is no set -e, return, exit, or conditional between the two assignments, so the value computed on line 183 is overwritten on every code path before it can be observed.

Why it slipped in: Looking at the diff hunk, the case block was added with a + but the original ternary line carries no -. The intent was clearly to replace the ternary with a richer case statement that also handles the new eagle3_fp8 value — the author just forgot to delete the old line.

Impact: Purely cosmetic — behavior is identical because the case always wins. But it's confusing for future readers who may wonder which assignment matters, especially when adding a new SPEC_DECODING value (they'd have to remember to update only the case, not both).

Step-by-step proof for SPEC_DECODING=eagle3_fp8:

  1. Line 183 executes: [[ "eagle3_fp8" == "mtp" ]] is false, so the ternary takes the || printf '' branch and SPEC_SUFFIX=''.
  2. Line 184 enters the case. The eagle3_fp8) arm matches and runs SPEC_SUFFIX='_eagle3_fp8'.
  3. After line 188, SPEC_SUFFIX='_eagle3_fp8'. The line-183 value never had any effect.

The same pattern holds for mtp (both assignments compute _mtp, but only line 187/188's value is the one used) and for any other value (line 183 sets '', then the * arm sets '' again).

Fix: Delete line 183 entirely. The project guidance in CLAUDE.md says "if you are certain that something is unused, you can delete it completely" — this is exactly that case.


PARTITION="compute"
SQUASH_FILE="/var/lib/squash/$(echo "$IMAGE" | sed 's/[\/:@#]/_/g').sqsh"
Expand Down
2 changes: 1 addition & 1 deletion utils/matrix_logic/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def test_conc_as_list(self, valid_single_node_matrix_entry):

def test_spec_decoding_values(self, valid_single_node_matrix_entry):
"""Spec decoding should accept valid literal values."""
for value in ["mtp", "draft_model", "none"]:
for value in ["mtp", "draft_model", "eagle3_fp8", "none"]:
valid_single_node_matrix_entry["spec-decoding"] = value
entry = SingleNodeMatrixEntry(**valid_single_node_matrix_entry)
assert entry.spec_decoding == value
Expand Down
8 changes: 4 additions & 4 deletions utils/matrix_logic/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class SingleNodeMatrixEntry(BaseModel):
model_prefix: str = Field(alias=Fields.MODEL_PREFIX.value)
precision: str
framework: str
spec_decoding: Literal["mtp", "draft_model", "none"] = Field(
spec_decoding: Literal["mtp", "draft_model", "eagle3_fp8", "none"] = Field(
alias=Fields.SPEC_DECODING.value
)
runner: str
Expand Down Expand Up @@ -125,7 +125,7 @@ class MultiNodeMatrixEntry(BaseModel):
model_prefix: str = Field(alias=Fields.MODEL_PREFIX.value)
precision: str
framework: str
spec_decoding: Literal["mtp", "draft_model", "none"] = Field(
spec_decoding: Literal["mtp", "draft_model", "eagle3_fp8", "none"] = Field(
alias=Fields.SPEC_DECODING.value
)
runner: str
Expand Down Expand Up @@ -271,7 +271,7 @@ class SingleNodeSearchSpaceEntry(BaseModel):

tp: int
ep: Optional[int] = None
spec_decoding: Literal["mtp", "draft_model", "none"] = Field(
spec_decoding: Literal["mtp", "draft_model", "eagle3_fp8", "none"] = Field(
default="none", alias=Fields.SPEC_DECODING.value)
dp_attn: Optional[bool] = Field(
default=None, alias=Fields.DP_ATTN.value)
Expand All @@ -291,7 +291,7 @@ class MultiNodeSearchSpaceEntry(BaseModel):
"""Multinode search space configuration."""
model_config = ConfigDict(extra='forbid', populate_by_name=True)

spec_decoding: Literal["mtp", "draft_model", "none"] = Field(
spec_decoding: Literal["mtp", "draft_model", "eagle3_fp8", "none"] = Field(
default="none", alias=Fields.SPEC_DECODING.value)
prefill: WorkerConfig
decode: WorkerConfig
Expand Down
Loading