跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • 浅色
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • 深色
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(LCZ-Blue)
  • 不使用皮肤
  • LCZ-Green
  • LCZ-Blue
  • LCZ-Black
折叠
品牌标识

抡锤者

首页 版块 标签 硬件 AI 广场
  1. 主页
  2. 版块
  3. LLM讨论区
  4. 無事折騰~單張 RX 7900 XTX(gfx1100 / 24GB)上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄

無事折騰~單張 RX 7900 XTX(gfx1100 / 24GB)上編 SGLang 跑 Qwen3.8-27B —— 完整實作與踩坑全紀錄

已定时 固定直到 2026/9/10 18:04 已锁定 已移动 LLM讨论区
7900xtxsg-langqwen-27b
5 帖子 5 发布者 147 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • CHIA AN YANGC 离线
    CHIA AN YANGC 离线
    CHIA AN YANG
    超凡大师
    编写于 最后由 编辑
    #1

    在單張 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 restart 15 秒回來

    一句話:單張 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.py gemma weight loader 加 device 對齊 只有你要用 --cpu-offload-gb
    P5 python/sglang/srt/utils/offloader.py 2 處 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-match
    

    torch-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 一換就全爛。

    防呆:

    1. 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
      
    2. 之後每做完一批 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' here
    

    dequant_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 --inplace
    

    setup_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 as tvm_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.json

    hf download Vishva007/Qwen3.8-27B-W4A16-AutoRound-GPTQ \
      --local-dir ~/models/qwen3.8-27b-mtp-fixed
    

    (W4A16 GPTQ,本體 5 個 shard ≈ 19GB+model_extra_tensors.safetensors 849MB 放 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」→ ❌ OOM

     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).
    

    🔴 坑 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_weight buffer 在 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=False
    

    Qwen3.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 batch log 一行都沒印。原因很直接:每個 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/thrust setup_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.11
    6 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 device in layernorm.py _weight_loader P4:gemma weight loader 加 device 對齊(只有用 --cpu-offload-gb 才會遇到)
    8 ValueError: functional_call got multiple values for keys ['linear_attn.A_log'...] which are tied P5: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 restart 15 秒回來,不用顧 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_rdna3 GPTQ kernel 補上的。

    寫於 2026-09-08。環境 ROCm 7.2.0 / torch 2.11.0+rocm7.2 / SGLang fork StevenChenSE/sglang@gfx1100-support(該日最新 commit)。

    kos orK 1 条回复 最后回复
    4
    • ,terryT terry 固定了此主题
    • terryT 离线
      terryT 离线
      terry
      超级版主
      编写于 最后由 编辑
      #2

      很好,踩坑了,那还是得上双卡,而且说实话代价还不低,还得有时间,我还是继续4090 48G。但是我也在探索Qwen3.8 27b的生产力,说真的是有的,只是还需要我继续探索。

      油管:https://www.youtube.com/@抡锤者

      1 条回复 最后回复
      1
      • CHIA AN YANGC CHIA AN YANG

        在單張 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 restart 15 秒回來

        一句話:單張 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.py gemma weight loader 加 device 對齊 只有你要用 --cpu-offload-gb
        P5 python/sglang/srt/utils/offloader.py 2 處 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-match
        

        torch-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 一換就全爛。

        防呆:

        1. 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
          
        2. 之後每做完一批 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' here
        

        dequant_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 --inplace
        

        setup_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 as tvm_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.json

        hf download Vishva007/Qwen3.8-27B-W4A16-AutoRound-GPTQ \
          --local-dir ~/models/qwen3.8-27b-mtp-fixed
        

        (W4A16 GPTQ,本體 5 個 shard ≈ 19GB+model_extra_tensors.safetensors 849MB 放 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」→ ❌ OOM

         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).
        

        🔴 坑 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_weight buffer 在 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=False
        

        Qwen3.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 batch log 一行都沒印。原因很直接:每個 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/thrust setup_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.11
        6 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 device in layernorm.py _weight_loader P4:gemma weight loader 加 device 對齊(只有用 --cpu-offload-gb 才會遇到)
        8 ValueError: functional_call got multiple values for keys ['linear_attn.A_log'...] which are tied P5: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 restart 15 秒回來,不用顧 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_rdna3 GPTQ kernel 補上的。

        寫於 2026-09-08。環境 ROCm 7.2.0 / torch 2.11.0+rocm7.2 / SGLang fork StevenChenSE/sglang@gfx1100-support(該日最新 commit)。

        kos orK 离线
        kos orK 离线
        kos or
        超凡大师
        编写于 最后由 kos or 编辑
        #3

        @CHIA-AN-YANG said:

        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.

        1 条回复 最后回复
        1
        • williamlouisW 在线
          williamlouisW 在线
          williamlouis
          超级版主
          编写于 最后由 编辑
          #4

          此贴不错。总结下 单卡 R9700 32G 貌似有戏。不过48-32=16G 显存的缺口也不小。

          个人主页:xlkj.org Telegram https://t.me/xlkjorg

          1 条回复 最后回复
          1
          • 懒人烘培懒 离线
            懒人烘培懒 离线
            懒人烘培
            编写于 最后由 编辑
            #5

            谢谢,总结的很好,到时候也能抄作业

            1 条回复 最后回复
            1

            你好!看起来您对这段对话很感兴趣,但您还没有一个账号。

            厌倦了每次访问都刷到同样的帖子?您注册账号后,您每次返回时都能精准定位到您上次浏览的位置,并可选择接收新回复通知(通过邮件或推送通知)。您还能收藏书签、为帖子顶,向社区成员表达您的欣赏。

            有了你的建议,这篇帖子会更精彩哦 💗

            注册 登录
            回复
            • 在新帖中回复
            登录后回复
            • 从旧到新
            • 从新到旧
            • 最多赞同


            • 登录

            • 登录或注册以进行搜索。
            • 第一个帖子
              最后一个帖子
            0
            • 版块
            • 最新
            • 标签
            • 热门
            • 用户
            • 群组