<?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[無事折騰~單張 RX 7900 XTX（gfx1100 / 24GB）上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄]]></title><description><![CDATA[<h1>在單張 RX 7900 XTX（gfx1100 / 24GB）上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄</h1>
<blockquote>
<p dir="auto">目標讀者：手上有<strong>一張</strong> 7900 XTX、看到「SGLang 雙卡爽跑 Qwen3.8-27B」那篇文章、想自己抄一份的人。<br />
這篇把「從讀大神 flyer666 雙卡7900xtx sglang文章 → 從原始碼編 → 單卡跑起來 → 為什麼還是不划算」整段寫清楚，<strong>8 個坑逐一標出來，你照走就不用再踩</strong>。<br />
環境、指令、錯誤訊息全部照實貼，方便對照。</p>
</blockquote>
<hr />
<h2>TL;DR（先講結論，省得你白花一個晚上）</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>你想做的</th>
<th>現實</th>
</tr>
</thead>
<tbody>
<tr>
<td>單卡複製那篇的 <strong>88～116 t/s</strong></td>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/274c.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--x" style="height:23px;width:auto;vertical-align:middle" title="❌" alt="❌" /> <strong>做不到</strong>。那些數字是<strong>雙卡 48GB + MTP-3</strong>。單張 24GB 塞不下「GPTQ 本體 18GB ＋ 獨立的 GPTQ MTP draft 5.5GB ＋ KV cache」</td>
</tr>
<tr>
<td>單卡「關掉 MTP」跑</td>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /> 跑得動，<strong>decode ~35 t/s</strong>，跟 llama.cpp 不開投機解碼同一個檔次</td>
</tr>
<tr>
<td>單卡用 <code>--cpu-offload-gb</code> 硬塞 MTP</td>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /> 能載入、MTP 有啟動，但<strong>每個 forward 要從記憶體串 8GB 權重過 PCIe，GPU 使用率 2%，實測 &lt;4 t/s，等於不能用</strong></td>
</tr>
<tr>
<td>對照組：llama.cpp（Vulkan）+ GGUF + 它自帶的 MTP</td>
<td><strong>程式碼 73 / 散文 33 / 平均 50 t/s</strong>，塞得進 24GB，<code>systemctl restart</code> 15 秒回來</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>一句話</strong>：<strong>單張 7900 XTX 就乖乖用 llama.cpp-HIP / llama.cpp-Vulkan。</strong> SGLang 這個 fork 的價值在雙卡 tensor-parallel + MTP-3，單卡拿不到，還要多顧一堆版本相依。要複製那篇，<strong>先買第二張 7900 XTX</strong>。</p>
<p dir="auto">這篇的價值：如果你之後真的有兩張卡，下面「怎麼編 / 踩過哪些坑」照樣有用（fork 的 build 部分兩張卡也一樣要做）。</p>
<hr />
<h2>這篇對照的原文</h2>
<ul>
<li>文章：<code>https://lcz.me/topic/1532</code>（SGLang 雙 7900 XTX，移植 vLLM kernel + EAGLE MTP-3）</li>
<li>對應 repo：<code>https://github.com/StevenChenSE/sglang</code> 的 <strong><code>gfx1100-support</code></strong> 分支</li>
<li>原文宣稱（<strong>全部是 TP=2 雙卡</strong>）：
<ul>
<li>單併發 decode 97～116 t/s、prefill ~562、TTFT ~0.16s</li>
<li>120k token agent 場景：平均 decode 87.9、最低 66.7（vs vLLM 最低只有 16.9）</li>
<li>4 併發總吞吐 147.5 t/s</li>
</ul>
</li>
</ul>
<hr />
<h2>我的環境（照你自己的對）</h2>
<pre><code>GPU        : AMD Radeon RX 7900 XTX (Navi 31, gfx1100, 24GB) ×1
OS         : Ubuntu 24.04.4 LTS, kernel 7.0.0-28-generic
ROCm       : 7.2.0  (HIP 7.2.26015, amdclang 22.0.0git)   ← 注意 repo README 寫的是 7.14
Python     : 3.12.3
主記憶體    : 128GB（cpu-offload 會用到）
</code></pre>
<blockquote>
<p dir="auto"><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️ <strong>本機同時裝了 NVIDIA CUDA toolkit（<code>nvidia-cuda-dev</code> / <code>libthrust-dev</code> / <code>libcu++-dev</code>）</strong>，因為另一張卡在跑別的東西。<strong>坑 1 就是它引起的</strong>。如果你的機器很乾淨、沒裝過任何 CUDA/thrust 開發套件，坑 1 可能不會遇到。</p>
</blockquote>
<hr />
<h2>全部要改的檔案一覽（先看這張，心裡有底）</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>#</th>
<th>檔案</th>
<th>改什麼</th>
<th>什麼時候需要</th>
</tr>
</thead>
<tbody>
<tr>
<td>P1</td>
<td><code>python/sglang/kernels/aot/setup_rocm.py</code></td>
<td>加 <code>-isystem /opt/rocm-7.2.0/include</code></td>
<td>機器裝過 CUDA/thrust 開發套件時</td>
</tr>
<tr>
<td>P2</td>
<td><code>setup_rocm.py</code> + <code>csrc/common_extension_rocm.cc</code></td>
<td>拿掉 <code>moe_q_gemm_rdna3.cu</code>、用巨集擋它的註冊</td>
<td>一定（fork 這檔有 bug，且 dense 模型用不到）</td>
</tr>
<tr>
<td>P3</td>
<td>模型的 <code>config.json</code></td>
<td><code>+:.*mtp.*</code> → <code>-:.*mtp.*</code> bits 16</td>
<td>一定（README 也有寫）</td>
</tr>
<tr>
<td>P4</td>
<td><code>python/sglang/srt/layers/layernorm.py</code></td>
<td>gemma weight loader 加 device 對齊</td>
<td>只有你要用 <code>--cpu-offload-gb</code></td>
</tr>
<tr>
<td>P5</td>
<td><code>python/sglang/srt/utils/offloader.py</code></td>
<td>2 處 <code>functional_call(..., tie_weights=False)</code></td>
<td>只有你要用 <code>--cpu-offload-gb</code></td>
</tr>
</tbody>
</table>
<hr />
<h2>Step 0：clone</h2>
<pre><code class="language-bash">cd ~/src
git clone --branch gfx1100-support --single-branch --depth 1 \
  https://github.com/StevenChenSE/sglang.git sglang-gfx1100
</code></pre>
<hr />
<h2>Step 1：建 venv、裝 PyTorch（ROCm 版）</h2>
<pre><code class="language-bash"># 用 uv 比較快，pip 也行
uv venv --python 3.12 ~/venvs/sglang-rocm
source ~/venvs/sglang-rocm/bin/activate

uv pip install "torch==2.11.0+rocm7.2" "pytorch-triton-rocm" \
  --index-url https://download.pytorch.org/whl/rocm7.2 \
  --index-strategy unsafe-best-match
</code></pre>
<blockquote>
<p dir="auto"><code>torch-2.12.0+rocm7.2</code> 也在同一個索引、也可用。README 寫「PyTorch 2.11.0+git」，抓 2.11.0+rocm7.2 最貼。</p>
</blockquote>
<p dir="auto"><strong>驗證（每次動完 pip 都要跑一下這個）：</strong></p>
<pre><code class="language-bash">python -c "import torch; print(torch.__version__, torch.version.hip, torch.cuda.is_available(), torch.cuda.get_device_properties(0).gcnArchName)"
# 期望： 2.11.0+rocm7.2 7.2.26015 True gfx1100
</code></pre>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 坑 1（PyTorch 被 CUDA 版覆蓋）</h3>
<p dir="auto"><strong>症狀</strong>：後面你裝別的套件（尤其 <code>torchvision</code> / <code>torchaudio</code> / 任何沒 pin 的東西），pip/uv 的相依求解會<strong>默默把 <code>torch</code> 換成 <code>torch-2.14.0+cu130</code>（CUDA 版）</strong>。之後 <code>torch.version.hip</code> 變 <code>None</code>、<code>torch.cuda.get_device_properties(0).name</code> 印出你的 NVIDIA 卡。編好的 <code>.so</code> 是對著 ROCm torch 連結的，torch 一換就全爛。</p>
<p dir="auto"><strong>防呆</strong>：</p>
<ol>
<li><code>torch</code> / <code>torchvision</code> / <code>torchaudio</code> <strong>一律從 <code>.../whl/rocm7.2</code> 這個索引裝、而且 pin 版本</strong>：<pre><code class="language-bash">uv pip install "torch==2.11.0+rocm7.2" "torchvision==0.26.0+rocm7.2" "pytorch-triton-rocm" \
  --index-url https://download.pytorch.org/whl/rocm7.2 --index-strategy unsafe-best-match
</code></pre>
</li>
<li>之後<strong>每做完一批 pip 安裝，就跑一次上面那個 <code>import torch</code> 驗證</strong>。被換掉就照上面重裝一次（uv 會把 CUDA 版移除）。</li>
</ol>
<hr />
<h2>Step 2：編 AOT HIP kernel（<code>sgl_kernel</code>）—— 成敗關鍵</h2>
<pre><code class="language-bash">source ~/venvs/sglang-rocm/bin/activate
uv pip install numpy setuptools wheel ninja "scikit-build-core&gt;=0.10" packaging

cd ~/src/sglang-gfx1100/python/sglang/kernels/aot
</code></pre>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 坑 2（rocThrust 被系統的 CUDA thrust 蓋掉）</h3>
<p dir="auto"><strong>症狀</strong>：一開編就爆 300+ 個錯，長這樣：</p>
<pre><code>/usr/include/vector_types.h:184:30: error: definition of type 'int2' conflicts with type alias of the same name
  184 | __cuda_builtin_vector_align8(int2, int x; int y;);
/opt/rocm-7.2.0/.../amd_hip_vector_types.h:811:1: note: 'int2' declared here
</code></pre>
<p dir="auto">往回追 include 鏈會看到：</p>
<pre><code>torch/headeronly/util/complex.h:9  →  #include &lt;thrust/complex.h&gt;
   ↓ 竟然解析到
/usr/include/thrust/complex.h        ← 這是 libthrust-dev（CUDA 味）的，不是 ROCm 的
</code></pre>
<p dir="auto"><strong>根因</strong>：clang 的 include 搜尋順序裡，<code>/usr/include</code> 排在 <code>/opt/rocm-7.2.0/include</code> <strong>前面</strong>（因為 rocm 的 include 目錄剛好也是 clang 內建系統路徑之一，你手動 <code>-I</code> 它會被 clang 判定重複而丟掉）。系統上 <code>libthrust-dev</code> + <code>nvidia-cuda-dev</code> 在 <code>/usr/include/thrust</code> 放了 CUDA 版 thrust，就贏了。</p>
<p dir="auto"><strong>修法（P1）</strong>：<code>setup_rocm.py</code> 裡把 <code>-isystem /opt/rocm-7.2.0/include</code> <strong>塞到編譯 flag 最前面</strong>。<code>-isystem</code> 會排在 <code>/usr/include</code> 之前，rocThrust 就勝出。</p>
<pre><code class="language-python"># setup_rocm.py

# 原本
cxx_flags = ["-O3"]
# 改成
cxx_flags = ["-isystem", "/opt/rocm-7.2.0/include", "-O3"]

# 原本
hipcc_flags = [
    "-DNDEBUG",
    ...
# 改成
hipcc_flags = [
    "-isystem",
    "/opt/rocm-7.2.0/include",
    "-DNDEBUG",
    ...
</code></pre>
<blockquote>
<p dir="auto">驗證這招有效的最小重現：</p>
<pre><code class="language-bash">echo '#include &lt;thrust/complex.h&gt;' &gt; /tmp/t.hip
/opt/rocm-7.2.0/lib/llvm/bin/clang++ -x hip --offload-arch=gfx1100 -isystem /opt/rocm-7.2.0/include -H -E /tmp/t.hip 2&gt;&amp;1 | grep 'thrust/complex.h'
# 要看到解析到 /opt/rocm-7.2.0/include/thrust/complex.h 才對
</code></pre>
<p dir="auto">如果你的機器<strong>沒裝過</strong> <code>libthrust-dev</code> / <code>nvidia-cuda-dev</code> / <code>libcu++-dev</code>，這坑不會出現，P1 可略。或者你也可以 <code>sudo apt remove libthrust-dev libcub-dev</code>（<code>nvidia-cuda-dev</code> 會被連帶移除，自己評估）。</p>
</blockquote>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 坑 3（<code>moe_q_gemm_rdna3.hip</code> 本身有 bug）</h3>
<p dir="auto"><strong>症狀</strong>：P1 修完剩 8 個錯，全在同一個檔：</p>
<pre><code>csrc/gemm/gptq/moe_q_gemm_rdna3.hip:292:68: error: non-const lvalue reference to type 'half2[2]'
  (aka '__half2[2]') cannot bind to a value of unrelated type 'half2' (aka '__half2')
qdq_4_rdna3.cuh:92:61: note: passing argument to parameter 'z1z16' here
</code></pre>
<p dir="auto"><code>dequant_4bit_8_fp16(...)</code> 的參數要 <code>half2 (&amp;)[2]</code>，caller 傳的是單個 <code>half2</code> —— fork 的 MoE 版寫錯了。</p>
<p dir="auto"><strong>修法（P2）</strong>：Qwen3.8-27B 是 <strong>dense</strong>，根本不會呼叫 MoE routing GEMM。把這個 source 拿掉、順便擋掉它在 extension 的註冊（不然 link 會缺符號）。</p>
<p dir="auto"><code>setup_rocm.py</code>，<code>sources</code> 清單裡：</p>
<pre><code class="language-python">    "csrc/gemm/gptq/q_gemm_rdna3.cu",
    "csrc/gemm/gptq/q_gemm_rdna3_wmma.cu",
    # "csrc/gemm/gptq/moe_q_gemm_rdna3.cu",   ← 註解掉
</code></pre>
<p dir="auto"><code>setup_rocm.py</code>，<code>is_rdna</code> 那段之後加一個編譯巨集：</p>
<pre><code class="language-python">if is_rdna:
    hipcc_flags.append("-DSGL_IS_RDNA")
    cxx_flags.append("-DSGL_IS_RDNA")

# 加這兩行
hipcc_flags.append("-DSGL_SKIP_MOE_GPTQ_RDNA3")
cxx_flags.append("-DSGL_SKIP_MOE_GPTQ_RDNA3")
</code></pre>
<p dir="auto"><code>csrc/common_extension_rocm.cc</code>，把 <code>moe_gptq_gemm_rdna3</code> 的 <strong>extern 宣告</strong> 和 <strong><code>m.def</code> + <code>m.impl</code></strong> 各自用 <code>#ifndef</code> 包起來：</p>
<pre><code class="language-cpp">#ifndef SGL_SKIP_MOE_GPTQ_RDNA3
  extern void moe_gptq_gemm_rdna3(torch::Tensor a, torch::Tensor c,
                                  ... 
                                  int64_t output_topk);
#endif
</code></pre>
<pre><code class="language-cpp">#ifndef SGL_SKIP_MOE_GPTQ_RDNA3
  m.def(
      "moe_gptq_gemm_rdna3(Tensor a, Tensor! c, ... int output_topk) -&gt; ()");
  m.impl("moe_gptq_gemm_rdna3", torch::kCUDA, &amp;moe_gptq_gemm_rdna3);
#endif
}
</code></pre>
<h3>開編</h3>
<pre><code class="language-bash">source ~/venvs/sglang-rocm/bin/activate
rm -rf build
AMDGPU_TARGET=gfx1100 PYTORCH_ROCM_ARCH=gfx1100 HIP_VISIBLE_DEVICES=0 MAX_JOBS=32 \
  ROCM_HOME=/opt/rocm-7.2.0 ROCM_PATH=/opt/rocm-7.2.0 \
  python setup_rocm.py build_ext --inplace
</code></pre>
<p dir="auto"><code>setup_rocm.py</code> 會自動從 <code>torch.cuda.get_device_properties(0).gcnArchName</code> 抓到 <code>gfx1100</code>（白名單裡本來就有 gfx1100，社群講的「要 patch 白名單」在這個 fork 不必），RDNA 分支會自動不編 CDNA 專用的 all-reduce。</p>
<p dir="auto">成功長這樣：</p>
<pre><code>[19/19] ...
creating build/lib.linux-x86_64-cpython-312/sgl_kernel
x86_64-linux-gnu-g++ ... -o build/.../sgl_kernel/common_ops.cpython-312-x86_64-linux-gnu.so
copying build/.../common_ops.cpython-312-x86_64-linux-gnu.so -&gt; python/sgl_kernel
</code></pre>
<p dir="auto">把 <code>.so</code> 放進 site-packages：</p>
<pre><code class="language-bash">SP=~/venvs/sglang-rocm/lib/python3.12/site-packages
mkdir -p $SP/sgl_kernel
cp -r python/sgl_kernel/. $SP/sgl_kernel/
python -c "import torch, sgl_kernel; print('sgl_kernel OK')"
</code></pre>
<hr />
<h2>Step 3：裝 sglang 本體（editable）+ 一堆 runtime 相依</h2>
<pre><code class="language-bash">cd ~/src/sglang-gfx1100/python
uv pip install --no-build-isolation --no-deps -e .
</code></pre>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 坑 4（<code>--no-deps</code> 是必須的，且 editable 會「消失」）</h3>
<ul>
<li><strong>必須 <code>--no-deps</code></strong>：<code>pyproject.toml</code> 的相依清單整串是 CUDA 專用（<code>cuda-python&gt;=13</code>、<code>flash-attn-4</code>、<code>flashinfer_python[cu13]</code>、<code>quack-kernels</code>、<code>tilelang</code>…），照裝會把 CUDA torch 拉回來、或直接裝不起來。</li>
<li><strong>editable 安裝會被後續的 <code>uv pip install</code> 悄悄移除</strong>：我遇到兩次「<code>pip install -e .</code> 成功 → 裝別的東西 → <code>import sglang</code> 又 No module named 'sglang'」。<br />
<strong>對策</strong>：① editable 用 <code>uv pip install ... -e .</code>（跟後面的 uv 操作同一個工具，比較不會打架）；② 每裝完一批相依，重跑 <code>python -c "import sglang"</code> 確認，掉了就再 <code>uv pip install --no-build-isolation --no-deps -e .</code> 一次。</li>
</ul>
<h3>手動補相依（照 import 錯誤一個個補）</h3>
<p dir="auto">先裝這批（都是 ROCm 安全的、不會拉 CUDA torch）：</p>
<pre><code class="language-bash">uv pip install \
  orjson transformers tokenizers safetensors huggingface_hub hf_transfer \
  fastapi "uvicorn[standard]" uvloop python-multipart requests aiohttp \
  pyzmq msgspec psutil setproctitle scipy pillow pydantic packaging \
  interegular llguidance einops sentencepiece tiktoken partial_json_parser \
  pybase64 blobfile compressed-tensors prometheus-client cloudpickle py-cpuinfo \
  anthropic openai ipython outlines datasets modelscope numba

# torchvision 一定要從 rocm 索引 pin，不然又把 CUDA torch 拉回來（坑 1 的變體）
uv pip install "torchvision==0.26.0+rocm7.2" \
  --index-url https://download.pytorch.org/whl/rocm7.2 --index-strategy unsafe-best-match
</code></pre>
<p dir="auto">然後迴圈補剩下的：</p>
<pre><code class="language-bash">cd ~/src/sglang-gfx1100/python
while true; do
  ERR=$(python -c "from sglang.srt.entrypoints.http_server import launch_server" 2&gt;&amp;1 | grep ModuleNotFoundError | tail -1)
  [ -z "$ERR" ] &amp;&amp; { echo "OK"; break; }
  MOD=$(echo "$ERR" | grep -oE "named '[^']+'" | tr -d "named '" | cut -d. -f1)
  case "$MOD" in
    tvm_ffi) PKG="apache-tvm-ffi==0.1.11";;   # ← 坑：import 名是 tvm_ffi，套件名不同
    *)       PKG="$MOD";;
  esac
  echo "缺 $MOD → 裝 $PKG"
  uv pip install --no-deps "$PKG"
done
</code></pre>
<p dir="auto">我這輪最後補進去的：<code>gguf</code>、<code>xgrammar</code>、<code>apache-tvm-ffi==0.1.11</code>（imported as <code>tvm_ffi</code>）、<code>soundfile</code>。</p>
<p dir="auto">驗證整條：</p>
<pre><code class="language-bash">python -c "
import torch; print('torch', torch.__version__, torch.version.hip, torch.cuda.get_device_properties(0).gcnArchName)
import sgl_kernel; print('sgl_kernel OK')
import sglang; print('sglang OK')
from sglang.srt.entrypoints.http_server import launch_server; print('launch_server OK')
"
python -m sglang.launch_server --help | head -3
</code></pre>
<hr />
<h2>Step 4：下模型 + patch <code>config.json</code></h2>
<pre><code class="language-bash">hf download Vishva007/Qwen3.8-27B-W4A16-AutoRound-GPTQ \
  --local-dir ~/models/qwen3.8-27b-mtp-fixed
</code></pre>
<p dir="auto">（W4A16 GPTQ，本體 5 個 shard ≈ 19GB＋<code>model_extra_tensors.safetensors</code> 849MB 放 MTP 張量。硬碟上約 19GB。）</p>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 坑 3.5 → P3（GPTQ loader 想量化 MTP 層 → 載入失敗）</h3>
<p dir="auto"><strong>症狀</strong>（不 patch 的話，載模型階段就爆）：loader 相信 <code>config.json</code> 裡 <code>quantization_config.dynamic</code> 的<br />
<code>"+:.*mtp.*"</code> / <code>"+:.*mtp\.fc.*"</code> 正向規則（bits 4、group 64），拿 4-bit 參數去載 MTP 層，但那些張量其實是 BF16 → 掛。</p>
<p dir="auto"><strong>修法（P3，README 也有寫）</strong>：</p>
<pre><code class="language-bash">cd ~/models/qwen3.8-27b-mtp-fixed
cp config.json config.json.orig
python3 - &lt;&lt;'EOF'
import json
c=json.load(open("config.json"))
dyn=c["quantization_config"]["dynamic"]
dyn.pop("+:.*mtp.*", None)
dyn.pop("+:.*mtp\\.fc.*", None)
dyn["-:.*mtp.*"] = {"bits": 16, "group_size": 128}   # 明確排除 MTP 層量化
json.dump(c, open("config.json","w"), indent=2, ensure_ascii=False)
print("patched")
EOF
</code></pre>
<hr />
<h2>Step 5：啟動（單卡）</h2>
<blockquote>
<p dir="auto">這個 fork 是<strong>雙卡專案</strong>，README 的範例是 <code>--tp-size 2 --context-length 196608 --mem-fraction-static 0.91</code>（給 2×24GB）。單卡要自己砍。</p>
</blockquote>
<h3>5a. 先試「照抄 + <code>--tp-size 1</code> + 帶 EAGLE MTP」→ <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/274c.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--x" style="height:23px;width:auto;vertical-align:middle" title="❌" alt="❌" /> OOM</h3>
<pre><code> Load weight end. type=Qwen3_5ForConditionalGeneration, quant=gptq, bits=4, mem usage=18.18 GB.
[...] Load weight end. type=Qwen3_5ForCausalLMMTP,          quant=gptq, bits=4, mem usage=5.53 GB.
[...] ValueError: Loaded weights leave no GPU memory for the KV cache under --mem-fraction-static=0.9.
      Raise --mem-fraction-static above 0.997 (minimum viable = 0.9961).
</code></pre>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 坑 6（單卡塞不下 target + MTP draft + KV）</h3>
<ul>
<li>GPTQ 本體 <strong>18.18GB</strong></li>
<li><strong>EAGLE 的 draft 是「整個 <code>Qwen3_5ForCausalLMMTP</code> 當獨立 GPTQ 模型載」= 5.53GB</strong>（不是 llama.cpp 那種塞在 GGUF 裡的小 head）</li>
<li>18.18 + 5.53 = 23.7GB，24GB 卡連 KV 都放不下</li>
</ul>
<p dir="auto">雙卡（48GB）就沒事 —— 這就是為什麼原文是雙卡。</p>
<h3>5b. 「關掉 MTP」→ <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /> 跑得動，~35 t/s</h3>
<p dir="auto">啟動腳本（存成 <code>run_sglang_singlecard.sh</code>）：</p>
<pre><code class="language-bash">#!/bin/bash
source ~/venvs/sglang-rocm/bin/activate
export HIP_VISIBLE_DEVICES=0
export SGL_DTYPE=bfloat16
export SGL_RDNA_VLLM_VERIFY=1
export SGL_RDNA_NO_FUSED=1
export SGL_RDNA_GEMMA_TRITON=1
export ROCM_HOME=/opt/rocm-7.2.0
export ROCM_PATH=/opt/rocm-7.2.0
export TOKENIZERS_PARALLELISM=false
exec python -m sglang.launch_server \
  --model-path ~/models/qwen3.8-27b-mtp-fixed \
  --host 0.0.0.0 --port 8080 \
  --served-model-name qwen3.8-27b \
  --tp-size 1 \
  --quantization gptq \
  --dtype bfloat16 \
  --mamba-ssm-dtype bfloat16 \
  --kv-cache-dtype auto \
  --attention-backend triton \
  --context-length 16384 \
  --mem-fraction-static 0.93 \
  --max-running-requests 2 \
  --max-mamba-cache-size 16 \
  --triton-attention-num-kv-splits 16 \
  --trust-remote-code
</code></pre>
<p dir="auto">成功關鍵行：</p>
<pre><code>Load weight end. type=Qwen3_5ForConditionalGeneration, quant=gptq, bits=4, mem usage=18.18 GB.
Mamba Cache is allocated. ssm_state size: 1.20GB
KV Cache is allocated. dtype: torch.bfloat16, #tokens: 42880, K size: 1.31 GB, V size: 1.31 GB
rdna_unified_verify ACTIVE (decode path)
Linear attention kernel backend: decode=triton, prefill=triton, verify=triton
Uvicorn running on http://0.0.0.0:8080
</code></pre>
<p dir="auto"><strong>實測（同機、暖機後）：</strong></p>
<pre><code>程式碼   decode ≈ 35.1 t/s
散文 x2  decode ≈ 35.3 t/s
Rust     decode ≈ 35.3 t/s
→ 平均 35.3 t/s（非常穩，35.1～35.4）
第一次請求（暖機）約 38 秒（graph / 編譯）
</code></pre>
<blockquote>
<p dir="auto">這個數字 = 純 GPTQ dense forward，跟 llama.cpp 不開投機解碼差不多。沒有優勢。</p>
</blockquote>
<h3>5c. 想單卡也吃 MTP：<code>--cpu-offload-gb</code> 硬塞 → 又踩 2 個坑，最後「能跑但太慢」</h3>
<p dir="auto">概念：把一部分 target 權重丟主機 RAM（<code>--cpu-offload-gb 8</code>），空出顯存給 5.5GB 的 MTP draft。</p>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 坑 7（<code>--cpu-offload-gb</code> + gemma layernorm，裝置不一致）</h3>
<pre><code>File ".../sglang/srt/layers/layernorm.py", line 1122, in _weight_loader
    torch.add(param.data, 1.0, out=self.gemma_weight)
RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!
</code></pre>
<p dir="auto">offload 把 <code>param</code> 放到 CPU，但 <code>self.gemma_weight</code> buffer 在 GPU。</p>
<p dir="auto"><strong>修法（P4）</strong> <code>python/sglang/srt/layers/layernorm.py</code> 的 <code>_weight_loader</code>：</p>
<pre><code class="language-python">    def _weight_loader(self, param, loaded_weight):
        assert param.size() == loaded_weight.size()
        param.data.copy_(loaded_weight)
        # --cpu-offload-gb 會讓 param 在 CPU、gemma_weight 在 GPU
        if self.gemma_weight.device != param.data.device:
            self.gemma_weight = self.gemma_weight.to(param.data.device)
        torch.add(param.data, 1.0, out=self.gemma_weight)
</code></pre>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f534.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--red_circle" style="height:23px;width:auto;vertical-align:middle" title="🔴" alt="🔴" /> 坑 8（<code>--cpu-offload-gb</code> + Qwen3.8 hybrid GDN，tied 權重）</h3>
<pre><code>File ".../sglang/srt/utils/offloader.py", line 148, in forward
    output = functional_call(module, device_state, args=args, kwargs=kwargs)
ValueError: functional_call got multiple values for keys
  ['linear_attn.A_log', 'linear_attn.attn.A_log'], which are tied.
  Consider using tie_weights=False
</code></pre>
<p dir="auto">Qwen3.8 的 Mamba/GDN 層那個 <code>A_log</code> 在 state_dict 裡以兩個名字出現（tied），offloader 的 <code>functional_call</code> 不吃。</p>
<p dir="auto"><strong>修法（P5）</strong> <code>python/sglang/srt/utils/offloader.py</code>，<strong>兩處</strong> <code>functional_call(...)</code> 都加 <code>tie_weights=False</code>：</p>
<pre><code class="language-python"># line ~148
output = functional_call(module, device_state, args=args, kwargs=kwargs, tie_weights=False)

# line ~269
output = functional_call(
    module, get_parameter_and_buffer_dicts(), args=args, kwargs=kwargs, tie_weights=False,
)
</code></pre>
<h3>5c 啟動腳本（帶 MTP + offload）</h3>
<p dir="auto">在 5b 的腳本上加/改：</p>
<pre><code class="language-bash">  --context-length 8192 \
  --mem-fraction-static 0.95 \
  --cpu-offload-gb 8 \
  --max-running-requests 1 \
  --max-mamba-cache-size 8 \
  --speculative-algorithm EAGLE \
  --speculative-draft-model-path ~/models/qwen3.8-27b-mtp-fixed \
  --speculative-num-steps 3 \
  --speculative-eagle-topk 1 \
  --speculative-num-draft-tokens 4 \
  --speculative-draft-kv-cache-dtype fp8_e4m3 \
  --cuda-graph-bs-decode 1 \
</code></pre>
<p dir="auto"><strong>這次真的載入成功了</strong>：</p>
<pre><code>Load weight end. type=Qwen3_5ForConditionalGeneration ... mem usage=10.05 GB.   ← 8GB offload 到 RAM
Load weight end. type=Qwen3_5ForCausalLMMTP           ... mem usage=4.84 GB.
KV Cache (target) bf16: 3.01 + 3.01 GB ; (draft) fp8: 0.09 + 0.09 GB
rdna_unified_verify ACTIVE (verify path)
rdna_unified_verify ACTIVE (decode path)
Uvicorn running on http://0.0.0.0:8080
</code></pre>
<p dir="auto"><strong>但實測</strong>：10-token 的暖機請求跑 3 分鐘沒回來；<code>rocm-smi</code> 看 GPU 使用率 <strong>2%</strong>；SGLang 的 <code>Decode batch</code> log 一行都沒印。原因很直接：<strong>每個 forward 都要把 8GB 的 GPTQ 權重從 RAM 串過 PCIe Gen4 x16（~31GB/s），光傳輸就 ~0.25s/token → &lt;4 t/s，GPU 全程在等</strong>。</p>
<p dir="auto">→ <strong>能跑 ≠ 能用。單卡 + offload 的 MTP 沒有意義。</strong></p>
<hr />
<h2>對照表（同一台機、同一顆 Qwen3.8-27B）</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>方案</th>
<th>decode（程式碼 / 散文）</th>
<th>塞得進 24GB？</th>
<th>備註</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SGLang 單卡，無 MTP</strong></td>
<td>~35 / ~35 t/s</td>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></td>
<td>= 純 GPTQ forward</td>
</tr>
<tr>
<td><strong>SGLang 單卡，MTP + <code>--cpu-offload-gb 8</code></strong></td>
<td><strong>&lt;4 t/s</strong></td>
<td>靠 offload</td>
<td>PCIe 串權重把速度打死</td>
</tr>
<tr>
<td>SGLang <strong>雙卡</strong> MTP-3（原文，非本機實測）</td>
<td>97～116 t/s</td>
<td>需 48GB</td>
<td>這才是那篇的數字</td>
</tr>
<tr>
<td><strong>llama.cpp（Vulkan）+ GGUF + 自帶 MTP</strong></td>
<td><strong>73 / 33 t/s（平均 50）</strong></td>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></td>
<td>prefill 6.4K=706 / 25K=607 t/s</td>
</tr>
<tr>
<td>llama.cpp 無 MTP</td>
<td>~35 t/s</td>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></td>
<td>跟 SGLang 無 MTP 一樣</td>
</tr>
</tbody>
</table>
<p dir="auto">llama.cpp 的 MTP head 是塞在 GGUF 裡的小東西（不是獨立 5.5GB 模型），所以<strong>單張 24GB 就能開 MTP</strong>，這是它單卡贏的關鍵。</p>
<hr />
<h2>已知坑速查（照這個順序就不會卡）</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>#</th>
<th>症狀關鍵字</th>
<th>解法</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>裝完 torchvision 後 <code>torch.version.hip</code> 變 <code>None</code> / 印出 NVIDIA 卡</td>
<td>torch/torchvision/torchaudio 一律 pin 版本從 <code>whl/rocm7.2</code> 索引裝；每次 pip 後驗證</td>
</tr>
<tr>
<td>2</td>
<td><code>error: definition of type 'int2' conflicts</code> / include 鏈跑到 <code>/usr/include/thrust</code></td>
<td><code>setup_rocm.py</code> 加 <code>-isystem /opt/rocm-7.2.0/include</code>（或移除 <code>libthrust-dev</code>）</td>
</tr>
<tr>
<td>3</td>
<td><code>moe_q_gemm_rdna3.hip:...: non-const lvalue reference to type 'half2[2]'</code></td>
<td>從 sources 拿掉該檔 + <code>-DSGL_SKIP_MOE_GPTQ_RDNA3</code> 擋註冊（dense 模型不用 MoE）</td>
</tr>
<tr>
<td>4</td>
<td><code>import sglang</code> → <code>No module named 'sglang'</code>（明明剛裝過）</td>
<td>editable 用 <code>uv pip install --no-build-isolation --no-deps -e .</code>；每次裝完相依重驗</td>
</tr>
<tr>
<td>5</td>
<td><code>No module named 'tvm_ffi'</code> 裝不起來</td>
<td>套件名是 <code>apache-tvm-ffi==0.1.11</code></td>
</tr>
<tr>
<td>6</td>
<td><code>ValueError: Loaded weights leave no GPU memory for the KV cache</code></td>
<td>單卡塞不下 target(18G)+MTP draft(5.5G)+KV → 關 MTP，或 <code>--cpu-offload-gb</code>（見坑 7/8，但會很慢）</td>
</tr>
<tr>
<td>7</td>
<td><code>RuntimeError: Expected all tensors to be on the same device</code> in <code>layernorm.py</code> <code>_weight_loader</code></td>
<td>P4：gemma weight loader 加 device 對齊（只有用 <code>--cpu-offload-gb</code> 才會遇到）</td>
</tr>
<tr>
<td>8</td>
<td><code>ValueError: functional_call got multiple values for keys ['linear_attn.A_log'...] which are tied</code></td>
<td>P5：<code>offloader.py</code> 兩處 <code>functional_call(..., tie_weights=False)</code></td>
</tr>
<tr>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️</td>
<td>—</td>
<td><strong>千萬不要對 gfx1100 下 <code>rocm-smi --gpureset</code></strong>（會鎖死 PCIe root complex，只能硬關機）—— README 自己的警告</td>
</tr>
<tr>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" />️</td>
<td>閒置時 2 個 CPU 核心 100%</td>
<td>ROCm KFD event-age busy-wait bug；<code>--sleep-on-idle</code> 或編 <code>scripts/rdna_ar/kfd_event_age_fix.c</code> 做 <code>LD_PRELOAD</code></td>
</tr>
<tr>
<td><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4a1.png?v=301515bb865" class="not-responsive emoji emoji-android emoji--bulb" style="height:23px;width:auto;vertical-align:middle" title="💡" alt="💡" /></td>
<td>log 一直說 "model can run with gptq_marlin ... faster"</td>
<td>marlin 是 CUDA kernel，RDNA3 維持 <code>--quantization gptq</code>（用 fork 的 <code>q_gemm_rdna3</code>）</td>
</tr>
</tbody>
</table>
<hr />
<h2>我的建議</h2>
<ul>
<li><strong>只有一張 7900 XTX</strong> → 用 <strong>llama.cpp</strong>（HIP 或 Vulkan）+ GGUF，開它自帶的 MTP。單卡場景它就是最佳解，<code>systemctl restart</code> 15 秒回來，不用顧 ROCm/torch 版本相依地獄。</li>
<li><strong>有兩張 7900 XTX</strong> → 這個 fork 才有意義。照 README 原始的 <code>--tp-size 2</code> 路徑跑（上面 P1～P3 的 build patch 兩張卡也要；P4/P5 是單卡 offload 專用，雙卡用不到）。那時 target+draft+KV 全塞進 48GB，就能拿到原文的 88～116 t/s。</li>
<li><strong>上游 SGLang 目前（2026-09）還沒有官方 consumer RDNA3 支援</strong>（追蹤在 sgl-project/sglang issue #30599）。這個社群 fork 是目前唯一把 <code>q_gemm_rdna3</code> GPTQ kernel 補上的。</li>
</ul>
<hr />
<p dir="auto"><em>寫於 2026-09-08。環境 ROCm 7.2.0 / torch 2.11.0+rocm7.2 / SGLang fork <code>StevenChenSE/sglang@gfx1100-support</code>（該日最新 commit）。</em></p>
]]></description><link>https://lcz.me/topic/1567</link><generator>RSS for Node</generator><lastBuildDate>Wed, 09 Sep 2026 22:54:23 GMT</lastBuildDate><atom:link href="https://lcz.me/topic/1567.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 08 Sep 2026 15:29:09 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to 無事折騰~單張 RX 7900 XTX（gfx1100 / 24GB）上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄 on Wed, 09 Sep 2026 06:46:01 GMT]]></title><description><![CDATA[<p dir="auto">谢谢，总结的很好，到时候也能抄作业</p>
]]></description><link>https://lcz.me/post/16847</link><guid isPermaLink="true">https://lcz.me/post/16847</guid><dc:creator><![CDATA[懒人烘培]]></dc:creator><pubDate>Wed, 09 Sep 2026 06:46:01 GMT</pubDate></item><item><title><![CDATA[Reply to 無事折騰~單張 RX 7900 XTX（gfx1100 / 24GB）上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄 on Wed, 09 Sep 2026 06:05:19 GMT]]></title><description><![CDATA[<p dir="auto">此贴不错。总结下 单卡 R9700 32G 貌似有戏。不过48-32=16G 显存的缺口也不小。</p>
]]></description><link>https://lcz.me/post/16842</link><guid isPermaLink="true">https://lcz.me/post/16842</guid><dc:creator><![CDATA[williamlouis]]></dc:creator><pubDate>Wed, 09 Sep 2026 06:05:19 GMT</pubDate></item><item><title><![CDATA[Reply to 無事折騰~單張 RX 7900 XTX（gfx1100 / 24GB）上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄 on Wed, 09 Sep 2026 05:56:04 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/chia-an-yang" aria-label="Profile: CHIA-AN-YANG">@<bdi>CHIA-AN-YANG</bdi></a> <a href="/post/16741">said</a>:</p>
<p dir="auto">SGLang 雙卡 MTP-3（原文，非本機實測）	97～116 t/s	需 48GB	這才是那篇的數字<br />
llama.cpp（Vulkan）+ GGUF + 自帶 MTP	73 / 33 t/s（平均 50）		prefill 6.4K=706 / 25K=607 t/s</p>
</blockquote>
<p dir="auto">如果沒有要追求更大的context size, 兩張單卡 跑兩個實例instances, 或許生產力產出 &gt; 雙卡一個實例</p>
<p dir="auto">如過是coding 需要更大的ctx size 讀取codebase 那倒真的需要 48GB VRAM,<br />
但有HiCache 可以補足單卡VRAM不足, 只是表現如何 我還沒嘗試過</p>
<p dir="auto">ps. Qwen3.8-27B 最大也只能有256K ctx size.</p>
]]></description><link>https://lcz.me/post/16841</link><guid isPermaLink="true">https://lcz.me/post/16841</guid><dc:creator><![CDATA[kos or]]></dc:creator><pubDate>Wed, 09 Sep 2026 05:56:04 GMT</pubDate></item><item><title><![CDATA[Reply to 無事折騰~單張 RX 7900 XTX（gfx1100 / 24GB）上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄 on Tue, 08 Sep 2026 18:06:16 GMT]]></title><description><![CDATA[<p dir="auto">很好，踩坑了，那还是得上双卡，而且说实话代价还不低，还得有时间，我还是继续4090 48G。但是我也在探索Qwen3.8 27b的生产力，说真的是有的，只是还需要我继续探索。</p>
]]></description><link>https://lcz.me/post/16767</link><guid isPermaLink="true">https://lcz.me/post/16767</guid><dc:creator><![CDATA[terry]]></dc:creator><pubDate>Tue, 08 Sep 2026 18:06:16 GMT</pubDate></item></channel></rss>