無事折騰~單張 RX 7900 XTX(gfx1100 / 24GB)上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄
-
在單張 RX 7900 XTX(gfx1100 / 24GB)上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄
目標讀者:手上有一張 7900 XTX、看到「SGLang 雙卡爽跑 Qwen3.8-27B」那篇文章、想自己抄一份的人。
這篇把「從讀大神 flyer666 雙卡7900xtx sglang文章 → 從原始碼編 → 單卡跑起來 → 為什麼還是不划算」整段寫清楚,8 個坑逐一標出來,你照走就不用再踩。
環境、指令、錯誤訊息全部照實貼,方便對照。
TL;DR(先講結論,省得你白花一個晚上)
你想做的 現實 單卡複製那篇的 88~116 t/s
做不到。那些數字是雙卡 48GB + MTP-3。單張 24GB 塞不下「GPTQ 本體 18GB + 獨立的 GPTQ MTP draft 5.5GB + KV cache」單卡「關掉 MTP」跑
跑得動,decode ~35 t/s,跟 llama.cpp 不開投機解碼同一個檔次單卡用 --cpu-offload-gb硬塞 MTP
能載入、MTP 有啟動,但每個 forward 要從記憶體串 8GB 權重過 PCIe,GPU 使用率 2%,實測 <4 t/s,等於不能用對照組:llama.cpp(Vulkan)+ GGUF + 它自帶的 MTP 程式碼 73 / 散文 33 / 平均 50 t/s,塞得進 24GB, systemctl restart15 秒回來一句話:單張 7900 XTX 就乖乖用 llama.cpp-HIP / llama.cpp-Vulkan。 SGLang 這個 fork 的價值在雙卡 tensor-parallel + MTP-3,單卡拿不到,還要多顧一堆版本相依。要複製那篇,先買第二張 7900 XTX。
這篇的價值:如果你之後真的有兩張卡,下面「怎麼編 / 踩過哪些坑」照樣有用(fork 的 build 部分兩張卡也一樣要做)。
這篇對照的原文
- 文章:
https://lcz.me/topic/1532(SGLang 雙 7900 XTX,移植 vLLM kernel + EAGLE MTP-3) - 對應 repo:
https://github.com/StevenChenSE/sglang的gfx1100-support分支 - 原文宣稱(全部是 TP=2 雙卡):
- 單併發 decode 97~116 t/s、prefill ~562、TTFT ~0.16s
- 120k token agent 場景:平均 decode 87.9、最低 66.7(vs vLLM 最低只有 16.9)
- 4 併發總吞吐 147.5 t/s
我的環境(照你自己的對)
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 會用到)
️ 本機同時裝了 NVIDIA CUDA toolkit(nvidia-cuda-dev/libthrust-dev/libcu++-dev),因為另一張卡在跑別的東西。坑 1 就是它引起的。如果你的機器很乾淨、沒裝過任何 CUDA/thrust 開發套件,坑 1 可能不會遇到。
全部要改的檔案一覽(先看這張,心裡有底)
# 檔案 改什麼 什麼時候需要 P1 python/sglang/kernels/aot/setup_rocm.py加 -isystem /opt/rocm-7.2.0/include機器裝過 CUDA/thrust 開發套件時 P2 setup_rocm.py+csrc/common_extension_rocm.cc拿掉 moe_q_gemm_rdna3.cu、用巨集擋它的註冊一定(fork 這檔有 bug,且 dense 模型用不到) P3 模型的 config.json+:.*mtp.*→-:.*mtp.*bits 16一定(README 也有寫) P4 python/sglang/srt/layers/layernorm.pygemma weight loader 加 device 對齊 只有你要用 --cpu-offload-gbP5 python/sglang/srt/utils/offloader.py2 處 functional_call(..., tie_weights=False)只有你要用 --cpu-offload-gb
Step 0:clone
cd ~/src git clone --branch gfx1100-support --single-branch --depth 1 \ https://github.com/StevenChenSE/sglang.git sglang-gfx1100
Step 1:建 venv、裝 PyTorch(ROCm 版)
# 用 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-matchtorch-2.12.0+rocm7.2也在同一個索引、也可用。README 寫「PyTorch 2.11.0+git」,抓 2.11.0+rocm7.2 最貼。驗證(每次動完 pip 都要跑一下這個):
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
坑 1(PyTorch 被 CUDA 版覆蓋)症狀:後面你裝別的套件(尤其
torchvision/torchaudio/ 任何沒 pin 的東西),pip/uv 的相依求解會默默把torch換成torch-2.14.0+cu130(CUDA 版)。之後torch.version.hip變None、torch.cuda.get_device_properties(0).name印出你的 NVIDIA 卡。編好的.so是對著 ROCm torch 連結的,torch 一換就全爛。防呆:
torch/torchvision/torchaudio一律從.../whl/rocm7.2這個索引裝、而且 pin 版本: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- 之後每做完一批 pip 安裝,就跑一次上面那個
import torch驗證。被換掉就照上面重裝一次(uv 會把 CUDA 版移除)。
Step 2:編 AOT HIP kernel(
sgl_kernel)—— 成敗關鍵source ~/venvs/sglang-rocm/bin/activate uv pip install numpy setuptools wheel ninja "scikit-build-core>=0.10" packaging cd ~/src/sglang-gfx1100/python/sglang/kernels/aot
坑 2(rocThrust 被系統的 CUDA thrust 蓋掉)症狀:一開編就爆 300+ 個錯,長這樣:
/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往回追 include 鏈會看到:
torch/headeronly/util/complex.h:9 → #include <thrust/complex.h> ↓ 竟然解析到 /usr/include/thrust/complex.h ← 這是 libthrust-dev(CUDA 味)的,不是 ROCm 的根因:clang 的 include 搜尋順序裡,
/usr/include排在/opt/rocm-7.2.0/include前面(因為 rocm 的 include 目錄剛好也是 clang 內建系統路徑之一,你手動-I它會被 clang 判定重複而丟掉)。系統上libthrust-dev+nvidia-cuda-dev在/usr/include/thrust放了 CUDA 版 thrust,就贏了。修法(P1):
setup_rocm.py裡把-isystem /opt/rocm-7.2.0/include塞到編譯 flag 最前面。-isystem會排在/usr/include之前,rocThrust 就勝出。# 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", ...驗證這招有效的最小重現:
echo '#include <thrust/complex.h>' > /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>&1 | grep 'thrust/complex.h' # 要看到解析到 /opt/rocm-7.2.0/include/thrust/complex.h 才對如果你的機器沒裝過
libthrust-dev/nvidia-cuda-dev/libcu++-dev,這坑不會出現,P1 可略。或者你也可以sudo apt remove libthrust-dev libcub-dev(nvidia-cuda-dev會被連帶移除,自己評估)。
坑 3(moe_q_gemm_rdna3.hip本身有 bug)症狀:P1 修完剩 8 個錯,全在同一個檔:
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' heredequant_4bit_8_fp16(...)的參數要half2 (&)[2],caller 傳的是單個half2—— fork 的 MoE 版寫錯了。修法(P2):Qwen3.8-27B 是 dense,根本不會呼叫 MoE routing GEMM。把這個 source 拿掉、順便擋掉它在 extension 的註冊(不然 link 會缺符號)。
setup_rocm.py,sources清單裡:"csrc/gemm/gptq/q_gemm_rdna3.cu", "csrc/gemm/gptq/q_gemm_rdna3_wmma.cu", # "csrc/gemm/gptq/moe_q_gemm_rdna3.cu", ← 註解掉setup_rocm.py,is_rdna那段之後加一個編譯巨集: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")csrc/common_extension_rocm.cc,把moe_gptq_gemm_rdna3的 extern 宣告 和m.def+m.impl各自用#ifndef包起來:#ifndef SGL_SKIP_MOE_GPTQ_RDNA3 extern void moe_gptq_gemm_rdna3(torch::Tensor a, torch::Tensor c, ... int64_t output_topk); #endif#ifndef SGL_SKIP_MOE_GPTQ_RDNA3 m.def( "moe_gptq_gemm_rdna3(Tensor a, Tensor! c, ... int output_topk) -> ()"); m.impl("moe_gptq_gemm_rdna3", torch::kCUDA, &moe_gptq_gemm_rdna3); #endif }開編
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 --inplacesetup_rocm.py會自動從torch.cuda.get_device_properties(0).gcnArchName抓到gfx1100(白名單裡本來就有 gfx1100,社群講的「要 patch 白名單」在這個 fork 不必),RDNA 分支會自動不編 CDNA 專用的 all-reduce。成功長這樣:
[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 -> python/sgl_kernel把
.so放進 site-packages: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')"
Step 3:裝 sglang 本體(editable)+ 一堆 runtime 相依
cd ~/src/sglang-gfx1100/python uv pip install --no-build-isolation --no-deps -e .
坑 4(--no-deps是必須的,且 editable 會「消失」)- 必須
--no-deps:pyproject.toml的相依清單整串是 CUDA 專用(cuda-python>=13、flash-attn-4、flashinfer_python[cu13]、quack-kernels、tilelang…),照裝會把 CUDA torch 拉回來、或直接裝不起來。 - editable 安裝會被後續的
uv pip install悄悄移除:我遇到兩次「pip install -e .成功 → 裝別的東西 →import sglang又 No module named 'sglang'」。
對策:① editable 用uv pip install ... -e .(跟後面的 uv 操作同一個工具,比較不會打架);② 每裝完一批相依,重跑python -c "import sglang"確認,掉了就再uv pip install --no-build-isolation --no-deps -e .一次。
手動補相依(照 import 錯誤一個個補)
先裝這批(都是 ROCm 安全的、不會拉 CUDA torch):
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然後迴圈補剩下的:
cd ~/src/sglang-gfx1100/python while true; do ERR=$(python -c "from sglang.srt.entrypoints.http_server import launch_server" 2>&1 | grep ModuleNotFoundError | tail -1) [ -z "$ERR" ] && { 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我這輪最後補進去的:
gguf、xgrammar、apache-tvm-ffi==0.1.11(imported astvm_ffi)、soundfile。驗證整條:
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
Step 4:下模型 + patch
config.jsonhf download Vishva007/Qwen3.8-27B-W4A16-AutoRound-GPTQ \ --local-dir ~/models/qwen3.8-27b-mtp-fixed(W4A16 GPTQ,本體 5 個 shard ≈ 19GB+
model_extra_tensors.safetensors849MB 放 MTP 張量。硬碟上約 19GB。)
坑 3.5 → P3(GPTQ loader 想量化 MTP 層 → 載入失敗)症狀(不 patch 的話,載模型階段就爆):loader 相信
config.json裡quantization_config.dynamic的
"+:.*mtp.*"/"+:.*mtp\.fc.*"正向規則(bits 4、group 64),拿 4-bit 參數去載 MTP 層,但那些張量其實是 BF16 → 掛。修法(P3,README 也有寫):
cd ~/models/qwen3.8-27b-mtp-fixed cp config.json config.json.orig python3 - <<'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
Step 5:啟動(單卡)
這個 fork 是雙卡專案,README 的範例是
--tp-size 2 --context-length 196608 --mem-fraction-static 0.91(給 2×24GB)。單卡要自己砍。5a. 先試「照抄 +
--tp-size 1+ 帶 EAGLE MTP」→
OOMLoad 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).
坑 6(單卡塞不下 target + MTP draft + KV)- GPTQ 本體 18.18GB
- EAGLE 的 draft 是「整個
Qwen3_5ForCausalLMMTP當獨立 GPTQ 模型載」= 5.53GB(不是 llama.cpp 那種塞在 GGUF 裡的小 head) - 18.18 + 5.53 = 23.7GB,24GB 卡連 KV 都放不下
雙卡(48GB)就沒事 —— 這就是為什麼原文是雙卡。
5b. 「關掉 MTP」→
跑得動,~35 t/s啟動腳本(存成
run_sglang_singlecard.sh):#!/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成功關鍵行:
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實測(同機、暖機後):
程式碼 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 / 編譯)這個數字 = 純 GPTQ dense forward,跟 llama.cpp 不開投機解碼差不多。沒有優勢。
5c. 想單卡也吃 MTP:
--cpu-offload-gb硬塞 → 又踩 2 個坑,最後「能跑但太慢」概念:把一部分 target 權重丟主機 RAM(
--cpu-offload-gb 8),空出顯存給 5.5GB 的 MTP draft。
坑 7(--cpu-offload-gb+ gemma layernorm,裝置不一致)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!offload 把
param放到 CPU,但self.gemma_weightbuffer 在 GPU。修法(P4)
python/sglang/srt/layers/layernorm.py的_weight_loader: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)
坑 8(--cpu-offload-gb+ Qwen3.8 hybrid GDN,tied 權重)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=FalseQwen3.8 的 Mamba/GDN 層那個
A_log在 state_dict 裡以兩個名字出現(tied),offloader 的functional_call不吃。修法(P5)
python/sglang/srt/utils/offloader.py,兩處functional_call(...)都加tie_weights=False:# 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, )5c 啟動腳本(帶 MTP + offload)
在 5b 的腳本上加/改:
--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 \這次真的載入成功了:
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但實測:10-token 的暖機請求跑 3 分鐘沒回來;
rocm-smi看 GPU 使用率 2%;SGLang 的Decode batchlog 一行都沒印。原因很直接:每個 forward 都要把 8GB 的 GPTQ 權重從 RAM 串過 PCIe Gen4 x16(~31GB/s),光傳輸就 ~0.25s/token → <4 t/s,GPU 全程在等。→ 能跑 ≠ 能用。單卡 + offload 的 MTP 沒有意義。
對照表(同一台機、同一顆 Qwen3.8-27B)
方案 decode(程式碼 / 散文) 塞得進 24GB? 備註 SGLang 單卡,無 MTP ~35 / ~35 t/s 
= 純 GPTQ forward SGLang 單卡,MTP + --cpu-offload-gb 8<4 t/s 靠 offload PCIe 串權重把速度打死 SGLang 雙卡 MTP-3(原文,非本機實測) 97~116 t/s 需 48GB 這才是那篇的數字 llama.cpp(Vulkan)+ GGUF + 自帶 MTP 73 / 33 t/s(平均 50) 
prefill 6.4K=706 / 25K=607 t/s llama.cpp 無 MTP ~35 t/s 
跟 SGLang 無 MTP 一樣 llama.cpp 的 MTP head 是塞在 GGUF 裡的小東西(不是獨立 5.5GB 模型),所以單張 24GB 就能開 MTP,這是它單卡贏的關鍵。
已知坑速查(照這個順序就不會卡)
# 症狀關鍵字 解法 1 裝完 torchvision 後 torch.version.hip變None/ 印出 NVIDIA 卡torch/torchvision/torchaudio 一律 pin 版本從 whl/rocm7.2索引裝;每次 pip 後驗證2 error: definition of type 'int2' conflicts/ include 鏈跑到/usr/include/thrustsetup_rocm.py加-isystem /opt/rocm-7.2.0/include(或移除libthrust-dev)3 moe_q_gemm_rdna3.hip:...: non-const lvalue reference to type 'half2[2]'從 sources 拿掉該檔 + -DSGL_SKIP_MOE_GPTQ_RDNA3擋註冊(dense 模型不用 MoE)4 import sglang→No module named 'sglang'(明明剛裝過)editable 用 uv pip install --no-build-isolation --no-deps -e .;每次裝完相依重驗5 No module named 'tvm_ffi'裝不起來套件名是 apache-tvm-ffi==0.1.116 ValueError: Loaded weights leave no GPU memory for the KV cache單卡塞不下 target(18G)+MTP draft(5.5G)+KV → 關 MTP,或 --cpu-offload-gb(見坑 7/8,但會很慢)7 RuntimeError: Expected all tensors to be on the same deviceinlayernorm.py_weight_loaderP4:gemma weight loader 加 device 對齊(只有用 --cpu-offload-gb才會遇到)8 ValueError: functional_call got multiple values for keys ['linear_attn.A_log'...] which are tiedP5: offloader.py兩處functional_call(..., tie_weights=False)
️— 千萬不要對 gfx1100 下 rocm-smi --gpureset(會鎖死 PCIe root complex,只能硬關機)—— README 自己的警告
️閒置時 2 個 CPU 核心 100% ROCm KFD event-age busy-wait bug; --sleep-on-idle或編scripts/rdna_ar/kfd_event_age_fix.c做LD_PRELOAD
log 一直說 "model can run with gptq_marlin ... faster" marlin 是 CUDA kernel,RDNA3 維持 --quantization gptq(用 fork 的q_gemm_rdna3)
我的建議
- 只有一張 7900 XTX → 用 llama.cpp(HIP 或 Vulkan)+ GGUF,開它自帶的 MTP。單卡場景它就是最佳解,
systemctl restart15 秒回來,不用顧 ROCm/torch 版本相依地獄。 - 有兩張 7900 XTX → 這個 fork 才有意義。照 README 原始的
--tp-size 2路徑跑(上面 P1~P3 的 build patch 兩張卡也要;P4/P5 是單卡 offload 專用,雙卡用不到)。那時 target+draft+KV 全塞進 48GB,就能拿到原文的 88~116 t/s。 - 上游 SGLang 目前(2026-09)還沒有官方 consumer RDNA3 支援(追蹤在 sgl-project/sglang issue #30599)。這個社群 fork 是目前唯一把
q_gemm_rdna3GPTQ kernel 補上的。
寫於 2026-09-08。環境 ROCm 7.2.0 / torch 2.11.0+rocm7.2 / SGLang fork
StevenChenSE/sglang@gfx1100-support(該日最新 commit)。 - 文章:
-
,
T terry 固定了此主题
-
在單張 RX 7900 XTX(gfx1100 / 24GB)上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄
目標讀者:手上有一張 7900 XTX、看到「SGLang 雙卡爽跑 Qwen3.8-27B」那篇文章、想自己抄一份的人。
這篇把「從讀大神 flyer666 雙卡7900xtx sglang文章 → 從原始碼編 → 單卡跑起來 → 為什麼還是不划算」整段寫清楚,8 個坑逐一標出來,你照走就不用再踩。
環境、指令、錯誤訊息全部照實貼,方便對照。
TL;DR(先講結論,省得你白花一個晚上)
你想做的 現實 單卡複製那篇的 88~116 t/s
做不到。那些數字是雙卡 48GB + MTP-3。單張 24GB 塞不下「GPTQ 本體 18GB + 獨立的 GPTQ MTP draft 5.5GB + KV cache」單卡「關掉 MTP」跑
跑得動,decode ~35 t/s,跟 llama.cpp 不開投機解碼同一個檔次單卡用 --cpu-offload-gb硬塞 MTP
能載入、MTP 有啟動,但每個 forward 要從記憶體串 8GB 權重過 PCIe,GPU 使用率 2%,實測 <4 t/s,等於不能用對照組:llama.cpp(Vulkan)+ GGUF + 它自帶的 MTP 程式碼 73 / 散文 33 / 平均 50 t/s,塞得進 24GB, systemctl restart15 秒回來一句話:單張 7900 XTX 就乖乖用 llama.cpp-HIP / llama.cpp-Vulkan。 SGLang 這個 fork 的價值在雙卡 tensor-parallel + MTP-3,單卡拿不到,還要多顧一堆版本相依。要複製那篇,先買第二張 7900 XTX。
這篇的價值:如果你之後真的有兩張卡,下面「怎麼編 / 踩過哪些坑」照樣有用(fork 的 build 部分兩張卡也一樣要做)。
這篇對照的原文
- 文章:
https://lcz.me/topic/1532(SGLang 雙 7900 XTX,移植 vLLM kernel + EAGLE MTP-3) - 對應 repo:
https://github.com/StevenChenSE/sglang的gfx1100-support分支 - 原文宣稱(全部是 TP=2 雙卡):
- 單併發 decode 97~116 t/s、prefill ~562、TTFT ~0.16s
- 120k token agent 場景:平均 decode 87.9、最低 66.7(vs vLLM 最低只有 16.9)
- 4 併發總吞吐 147.5 t/s
我的環境(照你自己的對)
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 會用到)
️ 本機同時裝了 NVIDIA CUDA toolkit(nvidia-cuda-dev/libthrust-dev/libcu++-dev),因為另一張卡在跑別的東西。坑 1 就是它引起的。如果你的機器很乾淨、沒裝過任何 CUDA/thrust 開發套件,坑 1 可能不會遇到。
全部要改的檔案一覽(先看這張,心裡有底)
# 檔案 改什麼 什麼時候需要 P1 python/sglang/kernels/aot/setup_rocm.py加 -isystem /opt/rocm-7.2.0/include機器裝過 CUDA/thrust 開發套件時 P2 setup_rocm.py+csrc/common_extension_rocm.cc拿掉 moe_q_gemm_rdna3.cu、用巨集擋它的註冊一定(fork 這檔有 bug,且 dense 模型用不到) P3 模型的 config.json+:.*mtp.*→-:.*mtp.*bits 16一定(README 也有寫) P4 python/sglang/srt/layers/layernorm.pygemma weight loader 加 device 對齊 只有你要用 --cpu-offload-gbP5 python/sglang/srt/utils/offloader.py2 處 functional_call(..., tie_weights=False)只有你要用 --cpu-offload-gb
Step 0:clone
cd ~/src git clone --branch gfx1100-support --single-branch --depth 1 \ https://github.com/StevenChenSE/sglang.git sglang-gfx1100
Step 1:建 venv、裝 PyTorch(ROCm 版)
# 用 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-matchtorch-2.12.0+rocm7.2也在同一個索引、也可用。README 寫「PyTorch 2.11.0+git」,抓 2.11.0+rocm7.2 最貼。驗證(每次動完 pip 都要跑一下這個):
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
坑 1(PyTorch 被 CUDA 版覆蓋)症狀:後面你裝別的套件(尤其
torchvision/torchaudio/ 任何沒 pin 的東西),pip/uv 的相依求解會默默把torch換成torch-2.14.0+cu130(CUDA 版)。之後torch.version.hip變None、torch.cuda.get_device_properties(0).name印出你的 NVIDIA 卡。編好的.so是對著 ROCm torch 連結的,torch 一換就全爛。防呆:
torch/torchvision/torchaudio一律從.../whl/rocm7.2這個索引裝、而且 pin 版本: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- 之後每做完一批 pip 安裝,就跑一次上面那個
import torch驗證。被換掉就照上面重裝一次(uv 會把 CUDA 版移除)。
Step 2:編 AOT HIP kernel(
sgl_kernel)—— 成敗關鍵source ~/venvs/sglang-rocm/bin/activate uv pip install numpy setuptools wheel ninja "scikit-build-core>=0.10" packaging cd ~/src/sglang-gfx1100/python/sglang/kernels/aot
坑 2(rocThrust 被系統的 CUDA thrust 蓋掉)症狀:一開編就爆 300+ 個錯,長這樣:
/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往回追 include 鏈會看到:
torch/headeronly/util/complex.h:9 → #include <thrust/complex.h> ↓ 竟然解析到 /usr/include/thrust/complex.h ← 這是 libthrust-dev(CUDA 味)的,不是 ROCm 的根因:clang 的 include 搜尋順序裡,
/usr/include排在/opt/rocm-7.2.0/include前面(因為 rocm 的 include 目錄剛好也是 clang 內建系統路徑之一,你手動-I它會被 clang 判定重複而丟掉)。系統上libthrust-dev+nvidia-cuda-dev在/usr/include/thrust放了 CUDA 版 thrust,就贏了。修法(P1):
setup_rocm.py裡把-isystem /opt/rocm-7.2.0/include塞到編譯 flag 最前面。-isystem會排在/usr/include之前,rocThrust 就勝出。# 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", ...驗證這招有效的最小重現:
echo '#include <thrust/complex.h>' > /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>&1 | grep 'thrust/complex.h' # 要看到解析到 /opt/rocm-7.2.0/include/thrust/complex.h 才對如果你的機器沒裝過
libthrust-dev/nvidia-cuda-dev/libcu++-dev,這坑不會出現,P1 可略。或者你也可以sudo apt remove libthrust-dev libcub-dev(nvidia-cuda-dev會被連帶移除,自己評估)。
坑 3(moe_q_gemm_rdna3.hip本身有 bug)症狀:P1 修完剩 8 個錯,全在同一個檔:
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' heredequant_4bit_8_fp16(...)的參數要half2 (&)[2],caller 傳的是單個half2—— fork 的 MoE 版寫錯了。修法(P2):Qwen3.8-27B 是 dense,根本不會呼叫 MoE routing GEMM。把這個 source 拿掉、順便擋掉它在 extension 的註冊(不然 link 會缺符號)。
setup_rocm.py,sources清單裡:"csrc/gemm/gptq/q_gemm_rdna3.cu", "csrc/gemm/gptq/q_gemm_rdna3_wmma.cu", # "csrc/gemm/gptq/moe_q_gemm_rdna3.cu", ← 註解掉setup_rocm.py,is_rdna那段之後加一個編譯巨集: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")csrc/common_extension_rocm.cc,把moe_gptq_gemm_rdna3的 extern 宣告 和m.def+m.impl各自用#ifndef包起來:#ifndef SGL_SKIP_MOE_GPTQ_RDNA3 extern void moe_gptq_gemm_rdna3(torch::Tensor a, torch::Tensor c, ... int64_t output_topk); #endif#ifndef SGL_SKIP_MOE_GPTQ_RDNA3 m.def( "moe_gptq_gemm_rdna3(Tensor a, Tensor! c, ... int output_topk) -> ()"); m.impl("moe_gptq_gemm_rdna3", torch::kCUDA, &moe_gptq_gemm_rdna3); #endif }開編
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 --inplacesetup_rocm.py會自動從torch.cuda.get_device_properties(0).gcnArchName抓到gfx1100(白名單裡本來就有 gfx1100,社群講的「要 patch 白名單」在這個 fork 不必),RDNA 分支會自動不編 CDNA 專用的 all-reduce。成功長這樣:
[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 -> python/sgl_kernel把
.so放進 site-packages: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')"
Step 3:裝 sglang 本體(editable)+ 一堆 runtime 相依
cd ~/src/sglang-gfx1100/python uv pip install --no-build-isolation --no-deps -e .
坑 4(--no-deps是必須的,且 editable 會「消失」)- 必須
--no-deps:pyproject.toml的相依清單整串是 CUDA 專用(cuda-python>=13、flash-attn-4、flashinfer_python[cu13]、quack-kernels、tilelang…),照裝會把 CUDA torch 拉回來、或直接裝不起來。 - editable 安裝會被後續的
uv pip install悄悄移除:我遇到兩次「pip install -e .成功 → 裝別的東西 →import sglang又 No module named 'sglang'」。
對策:① editable 用uv pip install ... -e .(跟後面的 uv 操作同一個工具,比較不會打架);② 每裝完一批相依,重跑python -c "import sglang"確認,掉了就再uv pip install --no-build-isolation --no-deps -e .一次。
手動補相依(照 import 錯誤一個個補)
先裝這批(都是 ROCm 安全的、不會拉 CUDA torch):
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然後迴圈補剩下的:
cd ~/src/sglang-gfx1100/python while true; do ERR=$(python -c "from sglang.srt.entrypoints.http_server import launch_server" 2>&1 | grep ModuleNotFoundError | tail -1) [ -z "$ERR" ] && { 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我這輪最後補進去的:
gguf、xgrammar、apache-tvm-ffi==0.1.11(imported astvm_ffi)、soundfile。驗證整條:
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
Step 4:下模型 + patch
config.jsonhf download Vishva007/Qwen3.8-27B-W4A16-AutoRound-GPTQ \ --local-dir ~/models/qwen3.8-27b-mtp-fixed(W4A16 GPTQ,本體 5 個 shard ≈ 19GB+
model_extra_tensors.safetensors849MB 放 MTP 張量。硬碟上約 19GB。)
坑 3.5 → P3(GPTQ loader 想量化 MTP 層 → 載入失敗)症狀(不 patch 的話,載模型階段就爆):loader 相信
config.json裡quantization_config.dynamic的
"+:.*mtp.*"/"+:.*mtp\.fc.*"正向規則(bits 4、group 64),拿 4-bit 參數去載 MTP 層,但那些張量其實是 BF16 → 掛。修法(P3,README 也有寫):
cd ~/models/qwen3.8-27b-mtp-fixed cp config.json config.json.orig python3 - <<'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
Step 5:啟動(單卡)
這個 fork 是雙卡專案,README 的範例是
--tp-size 2 --context-length 196608 --mem-fraction-static 0.91(給 2×24GB)。單卡要自己砍。5a. 先試「照抄 +
--tp-size 1+ 帶 EAGLE MTP」→
OOMLoad 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).
坑 6(單卡塞不下 target + MTP draft + KV)- GPTQ 本體 18.18GB
- EAGLE 的 draft 是「整個
Qwen3_5ForCausalLMMTP當獨立 GPTQ 模型載」= 5.53GB(不是 llama.cpp 那種塞在 GGUF 裡的小 head) - 18.18 + 5.53 = 23.7GB,24GB 卡連 KV 都放不下
雙卡(48GB)就沒事 —— 這就是為什麼原文是雙卡。
5b. 「關掉 MTP」→
跑得動,~35 t/s啟動腳本(存成
run_sglang_singlecard.sh):#!/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成功關鍵行:
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實測(同機、暖機後):
程式碼 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 / 編譯)這個數字 = 純 GPTQ dense forward,跟 llama.cpp 不開投機解碼差不多。沒有優勢。
5c. 想單卡也吃 MTP:
--cpu-offload-gb硬塞 → 又踩 2 個坑,最後「能跑但太慢」概念:把一部分 target 權重丟主機 RAM(
--cpu-offload-gb 8),空出顯存給 5.5GB 的 MTP draft。
坑 7(--cpu-offload-gb+ gemma layernorm,裝置不一致)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!offload 把
param放到 CPU,但self.gemma_weightbuffer 在 GPU。修法(P4)
python/sglang/srt/layers/layernorm.py的_weight_loader: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)
坑 8(--cpu-offload-gb+ Qwen3.8 hybrid GDN,tied 權重)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=FalseQwen3.8 的 Mamba/GDN 層那個
A_log在 state_dict 裡以兩個名字出現(tied),offloader 的functional_call不吃。修法(P5)
python/sglang/srt/utils/offloader.py,兩處functional_call(...)都加tie_weights=False:# 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, )5c 啟動腳本(帶 MTP + offload)
在 5b 的腳本上加/改:
--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 \這次真的載入成功了:
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但實測:10-token 的暖機請求跑 3 分鐘沒回來;
rocm-smi看 GPU 使用率 2%;SGLang 的Decode batchlog 一行都沒印。原因很直接:每個 forward 都要把 8GB 的 GPTQ 權重從 RAM 串過 PCIe Gen4 x16(~31GB/s),光傳輸就 ~0.25s/token → <4 t/s,GPU 全程在等。→ 能跑 ≠ 能用。單卡 + offload 的 MTP 沒有意義。
對照表(同一台機、同一顆 Qwen3.8-27B)
方案 decode(程式碼 / 散文) 塞得進 24GB? 備註 SGLang 單卡,無 MTP ~35 / ~35 t/s 
= 純 GPTQ forward SGLang 單卡,MTP + --cpu-offload-gb 8<4 t/s 靠 offload PCIe 串權重把速度打死 SGLang 雙卡 MTP-3(原文,非本機實測) 97~116 t/s 需 48GB 這才是那篇的數字 llama.cpp(Vulkan)+ GGUF + 自帶 MTP 73 / 33 t/s(平均 50) 
prefill 6.4K=706 / 25K=607 t/s llama.cpp 無 MTP ~35 t/s 
跟 SGLang 無 MTP 一樣 llama.cpp 的 MTP head 是塞在 GGUF 裡的小東西(不是獨立 5.5GB 模型),所以單張 24GB 就能開 MTP,這是它單卡贏的關鍵。
已知坑速查(照這個順序就不會卡)
# 症狀關鍵字 解法 1 裝完 torchvision 後 torch.version.hip變None/ 印出 NVIDIA 卡torch/torchvision/torchaudio 一律 pin 版本從 whl/rocm7.2索引裝;每次 pip 後驗證2 error: definition of type 'int2' conflicts/ include 鏈跑到/usr/include/thrustsetup_rocm.py加-isystem /opt/rocm-7.2.0/include(或移除libthrust-dev)3 moe_q_gemm_rdna3.hip:...: non-const lvalue reference to type 'half2[2]'從 sources 拿掉該檔 + -DSGL_SKIP_MOE_GPTQ_RDNA3擋註冊(dense 模型不用 MoE)4 import sglang→No module named 'sglang'(明明剛裝過)editable 用 uv pip install --no-build-isolation --no-deps -e .;每次裝完相依重驗5 No module named 'tvm_ffi'裝不起來套件名是 apache-tvm-ffi==0.1.116 ValueError: Loaded weights leave no GPU memory for the KV cache單卡塞不下 target(18G)+MTP draft(5.5G)+KV → 關 MTP,或 --cpu-offload-gb(見坑 7/8,但會很慢)7 RuntimeError: Expected all tensors to be on the same deviceinlayernorm.py_weight_loaderP4:gemma weight loader 加 device 對齊(只有用 --cpu-offload-gb才會遇到)8 ValueError: functional_call got multiple values for keys ['linear_attn.A_log'...] which are tiedP5: offloader.py兩處functional_call(..., tie_weights=False)
️— 千萬不要對 gfx1100 下 rocm-smi --gpureset(會鎖死 PCIe root complex,只能硬關機)—— README 自己的警告
️閒置時 2 個 CPU 核心 100% ROCm KFD event-age busy-wait bug; --sleep-on-idle或編scripts/rdna_ar/kfd_event_age_fix.c做LD_PRELOAD
log 一直說 "model can run with gptq_marlin ... faster" marlin 是 CUDA kernel,RDNA3 維持 --quantization gptq(用 fork 的q_gemm_rdna3)
我的建議
- 只有一張 7900 XTX → 用 llama.cpp(HIP 或 Vulkan)+ GGUF,開它自帶的 MTP。單卡場景它就是最佳解,
systemctl restart15 秒回來,不用顧 ROCm/torch 版本相依地獄。 - 有兩張 7900 XTX → 這個 fork 才有意義。照 README 原始的
--tp-size 2路徑跑(上面 P1~P3 的 build patch 兩張卡也要;P4/P5 是單卡 offload 專用,雙卡用不到)。那時 target+draft+KV 全塞進 48GB,就能拿到原文的 88~116 t/s。 - 上游 SGLang 目前(2026-09)還沒有官方 consumer RDNA3 支援(追蹤在 sgl-project/sglang issue #30599)。這個社群 fork 是目前唯一把
q_gemm_rdna3GPTQ kernel 補上的。
寫於 2026-09-08。環境 ROCm 7.2.0 / torch 2.11.0+rocm7.2 / SGLang fork
StevenChenSE/sglang@gfx1100-support(該日最新 commit)。SGLang 雙卡 MTP-3(原文,非本機實測) 97~116 t/s 需 48GB 這才是那篇的數字
llama.cpp(Vulkan)+ GGUF + 自帶 MTP 73 / 33 t/s(平均 50) prefill 6.4K=706 / 25K=607 t/s如果沒有要追求更大的context size, 兩張單卡 跑兩個實例instances, 或許生產力產出 > 雙卡一個實例
如過是coding 需要更大的ctx size 讀取codebase 那倒真的需要 48GB VRAM,
但有HiCache 可以補足單卡VRAM不足, 只是表現如何 我還沒嘗試過ps. Qwen3.8-27B 最大也只能有256K ctx size.
- 文章:
-
此贴不错。总结下 单卡 R9700 32G 貌似有戏。不过48-32=16G 显存的缺口也不小。