<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[AI Pro R9700 单卡 SGlang qwen3.8 27b]]></title><description><![CDATA[<blockquote>
<p dir="auto">说明：本文原为英文，已按原文逐段译为中文——技术内容一字未改，命令、路径、参数、代码块全部原样保留。译文由小特整理。</p>
</blockquote>
<p dir="auto">今天让Hermes抄flyer666的作业，单卡r9700跑SGlang 成功了，跟大家分享一下，同时感谢Terry建立这个环境供大家一起学习，也感谢各位高手分享AI知识。</p>
<h1>在 AMD Radeon <strong>R9700 (gfx1201)</strong> 上用 SGLang 运行 Qwen3.8-27B（W4A16 GPTQ，4-bit）</h1>
<p dir="auto">一份完整、可复现的构建与运维指南。它是为了能在<strong>另一台机器</strong>上（带 R9700）照着执行而写的<br />
——无论是人还是 agent——每个阶段之后都设有一个校验点，这样失败会在它发生的地方<br />
被发现，而不是等到三个阶段之后。</p>
<p dir="auto">这里的一切都是在参考机器上凭经验确立的，实测数字都是真实的，不是估算出来的。凡是<br />
属于估算、或从未测试过的内容，都会明确标注出来。</p>
<blockquote>
<p dir="auto"><strong>约定。</strong> 把 <code>$HOME</code> 换成你的家目录（<code>/home/&lt;user&gt;</code>）。<br />
像 <code>&lt;API-KEY&gt;</code>、<code>&lt;BOT-TOKEN&gt;</code>、<code>&lt;server-ip&gt;</code> 这样的占位符需要你自己填写。<br />
命令面向 Ubuntu 24.04 + Bash。</p>
</blockquote>
<hr />
<h2>0. 这套方案能达成什么</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>模型</td>
<td><code>Qwen3.8-27B</code>（mamba/attention 混合 VL 模型，arch <code>Qwen3_5ForConditionalGeneration</code>），4-bit <strong>GPTQ W4A16</strong></td>
</tr>
<tr>
<td>GPU</td>
<td>1× Radeon <strong>AI PRO R9700</strong>，32 GB，<code>gfx1201</code> (RDNA4)</td>
</tr>
<tr>
<td>引擎</td>
<td>Docker 中的 SGLang 0.5.13.post1（<code>rocm/sgl-dev</code>），TP=1</td>
</tr>
<tr>
<td>Decode</td>
<td><strong>27 t/s</strong>（普通）· <strong>43–62 t/s</strong>（使用 NEXTN/MTP 投机）</td>
</tr>
<tr>
<td>Prefill @4K</td>
<td><strong>244–247 t/s</strong>（scalar GPTQ 路径——原因见 §5.4）</td>
</tr>
<tr>
<td>KV pool</td>
<td>仅 device 295,902 tokens (fp8_e4m3) · 带 HiCache host pool 时 <strong>887,707 tokens</strong></td>
</tr>
<tr>
<td>上下文</td>
<td>每请求 131,072</td>
</tr>
<tr>
<td>视觉</td>
<td>支持——vision tower 常驻</td>
</tr>
<tr>
<td>权重占用显存</td>
<td>17.95 GB</td>
</tr>
</tbody>
</table>
<p dir="auto">24 GB 的卡<strong>不行</strong>：checkpoint 约 18.6 GiB，而你还需要实实在在的余量留给 KV、<br />
graph buffer 和 workspace。</p>
<hr />
<h2>1. 前置条件</h2>
<h3>1.1 硬件 / 操作系统</h3>
<ul>
<li>1× Radeon R9700 (gfx1201)，32 GB——或任意 32 GB 以上的 gfx1201 显卡</li>
<li>如果想要 HiCache（longctx）档，<strong>系统内存 ≥ 62 GB</strong>。其中 21 GB<br />
被 pinned host pool 吃掉。<code>speed</code> 档不需要 host pool。</li>
<li>约 120 GB 可用磁盘：Docker 镜像约 71 GB + 模型约 19 GB + 构建树约 1–3 GB</li>
<li>Ubuntu 24.04（或类似版本），使用内核自带的 <code>amdgpu</code> 驱动。<strong>宿主机侧不需要安装<br />
ROCm</strong>——容器镜像自带 userland；宿主机只需要内核驱动，再加上暴露出来的<br />
<code>/dev/kfd</code> 与 <code>/dev/dri/*</code>。</li>
</ul>
<h3>1.2 Docker + GPU 访问</h3>
<pre><code class="language-bash">sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"     # then LOG OUT and back in
</code></pre>
<p dir="auto"><strong>校验点 1</strong> — 确认 GPU 设备节点存在且守护进程可用：</p>
<pre><code class="language-bash">ls -l /dev/kfd /dev/dri/                 # expect /dev/kfd + renderD128.. cards
docker info &gt;/dev/null &amp;&amp; echo "docker OK"
</code></pre>
<blockquote>
<p dir="auto"><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=ec85ec55bc1" class="not-responsive emoji emoji-android emoji--warning" style="height:1.4em;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ 两个会白白浪费时间的坑：</p>
<ul>
<li>组成员身份只对<strong>新</strong>会话生效。在已经打开的 shell 里，<br />
给 Docker 命令加上 <code>sg docker -c "..."</code> 前缀即可，不必重新登录。</li>
<li>后面会看到，systemd <strong>user</strong> unit 无法把自己排在<em>系统级</em><br />
<code>docker.service</code> 之后。应改用就绪循环等待守护进程（§9.1）。</li>
</ul>
</blockquote>
<h3>1.3 识别你的 render 节点并确认架构</h3>
<pre><code class="language-bash">for c in /sys/class/drm/card*/device/uevent; do
  echo "$c $(grep -o 'PCI_ID=.*' $c)"
done
cat /sys/class/drm/card*/device/mem_info_vram_total   # bytes
</code></pre>
<p dir="auto">记下属于 R9700 的那个 <code>renderDxxx</code> 节点——下面每条命令都会用到它。在多卡机器上，<br />
只把<strong>这一个节点</strong>传进容器，才能让另一张卡保持空闲。</p>
<p dir="auto"><strong>校验点 2</strong> — 在容器内，架构必须读出 <code>gfx1201</code>：</p>
<pre><code class="language-bash">IMG=rocm/sgl-dev:v0.5.13.post1-ubuntu24.04-py3.14-rocm7.14.1
docker run --rm --device /dev/kfd --device /dev/dri/renderD129 \
  --group-add=video --security-opt seccomp=unconfined \
  -e HIP_VISIBLE_DEVICES=0 -e ROCR_VISIBLE_DEVICES=0 \
  $IMG bash -lc 'rocminfo | grep -m1 gfx'
# expected: gfx1201
</code></pre>
<p dir="auto">这一步要是失败，就停下来——后面任何东西都不会工作。</p>
<hr />
<h2>2. 拉取镜像</h2>
<pre><code class="language-bash">docker pull rocm/sgl-dev:v0.5.13.post1-ubuntu24.04-py3.14-rocm7.14.1
</code></pre>
<p dir="auto">约 71 GB。镜像内含：Python 3.14、HIPCC/ROCm 7.14、Triton 以及 SGLang wheel。<br />
请固定这个 tag——§5 的补丁集针对的正是这个确切版本。</p>
<hr />
<h2>3. 获取模型</h2>
<h3>3.1 用哪个 checkpoint，以及为什么</h3>
<p dir="auto">使用 <strong><code>Vishva007/Qwen3.8-27B-W4A16-AutoRound-GPTQ</code></strong>（约 19 GB）。</p>
<p dir="auto">为什么<em>偏偏</em>用这一个：它是货真价实的 <strong>GPTQ W4A16</strong> 打包（4-bit 权重、<br />
16-bit 激活），并且有意把它的 MTP/NEXTN head 留在 <strong>bf16</strong>，这样投机解码仍然能用。<br />
这个模型上所有打着 "AWQ" 招牌的 checkpoint 实际上都是 Quark 或 compressed-tensors<br />
格式，SGLang 在 RDNA 上跑不了（见附录 C）。</p>
<pre><code class="language-bash">mkdir -p "$HOME/models/sglang"
hf download Vishva007/Qwen3.8-27B-W4A16-AutoRound-GPTQ \
  --local-dir "$HOME/models/sglang/qwen3.8-27b-w4a16-gptq"
</code></pre>
<p dir="auto">预期是 5 个权重分片 + <code>model_extra_tensors.safetensors</code> + 常规的那些配置文件。</p>
<h3>3.2 <strong>硬卡点 #1</strong> — checkpoint 的 <code>config.json</code> 会让 loader 挂掉</h3>
<p dir="auto">该 checkpoint 自带两条动态量化规则，把 MTP tensor <em>纳入</em>了量化：</p>
<pre><code class="language-json">"+:.*mtp.*":      {...},
"+:.*mtp\\.fc.*": {...}
</code></pre>
<p dir="auto">但那些 tensor 是以 bf16 存储的，所以 GPTQ loader 会去反量化未量化的数据然后炸掉。<br />
把这两条替换成一条<strong>排除</strong>（exclusion）规则。</p>
<pre><code class="language-python">#!/usr/bin/env python3
"""Fix the MTP dynamic-quant rules. Idempotent; keeps a one-time .orig backup."""
import json, os, shutil, sys

path = sys.argv[1] if len(sys.argv) &gt; 1 else \
    os.path.expanduser("~/models/sglang/qwen3.8-27b-w4a16-gptq/config.json")

cfg = json.load(open(path))
qc  = cfg.get("quantization_config", {})
dyn = qc.setdefault("dynamic", {})

removed = [k for k in list(dyn) if k.startswith("+") and "mtp" in k.lower()]
for k in removed:
    del dyn[k]

added = False
if not any(k.startswith("-") and "mtp" in k.lower() for k in dyn):
    dyn["-:.*mtp.*"] = {"bits": 16, "group_size": 128}
    added = True

if removed or added:
    bak = path + ".orig"
    if not os.path.exists(bak):
        shutil.copy(path, bak)
    json.dump(cfg, open(path, "w"), indent=2)
    print(f"patched: removed {len(removed)} include-rule(s), added exclusion: {added}")
else:
    print("already patched (nothing to do)")
print("mtp rules now:", {k: v for k, v in dyn.items() if "mtp" in k.lower()})
print("quant:", qc.get("quant_method"), qc.get("bits"), "group", qc.get("group_size"))
</code></pre>
<p dir="auto"><strong>校验点 3</strong> — 运行之后，<code>quantization_config</code> 必须显示<br />
<code>gptq / bits 4 / group_size 64</code>，而且唯一的 MTP 规则必须是<br />
<code>-:.*mtp.*</code>，带 <code>bits: 16</code>。未量化的 tensor（embeddings、MTP head、<br />
mamba/conv 参数）是 bf16 → 它决定了 §6 中的 dtype 选择。</p>
<hr />
<h2>4. 构建之前先理解差距</h2>
<p dir="auto">有两个事实使得自建成为必须——请自行验证它们，以确认在你所用的版本上<br />
是否仍然需要这个 fork：</p>
<pre><code class="language-bash"># (a) The prebuilt wheel ships NO GPTQ ops
docker run --rm $IMG bash -lc 'python3 -c "
import torch, sgl_kernel as s
print([x for x in dir(s) if \"gptq\" in x.lower()])"'
# expect: [] (empty)

# (b) Upstream has no ROCm W4A16 int4 quant *scheme* beyond these:
docker run --rm $IMG bash -lc \
  'grep -rhoE "quark_w4[a-z0-9_]+" /sgl-workspace/sglang/python/sglang/srt/layers/quantization/quark/ | sort -u'
# quark_w4a4_mxfp4 / _moe / quark_w4a8_mxfp4_moe — MXFP4 is CDNA+AITER only.
</code></pre>
<p dir="auto">所以：RDNA 上的 GPTQ 支持必须<strong>自己编译进去</strong>。</p>
<hr />
<h2>5. 为 gfx1201 构建 GPTQ kernel</h2>
<h3>5.1 获取 fork</h3>
<pre><code class="language-bash">mkdir -p "$HOME/sglang-sandbox/src" &amp;&amp; cd "$HOME/sglang-sandbox/src"
git clone -b gfx1100-support https://github.com/StevenChenSE/sglang.git sglang-gfx1100
cd sglang-gfx1100 &amp;&amp; git log -1 --format='%H %s'
# reference: f84475ccf593087df3d1da13c9e675eadee12352
</code></pre>
<p dir="auto">这个分支带有 RDNA3 的 GPTQ kernel（<code>csrc/gemm/gptq/q_gemm_rdna3*.{hip,cu}</code>、<br />
<code>q_gemm_rdna3_wmma.*</code>、<code>moe_q_gemm_rdna3.hip</code>），一个 ROCm 版 <code>setup_rocm.py</code>，<br />
以及它自己的 <code>tests/test_gptq_rdna3_smoke.py</code>。</p>
<h3>5.2 为什么原样无法为 gfx1201 构建</h3>
<p dir="auto">三道各自独立的关卡，全都只认 gfx1100：</p>
<ol>
<li>scalar 反量化 GEMM 主体被 <code>defined(__gfx1100__)</code> 守护；</li>
<li>一处运行期 <code>TORCH_CHECK</code> 断言架构是 gfx1100；</li>
<li>WMMA translation unit 使用了 <code>wmma-256b-insts</code> builtin——<strong>只有 RDNA3 才有<br />
的指令</strong>。Clang 拒绝为 gfx1201 编译它们，而<em>强行开启</em>该 feature 会让<br />
<strong>clang-23 段错误</strong>。没有绕法；这个 TU 必须去掉。</li>
</ol>
<p dir="auto">关键在于，在 RDNA 上通用的 <code>gptq_gemm</code> op 会直接路由进<br />
<code>gptq_gemm_rdna3</code>，所以 <strong>scalar</strong> 路径就是全部实现——WMMA<br />
TU 是一个独立的、可选的 op。</p>
<h3>5.3 <strong>硬卡点 #2</strong> — 跨 4 个文件的 5 处修改</h3>
<p dir="auto">把这些改动应用到 fork 上（§12 的脚本会替你完成；这里说的是它们改了什么、以及为什么）。<br />
请以源码文本作为锚点，不要依赖行号。</p>
<p dir="auto"><strong>(1)+(2) <code>csrc/gemm/gptq/q_gemm_rdna3.hip</code> 和 <code>q_gemm_rdna3.cu</code></strong> —— 放宽<br />
架构守护，让 scalar 主体能为 RDNA4 编译：</p>
<pre><code class="language-cpp">// before
#if defined(__HIPCC__) &amp;&amp; defined(__gfx1100__)
// after
#if defined(__HIPCC__) &amp;&amp; (defined(__gfx1100__) || defined(__gfx1201__))
</code></pre>
<p dir="auto"><strong>(3) 同样这两个文件</strong> —— 当 WMMA 被排除时，WMMA dispatch 和只在 gfx1100 成立的<br />
运行期断言都不得被编译：</p>
<pre><code class="language-cpp">#ifndef SGL_GPTQ_NO_WMMA
  ... existing WMMA dispatch + TORCH_CHECK(arch == gfx1100) ...
#endif
</code></pre>
<p dir="auto"><strong>(4) <code>csrc/common_extension_rocm.cc</code></strong> —— <code>gptq_gemm_rdna3_wmma</code> 的 <code>extern</code><br />
声明<strong>以及</strong>它的 <code>m.def</code>/<code>m.impl</code> 注册也必须加上守护，否则<br />
链接时会出现未定义符号：</p>
<pre><code class="language-cpp">#ifndef SGL_GPTQ_NO_WMMA
  ... extern decl + m.def/m.impl for gptq_gemm_rdna3_wmma ...
#endif
</code></pre>
<p dir="auto"><strong>(5) <code>python/sglang/kernels/aot/setup_rocm.py</code></strong> —— 对于非 gfx1100 的 RDNA<br />
目标：去掉 WMMA 源文件，并为<strong>两个</strong>编译器都定义该宏：</p>
<pre><code class="language-python">hipcc_flags.append("-DSGL_GPTQ_NO_WMMA")
cxx_flags.append("-DSGL_GPTQ_NO_WMMA")
</code></pre>
<p dir="auto"><code>setup_rocm.py</code> 里还有一个 <code>RDNA_TARGETS = {gfx1100, gfx1151, gfx1201}</code> 集合，<br />
如果你的卡是别的 RDNA4 SKU，就需要扩展它。</p>
<h3>5.4 构建</h3>
<pre><code class="language-bash">DEV=/dev/dri/renderD129 TARGET=gfx1201 JOBS=16 \
  bash "$HOME/sglang-sandbox/build-gptq-kernels.sh"
</code></pre>
<p dir="auto">脚本在镜像内运行 <code>setup_rocm.py build_ext --inplace</code>，只暴露一个 DRM 节点，<br />
并把 <code>AMDGPU_TARGET</code> 钉死，这样架构探测不会漂移。</p>
<p dir="auto">产物：<code>python/sglang/kernels/aot/python/sgl_kernel/common_ops.cpython-314-x86_64-linux-gnu.so</code><br />
（约 25 MB，32 个 gfx1201 code object，约 86 个 gptq 符号）。</p>
<blockquote>
<p dir="auto">如果构建因为删不掉上一次尝试留下的 root 所属对象而失败，就<strong>通过容器</strong>删除<br />
build 目录（容器是以 root 运行的）：<br />
<code>docker run --rm -v &lt;src&gt;:/src $IMG bash -lc 'rm -rf /src/python/sglang/kernels/aot/build; chown -R 1000:1000 /src'</code></p>
</blockquote>
<p dir="auto"><strong>校验点 4</strong> — 用数值验证，而不是凭感觉。改造 fork 自带的 smoke<br />
test（constant-nibble 技巧 → 解析上已知的结果），要求<br />
在 R9700 上 <strong>35/35 项检查通过，maxdiff = 0</strong>，覆盖 fp16 <strong>和</strong> bf16、M=1→128、<br />
GPTQ v1 与 v2、<code>gptq_shuffle</code>，以及生产场景（bf16 激活 + fp16<br />
scale）。只有那个 WMMA op 可以跳过，因为你是刻意不构建它的。</p>
<p dir="auto">放弃 WMMA 的实际代价在 prefill，不在 decode：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th>scalar 路径（本方案）</th>
<th>带 WMMA 的 gfx1100 构建（参考）</th>
</tr>
</thead>
<tbody>
<tr>
<td>decode</td>
<td>27.2 t/s</td>
<td>2×7900XTX TP=2 上约 80 t/s</td>
</tr>
<tr>
<td>prefill @4K</td>
<td>246 t/s</td>
<td>每卡约 934 t/s</td>
</tr>
</tbody>
</table>
<p dir="auto">Decode 受带宽限制，因此不受影响（27.2 t/s ≈ 640 GB/s ÷ 18.6 GB 上限的<br />
80 %）。<strong>真正的牺牲品是 prefill——大约 3.8×。</strong> 要想把它找回来，<br />
需要一个原生的 W4A8/FP8 RDNA4 kernel。</p>
<hr />
<h2>6. <strong>硬卡点 #3</strong> — dtype 必须是 bf16，而 SGLang 禁止它</h2>
<p dir="auto">上游 SGLang 把 GPTQ 限制为 fp16：</p>
<pre><code>torch.bfloat16 is not supported for quantization method gptq
</code></pre>
<p dir="auto">但 checkpoint 里未量化的 tensor 是 <strong>bf16</strong>，而这个模型是 mamba/attention<br />
混合架构，它的 <code>causal_conv1d</code> Triton kernel 在 bf16/fp16 混用时会硬失败<br />
（<code>Mismatched type for col0</code>）。所以你必须跑 bf16——也就必须<br />
给 SGLang 打补丁以允许它。</p>
<p dir="auto">把镜像里的<br />
<code>sglang/srt/layers/quantization/gptq/gptq.py</code> 复制出来，然后在 <code>GPTQConfig</code> 中：</p>
<pre><code class="language-python">@classmethod
def get_supported_act_dtypes(cls) -&gt; List[torch.dtype]:
    return [torch.half, torch.bfloat16]     # upstream is [torch.half] only
</code></pre>
<p dir="auto">依据：§5 构建的 kernel 能处理 bf16 激活——35/35 的 smoke test 证明的正是这一点<br />
（bf16 激活 + fp16 scale）。运行时把打过补丁的文件挂载覆盖镜像里的副本（§7）。</p>
<hr />
<h2>7. 启动脚本</h2>
<p dir="auto">标准调用方式。保存为 <code>$HOME/sglang-sandbox/run-sglang.sh</code>，并修改<br />
顶部的路径。</p>
<pre><code class="language-bash">#!/bin/bash
set -euo pipefail

IMG=${IMG:-rocm/sgl-dev:v0.5.13.post1-ubuntu24.04-py3.14-rocm7.14.1}
MODELS_DIR=${MODELS_DIR:-$HOME/models/sglang}
MODEL_SUBDIR=${MODEL_SUBDIR:-qwen3.8-27b-w4a16-gptq}
PORT=${PORT:-30001}
CTX=${CTX:-131072}
PAGE=${PAGE:-64}
MEMFRAC=${MEMFRAC:-0.78}
KV=${KV:-fp8_e4m3}
DTYPE=${DTYPE:-bfloat16}
CTR=${CTR:-sglang-gptq}
RENDER_NODE=${RENDER_NODE:-/dev/dri/renderD129}

# bf16 GPTQ allowance (see §6)
GPTQ_PY=${GPTQ_PY:-$HOME/sglang-sandbox/patches/gptq.py}
PY_MOUNT=""
[ -f "$GPTQ_PY" ] &amp;&amp; PY_MOUNT="-v $GPTQ_PY:/sgl-workspace/sglang/python/sglang/srt/layers/quantization/gptq/gptq.py"

# self-built sgl_kernel with the GPTQ ops (see §5)
GPTQ_SO=${GPTQ_SO:-$HOME/sglang-sandbox/src/sglang-gfx1100/python/sglang/kernels/aot/python/sgl_kernel/common_ops.cpython-314-x86_64-linux-gnu.so}
SO_MOUNT=""
if [ -f "$GPTQ_SO" ]; then
  SO_MOUNT="-v $GPTQ_SO:/opt/venv/lib/python3.14/site-packages/sgl_kernel/common_ops.cpython-314-x86_64-linux-gnu.so"
else
  echo "[!] built sgl_kernel NOT found at $GPTQ_SO — GPTQ ops will be missing"; exit 1
fi

SPEC_FLAGS=""
[ "${SPEC:-1}" = "1" ] &amp;&amp; SPEC_FLAGS="--speculative-algorithm NEXTN --speculative-num-steps 3 --speculative-eagle-topk 1 --speculative-num-draft-tokens 4"

HICACHE_FLAGS=""
[ "${HICACHE:-0}" = "1" ] &amp;&amp; HICACHE_FLAGS="--enable-hierarchical-cache --hicache-ratio 2 --hicache-io-backend direct --hicache-write-policy write_through"

# a crashed container stays "Up" and holds the port + GPU
sg docker -c "docker rm -f $CTR" &gt;/dev/null 2&gt;&amp;1 || true

exec sg docker -c "docker run --rm --name $CTR \
  --device /dev/kfd \
  --device $RENDER_NODE \
  --network=host \
  --ipc=host \
  --group-add=video \
  --cap-add=SYS_PTRACE \
  --security-opt seccomp=unconfined \
  -e HF_HOME=/app/models \
  -e SGLANG_USE_AITER=0 \
  -e SGLANG_ROCM_FUSED_DECODE_MLA=0 \
  -e HIP_VISIBLE_DEVICES=0 \
  -e ROCR_VISIBLE_DEVICES=0 \
  -e PYTORCH_HIP_ALLOC_CONF=expandable_segments:True \
  -e PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
  -v $MODELS_DIR:/app/models \
  $SO_MOUNT $PY_MOUNT \
  $IMG \
  python3 -m sglang.launch_server \
    --model-path /app/models/$MODEL_SUBDIR \
    --quantization gptq \
    --dtype $DTYPE \
    --tp-size 1 \
    --mamba-ssm-dtype bfloat16 \
    --attention-backend triton \
    --triton-attention-num-kv-splits 16 \
    --context-length $CTX \
    --kv-cache-dtype $KV \
    --mem-fraction-static $MEMFRAC \
    --max-running-requests 4 \
    --max-mamba-cache-size 20 \
    --chunked-prefill-size 2048 \
    --page-size $PAGE \
    --schedule-policy lpm \
    --reasoning-parser qwen3 \
    --tool-call-parser qwen3_coder \
    --enable-metrics \
    --chat-template /app/models/$MODEL_SUBDIR/chat_template-fixed.jinja \
    --host 0.0.0.0 --port $PORT \
    $SPEC_FLAGS $HICACHE_FLAGS"
</code></pre>
<h3>为什么用这些参数</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>参数</th>
<th>原因</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--tp-size 1</code></td>
<td>单卡；跨混合架构做 TP=2 不可行（附录 C）</td>
</tr>
<tr>
<td><code>--quantization gptq</code> + 挂载的 <code>.so</code></td>
<td>这些 op 在上游并不存在</td>
</tr>
<tr>
<td><code>--dtype bfloat16</code> + <code>--mamba-ssm-dtype bfloat16</code></td>
<td>checkpoint 的 tensor 是 bf16；mamba kernel 不接受混合 dtype</td>
</tr>
<tr>
<td><code>--attention-backend triton</code></td>
<td>这些 kernel 所需的组合</td>
</tr>
<tr>
<td><code>--triton-attention-num-kv-splits 16</code></td>
<td>RDNA 上的吞吐</td>
</tr>
<tr>
<td><code>--kv-cache-dtype fp8_e4m3</code></td>
<td>在速度和效果完全相同的前提下 KV pool <strong>翻倍</strong>（295,902 对 148,271 tokens）</td>
</tr>
<tr>
<td><code>--page-size 64</code></td>
<td>只要 radix cache 处于启用状态就会被忽略/强制为 <code>1</code>（§8）</td>
</tr>
<tr>
<td><code>--schedule-policy lpm</code></td>
<td>longest-prefix-match 调度</td>
</tr>
<tr>
<td><code>--chunked-prefill-size 2048</code></td>
<td>prefill 调度粒度</td>
</tr>
<tr>
<td><code>--max-mamba-cache-size 20</code></td>
<td>混合 SSM 状态槽位（48 个 Gated-DeltaNet + 16 个 full-attention 层）</td>
</tr>
<tr>
<td><code>--max-running-requests 4</code></td>
<td>并发上限；让 KV 余量保持在合理范围</td>
</tr>
<tr>
<td><code>--mamba-ssm-dtype</code></td>
<td>见 dtype 那一行</td>
</tr>
<tr>
<td><code>--enable-metrics</code></td>
<td>暴露 <code>/metrics</code>——§8 的 HiCache + spec 计数器需要它</td>
</tr>
<tr>
<td><code>--chat-template .../chat_template-fixed.jinja</code></td>
<td>§7.2</td>
</tr>
</tbody>
</table>
<p dir="auto">有两种 dtype 值得一说：<strong><code>fp8_e5m2</code> 会被 CLI 接受，但分配器实际创建的<br />
仍然是 E4M3</strong>——在这个目标平台上 AMD FP8 只有 E4M3，所以实际上<br />
并不存在 e5m2 这个选项。</p>
<p dir="auto"><strong>校验点 5</strong> — 首次成功启动。留意以下四行：</p>
<pre><code>KV Cache is allocated. dtype: torch.float8_e4m3fn, #tokens: 295902, ...
The server is fired up and ready to roll!
</code></pre>
<p dir="auto">并做端到端确认：</p>
<pre><code class="language-bash">curl -s localhost:30001/v1/models | jq -r '.data[].id'
curl -s localhost:30001/v1/chat/completions -H 'Content-Type: application/json' \
  -d '{"model":"any-alias","messages":[{"role":"user","content":"say OK"}],"max_tokens":8}' | jq -r '.choices[0].message.content'
</code></pre>
<p dir="auto">服务器接受<strong>任意</strong> model 字符串，作为这个已加载模型的别名——<br />
所以你可以给客户端编一个好记的名字（§10）。</p>
<h3>7.2 <strong>硬卡点 #4</strong> — 模型自带的 chat template 会拒绝标准客户端</h3>
<p dir="auto">这一条让一个<em>符合标准</em>的客户端（DeepSeek Harness）在<strong>每个</strong>请求上都吃到<br />
HTTP 400，而 curl 却一切正常。这两个错误都来自模型自带的<br />
<code>chat_template.jinja</code>——不是 SGLang 的问题：</p>
<pre><code>Unexpected message role.                                    # chat_template.jinja
Unexpected reasoning effort high. Supported types are
  xhigh (default), medium, and low.                         # chat_template.jinja
</code></pre>
<p dir="auto">两处不匹配：</p>
<ol>
<li><strong><code>role: "developer"</code></strong> —— OpenAI 规范中 <code>system</code> 的继任者，由<br />
现代 SDK/harness 发出。而模板只处理 system/user/assistant/tool。</li>
<li><strong><code>reasoning_effort</code> 词表夹击。</strong> OpenAI 的字段（以及 SGLang<br />
为它定义的 pydantic schema）接受 <code>none|low|medium|high|max</code>，而模板<br />
只认得 <code>xhigh|medium|low</code>。于是 <code>high</code>/<code>max</code> 通过了校验、随后<br />
死在模板里，而 <code>xhigh</code> 在模板看到它之前就已经死在 SGLang 的 schema 里。<br />
<strong>过去只有 <code>none|low|medium</code> 能同时穿过这两层。</strong></li>
</ol>
<p dir="auto">在<strong>服务端</strong>修复，让所有客户端都受益——补丁打在模板上，而不是<br />
逐个客户端去改：</p>
<pre><code class="language-jinja">{#- 1. normalise the OpenAI effort vocabulary onto the template's three levels -#}
{%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}
{%- if resolved_reasoning_effort in ('high', 'max') %}
    {%- set resolved_reasoning_effort = 'xhigh' %}
{%- elif resolved_reasoning_effort in ('none', 'minimal') %}
    {%- set resolved_reasoning_effort = 'low' %}
{%- endif %}

{#- 2. treat the developer role exactly like system -#}
{%- if messages[0].role in ('system', 'developer') %}
{%- if message.role in ("system", "developer") %}
</code></pre>
<p dir="auto">把它写到与原文件<strong>并列</strong>的 <code>chat_template-fixed.jinja</code>（原文件保持不动），<br />
再用 <code>--chat-template</code> 让服务器指向它。</p>
<p dir="auto"><strong>校验点 6</strong> — 完整矩阵必须全部返回 HTTP 200：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>请求</th>
<th>预期</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>role: developer</code></td>
<td>200</td>
</tr>
<tr>
<td><code>reasoning_effort: none</code></td>
<td>200，<strong>0 个 reasoning 字符</strong>（证明禁用路径生效）</td>
</tr>
<tr>
<td><code>reasoning_effort: low / medium / high / max</code></td>
<td>200</td>
</tr>
<tr>
<td><code>chat_template_kwargs: {reasoning_effort: xhigh}</code></td>
<td>200</td>
</tr>
</tbody>
</table>
<hr />
<h2>8. 两套运行档，因为在 ROCm 上它们互斥</h2>
<p dir="auto">只要 speculative decoding 开着，SGLang 就会自动禁用 radix cache：</p>
<pre><code>Speculative decoding for Qwen3_5ForConditionalGeneration is not compatible
with radix cache on ROCm devices. Automatically disabling radix cache.
</code></pre>
<p dir="auto">而 HiCache <em>就是</em> radix cache 的一个扩展 —— 所以 <code>--enable-hierarchical-cache</code><br />
加上 speculation 会在启动时直接中止，报<br />
<code>enable-hierarchical-cache and disable-radix-cache are mutually exclusive</code>。</p>
<p dir="auto">与其跟它较劲，不如把两者都做成具名运行档：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>运行档</th>
<th>Flags</th>
<th>设备池</th>
<th>有效容量</th>
<th>Decode</th>
<th>最适合</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>speed</strong></td>
<td><code>SPEC=1 HICACHE=0 MEMFRAC=0.78 KV=fp8_e4m3 PAGE=64</code></td>
<td>133,791 tok</td>
<td>133,791 tok</td>
<td><strong>43–62 t/s</strong></td>
<td>交互式、写代码</td>
</tr>
<tr>
<td><strong>longctx</strong></td>
<td><code>SPEC=0 HICACHE=1 MEMFRAC=0.90 KV=fp8_e4m3 PAGE=1</code></td>
<td>295,902 tok</td>
<td><strong>887,707 tok</strong>（device + 2× host）</td>
<td>27 t/s</td>
<td>长 agent 会话、大文档</td>
</tr>
</tbody>
</table>
<h3>这些数字背后的实测支撑</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>配置</th>
<th>KV pool</th>
<th>Decode</th>
<th>Prefill @4K</th>
</tr>
</thead>
<tbody>
<tr>
<td>bf16 KV，无 spec</td>
<td>148,271</td>
<td>27.2 t/s</td>
<td>246 t/s</td>
</tr>
<tr>
<td>fp8_e4m3 KV，无 spec</td>
<td><strong>296,542</strong></td>
<td>27.1 t/s</td>
<td>244 t/s</td>
</tr>
<tr>
<td>fp8_e4m3 + NEXTN</td>
<td>133,791</td>
<td><strong>43.0 / 62.0 t/s</strong></td>
<td>247 t/s</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>NEXTN/MTP speculation</strong>：<code>accept_length 3.1</code>、<code>accept_rate 0.70</code>（对<br />
greedy 无损 —— 输出与非 spec 运行逐字节相同）。Decode 之所以能突破<br />
约 34 t/s 的带宽天花板，是因为现在每次权重遍历返回的是约 3 个被接受的<br />
token，而不是 1 个。</li>
<li><strong>代价</strong>：eagle worker 的缓冲区位于 <code>mem-fraction-static</code> <em>之外</em>，<br />
所以 spec 会强制把 pool 压低。带 spec 时可用值是 <code>MEMFRAC=0.78</code> ——<br />
0.90 / 0.85 / 0.80 都会把 draft worker 撑到 OOM（分别差 2.37 GiB / 2.37 GiB / 152 MiB）。<br />
没有 spec 时，0.90 没问题。</li>
<li><strong>HiCache</strong>：<code>--hicache-ratio 2</code> 给出的 host pool 恰好是 <strong>591,805 tokens</strong><br />
（通过 <code>hicache_host_total_tokens</code> 实测），即 887,707 有效容量。代价约<br />
22.6 GB 的 pinned host RAM（19.4 GB KV + 3.2 GB mamba）。已验证可用：<br />
使用后 <code>hicache_host_used_tokens &gt; 0</code>，并且在 4k prompt 上 warm-prefix TTFT<br />
<strong>16.67 s → 0.74 s（22.5×）</strong>。</li>
<li><strong>radix cache 生效时 <code>--page-size 64</code> 会被静默强制成 1</strong><br />
（这个模型在 <code>no_buffer</code> mamba 调度下需要如此）—— 因此两套运行档<br />
用了不同的 page size。</li>
<li>注意：warm-prefix 测试测的是 <em>radix</em> 复用。host pool 自己的职责 ——<br />
把从 device 逐出的条目重新水化 —— 需要把 pool 灌过 296k tokens 才能测，<br />
这一点没有尝试。</li>
</ul>
<h3>内存占用（longctx，实测）</h3>
<pre><code>Device (R9700):  weights 17.95 GB · KV 9.03 GB (K 4.52 + V 4.52) · graph 0.25 GB
                 → 30.50 / 31.86 GiB used  (~1.4 GiB free)
Host RAM:        HiCache KV 19.39 GB + mamba 3.22 GB ≈ 21.1 GiB pinned/shared
</code></pre>
<p dir="auto">不要把 <code>--mem-fraction-static</code> 调得比运行档里的值更高 —— 这点余量是<br />
刻意留得很薄的（仅 device 时 0.90，带 spec 时 0.78）。</p>
<hr />
<h2>9. 自启动与启动顺序</h2>
<h3>9.1 systemd user unit</h3>
<pre><code class="language-ini"># ~/.config/systemd/user/sglang.service
[Unit]
Description=SGLang GPTQ server (R9700)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
# A *user* unit cannot order against the system-level docker.service — wait for it.
ExecStartPre=/bin/bash -c 'for i in $(seq 1 90); do (docker info &gt;/dev/null 2&gt;&amp;1 || sg docker -c "docker info" &gt;/dev/null 2&gt;&amp;1) &amp;&amp; exit 0; sleep 2; done; exit 1'
ExecStart=/bin/bash %h/sglang-sandbox/sglang.sh longctx
# READINESS GATE — see below. Blocks the start job (and every After= dependent)
# until the model actually answers. "-" = degrade to "started anyway" on timeout.
ExecStartPost=-/bin/bash -c 'for i in $(seq 1 100); do curl -sf -o /dev/null --max-time 4 http://127.0.0.1:30001/v1/models &amp;&amp; exit 0; sleep 6; done; exit 1'
Restart=on-failure
RestartSec=30
TimeoutStartSec=1200
TimeoutStopSec=120
KillMode=mixed

[Install]
WantedBy=default.target
</code></pre>
<pre><code class="language-bash">systemctl --user daemon-reload
systemctl --user enable --now sglang.service
</code></pre>
<h3>9.2 为什么就绪门（readiness gate）是必需项，而不是可选项</h3>
<p dir="auto">模型加载要花约 4 分钟。该 unit 是 <code>Type=simple</code>，所以容器一启动 systemd 就把它标为<br />
<em>started</em> —— 远早于端口开始响应。<strong>因此仅凭 <code>After=</code> 并不能阻止客户端与加载过程抢跑。</strong><br />
真正让 <code>After=</code> 变得有意义的是 <code>ExecStartPost</code> 里的探针。（在这台机器上做过实验验证：<br />
一个 <code>Type=simple</code> 的 unit，其服务 15 s 后才开始响应，再配一个带 <code>After=</code>/<code>Wants=</code> 的<br />
依赖单元 → 该依赖单元的 start job 正好耗时 15 s。）</p>
<h3>9.3 每个依赖单元上的顺序</h3>
<p dir="auto">给每个要跟模型通信的 unit 都放一个 drop-in：</p>
<pre><code class="language-ini"># ~/.config/systemd/user/&lt;unit&gt;.service.d/wait-for-sglang.conf
[Unit]
Wants=sglang.service      # not Requires= — a broken model must never block boot
After=sglang.service
</code></pre>
<p dir="auto">需要意识到的后果：<code>Wants=</code> 意味着在模型没起来时启动这样一个 unit，也会<strong>启动</strong><br />
模型并等它就绪。</p>
<blockquote>
<p dir="auto"><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=ec85ec55bc1" class="not-responsive emoji emoji-android emoji--warning" style="height:1.4em;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ <strong><code>%</code> 在 unit 文件里是 systemd specifier。</strong> <code>ExecStart</code> 里的 <code>date +%s</code> 会<br />
静默变成<em>用户的 shell 路径</em>（<code>%s</code> → shell），产出一堆乱东西<br />
（例如一个内容为 <code>/bin/bash</code> 的文件）。请改用 <code>$(( ... ))</code> 算术，或转义为<br />
<code>%%</code>。<strong>刻意使用的 specifier 没问题</strong> —— 上面用的 <code>%h</code>（家目录）就是有意为之。<br />
用 <code>grep -c '%' &lt;unit&gt;</code> 审计。</p>
</blockquote>
<hr />
<h2>10. 接入 OpenAI 兼容客户端</h2>
<p dir="auto">任何 OpenAI 兼容客户端都能用：</p>
<pre><code>base_url   http://127.0.0.1:30001/v1
api_key    any non-empty placeholder (the server ignores auth)
model      any alias — e.g. qwen3.8-27b-gptq-r9700
</code></pre>
<p dir="auto"><strong>上下文长度：</strong> 服务端的每请求上限是 <code>--context-length</code>（这里是 131,072）。<br />
客户端上声明 <strong>128,000</strong>，给输出留出空间。</p>
<blockquote>
<p dir="auto"><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=ec85ec55bc1" class="not-responsive emoji emoji-android emoji--warning" style="height:1.4em;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ <strong>Hermes Agent 特别强制要求最小 64,000 tokens 的上下文窗口</strong><br />
（agent 初始化时的 <code>MINIMUM_CONTEXT_LENGTH</code>）。声明得比这更少，<br />
<em>每一个</em>请求都会失败并报<br />
<code>ValueError: Model X has a context window of N tokens, which is below the minimum 64,000</code><br />
—— 在它抵达服务端之前就失败了。</p>
</blockquote>
<p dir="auto">自定义 provider 配置示例：</p>
<pre><code class="language-yaml">custom_providers:
  - name: sglang
    base_url: http://127.0.0.1:30001/v1
    api_key: 'local-no-auth'
    model: qwen3.8-27b-gptq-r9700
    models:
      qwen3.8-27b-gptq-r9700:
        context_length: 128000     # &gt;= 64000 required by Hermes
        supports_vision: true      # vision tower is resident — verified
</code></pre>
<p dir="auto"><strong>Reasoning effort</strong> —— 打上 §7.2 的模板补丁后，标准 OpenAI<br />
词表就能直接被接受（<code>none|low|medium|high|max</code>；<code>high</code>/<code>max</code> 都<br />
表示模型的最大值 <code>xhigh</code>）。如果客户端支持显式 body 透传，也可以用<br />
<code>chat_template_kwargs: {reasoning_effort: xhigh}</code> 来直接控制模板。</p>
<p dir="auto"><strong>Vision 已验证可用</strong>（一张图片 → 期望的文本）。不需要<br />
<code>mmproj</code> 文件 —— SGLang 用的是 HF checkpoint 里的 vision tower<br />
（<code>enable_multimodal</code> 会自动检测到它）。</p>
<h3>调试客户端集成</h3>
<p dir="auto">客户端侧的 400 是常态；服务端日志往往只记录状态码，不记录<br />
消息体。两个辅助手段：</p>
<ul>
<li><strong>透传日志代理</strong> —— 在另一个端口上跑一个极小的代理，把每个请求体<br />
都 dump 出来，把客户端的 <code>base_url</code> 临时指向它，然后用 curl<br />
重放捕获到的 body 来读真正的错误。§7.2 里的<br />
<code>developer</code> role 和 <code>reasoning_effort</code> 问题就是这样找到的。</li>
<li><strong>Payload 形状探针</strong> —— 先发基线请求，然后一次加一个字段<br />
（<code>role: developer</code>、<code>max_completion_tokens</code> 与 <code>max_tokens</code>、<code>store</code>、<br />
<code>tools</code>、顶层的 effort 与 <code>chat_template_kwargs</code> 里的 effort），并把每个 HTTP 状态<br />
<em>连同它的响应体</em>打印出来。</li>
</ul>
<hr />
<h2>11. 运维</h2>
<pre><code class="language-bash">sglang.sh speed       # NEXTN speculation: 43-62 t/s, 133k pool
sglang.sh longctx     # HiCache: ~888k effective, 27 t/s        &lt;- autostart default
sglang.sh status      # kernels, pool, VRAM/RAM breakdown
sglang.sh stop
</code></pre>
<p dir="auto">运行时健康检查：</p>
<pre><code class="language-bash">curl -s localhost:30001/get_server_info | jq '{
  ctx: .context_length, tokens: .max_total_num_tokens, kv: .kv_cache_dtype }'
curl -s localhost:30001/metrics | grep -E 'hicache_(host_used|host_total)_tokens'
curl -s localhost:30001/metrics | grep -E 'spec_accept|accept_length'   # speed profile
</code></pre>
<p dir="auto">切换运行档需要完整重新加载模型（约 3–4 分钟）。</p>
<hr />
<h2>12. 故障排查对照表</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>修复</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>torch.bfloat16 is not supported for quantization method gptq</code></td>
<td>上游 dtype 限制</td>
<td>§6 补丁；确认它确实被挂载上了</td>
</tr>
<tr>
<td><code>Mismatched type for col0</code>（Triton）</td>
<td>mamba kernel 里混用了 bf16/fp16</td>
<td><code>--dtype</code> 和 <code>--mamba-ssm-dtype</code> 都跑 bf16</td>
</tr>
<tr>
<td>GPTQ loader 在 MTP 张量上炸掉</td>
<td>checkpoint 的 <code>+:.*mtp.*</code> 规则</td>
<td>§3.2</td>
</tr>
<tr>
<td><code>torch.ops.sgl_kernel</code> 为空 / 没有 gptq 符号</td>
<td>预编译 wheel 不带 GPTQ ops</td>
<td>§5 构建并挂载那个 <code>.so</code></td>
</tr>
<tr>
<td>clang-23 <strong>segfault</strong></td>
<td>为 gfx1201 强行开启 <code>wmma-256b-insts</code></td>
<td>永远不要开；去掉 WMMA TU（§5.2）</td>
</tr>
<tr>
<td>链接时出现 Undefined symbol</td>
<td><code>common_extension_rocm.cc</code> 的注册没有被 guard</td>
<td>§5.3 的编辑（4）</td>
</tr>
<tr>
<td>崩溃后容器显示 "Up" 但端口已死</td>
<td>崩掉的 scheduler 占着端口 + GPU</td>
<td>先 <code>docker rm -f</code>（launcher 会做）</td>
</tr>
<tr>
<td>构建在删除 root 所属文件时失败</td>
<td>容器以 root 运行</td>
<td><em>通过</em>容器去删构建目录</td>
</tr>
<tr>
<td><code>enable-hierarchical-cache and disable-radix-cache are mutually exclusive</code></td>
<td>spec 与 HiCache 同时开</td>
<td>只用一套运行档（§8）</td>
</tr>
<tr>
<td>eagle worker 里 spec OOM</td>
<td>draft 缓冲区在 <code>mem-fraction-static</code> 之外</td>
<td><code>MEMFRAC=0.78</code></td>
</tr>
<tr>
<td><code>Unexpected message role.</code> / <code>Unexpected reasoning effort high.</code></td>
<td>原生 chat template</td>
<td>§7.2 模板补丁</td>
</tr>
<tr>
<td><code>Model X has a context window of N tokens, below the minimum 64,000</code></td>
<td>客户端声明 &lt;64k</td>
<td>声明 ≥64k（用 128,000）</td>
</tr>
<tr>
<td>start job 在模型就绪之前就返回</td>
<td><code>Type=simple</code> 且没有门</td>
<td>§9.2 就绪门</td>
</tr>
<tr>
<td>unit 输出里出现 <code>%s</code>/<code>%n</code> 乱码</td>
<td><code>%</code> 是 systemd specifier</td>
<td><code>$(( ))</code> 或 <code>%%</code></td>
</tr>
</tbody>
</table>
<hr />
<h2>附录 A —— 实测参考数字</h2>
<p dir="auto">参考机器：1× R9700（gfx1201，32 GB，31.86 GiB 可用）+ 62 GB RAM，Ubuntu<br />
24.04，<code>rocm/sgl-dev:v0.5.13.post1-ubuntu24.04-py3.14-rocm7.14.1</code>。</p>
<pre><code>load time          12.2 s (weights 17.95 GB, quant=gptq bits=4)
architecture       Qwen3_5ForConditionalGeneration (hybrid: 48 GatedDeltaNet + 16 full attention)
KV, bf16           148,271 tokens
KV, fp8_e4m3       296,542 tokens (device pool at MEMFRAC=0.90)
KV, fp8_e4m3+spec  133,791 tokens (device) at MEMFRAC=0.78
HiCache host pool  591,805 tokens (--hicache-ratio 2), + 3.22 GB mamba pool
effective total    887,707 tokens
decode             27.2 t/s plain · 43.0 t/s prose with NEXTN · 62.0 t/s predictable text
prefill @4K        246 t/s (TTFT ~17 s cold)
accept_length      3.1   accept_rate 0.70
quality            5/5 deterministic prompts, identical with fp8 KV and with spec
warm-prefix TTFT   16.67 s → 0.74 s (22.5×)
</code></pre>
<h2>附录 B —— 复现清单</h2>
<pre><code>[ ] docker present, user in docker group, /dev/kfd + /dev/dri visible
[ ] rocminfo inside the image reports gfx1201
[ ] checkpoint downloaded; config.json MTP rules fixed (§3.2)
[ ] fork cloned at the pinned commit; 5 edits applied across 4 files
[ ] kernel build completes; smoke test 35/35 maxdiff=0 on THIS card
[ ] patched gptq.py in place (bf16 allowed)
[ ] chat_template-fixed.jinja generated; role+effort matrix all 200
[ ] launcher boots: "server is fired up and ready to roll!"
[ ] /v1/models + a chat completion both answer
[ ] systemd unit enabled with the readiness gate; dependents ordered After=
[ ] clients configured: alias, context_length &gt;= 64000, vision if wanted
</code></pre>
<h2>附录 C —— 在 RDNA 上<strong>不</strong>可行的事（死路，附原因）</h2>
<p dir="auto">帮你省下它们耗掉的那几天：</p>
<ul>
<li><strong>Quark / compressed-tensors int4</strong> —— SGLang 只实现了<br />
<code>quark_w4a4_mxfp4</code>、<code>..._moe</code>、<code>quark_w4a8_mxfp4_moe</code>、<code>w8a8_fp8</code>、<br />
<code>w8a8_fp8_moe</code>。<strong>没有 W4A16 方案</strong>；Quark 会抛<br />
<code>NotImplementedError</code>，compressed-tensors 则反量化到 bf16（约 54 GB）→ OOM。<br />
这个模型所有打着 "AWQ" 幌子宣传的 checkpoint 都是 Quark/CT-pack，而不是<br />
AWQ-pack。</li>
<li><strong>MXFP4</strong> —— 仅限 CDNA3/4 + AITER。<strong>RDNA 没有 FP4 硬件。</strong></li>
<li><strong>单张 32 GB 卡上的 FP8 27B</strong> —— 权重 28.70 GiB 对 31.86 GiB 可用，<br />
只剩约 3 GiB 给 KV + workspace。不可行。</li>
<li><strong>跨不匹配 AMD 架构的 <code>--tp-size 2</code></strong>（例如 gfx1100 + gfx1201）—— FP8/4-bit<br />
kernel 逐架构不同，混合架构的 RCCL 很脆弱。</li>
<li><strong>这个 fork 的 WMMA GPTQ 路径在 gfx1201 上</strong> —— clang-23 segfault（§5.2）。</li>
<li><strong>vLLM (ROCm) int4</strong> —— 未继续追的线索。AMD 的文档提到 AWQ 保持 INT4，而一个<br />
ROCm vLLM fork 记录了 <code>VLLM_ROCM_USE_RDNA_W4A16</code>（镜像<br />
<code>vllm/vllm-openai-rocm</code>）。用 Quark checkpoint 时预计会看到<br />
<code>AttributeError: 'dict' object has no attribute 'endswith'</code>。<br />
如果你需要把 prefill 吞吐拿回来，值得一试。</li>
</ul>
]]></description><link>https://lcz.me/topic/1719</link><generator>RSS for Node</generator><lastBuildDate>Fri, 23 Oct 2026 01:44:57 GMT</lastBuildDate><atom:link href="https://lcz.me/topic/1719.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 15 Sep 2026 08:29:12 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Mon, 21 Sep 2026 13:37:33 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/learningai" aria-label="Profile: learningai">@<bdi>learningai</bdi></a><br />
可以給原始連結嗎? 抄作業卡到一半了</p>
]]></description><link>https://lcz.me/post/19797</link><guid isPermaLink="true">https://lcz.me/post/19797</guid><dc:creator><![CDATA[kindalo]]></dc:creator><pubDate>Mon, 21 Sep 2026 13:37:33 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Fri, 18 Sep 2026 12:04:34 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/learningai" aria-label="Profile: LearningAI">@<bdi>LearningAI</bdi></a><br />
记得做好备分</p>
]]></description><link>https://lcz.me/post/19147</link><guid isPermaLink="true">https://lcz.me/post/19147</guid><dc:creator><![CDATA[fcme]]></dc:creator><pubDate>Fri, 18 Sep 2026 12:04:34 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Fri, 18 Sep 2026 07:52:07 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/fcme" aria-label="Profile: fcme">@<bdi>fcme</bdi></a> 谢谢，我又折腾一整天，没成功，再上网看看有没有别的启发信息<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f605.png?v=ec85ec55bc1" class="not-responsive emoji emoji-android emoji--sweat_smile" style="height:1.4em;width:auto;vertical-align:middle" title="😅" alt="😅" /></p>
]]></description><link>https://lcz.me/post/19042</link><guid isPermaLink="true">https://lcz.me/post/19042</guid><dc:creator><![CDATA[LearningAI]]></dc:creator><pubDate>Fri, 18 Sep 2026 07:52:07 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Fri, 18 Sep 2026 01:03:02 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/fcme" aria-label="Profile: fcme">@<bdi>fcme</bdi></a> 同意。wmma 在 RDNA4（gfx1201）上主要是把 prefill 从通用 FP32/BF16 路径切到矩阵核心，prefill 涨 30%+ 合理。</p>
<p dir="auto">补两点，别把它当万能药：</p>
<ul>
<li>R9700 单卡约 640 GB/s，dense 27B 的 decode 基本贴这条线，wmma 几乎不改 tg，只改 pp。用 bench_serving 把 prefill 和 decode 分开测，别用混合 t/s 判断效果。</li>
<li>开 wmma 后 kernel 选择会变，先看 SGLang 日志有没有回落到非融合 matmul，再对一下 mem-fraction-static；RDNA4 的 FP8 和 attention 后端对版本敏感，跑不通就退一个后端对照。</li>
</ul>
<p dir="auto">35B A3B 是激活 3B 的 MoE，prefill 是主瓶颈，wmma 的收益会比 dense 27B 更明显。</p>
]]></description><link>https://lcz.me/post/18962</link><guid isPermaLink="true">https://lcz.me/post/18962</guid><dc:creator><![CDATA[Xiaote]]></dc:creator><pubDate>Fri, 18 Sep 2026 01:03:02 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Thu, 17 Sep 2026 23:28:41 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/learningai" aria-label="Profile: LearningAI">@<bdi>LearningAI</bdi></a><br />
wmma一定要开启, 你让AI研究一下，一般是可以搞定的。 我主要跑35b的那个模型，开启了以后prefill的速度暴涨百分之35~40！ 就很丝滑了</p>
]]></description><link>https://lcz.me/post/18956</link><guid isPermaLink="true">https://lcz.me/post/18956</guid><dc:creator><![CDATA[fcme]]></dc:creator><pubDate>Thu, 17 Sep 2026 23:28:41 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Wed, 16 Sep 2026 08:04:06 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/terry" aria-label="Profile: terry">@<bdi>terry</bdi></a> 啊？ 英文帖子有风险？不明白啊。能解释一下吗？我平常都是用英文跟AI聊的。要是需要，以后发帖就让AI翻译成中文</p>
]]></description><link>https://lcz.me/post/18566</link><guid isPermaLink="true">https://lcz.me/post/18566</guid><dc:creator><![CDATA[LearningAI]]></dc:creator><pubDate>Wed, 16 Sep 2026 08:04:06 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Tue, 15 Sep 2026 19:54:29 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/learningai" aria-label="Profile: LearningAI">@<bdi>LearningAI</bdi></a> 提醒一句：论坛以后请用中文发帖（外文帖有合规风险）。原帖已按原文译成中文、就地替换，<strong>本次警告一次，下不为例</strong>。</p>
]]></description><link>https://lcz.me/post/18453</link><guid isPermaLink="true">https://lcz.me/post/18453</guid><dc:creator><![CDATA[Xiaote]]></dc:creator><pubDate>Tue, 15 Sep 2026 19:54:29 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Tue, 15 Sep 2026 19:43:02 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/learningai" aria-label="Profile: LearningAI">@<bdi>LearningAI</bdi></a> 既然感谢，你还发英语帖子？你让AI整理下，举手之劳，何必让论坛承担风险呢。下不为例。</p>
]]></description><link>https://lcz.me/post/18450</link><guid isPermaLink="true">https://lcz.me/post/18450</guid><dc:creator><![CDATA[terry]]></dc:creator><pubDate>Tue, 15 Sep 2026 19:43:02 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Tue, 15 Sep 2026 12:24:52 GMT]]></title><description><![CDATA[<p dir="auto">恭喜跑動了R9700 32G真的很舒服, 7900 XTX 24GB 也不錯</p>
<p dir="auto">讓 ChatGPT 上網找一下 R9700 跑相關LLM的最佳表現, 然後把帖子丟給 Hermes Deepseek, 應該會有更好的產出</p>
]]></description><link>https://lcz.me/post/18367</link><guid isPermaLink="true">https://lcz.me/post/18367</guid><dc:creator><![CDATA[kos or]]></dc:creator><pubDate>Tue, 15 Sep 2026 12:24:52 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Tue, 15 Sep 2026 11:10:29 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/paul-hou" aria-label="Profile: paul-hou">@<bdi>paul-hou</bdi></a> 今天走通了我也很高兴，赶紧让Hermes把配置和过程给总结下来，跟大家分享一下。具体测试我有空再做，或者其他有r9700的朋友们有空也可以测一下</p>
]]></description><link>https://lcz.me/post/18357</link><guid isPermaLink="true">https://lcz.me/post/18357</guid><dc:creator><![CDATA[LearningAI]]></dc:creator><pubDate>Tue, 15 Sep 2026 11:10:29 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Tue, 15 Sep 2026 11:07:28 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/fcme" aria-label="Profile: fcme">@<bdi>fcme</bdi></a> 好像是因为不支持WMMA影响了prefill 的速度。 但是我搞不定，因为都是让Hermes + Deepseek V4.1 flash抄作业的<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f605.png?v=ec85ec55bc1" class="not-responsive emoji emoji-android emoji--sweat_smile" style="height:1.4em;width:auto;vertical-align:middle" title="😅" alt="😅" /></p>
]]></description><link>https://lcz.me/post/18356</link><guid isPermaLink="true">https://lcz.me/post/18356</guid><dc:creator><![CDATA[LearningAI]]></dc:creator><pubDate>Tue, 15 Sep 2026 11:07:28 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Tue, 15 Sep 2026 10:03:46 GMT]]></title><description><![CDATA[<p dir="auto">pp 二三百在 agent 场景够不够，要看你把成本算在哪：20K 上下文按 250 t/s prefill 约 80s，这是每轮 agent 的真实等待；但 SGLang 的强项不是 raw prefill，而是 radix/HiCache 命中——同一会话后续轮次大部分前缀命中，prefill 会从秒级掉到百毫秒级，多轮 agent 体验和单轮 benchmark 完全两回事。</p>
<p dir="auto">要压 TTFT：① 把 HiCache/radix cache 命中率做上去，pp 就不是瓶颈；② 开 chunked prefill，别让长 prompt 独占；③ 多并发。paul hou 说得对，SGLang 的强项就是 continuous batching——4 并发下总吞吐通常明显高于单流，代价是单流延迟上升，agent 场景反而更适合。</p>
<p dir="auto">R9700 单卡 prefill 二三百属正常水平，别拿数据中心卡的标准要求它。</p>
]]></description><link>https://lcz.me/post/18350</link><guid isPermaLink="true">https://lcz.me/post/18350</guid><dc:creator><![CDATA[Xiaote]]></dc:creator><pubDate>Tue, 15 Sep 2026 10:03:46 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Tue, 15 Sep 2026 09:31:17 GMT]]></title><description><![CDATA[<p dir="auto">SGLang要跑起來不容易，是否可以麻煩測試多併發，這個才是SGLang的強項。</p>
]]></description><link>https://lcz.me/post/18342</link><guid isPermaLink="true">https://lcz.me/post/18342</guid><dc:creator><![CDATA[paul hou]]></dc:creator><pubDate>Tue, 15 Sep 2026 09:31:17 GMT</pubDate></item><item><title><![CDATA[Reply to AI Pro R9700 单卡 SGlang qwen3.8 27b on Tue, 15 Sep 2026 08:45:54 GMT]]></title><description><![CDATA[<p dir="auto">这个pp速度agent场景下能用吗？二三百感觉不太够呢</p>
]]></description><link>https://lcz.me/post/18329</link><guid isPermaLink="true">https://lcz.me/post/18329</guid><dc:creator><![CDATA[fcme]]></dc:creator><pubDate>Tue, 15 Sep 2026 08:45:54 GMT</pubDate></item></channel></rss>