把 Strata 搬上 SGLang 引擎 的嘗試
-
-
續:把 Strata 搬上 SGLang 引擎(三)—— 第五個坑修好終於講人話;然後發現牆不是 kernel,是把 kernel 直接搬過來
上一篇結束時的狀態:serve 得、四個慣例坑已修、但輸出仍然退化,承諾「下一篇是逐 op parity 的結果」。這篇就是,加上之後性能線的完整轉折:三個優化被同窗 A/B 判死、profiler 拆出真牆、最後把生產引擎現成的 kernel 直接橋進 torch,一次過把 decode 和 prefill 兩條 kernel 線實測過判準。數字全部實測。
硬體:雙 7900XTX(gfx1100 / ROCm)、94 GB RAM;模型:Qwen3.8-Flash-Next ISTA GSQ-RCO IQ3_S(MoE,512 experts × 48 層,top-10 路由)
建議 tags:7900xtx strata sglang 多卡部署 qwen-flashTL;DR
- 逐 op parity 釘到第五個真 bug:GDN 的 value-head → key-head 配對,引擎用 modulo、fork 用 interleave,44/48 個 head 讀錯 key。修完(config 一行)輸出逐字正確:法國首都→巴黎、17×23→391、通順廣東話,與Strata一致。
- 語義收線之後全力追速度,三個結果:expert-scoped 佈局(只算路由到該 expert 的 rows)把 prefill 18.3 → 125.5 tok/s(6.87×);但之後兩個「減少呼叫次數」的改寫——micro-batch(op 數削 35 倍)和 bf16 slab(呼叫數削 64 倍)——同窗 A/B 實測一個只快 0–6%、一個反而慢 9–28%,全部判死。
- 進程內 profiler 拆牆:每個 expert 單元 2.127 ms 裡,CPU 讀取+dequant 佔 84.5%,H2D 只 4.6%,GEMM 10.9%。每 token 要搬 480 份 ×9 MB 權重 ≈ 3.8 GB/s——牆是搬 bytes,不是算力、不是 op 數、也不是 PCIe。
- 於是換路:不自己寫 kernel,把生產引擎現成的 grouped expert kernel(一次 call 做齊 gather→dequant→gate/up→SwiGLU→down→scatter)用薄 torch 橋接直接用。真 artifact bytes 上實渗 decode 4.96 µs/expert——判準預算是 30 µs,6 倍餘裕。
- 橋接本身 15/15 gates:三種量化格式 × 兩個 kernel 佈局 × 引擎的優化臂,輸出 bitwise 相同;prefill 那邊的 MMQ kernel 也實測四個 context 判準點全過。
- 誠實講:還沒接線進 SGLang 主路徑。下一個裁決點是 expert 常駐 VRAM 的命中率——全模型 expert 46.8 GiB,對 2×24 GB 顯卡,這才是計劃成立與否的真正未知。
一、第五個坑:GDN head 配對(講人話的最後一里)
上一篇的方法:fork 側用環境變數 gate 逐 op dump,對 numpy 參考(真權重+從引擎源碼逆向的公式),報第一個超容差的 op。
結果:conv 3.7e-3、split 0.0、beta 7.7e-4、dt 2.2e-8、decay 1.9e-8、epilogue 1.7e-3——全部在 bf16 噪聲內。唯一爆表的是 delta rule 的 head 配對:rel 1.246。
機制:48 個 value head 共享 16 個 key head。引擎 kernel 用 modulo 配對(h % h_k),fork 的 FLA kernels 用 interleave(i_h // (H/Hg))。兩種都是合法慣例,但同一個模型只能用一種——錯了之後 44/48 個 head 在錯的 key 上累積 delta state。形狀全對、無 NaN、文字流暢,「講得出話、講錯事」,而且狀態越長崩得越厲害。
修法:bridge config 一行 gdn_head_pairing = "modulo"(默認 interleave 不變,其他模型零影響;prefill/decode 兩條 kernel 路都要覆蓋)。
修完的語義驗收錨是「短 prompt 答案與生產逐字相同」,不是 gate 數、不是 tok/s:巴黎、391、通順粵語,三條全中。五個坑修完(A_log 變換、plain gain norm、sigmoid gate、PLE 層號+大表、head 配對),「跑得對」這一關正式收線。
二、速度線:兩個「減少呼叫」的優化判死,profiler 拆出真牆
expert-scoped 佈局修掉的是真正的浪費:舊路對每個受路由 expert 都跑完整批 token 的 FFN 再用 mask 丟結果,每 chunk 實做 990 TFLOP、必要 19.3 TFLOP(51.2× 冗餘);修完 prefill 18.3 → 125.5 tok/s。
之後的瓶頸量測指向「每 expert 一次 Python 呼叫 ×0.73 ms」,於是做了兩個直覺上正確的改寫:
- micro-batch:把每層 N 個 expert × 11 個 torch op 壓成常數個 batched GEMM。op 數 19,478 → 550(35×)、fp32 權重展開 1,536 次 → 0。同窗兩腿 A/B:只快 0–6%,T=7 反而慢 9%。
- bf16 slab:把整個 block 的 experts 疊成一個大矩陣,叫上游 triton fused-experts kernel 一次算完。同窗 A/B:慢 9–28%。
第二個的死因有外部佐證:log 裡每個 block 都印 "Using default MoE kernel config"。實查 SGLang 樹:configs/ 有 386 個 device json,MI300 有 8 個,RX7900XTX 是 0 個;而且 configs 目錄只有 triton 3.5/3.6/3.7 的版本目錄,我們跑的是 3.8——就算有人補了 config 也讀不到。
但真正值錢的是反推:micro-batch 把 op 削了 35 倍而時間不動,說明 0.73 ms/expert 不是 Python dispatch 的錢,是搬權重的錢——24,576 次 expert 呼叫 × 9 MB ≈ 221 GB / 57.9 s ≈ 3.8 GB/s,PCIe 級。再用進程內 profiler(環境變數 gate,每 expert 單元累計 read+dequant / H2D / GEMM,引擎退出時印一行)把 decode 拆開:每單元 2.127 ms,讀取+dequant 84.5%、H2D 4.6%、GEMM 10.9%。H2D 只 0.098 ms——「PCIe 是瓶頸」這個講法也被否證,牆一直在 CPU 側的解壓。
結論收束成一句:expert 權重要以量化 bytes 常駐 VRAM、dequant 融進 kernel 裡,同一塊 VRAM 容納 4.7× 單元數——這正是生產引擎的設計。那為什麼不直接用它的 kernel?
三、判準定調,然後把生產 kernel 橋進 torch
判準先定(不然 gate 都是拍腦殼):單流,Path B 要達到生產基準的 2/3(差距 ≤ 1/3)。生產基準(temp 0、nonce、usage 計數、3 reps 取中位):
context decode 基準 prefill 基準 Path B 判準 64K 96.5 tok/s 6,130 tok/s decode ≥ 64.3 / prefill ≥ 4,087 256K 105.7 4,343 ≥ 70.5 / ≥ 2,895 decode 判準換算:每 token 15.55 ms,480 個 expert 單元 ⇒ 30 µs/單位。現況是 2,127 µs——差 66 倍,自己的 torch 路永遠追不到,必須 grouped fused kernel。
做法:寫一個 torch extension(一個 .cpp),直接 link 生產引擎已編好的 kernel 靜態庫,暴露五個 op。不寫任何量化 kernel。
decode 實測(引擎自帶 bench、真 GGUF rows、兩個 kernel 佈局互相 bitwise 對拍、interleave 取三輪最小;T=1 token、G=10 experts=一層一次 grouped call;六種 gate/up×down 格式組合全覆蓋):
gate/up / down 格式 每層 call gate/up 頻寬 IQ3_XXS / IQ4_NL 47.0 µs 520 GB/s IQ2_S / Q2_0 50.6 µs 377 GB/s IQ3_S / IQ4_NL 50.4 µs 521 GB/s (其餘三組合) 47.8–49.9 µs — 49.6 µs ÷ 10 = 4.96 µs/單位;×48 層 = 2.38 ms/token = decode 預算的 15–17%。launch 攤薄的算術也實測了:單 expert 一次 call 16.9 µs(launch 主導,kernel 內約 4 個 launch),grouped 10 個的邊際成本 3.64 µs/個;40 個 expert 一次過 3.86 µs/個 @ 564–672 GB/s。「每層一次 call」不是風格,是算術必須。
torch 橋接 15/15 gates:blob 幾何三源一致(kernel 庫算的 ≡ 引擎 pack 表 ≡ 2,176,000/1,510,400/2,329,600 B 逐層吻合);mode A/B × EXPDEC 優化臂三臂輸出 hash 完全相同(bitwise);每層 call 57–60 µs(多出的 ~10 µs 是 Python dispatch,graph capture 可收回)。一個實務細節給想 replicate 的人:torch 的 ROCm wheel 環境沒有 cuda_runtime_api.h,ATen 的 CUDAContext.h 編不過——解法是照 sgl_kernel AOT 的慣例,stream 由 Python 端以 int64 傳進來,shim 不 include 任何 CUDA header。
prefill 那邊(MMQ 級 batched 量化 GEMM,一批 token 共享一次權重讀取):4,096-token chunk 的形狀(每 expert 平均 80 rows)實測每 16-expert launch 0.546 ms,一個 chunk 的 MoE 共 839 ms。對判準:
context 判準 實測(MoE+attention 估算) 判 64K 4,087 tok/s 4,548 過 128K 3,659 4,255 過 192K 3,264 4,001 過 256K 2,895 3,766 過 (attention 項用先前實測的線性律斜率估算 62–249 ms/chunk;MMQ 臂只量了 IQ3_S+IQ4_NL 兩種格式+合成權重,是可行性證據不是端到端承諾。效率隨 chunk 增大:600-token chunk 只有 ~1,600 tok/s,32K chunk ~11,300——launch 攤薄差。)
數據通路也閉了:引擎的 expert blob 與我們 pack 的 objects(gate‖up‖down 拼接)逐 byte 相同(實測對拍 True),GGUF plane 內每 expert stride 704,000 B 無 padding——零轉換直接餵。
四、還沒證的(比已證的重要)
- 所有 kernel 數字都假設 expert 已在 VRAM。全模型 46.8 GiB,雙卡 48 GB 理論剛好,但 KV、mamba state、dense 權重都要位。生產引擎靠 KV 放 host RAM+int8 KV 才塞得下;Path B 的命中率 ≥80% 未實證——這是下一個裁決點。
- 雙卡 expert 並行的 all-to-all 在 gfx1100 沒跑過;hybrid(mamba/GDN)模型的 host KV 在 SGLang 沒證。這兩個任何一個不成立,工期翻倍。
- 沒有 MTP(生產 --spec 3、accept 0.81);對「取代」對照時要補,投機的倍數不能記在 kernel 頭上。
- 還沒接線:decode/prefill 都還是獨立 bench 的數字,SGLang 主路徑的接線正在做。
兩個要收回的舊結論:一、「TP=2 有 block 對齊死牆」——建立在抄錯的量化表上(把 IQ4_NL 的 (32,18) 當成 256 值 block),live manifest 逐 object 驗算後 down 層全部整除,而 expert 維切分(EP=2)本來就零 pack 改動、負載平衡 1.000000。二、「單流做完也就 25–40 tok/s」——那個階梯假設要自寫 kernel;現成 kernel 的實測數字已經超過它的終點值。
性能(誠實版,同 harness 對拍)
量 Path B(語義正確版) 生產 Strata decode(長文 sweep 64K–256K) 1.67–1.70 tok/s 96.5–105.7 prefill 短請求(~5.9K token) 94.8 tok/s — prefill(長文 sweep 64K–256K) (外推 ~30,未實測 64K 點) 4,343–6,130 方法論(這輪救了幾次)
- 同窗兩腿是唯一可信比較:同一 config 隔幾小時的對照腿只剩歷史值的 ~75%(94.9 vs 125.5 tok/s)。跨窗口的歷史數字不是 baseline,任何「快了/慢了」都當場同窗重跑控制腿。
- 報 N 倍必附可重算算式;推導值與 instrumented 實測值分開標。micro-batch 那次就是算式推翻了直覺:counter 綠(op 數大跌)而 wall 不動 ⇒ 成本模型錯,不是量測不準。
- bitwise 對拍 + 負控制:兩個佈局互相 bitwise 才信 wiring;驗證鏈要故意弄壞一個 byte 證明會紅。一個從未紅過的綠鏈沒有證據力。
- 效果 gate 的量綱先對齊:拿一個 5.9K-token 請求去除以「每 4,096-chunk」的律,會憑空多出 3 倍假差距——先對齊 chunk 數再比。
- GPU 實測全程停產引擎窗口(stop→test→restore trap),單窗中斷 <10 秒,收窗驗 health 200。
一句話
「跑得對」與「跑得快」之間隔著 480 份 9 MB;這一輪證明了不用自己填——把生產引擎的橋直接架過來就行,剩下的難題是停車位(VRAM residency),不是引擎。
下一篇應該是接線後 SGLang 自己跑出來的第一組數字。
-
-
-
【7900xtx·qwen-flash】Flash-Next 混合 GDN 移植日記(二):併發開考 7.7× + MTP 頭移植八堵牆拆七
環境聲明(對齊單位先):雙 XTX 7900(gfx1100,PCIe 無 P2P)· SGLang fork(gfx1100 系)·
Qwen3.8-Flash-Next GSQ IQ3_S(48 層=36 GDN+12 attention,512 專家/層,專家由 host 流式進
VRAM arena)· 兩卡 layer-split(各 24 層專家全套)· 無 CUDA graph、無 MTP。以下數字全部
端到端 wall 口徑、usage.completion_tokens實算,counting 任務(250 token/流、temp 0)、
同窗同 harness,冷/暖態標明。
一、decode 速度:併發全梯(本篇主菜)
先講一個前提故事。開
--max-running-requests 8打 4 條併發 request,聚合睇落有「1.9×」
加速——但翻 rank log:45 條 Decode batch 紀錄,#running-req 全部=1。由頭到尾冇 batch 過,
「加速」純粹係首 phase 冷啟(Triton/arena 暖機)拖低咗分母。教訓:判斷併發之前必睇
running-req 直方圖,聚合吞吐會講大話。根因:mamba admission 鉗制(hybrid GDN 模型專有)
追
kv_cache_configurator.py嘅 admission 計算:max_running_requests = min( 你叫嘅 8 , max_mamba_cache_size ÷ 每請求 slot 數 ) = min( 8 , 7 ÷ 5 ) = 1 ← 靜靜鉗死- radix+overlap 開 → 每請求帳面 5 個 mamba state slot;專家 arena 食緊 VRAM,pool 淨得 7 slot
- 7÷5=1 → scheduler 永遠零新請求准入。server 唔報錯,得 startup 一行提示
- 解法一個 flag:
--disable-radix-cache(slot 5→1,pool 直接對齊 8)。獨立 prompt 場景零損失
全梯實測(250 token/流,八流完全公平)
併發 聚合 tok/s 每流 tok/s 加速比 1(冷啟) 2.55 2.55 1× 1(暖態) ~4.0 ~4.0 — 2 6.97 3.48 2.7× 4 12.01 3.00 4.7× 8 19.51(重跑 18.11) 2.44 7.7× - running-req 直方圖齊晒 1→2→4→8,引擎瞬時 20.2 tok/s 仲未見頂
- 每流 3.48→3.00→2.44 緩降=專家搬運攤薄紅利漸被 batch 計算量抵消,但 c=4→8 聚合仲有 +63%
- 呢個架構(專家 host 流式入 VRAM)嘅 throughput 鑰匙係併發:N 條流嘅路由專家喺同一次
arena pass 撞埋一齊,fetch 次數唔線性涨。單流 kernel 微調救唔到 decode(我哋實測過,零效果) - 對照組(單位講明):同 harness 全 resident 引擎單流 95-107 tok/s——我哋單流輸緊一截,
併發斜率先係呢條路嘅存在理由
二、MTP 頭移植:八堵牆拆咗七(改咗幾多代碼)
Flash-Next 原生帶 MTP 頭(單層尾部 drafter:1 個 full-attn+MoE block+input fusion,
共享主模型 lm_head;權重源係 HF BF16 checkpoint,GGUF 本身冇)。目標=接上 SGLang 嘅
NEXTN 投機解碼,乘上 accept rate。由「draft 連 load 都入唔到」去到「draft→target verify
行勻兩卡專家 arena」,八堵牆:# 牆 根因 修復 1 draft 撞主模型嘅 GGUF loader draft 繼承全局 --load-format--speculative-draft-load-format auto2 loader 拒 13 個 GSQ config key 全局 --model-loader-extra-config流入 draft樹 patch:draft 載入時掉目標 extra-config(loud log) 3 layer-split 要求 ≥2 層 全局 GSQ_LAYER_SPLITenv 流入 draft(佢得 1 層)樹 patch:draft runner 豁免 4 KV pool 起唔到 BF16 draft 7GB+投機 mamba intermediate 擠爆 24GB 卡 --max-running-requests 4(intermediate 2.15GB 實測)+ mem-fraction 調整5 lm_head.weight唔存在GSQ 量化 head 只有 qweight;共享機制假設 BF16 tensor 樹 patch:量化 head 回傳 None,draft 直接共享成個 module(零 VRAM) 6 mamba state commit assert 兩卡 layer-split 手術將 mamba_cache 換成跨卡路由視圖,isinstance 唔識 樹 patch:放行路由視圖(逐層存取本來就回傳真 tensor,verify 前向已實證) 7-8 觀測盲區 error 訊息用扣減前嘅數,raise 條件對 fraction 唔敏感 觀測器 patch:admission 全代數印實數(available/slack/intermediate) 打字打到呢度先講清楚:全部 patch additive——唔開 spec flags=逐 byte 原行為,
.bak留底,py_compile 過先上樹。現狀:首個完整 spec-on boot 差最後一個 commit 接縫
(修復已落樹,驗證窗跑緊)。spec-on 數據(accept rate、單流/c=8×MTP)下一帖交。量化臂同時開咗題:draft 專家 Q2_0 化(675MB,同引擎生產配方 bitstream 一致),
draft 常駐 5GB→0.83GB——因為牆#4 證明 BF16 draft 同專家 arena 喺 24GB 卡本質唔共存。
builder 已寫好跑通(重打包 vs 官方 gguf 位元級一致)。三、附:早前同批嘅 TP2 silent 語義地雷(未發過,一併記低)
兩進程 TP2 起機後語義全錯但輸出流利、零 NaN、兩卡 bit-identical。逐層 capture 鎖定
第一個 GDN 層輸出已偏,最終根因:呢隻 artifact 用 modulo head-pairing(v-head h 讀 k-head h%16)。fla Triton kernel 嘅
i_h % Hg喺 TP 下攞本卡 local Hg=8——連續半切之下 16/48 個 v-head 讀咗隔離卡嘅
k-head。值貌似合理、兩 rank 對稱壞,shape check 全部捉唔到。interleave pairing 唔中招。呢個已經落咗 fail-closed guard(modulo×TP>1 直接 raise 拒絕起機,唔會靜靜服務壞數)。
標籤建議:
7900xtxqwen-flash多卡部署 -
-
,
T terry 固定了此主题
-
这项目搞通了论坛里高低要混把大神交椅
加油 -
【7900xtx·qwen-flash】Flash-Next 混合 GDN 移植日記(二):併發開考 7.7× + MTP 頭移植八堵牆拆七
環境聲明(對齊單位先):雙 XTX 7900(gfx1100,PCIe 無 P2P)· SGLang fork(gfx1100 系)·
Qwen3.8-Flash-Next GSQ IQ3_S(48 層=36 GDN+12 attention,512 專家/層,專家由 host 流式進
VRAM arena)· 兩卡 layer-split(各 24 層專家全套)· 無 CUDA graph、無 MTP。以下數字全部
端到端 wall 口徑、usage.completion_tokens實算,counting 任務(250 token/流、temp 0)、
同窗同 harness,冷/暖態標明。
一、decode 速度:併發全梯(本篇主菜)
先講一個前提故事。開
--max-running-requests 8打 4 條併發 request,聚合睇落有「1.9×」
加速——但翻 rank log:45 條 Decode batch 紀錄,#running-req 全部=1。由頭到尾冇 batch 過,
「加速」純粹係首 phase 冷啟(Triton/arena 暖機)拖低咗分母。教訓:判斷併發之前必睇
running-req 直方圖,聚合吞吐會講大話。根因:mamba admission 鉗制(hybrid GDN 模型專有)
追
kv_cache_configurator.py嘅 admission 計算:max_running_requests = min( 你叫嘅 8 , max_mamba_cache_size ÷ 每請求 slot 數 ) = min( 8 , 7 ÷ 5 ) = 1 ← 靜靜鉗死- radix+overlap 開 → 每請求帳面 5 個 mamba state slot;專家 arena 食緊 VRAM,pool 淨得 7 slot
- 7÷5=1 → scheduler 永遠零新請求准入。server 唔報錯,得 startup 一行提示
- 解法一個 flag:
--disable-radix-cache(slot 5→1,pool 直接對齊 8)。獨立 prompt 場景零損失
全梯實測(250 token/流,八流完全公平)
併發 聚合 tok/s 每流 tok/s 加速比 1(冷啟) 2.55 2.55 1× 1(暖態) ~4.0 ~4.0 — 2 6.97 3.48 2.7× 4 12.01 3.00 4.7× 8 19.51(重跑 18.11) 2.44 7.7× - running-req 直方圖齊晒 1→2→4→8,引擎瞬時 20.2 tok/s 仲未見頂
- 每流 3.48→3.00→2.44 緩降=專家搬運攤薄紅利漸被 batch 計算量抵消,但 c=4→8 聚合仲有 +63%
- 呢個架構(專家 host 流式入 VRAM)嘅 throughput 鑰匙係併發:N 條流嘅路由專家喺同一次
arena pass 撞埋一齊,fetch 次數唔線性涨。單流 kernel 微調救唔到 decode(我哋實測過,零效果) - 對照組(單位講明):同 harness 全 resident 引擎單流 95-107 tok/s——我哋單流輸緊一截,
併發斜率先係呢條路嘅存在理由
二、MTP 頭移植:八堵牆拆咗七(改咗幾多代碼)
Flash-Next 原生帶 MTP 頭(單層尾部 drafter:1 個 full-attn+MoE block+input fusion,
共享主模型 lm_head;權重源係 HF BF16 checkpoint,GGUF 本身冇)。目標=接上 SGLang 嘅
NEXTN 投機解碼,乘上 accept rate。由「draft 連 load 都入唔到」去到「draft→target verify
行勻兩卡專家 arena」,八堵牆:# 牆 根因 修復 1 draft 撞主模型嘅 GGUF loader draft 繼承全局 --load-format--speculative-draft-load-format auto2 loader 拒 13 個 GSQ config key 全局 --model-loader-extra-config流入 draft樹 patch:draft 載入時掉目標 extra-config(loud log) 3 layer-split 要求 ≥2 層 全局 GSQ_LAYER_SPLITenv 流入 draft(佢得 1 層)樹 patch:draft runner 豁免 4 KV pool 起唔到 BF16 draft 7GB+投機 mamba intermediate 擠爆 24GB 卡 --max-running-requests 4(intermediate 2.15GB 實測)+ mem-fraction 調整5 lm_head.weight唔存在GSQ 量化 head 只有 qweight;共享機制假設 BF16 tensor 樹 patch:量化 head 回傳 None,draft 直接共享成個 module(零 VRAM) 6 mamba state commit assert 兩卡 layer-split 手術將 mamba_cache 換成跨卡路由視圖,isinstance 唔識 樹 patch:放行路由視圖(逐層存取本來就回傳真 tensor,verify 前向已實證) 7-8 觀測盲區 error 訊息用扣減前嘅數,raise 條件對 fraction 唔敏感 觀測器 patch:admission 全代數印實數(available/slack/intermediate) 打字打到呢度先講清楚:全部 patch additive——唔開 spec flags=逐 byte 原行為,
.bak留底,py_compile 過先上樹。現狀:首個完整 spec-on boot 差最後一個 commit 接縫
(修復已落樹,驗證窗跑緊)。spec-on 數據(accept rate、單流/c=8×MTP)下一帖交。量化臂同時開咗題:draft 專家 Q2_0 化(675MB,同引擎生產配方 bitstream 一致),
draft 常駐 5GB→0.83GB——因為牆#4 證明 BF16 draft 同專家 arena 喺 24GB 卡本質唔共存。
builder 已寫好跑通(重打包 vs 官方 gguf 位元級一致)。三、附:早前同批嘅 TP2 silent 語義地雷(未發過,一併記低)
兩進程 TP2 起機後語義全錯但輸出流利、零 NaN、兩卡 bit-identical。逐層 capture 鎖定
第一個 GDN 層輸出已偏,最終根因:呢隻 artifact 用 modulo head-pairing(v-head h 讀 k-head h%16)。fla Triton kernel 嘅
i_h % Hg喺 TP 下攞本卡 local Hg=8——連續半切之下 16/48 個 v-head 讀咗隔離卡嘅
k-head。值貌似合理、兩 rank 對稱壞,shape check 全部捉唔到。interleave pairing 唔中招。呢個已經落咗 fail-closed guard(modulo×TP>1 直接 raise 拒絕起機,唔會靜靜服務壞數)。
標籤建議:
7900xtxqwen-flash多卡部署 -
-
【7900xtx·qwen-flash】prefill 36.8 → 334 tok/s:把 Strata 的三個原理逐條驗證,同 decode 真正的牆
一句話:照 Strata 的源碼+論文逐條驗證它的三個核心原理,prefill 由 36.8 升到 334.1 tok/s(9.1×),
兩日內把差距由 26× 收到 5.2×;但 decode 的真正牆不是我一開始以為的地方 —— 它藏在一個 counter 裡。
對象與環境
- 機器:雙 Radeon 7900 XTX(gfx1100,PCIe 無 P2P)· Ryzen 5 3700X(Zen2,無 AVX-512)· 94 GB DDR4
- 模型:Qwen3.8-Flash-Next GSQ IQ3_S,48 層(36 GDN + 12 attention)× 512 專家,pack 46.98 GiB
- 引擎:SGLang fork(Path B)vs Strata v0.2.7(同機同 pack,作基準)
- 量測:全部端到端 wall,
PREFILL2 aggregate/CONC aggregate走usage口徑,temperature 0 - 基準線:Strata prefill 8K = 1,719 tok/s、decode 單流 = 78 tok/s
所有數字都標明
arena / 分塊 / c / 冷暖。跨 config 唔相減 —— 這條紀律今天救了我兩次。
問題
我們(起點) Strata 差距 prefill 8K 暖 36.8 tok/s 1,719 47× decode 單流 2.37 tok/s 78 33×
做法:Strata 的三個原理,逐條量
論文與源碼(
expert_cache.hpp、prefill.cpp、docs/paper/Strata-Paper.pdf)拆得出三個可驗證的原理:- expert cache 要填滿剩餘 VRAM | 每 GB ≈ 700 個專家,每個在 VRAM 的專家就係 CPU 唔使算的。
- prefill 的 miss 要分塊 stage | 我們一次過把整層 miss 塞入 arena,arena 比 miss 數細 ⇒ 自己逐出自己。
h_layer = 0.0456| 只有 4.6% 的 (layer, token) 對能讓 10 個專家全部 resident ⇒ per-layer grouped kernel 數學上無用。
原理 1 的實測:我們白放了 13.5 GB VRAM
card0 used 20,050 / 24,560 MiB → 白放 4,510 card1 used 15,564 / 24,560 MiB → 白放 8,99648 GB 卡只用 35.5 GB。但這裡有個坑:arena 係計入
--mem-fraction-static預算的,
所以「arena 加大 + mf 調細」的直覺係錯的(mf 0.28直接被拒:權重已佔 0.419)。
card0 的 arena 天花板 ≈ 24,560 − 10,550 ≈ 13,000 MiB。原理 2 的實測:thrash 才是真兇
GSQ_GFM_CHUNK把「一次 stage 350 個 miss」切成「每批 64」。效果:arena 分塊 prefill rep1 rep2(暖) 9,500/12,000 冇 43.1 65.6 9,500/12,000 64 98.1 273.6 12,000/18,500 冇 54.9 121.8 12,000/18,500 64 101.8 330.5 12,000/18,500 128 104.0 334.1 兩個槓桿不是乘法:分塊單獨 4.17×、arena 單獨 1.86×,但兩者同時只有 5.04×。
因為分塊消除 thrash 之後,arena 變大的邊際由 1.86 跌到 1.21。
命中率確實由 52.8% 升到 67.0%,但 tok/s 只多 21% —— 成本搬到別處了。
結果
項目 起點 現在 倍數 對 Strata prefill 8K 暖 36.8 334.1 9.1× 5.2× 落後(原 47×) prefill 8K 冷 25.5 104.0 4.1× — decode 單流 2.37 2.89 1.22× 27× 落後 decode c=8 聚合 — 19.86 8.95× vs c=1 Strata 做不到 命中率 @40k 52.8% 67.0% +14pp Strata adaptive 72%
decode 真正的牆:唔是 staging、唔是命中率、唔是併發
四個窗的 steady-state 最小二乘(n=26,交叉檢查誤差 +0.7%):
ms/fwd = 10.887 × units_staged + 0.567 × hits hits + units = 479.98/fwd = 48 層 × top_k 10 ← counter 自洽 356.3 ms/fwd 拆解: hit 路徑 268.0 ms (75.2%) ← 牆 staging 79.3 ms (22.2%) rendezvous 0.6 ms (0.2%) 未解釋 8.4 ms480 個 hit unit × 0.567 ms = 272 ms。 這個數字同「48 層 × 10 專家」完全對得上,
所以它不是數學成本,而是「每個 expert 一次 dispatch」。對照 Strata:78 tok/s = 12.8 ms/fwd,我們 356 ms —— 28×。
Strata 的論文原話:「The whole 48-layer pass is one captured CUDA graph per window size,
so the host never waits on a synchronizing driver call.」而我們全部窗都帶
--disable-cuda-graph。試過開 graph,引擎直接在載入前拒絕:GSQInvariantError: cuda graphs not disabled (GSQ v1 needs --disable-cuda-graph; GSQ_V2=1 lifts this only for the v2 fast path)引擎自己知道 graph 要配
GSQ_V2=1。 這是下一步。
併發:Strata 結構上做不到的 8.95×
c 每流 聚合 vs c=1 1 2.22 2.22 1.00× 2 3.67 7.34 3.31× 4 3.15 12.61 5.68× 8 2.48 19.86 8.95× c=1 : 450 ms/forward(每次 1 token) c=8 : 403 ms/forward(每次 8 token) ⇒ 8 倍 token、0.90 倍時間。第 2–8 個 token 邊際成本 ≈ 0原因同上面的歸因一致:forward 的成本是按 480 個 expert 計,唔是按 token 計。
Strata 是 FIFO 串行,這個乘數它拿不到。但誠實講:我們 c=8 的聚合 19.86,仍然只有 Strata 單流 78 的 25%。
併發乘的是一個太細的基數。
今日我錯了三次(都記在 counter 與日誌裡)
pin190結論作廢 —— 我以為「arena 按熱度釘選」無效,其實我的 patch 有 bug:
TOP=190對 190 個 slot ⇒ 每個 resident 都被 pin ⇒victim is None⇒ 退化成純 LRU。
即係根本冇測到 pin。這是對抗審查(Claude Code,16 KB 逐 file:line)抓出來的。- 「跨層 prefetch 是第一槓桿」錯 —— 它只碰得到 staging 的 22%。真牆在 hit 路徑。
- 一個 agent 講「prefill 從來冇行 grouped kernel」 —— 我用 counter 反證:
4049a bypass次數 = 0,加上源碼註釋「GSQ_GROUPED_DEVICE_MAX_TOK=offrestores the old
same-device behaviour」,證明 prefill 確實在跑 kernel。
三個結構性後續(都不是兩日能完成)
# 做什麼 狀態 1 packed bytes 直接餵 kernel 早已在樹上( GSQ_GROUPED_FIRST_MISS=1)。kernel 本來就食 packed bytes;read_dequant那個 28% 來自未開 GFM 的舊 config,不適用於快路徑。2 固定 ring + 指針表 + CUDA graph 週級工程。 v2_fast_launch目前零 call site,且 AT=24 拓撲下 capture 死在跨卡 index(index is on cuda:1)。天花板取決於 decode 的 device-busy 比例,未量。3 H2D 與 compute 重疊 降級。PROF 的 h2d桶量的是 4039 fallback 路徑的 blocking pageable copy(bf16, 10.65 MB/expert),而 arena 走 packed blob(2.33 MB/expert)。按 ρ=4.57 重算,真路徑的 h2d 只佔 4–16%,原本「≥30% 才做」的判準在目標路徑上不成立。這三條都不是可以「今晚開工、明天收貨」的東西。 原本我把 #1 當成最快的一刀,查證後發現它已經完成了;#3 的 33% 是我用舊路徑的數字去論證新路徑,跨 config 了。
真正的問題回到一個更基本的量測:decode 的 362 ms 裡,GPU 忙幾多 %?
≥60% ⇒ device-bound,槓桿在 kernel(per-group 小 GEMM、無 dedup);
≤30% ⇒ host-bound,graph 與 batching 才有意義。
這個數字未出之前,任何結構性重構都只是押注。
三條方法論紀律(其中一條是今天我違反了的)
- 預先寫判準,再看數據。 我寫死「h2d 佔比 ≥ 30% 才值得做 stream 重構」,量到 33%,
一度以為過關。但查 PROF 的定義之後發現:那個h2d桶量的是 4039 fallback 路徑
(dequant 後 bf16、10.65 MB/expert),而 arena 走的是 packed blob(2.33 MB/expert)。
同一個名字,兩條路徑,差 4.57 倍 bytes。 判準本身沒錯,是我把它套在錯的路徑上。 - 跨 config 唔相減,跨路徑唔借比例。 這條我今天犯了三次(arena vs 分塊、
h2d舊路徑 vs 快路徑、read_dequant的 28% 其實來自冇開 GFM 的 config)。
每次都是「同一個指標名」讓我放鬆了警惕。 - counter 要自證。
hits + units = 479.98/fwd = 48 × 10這種恆等式,比任何解釋都可靠。
反面教材:一個 agent 用「全部 512 專家都要搬」推出 51.9 GB 上限,而我們實際只搬 33% miss。
現在的誠實定位
prefill 334.1 vs 1,719 → 差 5.2×(起點 47×) decode 2.89 vs 78 → 差 27×(單流) 19.86 vs 78 串行 → c=8 聚合仍然落後 3.9×兩日內能交的是「prefill 9.1× + 併發 8.95× + decode 完整的歸因」,
不是「追平 Strata」。要追平必須完成上面三件結構性的事 —— 那是週級,不是日級。把這句話寫在交付前面,比交付一個漂亮數字重要。
-
,系统 取消固定了此主题
版主审定:本帖设为精华,作者 +6 积分奖励,希望再接再厉!
本帖参与讨论人数已达 10+,入选热议,作者 +3 积分奖励!