Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
348 changes: 348 additions & 0 deletions benchmarks/single_node/agentic/qwen3.8next_fp8_mi355x_sglang_mtp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
#!/usr/bin/env bash
set -euo pipefail
set -x

# Agentic trace replay benchmark for Qwen3.8-Flash-Next FP8 on MI355X using
# SGLang with MTP speculative decoding. Day-zero recipe for the hybrid
# GDN + QSA architecture on CDNA4 (gfx950).
#
# MI355X is ROCm, so this arm is FP8 (Qwen/Qwen3.8-Flash-Next-FP8).
# NVFP4 is not available on AMD GPUs. Attention backend is aiter (ROCm).
#
# The SGLang cookbook (docs.sglang.io) verified flags for MI355X are:
# --tp-size 8 --attention-backend aiter --page-size 32 --kv-cache-dtype auto
# --chunked-prefill-size 16384 --watchdog-timeout 1200 --mem-fraction-static 0.9
#
# Speculative decoding uses NEXTN (the Qwen3.8 Flash Next native MTP head),
# 3 steps, eagle-topk 1, 4 draft tokens — mirroring the H200 agentic recipe.
#
# Required env vars:
# MODEL, TP, CONC, EP_SIZE, KV_OFFLOADING,
# TOTAL_CPU_DRAM_GB, RESULT_DIR, DURATION

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 "${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_with_subagents_256k
resolve_trace_source
install_agentic_deps

export AIPERF_SERVER_METRICS_URLS="http://localhost:${PORT}/metrics"
export AIPERF_REQUIRED_SERVER_METRIC_PREFIX="sglang:"

SERVER_LOG="$RESULT_DIR/server.log"
mkdir -p "$RESULT_DIR"

SERVER_PID=""
cleanup_agentic_services() {
local exit_code=$?
trap - EXIT INT TERM
set +e
stop_background_process_tree "$SERVER_PID" "SGLang server" 60
exit "$exit_code"
}
trap cleanup_agentic_services EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

# ---- HiCache (host-DRAM KV offload) ----------------------------------------
CACHE_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"
HICACHE_HOST_POOL_COUNT="${HICACHE_HOST_POOL_COUNT:-2}"
HICACHE_WRITE_POLICY="${HICACHE_WRITE_POLICY:-write_through_selective}"
MAX_HICACHE_SIZE_GB=$((TOTAL_CPU_DRAM_GB / TP / HICACHE_HOST_POOL_COUNT))
HICACHE_SIZE_GB="${HICACHE_SIZE_GB:-$MAX_HICACHE_SIZE_GB}"
if [ "$HICACHE_SIZE_GB" -gt "$MAX_HICACHE_SIZE_GB" ]; then
echo "Error: HICACHE_SIZE_GB=$HICACHE_SIZE_GB exceeds configured per-pool limit $MAX_HICACHE_SIZE_GB" >&2
exit 1
fi
if [ "$HICACHE_SIZE_GB" -lt 1 ]; then
echo "Error: computed HICACHE_SIZE_GB=$HICACHE_SIZE_GB from TOTAL_CPU_DRAM_GB=$TOTAL_CPU_DRAM_GB, TP=$TP, HICACHE_HOST_POOL_COUNT=$HICACHE_HOST_POOL_COUNT" >&2
exit 1
fi
echo "HiCache CPU pool: ${HICACHE_SIZE_GB} GB per rank per host pool across TP=${TP}, host_pool_count=${HICACHE_HOST_POOL_COUNT}"
CACHE_ARGS=(
--enable-hierarchical-cache
--hicache-size "$HICACHE_SIZE_GB"
--hicache-io-backend kernel
--hicache-mem-layout page_first
--hicache-write-policy "$HICACHE_WRITE_POLICY"
)
fi

# ---- Parallelism ------------------------------------------------------------
PARALLEL_ARGS=(
--tp-size "$TP"
--dp-size 1
--ep-size "$EP_SIZE"
)

TOKENIZER_ARGS=()
if [ "$TP" -ge 4 ]; then
TOKENIZER_ARGS=(--tokenizer-worker-num 6)
fi

MAX_RUNNING_REQUESTS=$((2 * CONC))
CUDA_GRAPH_MAX_BS="$CONC"
[ "$CUDA_GRAPH_MAX_BS" -gt 128 ] && CUDA_GRAPH_MAX_BS=128

# ---- SGLang runtime patches (FP8 PLE weight_scale — SGLang PR #36497) -------
# The qwen38flashnext Docker image predates the FP8 PLE weight_scale fix.
# Rather than rebuilding the image we apply 5 targeted in-place patches to the
# container's sglang source before launching the server.
#
# Patched file: /workspace/sglang-qwen-next/python/sglang/srt/models/qwen4_exp.py
# Commit ref: sgl-project/sglang@3003ddf1574ef5004e21a10e36aaabc364766921
#
# The patches are idempotent — safe to run multiple times, already-applied
# patches are skipped.
_SGLANG_ROOT="${SGLANG_ROOT:-/workspace/sglang-qwen-next/python/sglang}"

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.

🔴 This script rewrites the pinned image's installed sglang source (5 python3 heredocs patching qwen4_exp.py) before launch, which docs/PR_REVIEW_CHECKLIST.md explicitly bars except with a filled-out docs/waiver/<PR_NUMBER>.md; no docs/waiver/2924.md is included in this PR (only pre-existing docs/waiver/2508.md exists). Merging as-is means the pinned lmsysorg/sglang-rocm:qwen38flashnext image never actually runs as shipped, silently defeating the checklist's engine-integrity guarantee for anyone reviewing/trusting this recipe. Fix: add docs/waiver/2924.md stating what is patched, why the unmodified image can't run this benchmark, the upstream PR link, and a removal plan, or drop the inline patches and wait for a fixed image.

Extended reasoning...

docs/PR_REVIEW_CHECKLIST.md line 30 (checked against docs/PR_REVIEW_CHECKLIST.md in this repo) requires: 'this PR does not patch the inference engine or serving stack ... The only exception is a patch covered by a filled-out waiver at docs/waiver/<PR_NUMBER>.md'. This PR's script applies 5 python3-heredoc patches plus a sed patch to /workspace/sglang-qwen-next/.../qwen4_exp.py and /sgl-workspace/sglang/.../multi_tokenizer_mixin.py before sglang.launch_server runs, i.e. exactly the patched-inference-engine pattern the checklist calls out by name. The diff includes no docs/waiver/2924.md (verified via ls docs/waiver, which only contains 2508.md unrelated to this PR/model). The PR's own changelog text acknowledges these are stopgap patches rather than pointing to a waiver, so the required sign-off artifact is missing and the resulting benchmark numbers are produced by a modified engine, not the pinned image as shipped.

Verification: normal (conflicts with stated purpose: the patching is the PR's mechanism, but the checklist requires it be gated on a waiver, which fixing does not undo). The script rewrites the pinned image's installed engine sources before launch — five python3 heredocs (lines 137-256) that read/replace/write ${_SGLANG_ROOT}/srt/models/qwen4_exp.py (default… | normal. The script rewrites installed SGLang…

_QWEN4_EXP="${_SGLANG_ROOT}/srt/models/qwen4_exp.py"

if [[ -f "$_QWEN4_EXP" ]]; then
echo "Applying SGLang runtime patches to $_QWEN4_EXP ..."

# Patch 1: Register weight_scale buffer in Qwen4ExpNGramEmbedding.__init__
if ! grep -q 'weight_scale.*torch.ones' "$_QWEN4_EXP"; then
echo " PATCH 1: Registering weight_scale buffer in Qwen4ExpNGramEmbedding"
python3 -c "
import sys
with open('$_QWEN4_EXP', 'r') as f:
content = f.read()
old = 'self.short_conv_dilation = self.ple_embedding.ngram_size'
if old not in content:
print(' PATCH 1: Target line not found, skipping', file=sys.stderr)
sys.exit(0)
new = '''self.ple_embedding.ngram_embedding.register_buffer(
\"weight_scale\", torch.ones(1, dtype=torch.bfloat16), persistent=True
)
self.short_conv_dilation = self.ple_embedding.ngram_size'''
content = content.replace(old, new, 1)
with open('$_QWEN4_EXP', 'w') as f:
f.write(content)
print(' PATCH 1: Applied successfully')
"
else
echo " PATCH 1: Already applied"
fi

# Patch 2: Multiply embeddings by weight_scale in forward()
if ! grep -q 'embeddings \* self.ngram_embedding.weight_scale' "$_QWEN4_EXP"; then
echo " PATCH 2: Adding weight_scale multiply in forward()"
python3 -c "
import sys
with open('$_QWEN4_EXP', 'r') as f:
content = f.read()
old = 'embeddings = self.ngram_embedding(lookup_ids)\n return self._finish_embedding_lookup('
if old not in content:
print(' PATCH 2: Target pattern not found, skipping', file=sys.stderr)
sys.exit(0)
new = '''embeddings = self.ngram_embedding(lookup_ids)
embeddings = embeddings * self.ngram_embedding.weight_scale
return self._finish_embedding_lookup('''
content = content.replace(old, new, 1)
with open('$_QWEN4_EXP', 'w') as f:
f.write(content)
print(' PATCH 2: Applied successfully')
"
else
echo " PATCH 2: Already applied"
fi

# Patch 3: Add weight_scale to PLE buffer allowlist in _load_qwen4_exp_ple_buffer
if ! grep -q '"weight_scale"' "$_QWEN4_EXP" 2>/dev/null || \
! python3 -c "
with open('$_QWEN4_EXP') as f:
content = f.read()
assert '\"weight_scale\",' in content and 'ngram_heads_vocab_sizes' in content
idx_ws = content.index('\"weight_scale\",')
idx_nv = content.index('\"ngram_heads_vocab_sizes\"')
assert abs(idx_ws - idx_nv) < 200
" 2>/dev/null; then
echo " PATCH 3: Adding weight_scale to PLE buffer allowlist"
python3 -c "
import sys
with open('$_QWEN4_EXP', 'r') as f:
content = f.read()
old = '''\"ngram_heads_vocab_sizes\",
}:'''
if old not in content:
print(' PATCH 3: Target pattern not found, skipping', file=sys.stderr)
sys.exit(0)
new = '''\"ngram_heads_vocab_sizes\",
\"weight_scale\",
}:'''
content = content.replace(old, new, 1)
with open('$_QWEN4_EXP', 'w') as f:
f.write(content)
print(' PATCH 3: Applied successfully')
"
else
echo " PATCH 3: Already applied"
fi

# Patch 4: weight_scale multiply in offloaded PLE reduce path
if ! grep -q 'ngram_embedding.reduce(embeddings)\n.*\* self.ple_embedding.ngram_embedding.weight_scale' "$_QWEN4_EXP" 2>/dev/null; then

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.

🟡 (optional) Patch 4's "already applied" pre-check uses grep -q 'ngram_embedding.reduce(embeddings)\n.*\* ...weight_scale' with a literal \n inside a single-line BRE pattern; grep matches per-line and never sees an embedded newline, so this grep always fails to match and the else "Already applied" branch (line 233) is permanently dead code. Fix: detect already-applied state with a pattern that fits on one line (e.g. grep for the inserted embeddings = embeddings * self.ple_embedding.ngram_embedding.weight_scale line alone), or drop the outer grep gate and rely solely on the inner Python old not in content check.

Extended reasoning...

Every invocation of the script takes the if branch and re-runs the Patch 4 python3 heredoc, even on a container where the patch was already applied by a prior run (this benchmark's own comment says the container/source persists across the 9-point conc-list sweep, and patches are meant to be idempotent no-ops on re-run). The python3 heredoc's own old not in content guard happens to prevent a second textual replacement, so no double-patch occurs, but the operator-facing log always prints "PATCH 4: Adding weight_scale multiply..." followed by "Target pattern not found, skipping" instead of the intended "Already applied" — misleading anyone debugging a later failed run (e.g. a real image update that legitimately makes the patch a no-op looks identical to a normal re-run).

Verification: nit. Line 214: if ! grep -q 'ngram_embedding.reduce(embeddings)\n.*\* self.ple_embedding.ngram_embedding.weight_scale' "$_QWEN4_EXP" embeds a literal \n in a single-line grep pattern. grep matches per line (newlines stripped), and the two anchor substrings live on separate lines in the file, so this grep can never match. ! grep is therefore always true, the if branch (line 215)…

echo " PATCH 4: Adding weight_scale multiply in offloaded PLE reduce path"
python3 -c "
import sys
with open('$_QWEN4_EXP', 'r') as f:
content = f.read()
old = 'embeddings = self.ple_embedding.ngram_embedding.reduce(embeddings)\n embeddings = self.ple_embedding._finish_embedding_lookup('
if old not in content:
print(' PATCH 4: Target pattern not found, skipping', file=sys.stderr)
sys.exit(0)
new = '''embeddings = self.ple_embedding.ngram_embedding.reduce(embeddings)
embeddings = embeddings * self.ple_embedding.ngram_embedding.weight_scale
embeddings = self.ple_embedding._finish_embedding_lookup('''
content = content.replace(old, new, 1)
with open('$_QWEN4_EXP', 'w') as f:
f.write(content)
print(' PATCH 4: Applied successfully')
"
else
echo " PATCH 4: Already applied"
fi

# Patch 5: Register weight_scale in Qwen4ExpPinnedHostEmbedding
if ! grep -q 'register_buffer.*"weight_scale".*embedding.weight_scale' "$_QWEN4_EXP" 2>/dev/null; then
echo " PATCH 5: Registering weight_scale in Qwen4ExpPinnedHostEmbedding"
python3 -c "
import sys
with open('$_QWEN4_EXP', 'r') as f:
content = f.read()
old = 'cpu_weight.weight_loader = self.weight_loader\n self.register_parameter(\"weight\", cpu_weight)'
if old not in content:
print(' PATCH 5: Target pattern not found, skipping', file=sys.stderr)
sys.exit(0)
new = '''cpu_weight.weight_loader = self.weight_loader
self.register_parameter(\"weight\", cpu_weight)
self.register_buffer(\"weight_scale\", embedding.weight_scale, persistent=True)'''
content = content.replace(old, new, 1)
with open('$_QWEN4_EXP', 'w') as f:
f.write(content)
print(' PATCH 5: Applied successfully')
"
else
echo " PATCH 5: Already applied"
fi

echo "All SGLang runtime patches applied."
else
echo "WARNING: qwen4_exp.py not found at $_QWEN4_EXP; running with container defaults" >&2
fi

# ---- ROCm / aiter environment -----------------------------------------------
export PYTHONNOUSERSITE=1
export SGLANG_USE_AITER=1
export SGLANG_USE_AITER_UNIFIED_ATTN=1
export AITER_FLYDSL_FORCE=1
export ROCM_QUICK_REDUCE_QUANTIZATION=INT8
export SGLANG_TIMEOUT_KEEP_ALIVE=1800
export SGLANG_ENABLE_SPEC_V2=1

# ---- Speculative decoding (NEXTN / MTP) -------------------------------------
SPEC_ARGS=(
--speculative-algorithm NEXTN
--speculative-num-steps 3
--speculative-eagle-topk 1
--speculative-num-draft-tokens 4
)

# golden_al_distribution/qwen3.8next_mtp.yaml:
# qwen3.8-flash-next-fp8.thinking_on[3] = 2.32.
# AgentX replays run with thinking on, so the thinking_on row is the right one.
if [ "${EVAL_ONLY:-false}" != "true" ]; then
export SGLANG_SIMULATE_ACC_LEN=2.32
export SGLANG_SIMULATE_ACC_METHOD=match-expected
export SGLANG_SIMULATE_ACC_TOKEN_MODE=real-draft-token
fi

# ---- multi_tokenizer cached_tokens_details patch ----------------------------
SGLANG_MULTI_TOKENIZER=/sgl-workspace/sglang/python/sglang/srt/managers/multi_tokenizer_mixin.py
if [ -f "$SGLANG_MULTI_TOKENIZER" ]; then
if ! sed -n '/elif isinstance(output, BatchStrOutput):/,/input_token_logprobs_val=_extract_field_by_index/p' "$SGLANG_MULTI_TOKENIZER" \
| grep -q 'cached_tokens_details=_extract_field_by_index'; then
sed -i '/elif isinstance(output, BatchStrOutput):/,/cached_tokens=_extract_field_by_index(output, "cached_tokens", i),/ {
/cached_tokens=_extract_field_by_index(output, "cached_tokens", i),/a\
cached_tokens_details=_extract_field_by_index(\
output, "cached_tokens_details", i\
),
}' "$SGLANG_MULTI_TOKENIZER"
fi
fi

# ---- Launch server -----------------------------------------------------------
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
--page-size 32
--kv-cache-dtype auto
--mem-fraction-static 0.90
--model-loader-extra-config '{"enable_multithread_load": true}'
--watchdog-timeout 1200
--chunked-prefill-size 16384
--mamba-ssm-dtype bfloat16
--cuda-graph-max-bs "$CUDA_GRAPH_MAX_BS"
--max-running-requests "$MAX_RUNNING_REQUESTS"
--scheduler-recv-interval "$SCHEDULER_RECV_INTERVAL"
--stream-interval 50
"${TOKENIZER_ARGS[@]}"
--tokenizer-path "$MODEL"
--reasoning-parser qwen3
--tool-call-parser qwen3_coder
"${SPEC_ARGS[@]}"
--enable-metrics
--enable-cache-report
"${CACHE_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=$!

wait_for_server_ready --port "$PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID"

if [ "${EVAL_ONLY:-false}" = "true" ]; then
run_eval --port "$PORT"
else
build_replay_cmd "$RESULT_DIR"
REPLAY_CMD+=" --apply-chat-template"
run_agentic_replay_and_write_outputs "$RESULT_DIR"
fi
14 changes: 14 additions & 0 deletions configs/amd-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,20 @@ qwen3.5-fp8-mi300x-sglang-agentic-mtp:
- { 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] }

qwen3.8-flash-next-fp8-mi355x-sglang-agentic-mtp:
image: lmsysorg/sglang-rocm:qwen38flashnext
model: Qwen/Qwen3.8-Flash-Next-FP8
model-prefix: qwen3.8-flash-next

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong model-prefix breaks script lookup

High Severity

model-prefix is qwen3.8-flash-next, but the recipe is named qwen3.8next_fp8_mi355x_sglang_mtp.sh. The MI355X launcher resolves the script from ${EXP_NAME%%_*}, which comes from model-prefix, so CI will look for qwen3.8-flash-next_fp8_mi355x_sglang_mtp.sh and fail to start. Sibling Qwen3.8-Flash-Next configs and MODELS.md use qwen3.8next.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4dc6c45. Configure here.

runner: cluster:mi355x-amds
precision: fp8
framework: sglang
multinode: false
scenarios:
agentic-coding:
- dram-utilization: 0.80
search-space:
- { tp: 4, ep: 1, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 16, 20, 24, 28, 32] }

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
Expand Down
9 changes: 9 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6947,3 +6947,12 @@
- "Update vLLM ROCm image from vllm/vllm-openai-rocm:v0.27.1 (v0.27.1 release) to vllm/vllm-openai-rocm:nightly-d9105ea8001e0a6d77a96327d17515bb5791fb36 (2026-09-07 upstream ROCm nightly, digest sha256:74d4a95f3ae672ecddf9acb7296917def82d9eca51687fa2862ae72b03ff1907, tag commit vllm-project/vllm@d9105ea8; Docker Hub last pushed 2026-09-07T05:26:48Z). benchmarks/single_node/agentic/minimaxm3_fp8_mi300x_mtp.sh is unchanged: TRITON_ATTN attention, fp8 KV, block-size 128, EAGLE3 speculative decoding with the Inferact MiniMax-M3 EAGLE3-GQA draft and the committed golden synthetic acceptance length, minimax_m3 tool-call and reasoning parsers; the search space is unchanged. The upstream commit-pinned ROCm nightly is the same vllm commit the B200 MiniMax-M3 AgentX recipe moved to in #2860. Note that vllm-openai-rocm commit-nightly tags have expired from Docker Hub within days in the past; node squash caches keep merged configs running, but a re-pin to a durable tag may be needed later."
- "Add --compilation-config cudagraph_mode=FULL_DECODE_ONLY to the serve command. The upstream nightly does not torch-compile MiniMaxM3SparseForConditionalGeneration, so with VLLM_USE_BREAKABLE_CUDAGRAPH=0 the default FULL_AND_PIECEWISE mode aborts at engine init with piecewise CUDA graphs unavailable (first sweep, run 34174124043, eval cell); full decode-only graphs are what the MI355X MiniMax-M3 sibling runs on its nightly (#2825)."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2873

- config-keys:
- qwen3.8-flash-next-fp8-mi355x-sglang-agentic-mtp
scenario-type:
- agentic-coding
description:
- "Add day-zero Qwen3.8-Flash-Next FP8 agentic benchmark on MI355X with NEXTN MTP speculative decoding. New config block qwen3.8-flash-next-fp8-mi355x-sglang-agentic-mtp using lmsysorg/sglang-rocm:qwen38flashnext image with model Qwen/Qwen3.8-Flash-Next-FP8 (176B total, 6B active, hybrid GDN+QSA architecture). TP=4, EP=1, NEXTN MTP (3 steps, eagle-topk 1, 4 draft tokens), aiter attention backend, page-size 32, kv-cache-dtype auto, mem-fraction-static 0.90, mamba-ssm-dtype bfloat16, golden acceptance length SGLANG_SIMULATE_ACC_LEN=2.32. Concurrency grid [1, 4, 8, 12, 16, 20, 24, 28, 32], no kv-offloading."
- "Add benchmarks/single_node/agentic/qwen3.8next_fp8_mi355x_sglang_mtp.sh with 5 inlined runtime patches (backport from SGLang PR #36497) that fix the FP8 PLE weight_scale assertion in the stale qwen38flashnext Docker image. Patches register weight_scale buffers in Qwen4ExpNGramEmbedding and Qwen4ExpPinnedHostEmbedding, add weight_scale to the PLE buffer allowlist, and multiply embeddings by the loaded scale for correct FP8 dequantization. Patches are idempotent and will become no-ops once a newer image ships with the upstream fix."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2924