From 81b850a1952f46dff7cb132f1389eee0cb34e7e5 Mon Sep 17 00:00:00 2001 From: Cam Quilici Date: Tue, 11 Aug 2026 16:46:48 -0500 Subject: [PATCH 1/6] feat(amd): add MI300X Qwen3.5 FP8 AgentX MTP --- .../agentic/qwen3.5_fp8_mi300x_mtp.sh | 173 ++++++++++++++++++ configs/amd-master.yaml | 21 ++- perf-changelog.yaml | 11 ++ 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100755 benchmarks/single_node/agentic/qwen3.5_fp8_mi300x_mtp.sh diff --git a/benchmarks/single_node/agentic/qwen3.5_fp8_mi300x_mtp.sh b/benchmarks/single_node/agentic/qwen3.5_fp8_mi300x_mtp.sh new file mode 100755 index 000000000..df2da964f --- /dev/null +++ b/benchmarks/single_node/agentic/qwen3.5_fp8_mi300x_mtp.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +set -euo pipefail +set -x + +# AgentX trace replay for Qwen3.5-397B-A17B FP8 on MI300X (gfx942) with +# SGLang native EAGLE/NEXTN MTP. Throughput uses the committed golden +# synthetic acceptance length; evals retain real target-model verification. + +source "$(dirname "$0")/../../benchmark_lib.sh" + +export EVAL_FRAMEWORK="lm-eval" + +check_env_vars \ + MODEL TP CONC EP_SIZE KV_OFFLOADING \ + TOTAL_CPU_DRAM_GB RESULT_DIR DURATION + +SCHEDULER_RECV_INTERVAL=${SCHEDULER_RECV_INTERVAL:-30} + +if [[ -n "${SLURM_JOB_ID:-}" ]]; then + echo "JOB $SLURM_JOB_ID running on ${SLURMD_NODENAME:-unknown}" +fi + +if [[ -n "${ROCR_VISIBLE_DEVICES:-}" ]]; then + export HIP_VISIBLE_DEVICES="$ROCR_VISIBLE_DEVICES" +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 +rocm-smi || true +amd-smi || true + +export WEKA_LOADER_OVERRIDE=semianalysis_cc_traces_weka_062126_256k +resolve_trace_source +install_agentic_deps + +SERVER_LOG="$RESULT_DIR/server.log" +mkdir -p "$RESULT_DIR" + +CACHE_ARGS=() +WARMUP_ARGS=() +if require_agentic_kv_offload_backend hicache; then + REQUESTED_HICACHE_TOTAL_GB="${HICACHE_TOTAL_CPU_DRAM_GB:-$TOTAL_CPU_DRAM_GB}" + if [ "$REQUESTED_HICACHE_TOTAL_GB" -gt "$TOTAL_CPU_DRAM_GB" ]; then + echo "Error: requested HiCache pool ${REQUESTED_HICACHE_TOTAL_GB} GB exceeds configured capacity ${TOTAL_CPU_DRAM_GB} GB" >&2 + exit 1 + fi + TOTAL_CPU_DRAM_GB="$REQUESTED_HICACHE_TOTAL_GB" + + # Qwen3.5 allocates target KV and Mamba host pools per rank. Native MTP + # adds a one-attention-layer draft KV pool (1/15 of target KV), so enforce + # H * 31/15 per rank against the workflow's node-total DRAM budget. + HICACHE_ALIGNMENT_RESERVE_GB=$TP + HICACHE_USABLE_TOTAL_GB=$((TOTAL_CPU_DRAM_GB - HICACHE_ALIGNMENT_RESERVE_GB)) + if [ "$HICACHE_USABLE_TOTAL_GB" -lt 1 ]; then + echo "Error: insufficient DRAM after HiCache alignment reserve" >&2 + exit 1 + fi + MAX_HICACHE_SIZE_GB=$((HICACHE_USABLE_TOTAL_GB * 15 / TP / 31)) + HICACHE_SIZE_GB="${HICACHE_SIZE_GB:-$MAX_HICACHE_SIZE_GB}" + if [ "$HICACHE_SIZE_GB" -lt 1 ] || [ "$HICACHE_SIZE_GB" -gt "$MAX_HICACHE_SIZE_GB" ]; then + echo "Error: HICACHE_SIZE_GB=$HICACHE_SIZE_GB outside 1..$MAX_HICACHE_SIZE_GB" >&2 + exit 1 + fi + PROJECTED_HICACHE_TOTAL_GB=$(((HICACHE_SIZE_GB * TP * 31 + 14) / 15 + HICACHE_ALIGNMENT_RESERVE_GB)) + if [ "$PROJECTED_HICACHE_TOTAL_GB" -gt "$TOTAL_CPU_DRAM_GB" ]; then + echo "Error: projected HiCache use ${PROJECTED_HICACHE_TOTAL_GB} GB exceeds configured capacity ${TOTAL_CPU_DRAM_GB} GB" >&2 + exit 1 + fi + echo "HiCache CPU pools: ${HICACHE_SIZE_GB} GB target + Mamba + 1/15 draft per rank across TP=${TP}; projected node total ${PROJECTED_HICACHE_TOTAL_GB} GB <= ${TOTAL_CPU_DRAM_GB} GB" + + # Qwen3.5's hybrid Mamba path uses SGLang's no_buffer scheduler. On ROCm, + # page_size=1 with direct/layer_first is the exercised HiCache copy path. + CACHE_ARGS=( + --page-size 1 + --enable-hierarchical-cache + --hicache-size "$HICACHE_SIZE_GB" + --hicache-io-backend direct + --hicache-mem-layout layer_first + --hicache-write-policy write_through_selective + ) + WARMUP_ARGS=(--skip-server-warmup) +fi + +PARALLEL_ARGS=( + --tensor-parallel-size "$TP" + --data-parallel-size 1 + --ep-size "$EP_SIZE" +) + +# AgentX concurrency counts live session trees, not individual HTTP requests. +MAX_RUNNING_REQUESTS=$((2 * CONC)) +CUDA_GRAPH_MAX_BS="$CONC" +[ "$CUDA_GRAPH_MAX_BS" -gt 64 ] && CUDA_GRAPH_MAX_BS=64 +if agentic_kv_offload_enabled && [ "$CUDA_GRAPH_MAX_BS" -gt 16 ]; then + CUDA_GRAPH_MAX_BS=16 +fi + +export PYTHONNOUSERSITE=1 +export SGLANG_ENABLE_SPEC_V2=1 +export SGLANG_TIMEOUT_KEEP_ALIVE=1800 + +if [ "${EVAL_ONLY:-false}" != "true" ]; then + export SGLANG_SIMULATE_ACC_LEN=3.39 + export SGLANG_SIMULATE_ACC_METHOD=match-expected + export SGLANG_SIMULATE_ACC_TOKEN_MODE=real-draft-token +fi + +SGLANG_CMD=( + python3 -m sglang.launch_server + --model-path "$MODEL_PATH" + --served-model-name "$MODEL" + --host 0.0.0.0 + --port "$PORT" + --trust-remote-code + "${PARALLEL_ARGS[@]}" + --attention-backend aiter + --enable-aiter-allreduce-fusion + --mamba-ssm-dtype bfloat16 + --tokenizer-worker-num 6 + --cuda-graph-max-bs "$CUDA_GRAPH_MAX_BS" + --max-running-requests "$MAX_RUNNING_REQUESTS" + --max-prefill-tokens 32768 + --scheduler-recv-interval "$SCHEDULER_RECV_INTERVAL" + --mem-fraction-static 0.75 + --tokenizer-path "$MODEL" + --reasoning-parser qwen3 + --tool-call-parser qwen3_coder + --speculative-algorithm EAGLE + --speculative-num-steps 3 + --speculative-eagle-topk 1 + --speculative-num-draft-tokens 4 + --enable-metrics + --enable-cache-report + "${CACHE_ARGS[@]}" + "${WARMUP_ARGS[@]}" +) + +printf '%q ' "${SGLANG_CMD[@]}" | tee "$RESULT_DIR/sglang_command.txt" +printf '\n' | tee -a "$RESULT_DIR/sglang_command.txt" +"${SGLANG_CMD[@]}" > "$SERVER_LOG" 2>&1 & +SERVER_PID=$! + +capture_cache_metrics() { + { + echo "=== SGLang cache metrics snapshot $(date --iso-8601=seconds) ===" + curl -fsS "http://localhost:$PORT/metrics" 2>/dev/null \ + | grep -E '^(sglang:(cache_hit_rate|cached_tokens_total|prompt_tokens_total|hicache_host_used_tokens|hicache_host_total_tokens|token_usage|num_requests_running|num_requests_waiting))' \ + || true + echo "============================================================" + } >> "$SERVER_LOG" +} + +wait_for_server_ready --port "$PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID" + +capture_cache_metrics +trap capture_cache_metrics EXIT + +if [ "${EVAL_ONLY:-false}" = "true" ]; then + run_eval --port "$PORT" +else + # Aggregate deployment: the client-facing SGLang engine is the single + # logical Prometheus target. Make it explicit and require engine metrics. + export AIPERF_SERVER_METRICS_URLS="http://localhost:$PORT/metrics" + export AIPERF_REQUIRED_SERVER_METRIC_PREFIX="sglang:" + build_replay_cmd "$RESULT_DIR" + run_agentic_replay_and_write_outputs "$RESULT_DIR" +fi diff --git a/configs/amd-master.yaml b/configs/amd-master.yaml index 9bc224b16..0a0ae859a 100644 --- a/configs/amd-master.yaml +++ b/configs/amd-master.yaml @@ -402,6 +402,26 @@ qwen3.5-fp8-mi300x-sglang: search-space: - { tp: 8, conc-start: 4, conc-end: 64 } +# AgentX discovery sweep for Qwen3.5 FP8 on MI300X. TP8 is required by the +# gfx942 192 GB HBM capacity; compare the established tensor-parallel arm with +# EP8 and test HiCache only from the expected high-concurrency knee onward. +qwen3.5-fp8-mi300x-sglang-agentic-mtp: + image: lmsysorg/sglang-rocm:v0.5.16-rocm720-mi30x-20260730 + model: Qwen/Qwen3.5-397B-A17B-FP8 + model-prefix: qwen3.5 + runner: cluster:mi300x-amds + precision: fp8 + framework: sglang + multinode: false + scenarios: + agentic-coding: + - dram-utilization: 0.80 + search-space: + - { tp: 8, ep: 1, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64] } + - { tp: 8, ep: 8, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64] } + - { tp: 8, ep: 1, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [32, 40, 48, 56, 64, 68, 72, 76] } + - { tp: 8, ep: 8, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [32, 40, 48, 56, 64, 68, 72, 76] } + dsr1-fp8-mi355x-atom: image: rocm/atom:rocm7.2.3_ubuntu24.04_py3.12_pytorch_release_2.10.0_atom20260511 model: deepseek-ai/DeepSeek-R1-0528 @@ -1633,4 +1653,3 @@ glm5.2-fp4-mi355x-sglang-agentic-mtp: search-space: - { tp: 4, ep: 4, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [1, 2, 4, 8, 10, 12, 16], spec-decoding: mtp } - { tp: 8, ep: 8, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [1, 2, 4], spec-decoding: mtp } - diff --git a/perf-changelog.yaml b/perf-changelog.yaml index 0bce8d894..9f1a696e9 100644 --- a/perf-changelog.yaml +++ b/perf-changelog.yaml @@ -5818,3 +5818,14 @@ description: - "Extend the SimpleCPUOffloadConnector grid to c8/c12/c16/c20/c24/c28/c32/c48/c64 to locate its crossover against the resident curve" pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2475 + +- config-keys: + - qwen3.5-fp8-mi300x-sglang-agentic-mtp + scenario-type: + - agentic-coding + description: + - "Add Qwen3.5-397B-A17B FP8 AgentX on MI300X with SGLang native EAGLE MTP and golden synthetic acceptance length 3.39" + - "Use a broad agentx-fast discovery grid across TP8/EP1, TP8/EP8, and high-concurrency HiCache arms before pruning to the final Pareto sweep" + - "Collect and require SGLang Prometheus metrics from the aggregate engine endpoint" + - "Image: lmsysorg/sglang-rocm:v0.5.16-rocm720-mi30x-20260730" + pr-link: TBD From 81caa5779c6c91cc388856200937b6f75fcc1106 Mon Sep 17 00:00:00 2001 From: Cam Quilici Date: Tue, 11 Aug 2026 16:47:47 -0500 Subject: [PATCH 2/6] chore: link MI300X Qwen3.5 AgentX PR --- perf-changelog.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perf-changelog.yaml b/perf-changelog.yaml index 9f1a696e9..5545dcc29 100644 --- a/perf-changelog.yaml +++ b/perf-changelog.yaml @@ -5828,4 +5828,4 @@ - "Use a broad agentx-fast discovery grid across TP8/EP1, TP8/EP8, and high-concurrency HiCache arms before pruning to the final Pareto sweep" - "Collect and require SGLang Prometheus metrics from the aggregate engine endpoint" - "Image: lmsysorg/sglang-rocm:v0.5.16-rocm720-mi30x-20260730" - pr-link: TBD + pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2563 From 316ba3ce0590a0c6810ed6d4b6776d615962f533 Mon Sep 17 00:00:00 2001 From: Cam Quilici Date: Tue, 11 Aug 2026 16:52:59 -0500 Subject: [PATCH 3/6] fix(mi300x): stage Slurm workspace onto compute nodes --- runners/launch_mi300x-amds.sh | 68 ++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/runners/launch_mi300x-amds.sh b/runners/launch_mi300x-amds.sh index fdd03889a..ca72f806e 100644 --- a/runners/launch_mi300x-amds.sh +++ b/runners/launch_mi300x-amds.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -set -eo pipefail +set -euo pipefail export HF_HUB_CACHE_MOUNT="/raid/hf-hub-cache/" -export PORT=8888 PARTITION="compute" -SQUASH_FILE="/home/gharunner/gharunners/squash/$(echo "$IMAGE" | sed 's/[\/:@#]/_/g').sqsh" +SQUASH_DIR="/raid/hf-hub-cache/runtime-cache/enroot" +SQUASH_FILE="$SQUASH_DIR/$(echo "$IMAGE" | sed 's/[\/:@#]/_/g').sqsh" LOCK_FILE="${SQUASH_FILE}.lock" # Route spec-decoding=mtp configs to the _mtp benchmark script (parity with @@ -22,14 +22,43 @@ set -x JOB_ID=$(set +o pipefail; salloc --partition=$PARTITION --exclude=chi-mi300x-049,chi-mi300x-121 --gres=gpu:$GPU_COUNT --cpus-per-task=256 --time=180 --no-shell --job-name="$RUNNER_NAME" 2>&1 | tee /dev/stderr | grep -oP 'Granted job allocation \K[0-9]+') if [ -z "$JOB_ID" ]; then - echo "ERROR: salloc failed to allocate a job" + echo "ERROR: salloc failed to allocate a job" >&2 exit 1 fi -trap 'rc=$?; scancel "$JOB_ID" 2>/dev/null || true; exit "$rc"' EXIT +export PORT=$((40000 + (JOB_ID % 10000))) + +# The GitHub runners live on the Slurm controller, whose /home filesystem is +# not mounted on compute nodes. Stage the checked-out source with sbcast, then +# copy only benchmark artifacts back over srun stdout before releasing the job. +LOCAL_WORKSPACE_TAR=$(mktemp "/tmp/inferencex-${JOB_ID}.XXXXXX.tar.gz") +REMOTE_WORKSPACE_TAR="/tmp/inferencex-${JOB_ID}.tar.gz" +REMOTE_WORKSPACE="/tmp/inferencex-${JOB_ID}" + +cleanup() { + local rc=$? + srun --jobid="$JOB_ID" bash -c "rm -rf '$REMOTE_WORKSPACE' '$REMOTE_WORKSPACE_TAR'" >/dev/null 2>&1 || true + scancel "$JOB_ID" 2>/dev/null || true + rm -f "$LOCAL_WORKSPACE_TAR" + exit "$rc" +} +trap cleanup EXIT + +tar -C "$GITHUB_WORKSPACE" \ + --exclude=.git --exclude='*/.git' \ + -czf "$LOCAL_WORKSPACE_TAR" . +sbcast --jobid="$JOB_ID" --force "$LOCAL_WORKSPACE_TAR" "$REMOTE_WORKSPACE_TAR" +srun --jobid="$JOB_ID" --job-name="$RUNNER_NAME" bash -c " + set -euo pipefail + rm -rf '$REMOTE_WORKSPACE' + mkdir -p '$REMOTE_WORKSPACE' + tar -C '$REMOTE_WORKSPACE' -xzf '$REMOTE_WORKSPACE_TAR' +" # Use flock to serialize concurrent imports to the same squash file -srun --jobid=$JOB_ID --job-name="$RUNNER_NAME" bash -c " +srun --jobid="$JOB_ID" --job-name="$RUNNER_NAME" bash -c " + set -euo pipefail + mkdir -p '$SQUASH_DIR' exec 9>\"$LOCK_FILE\" flock -w 600 9 || { echo 'Failed to acquire lock for $SQUASH_FILE'; exit 1; } if unsquashfs -l \"$SQUASH_FILE\" > /dev/null 2>&1; then @@ -39,14 +68,35 @@ srun --jobid=$JOB_ID --job-name="$RUNNER_NAME" bash -c " enroot import -o \"$SQUASH_FILE\" docker://$IMAGE fi " -srun --jobid=$JOB_ID \ + +set +e +srun --jobid="$JOB_ID" \ --container-image=$SQUASH_FILE \ ---container-mounts=$GITHUB_WORKSPACE:/workspace/,$HF_HUB_CACHE_MOUNT:$HF_HUB_CACHE,/dev/kfd:/dev/kfd,/dev/dri:/dev/dri \ +--container-mounts=$REMOTE_WORKSPACE:/workspace/,$HF_HUB_CACHE_MOUNT:$HF_HUB_CACHE,/dev/kfd:/dev/kfd,/dev/dri:/dev/dri \ --container-mount-home \ --container-writable \ --container-remap-root \ --container-workdir=/workspace/ \ --no-container-entrypoint --export=ALL \ bash benchmarks/single_node/${SCENARIO_SUBDIR}${EXP_NAME%%_*}_${PRECISION}_mi300x${SPEC_SUFFIX}.sh +BENCHMARK_RC=$? +set -e + +# Stream runtime artifacts back to the controller-side Actions workspace. The +# binary archive is stdout-only; Slurm diagnostics remain on stderr. +srun --jobid="$JOB_ID" bash -c " + set -euo pipefail + cd '$REMOTE_WORKSPACE' + shopt -s nullglob + artifacts=() + for path in results LOGS *.json *.log gpu_metrics.csv profile_*.trace.json.gz eval_results*; do + [[ -e \"\$path\" ]] && artifacts+=(\"\$path\") + done + if (( \${#artifacts[@]} == 0 )); then + tar -czf - --files-from /dev/null + else + tar -czf - \"\${artifacts[@]}\" + fi +" | tar -C "$GITHUB_WORKSPACE" -xzf - -scancel $JOB_ID +exit "$BENCHMARK_RC" From bb96f21af803a6867e8aebd85bf1bfe6675b8c4e Mon Sep 17 00:00:00 2001 From: Cam Quilici Date: Tue, 11 Aug 2026 17:38:15 -0500 Subject: [PATCH 4/6] fix(mi300x): provision compute-local enroot runtime --- runners/launch_mi300x-amds.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runners/launch_mi300x-amds.sh b/runners/launch_mi300x-amds.sh index ca72f806e..f444a9bcb 100644 --- a/runners/launch_mi300x-amds.sh +++ b/runners/launch_mi300x-amds.sh @@ -7,6 +7,10 @@ PARTITION="compute" SQUASH_DIR="/raid/hf-hub-cache/runtime-cache/enroot" SQUASH_FILE="$SQUASH_DIR/$(echo "$IMAGE" | sed 's/[\/:@#]/_/g').sqsh" LOCK_FILE="${SQUASH_FILE}.lock" +# Some MI300X compute images do not provision the login user's /run/user/$UID +# directory. Keep Enroot's runtime state node-local instead of inheriting the +# controller's XDG_RUNTIME_DIR, which is not valid on those compute nodes. +export XDG_RUNTIME_DIR="/tmp/enroot-runtime-${UID}" # Route spec-decoding=mtp configs to the _mtp benchmark script (parity with # the h200 launchers, which have carried SPEC_SUFFIX since #392). @@ -50,6 +54,8 @@ tar -C "$GITHUB_WORKSPACE" \ sbcast --jobid="$JOB_ID" --force "$LOCAL_WORKSPACE_TAR" "$REMOTE_WORKSPACE_TAR" srun --jobid="$JOB_ID" --job-name="$RUNNER_NAME" bash -c " set -euo pipefail + mkdir -p '$XDG_RUNTIME_DIR' + chmod 700 '$XDG_RUNTIME_DIR' rm -rf '$REMOTE_WORKSPACE' mkdir -p '$REMOTE_WORKSPACE' tar -C '$REMOTE_WORKSPACE' -xzf '$REMOTE_WORKSPACE_TAR' From 960e43db1c2b03ca241c67f151f221fd3b4a0358 Mon Sep 17 00:00:00 2001 From: Cam Quilici Date: Tue, 11 Aug 2026 17:46:31 -0500 Subject: [PATCH 5/6] fix(mi300x): place enroot imports on raid scratch --- runners/launch_mi300x-amds.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runners/launch_mi300x-amds.sh b/runners/launch_mi300x-amds.sh index f444a9bcb..96d92e23d 100644 --- a/runners/launch_mi300x-amds.sh +++ b/runners/launch_mi300x-amds.sh @@ -7,6 +7,7 @@ PARTITION="compute" SQUASH_DIR="/raid/hf-hub-cache/runtime-cache/enroot" SQUASH_FILE="$SQUASH_DIR/$(echo "$IMAGE" | sed 's/[\/:@#]/_/g').sqsh" LOCK_FILE="${SQUASH_FILE}.lock" +COMPUTE_TMPDIR="$SQUASH_DIR/tmp-${UID}" # Some MI300X compute images do not provision the login user's /run/user/$UID # directory. Keep Enroot's runtime state node-local instead of inheriting the # controller's XDG_RUNTIME_DIR, which is not valid on those compute nodes. @@ -56,6 +57,8 @@ srun --jobid="$JOB_ID" --job-name="$RUNNER_NAME" bash -c " set -euo pipefail mkdir -p '$XDG_RUNTIME_DIR' chmod 700 '$XDG_RUNTIME_DIR' + mkdir -p '$COMPUTE_TMPDIR' + chmod 700 '$COMPUTE_TMPDIR' rm -rf '$REMOTE_WORKSPACE' mkdir -p '$REMOTE_WORKSPACE' tar -C '$REMOTE_WORKSPACE' -xzf '$REMOTE_WORKSPACE_TAR' @@ -64,6 +67,7 @@ srun --jobid="$JOB_ID" --job-name="$RUNNER_NAME" bash -c " # Use flock to serialize concurrent imports to the same squash file srun --jobid="$JOB_ID" --job-name="$RUNNER_NAME" bash -c " set -euo pipefail + export TMPDIR='$COMPUTE_TMPDIR' mkdir -p '$SQUASH_DIR' exec 9>\"$LOCK_FILE\" flock -w 600 9 || { echo 'Failed to acquire lock for $SQUASH_FILE'; exit 1; } From 429229453b7e587826936eab7cb739dd79671f4f Mon Sep 17 00:00:00 2001 From: Cam Quilici Date: Wed, 12 Aug 2026 00:08:37 -0500 Subject: [PATCH 6/6] perf(mi300x): select the Qwen3.5 AgentX Pareto sweep Prune the completed fast-discovery grid to representative EP1 and EP8 points spanning the latency floor, throughput knee, and first post-knee regression. Remove the nonfunctional HiCache path rejected by the pinned SGLang hybrid-cache layout. --- .../agentic/qwen3.5_fp8_mi300x_mtp.sh | 76 ++----------------- configs/amd-master.yaml | 11 +-- perf-changelog.yaml | 3 +- 3 files changed, 12 insertions(+), 78 deletions(-) diff --git a/benchmarks/single_node/agentic/qwen3.5_fp8_mi300x_mtp.sh b/benchmarks/single_node/agentic/qwen3.5_fp8_mi300x_mtp.sh index df2da964f..21cbd9964 100755 --- a/benchmarks/single_node/agentic/qwen3.5_fp8_mi300x_mtp.sh +++ b/benchmarks/single_node/agentic/qwen3.5_fp8_mi300x_mtp.sh @@ -1,10 +1,8 @@ #!/usr/bin/env bash -set -euo pipefail +set -eo pipefail set -x -# AgentX trace replay for Qwen3.5-397B-A17B FP8 on MI300X (gfx942) with -# SGLang native EAGLE/NEXTN MTP. Throughput uses the committed golden -# synthetic acceptance length; evals retain real target-model verification. +# Qwen3.5-397B-A17B FP8 on MI300X with native EAGLE/NEXTN MTP. source "$(dirname "$0")/../../benchmark_lib.sh" @@ -12,7 +10,9 @@ export EVAL_FRAMEWORK="lm-eval" check_env_vars \ MODEL TP CONC EP_SIZE KV_OFFLOADING \ - TOTAL_CPU_DRAM_GB RESULT_DIR DURATION + RESULT_DIR DURATION + +require_agentic_kv_offload_none SCHEDULER_RECV_INTERVAL=${SCHEDULER_RECV_INTERVAL:-30} @@ -42,51 +42,6 @@ install_agentic_deps SERVER_LOG="$RESULT_DIR/server.log" mkdir -p "$RESULT_DIR" -CACHE_ARGS=() -WARMUP_ARGS=() -if require_agentic_kv_offload_backend hicache; then - REQUESTED_HICACHE_TOTAL_GB="${HICACHE_TOTAL_CPU_DRAM_GB:-$TOTAL_CPU_DRAM_GB}" - if [ "$REQUESTED_HICACHE_TOTAL_GB" -gt "$TOTAL_CPU_DRAM_GB" ]; then - echo "Error: requested HiCache pool ${REQUESTED_HICACHE_TOTAL_GB} GB exceeds configured capacity ${TOTAL_CPU_DRAM_GB} GB" >&2 - exit 1 - fi - TOTAL_CPU_DRAM_GB="$REQUESTED_HICACHE_TOTAL_GB" - - # Qwen3.5 allocates target KV and Mamba host pools per rank. Native MTP - # adds a one-attention-layer draft KV pool (1/15 of target KV), so enforce - # H * 31/15 per rank against the workflow's node-total DRAM budget. - HICACHE_ALIGNMENT_RESERVE_GB=$TP - HICACHE_USABLE_TOTAL_GB=$((TOTAL_CPU_DRAM_GB - HICACHE_ALIGNMENT_RESERVE_GB)) - if [ "$HICACHE_USABLE_TOTAL_GB" -lt 1 ]; then - echo "Error: insufficient DRAM after HiCache alignment reserve" >&2 - exit 1 - fi - MAX_HICACHE_SIZE_GB=$((HICACHE_USABLE_TOTAL_GB * 15 / TP / 31)) - HICACHE_SIZE_GB="${HICACHE_SIZE_GB:-$MAX_HICACHE_SIZE_GB}" - if [ "$HICACHE_SIZE_GB" -lt 1 ] || [ "$HICACHE_SIZE_GB" -gt "$MAX_HICACHE_SIZE_GB" ]; then - echo "Error: HICACHE_SIZE_GB=$HICACHE_SIZE_GB outside 1..$MAX_HICACHE_SIZE_GB" >&2 - exit 1 - fi - PROJECTED_HICACHE_TOTAL_GB=$(((HICACHE_SIZE_GB * TP * 31 + 14) / 15 + HICACHE_ALIGNMENT_RESERVE_GB)) - if [ "$PROJECTED_HICACHE_TOTAL_GB" -gt "$TOTAL_CPU_DRAM_GB" ]; then - echo "Error: projected HiCache use ${PROJECTED_HICACHE_TOTAL_GB} GB exceeds configured capacity ${TOTAL_CPU_DRAM_GB} GB" >&2 - exit 1 - fi - echo "HiCache CPU pools: ${HICACHE_SIZE_GB} GB target + Mamba + 1/15 draft per rank across TP=${TP}; projected node total ${PROJECTED_HICACHE_TOTAL_GB} GB <= ${TOTAL_CPU_DRAM_GB} GB" - - # Qwen3.5's hybrid Mamba path uses SGLang's no_buffer scheduler. On ROCm, - # page_size=1 with direct/layer_first is the exercised HiCache copy path. - CACHE_ARGS=( - --page-size 1 - --enable-hierarchical-cache - --hicache-size "$HICACHE_SIZE_GB" - --hicache-io-backend direct - --hicache-mem-layout layer_first - --hicache-write-policy write_through_selective - ) - WARMUP_ARGS=(--skip-server-warmup) -fi - PARALLEL_ARGS=( --tensor-parallel-size "$TP" --data-parallel-size 1 @@ -97,9 +52,6 @@ PARALLEL_ARGS=( MAX_RUNNING_REQUESTS=$((2 * CONC)) CUDA_GRAPH_MAX_BS="$CONC" [ "$CUDA_GRAPH_MAX_BS" -gt 64 ] && CUDA_GRAPH_MAX_BS=64 -if agentic_kv_offload_enabled && [ "$CUDA_GRAPH_MAX_BS" -gt 16 ]; then - CUDA_GRAPH_MAX_BS=16 -fi export PYTHONNOUSERSITE=1 export SGLANG_ENABLE_SPEC_V2=1 @@ -137,8 +89,6 @@ SGLANG_CMD=( --speculative-num-draft-tokens 4 --enable-metrics --enable-cache-report - "${CACHE_ARGS[@]}" - "${WARMUP_ARGS[@]}" ) printf '%q ' "${SGLANG_CMD[@]}" | tee "$RESULT_DIR/sglang_command.txt" @@ -146,26 +96,12 @@ printf '\n' | tee -a "$RESULT_DIR/sglang_command.txt" "${SGLANG_CMD[@]}" > "$SERVER_LOG" 2>&1 & SERVER_PID=$! -capture_cache_metrics() { - { - echo "=== SGLang cache metrics snapshot $(date --iso-8601=seconds) ===" - curl -fsS "http://localhost:$PORT/metrics" 2>/dev/null \ - | grep -E '^(sglang:(cache_hit_rate|cached_tokens_total|prompt_tokens_total|hicache_host_used_tokens|hicache_host_total_tokens|token_usage|num_requests_running|num_requests_waiting))' \ - || true - echo "============================================================" - } >> "$SERVER_LOG" -} - wait_for_server_ready --port "$PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID" -capture_cache_metrics -trap capture_cache_metrics EXIT - if [ "${EVAL_ONLY:-false}" = "true" ]; then run_eval --port "$PORT" else - # Aggregate deployment: the client-facing SGLang engine is the single - # logical Prometheus target. Make it explicit and require engine metrics. + # Aggregate serving exposes one logical SGLang Prometheus target. export AIPERF_SERVER_METRICS_URLS="http://localhost:$PORT/metrics" export AIPERF_REQUIRED_SERVER_METRIC_PREFIX="sglang:" build_replay_cmd "$RESULT_DIR" diff --git a/configs/amd-master.yaml b/configs/amd-master.yaml index 0a0ae859a..56358fd40 100644 --- a/configs/amd-master.yaml +++ b/configs/amd-master.yaml @@ -402,9 +402,8 @@ qwen3.5-fp8-mi300x-sglang: search-space: - { tp: 8, conc-start: 4, conc-end: 64 } -# AgentX discovery sweep for Qwen3.5 FP8 on MI300X. TP8 is required by the -# gfx942 192 GB HBM capacity; compare the established tensor-parallel arm with -# EP8 and test HiCache only from the expected high-concurrency knee onward. +# AgentX Pareto sweep for Qwen3.5 FP8 on MI300X. The fast discovery run peaks +# at c24; c32 records the post-knee throughput and latency cliff. qwen3.5-fp8-mi300x-sglang-agentic-mtp: image: lmsysorg/sglang-rocm:v0.5.16-rocm720-mi30x-20260730 model: Qwen/Qwen3.5-397B-A17B-FP8 @@ -417,10 +416,8 @@ qwen3.5-fp8-mi300x-sglang-agentic-mtp: agentic-coding: - dram-utilization: 0.80 search-space: - - { tp: 8, ep: 1, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64] } - - { tp: 8, ep: 8, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 16, 20, 24, 32, 40, 48, 56, 64] } - - { tp: 8, ep: 1, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [32, 40, 48, 56, 64, 68, 72, 76] } - - { tp: 8, ep: 8, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [32, 40, 48, 56, 64, 68, 72, 76] } + - { tp: 8, ep: 1, spec-decoding: mtp, kv-offloading: none, conc-list: [4, 16, 20, 24, 32] } + - { tp: 8, ep: 8, spec-decoding: mtp, kv-offloading: none, conc-list: [4, 16, 20, 24, 32] } dsr1-fp8-mi355x-atom: image: rocm/atom:rocm7.2.3_ubuntu24.04_py3.12_pytorch_release_2.10.0_atom20260511 diff --git a/perf-changelog.yaml b/perf-changelog.yaml index 5545dcc29..e8084753a 100644 --- a/perf-changelog.yaml +++ b/perf-changelog.yaml @@ -5825,7 +5825,8 @@ - agentic-coding description: - "Add Qwen3.5-397B-A17B FP8 AgentX on MI300X with SGLang native EAGLE MTP and golden synthetic acceptance length 3.39" - - "Use a broad agentx-fast discovery grid across TP8/EP1, TP8/EP8, and high-concurrency HiCache arms before pruning to the final Pareto sweep" + - "Use TP8/EP1 and TP8/EP8 Pareto sweeps at c4/c16/c20/c24/c32; fast discovery found peak throughput at c24 and a post-knee regression at c32" + - "Exclude HiCache because SGLang v0.5.16 rejects Qwen3.5's layer-first hybrid cache layout" - "Collect and require SGLang Prometheus metrics from the aggregate engine endpoint" - "Image: lmsysorg/sglang-rocm:v0.5.16-rocm720-mi30x-20260730" pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2563