iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
IT Operation

地端機房的三十天維運:開源服務封裝、GPU 節點守護與可稽核的變更管理系列 第 10

Day 10|上游引擎一改版,地端 AI 環境用的配方壞了怎麼辦 ? patch 移植實戰紀錄

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260924/20141816GFV35kdguT.png

前傳:這兩台機器的雙機 RoCE 直連、KV 池與跨機張量平行的前提,都在前一個系列的 Day 27|雙 DGX Spark GB10 合體與分工合作的部署與實測。這篇是同一台機器的續集。

這篇系列的前段文章是以封裝為主,如Day 3 說 image 要鎖 digest,理由是上游的 tag 會被移動。今天用一個真實案例示範同一件事的另一面。當你的服務不只是官方 image,還疊了一層社群 patch,上游一發正式版,patch 會壞在哪裡、怎麼判斷哪些還要留、怎麼證明搬過去的東西跟原來一樣。這也是本系列開始中段文章後,第一次真的動到這兩台 DGX Spark。Day 9 算出來兩個模型擠在一台時餘裕只剩 6 GB 不能當平時組態,今天的實驗就緒後可用記憶體只剩約 2 GiB,這是地端 AI 環境偶爾會碰到的情形,本文的第五節與第六節會說明這個數字。

環境:兩台 GB10(各 128 GB 統一記憶體),以 RoCE v2 直連
程式碼:ivanusto/,釘在 tag <tag>。差異檔在 patches/,掛載的九個檔案在 files/,量測條件另見 RESULTS.md

vLLM 0.30.0 這個著名 AI 推論引擎平台的 GitGub 發行說明上面寫著支援 DeepSeek V4.1 Flash。看到這行字,合理的期待是,拉官方映像、指向權重、開跑。實際在兩台 GB10 上試,會發現距離「開跑」還有九個檔案的距離。

這篇記錄的是把一份社群配方從 2026-09-11 的 nightly 版搬到官方正式版的完整過程。
會寫下來,是因為過程中遇到的每一個問題都不是這個模型獨有的,而是每個模型會碰到的,當「上游改版之後,既有 patch 怎麼搬」這件事的通則。

重點先放在這邊

  • 原版引擎不足以開箱即用:注意力 kernel 缺了雙機需要的實體化版本,分頁幾何也還要修,而對應的上游修正到 2026-09-23 為止仍未合併。
  • 原配方的七個 patch,最後只有三個能直接沿用:兩個因為目錄改名而完全失效,一個已被官方參數取代,一個在自動套用時安靜地落到錯誤位置。
  • 移植完成的成果是九個檔案的修改,功能驗收全過。但這個組合不會拿來當正式服務, 原因在文末說明,而且這個判斷在動手之前就能推導出來。

名詞說明

熟悉地端 AI 推論部署的讀者,可以跳過這節,這段是給較不熟悉地端 AI 推論的人獲得一些心得用的。

  • 張量平行:把同一層的矩陣運算切開,分給多張卡同時算。這裡是切給兩台機器,所以每一步都要跨機交換資料,
    網路延遲直接吃進每個 token 的時間。
  • RoCE:讓遠端直接記憶體存取跑在乙太網路上的做法。兩台機器直連、不經交換器時常用這種配置。
  • 統一記憶體:CPU 與 GPU 共用同一池實體記憶體。好處是不必來回搬資料,代價是「把東西放到主機記憶體省下顯示記憶體」
    這類常見手法完全失效,本文第一節的困境就來自這裡。
  • 量化與 bpw:把權重用更少的位元表示以換取容量與速度,bpw 是平均每個權重幾個位元。
    FP8、MXFP4、NVFP4 是不同的數值格式,EXL3 則是某個社群專案的格式。
    格式是否被引擎原生支援,決定了能不能用官方映像直接載入,這是本文選型的主要限制。
  • 專家混合與剪枝:這類模型每一層有數百個「專家」子網路,每個 token 只會用到其中幾個。
    剪枝就是刪掉貢獻較低的專家來縮小模型,代價是品質下降。本文用的檢查點是從 384 個剪到 256 個。
  • 困惑度:衡量語言模型預測文字有多「不意外」的指標,數字越低越好。用來比較剪枝或量化造成的品質損失。
  • KV 快取與前綴快取:生成時把先前算過的中間狀態留著,避免重算。若新請求的開頭和舊的一樣,
    還能直接重用那段,這就是前綴快取。所以同一個提示送第二次一定比較快,量測時要把冷熱分開報。
  • 首個 token 延遲:送出請求到收到第一個字的時間,決定互動時的體感,解碼速度則決定後續吐字的快慢。
  • 投機解碼與草稿模型:先用一個小模型快速猜出接下來幾個 token,再讓大模型一次驗證。
    猜中就省時間,猜錯就丟掉重來。本文的組態是一次猜五個。
  • kernel 與實體化:kernel 是在 GPU 上執行的運算核心。有些 kernel 會針對固定的形狀(例如每張卡幾個注意力頭)
    預先產生專用版本,形狀不在清單裡就沒有可用的版本,這是第五個發現的根源。
  • CUDA graph 與 eager:CUDA graph 把一串 GPU 操作錄下來重播以降低開銷,eager 則是每次逐一送出。
    本文因為每一步都要回主機讀檔,只能用 eager。
  • Engram:這個模型特有的一組查表權重,推論時依 n-gram 雜湊去查對應的列。它體積很大但每步只用到少數幾列, 這個性質正是「留在磁碟」可行的原因。
  • 針取回測試:在很長的文章中埋入一段隨機代碼,再要求模型原樣找出來,用來驗證長上下文沒有壞掉。

一、問題在哪裡呢?

DeepSeek V4.1 Flash 這個新模型現有的幾個版本,有兩個特別之處,決定了移植的難度。

第一,它有一組叫做 Engram 的查表權重,每張卡分到約 47 GiB。在一般伺服器上,可以把這張表放在主機記憶體,需要時再讓 GPU 讀過去。但在統一記憶體架構的機器上,「主機記憶體」和「顯示記憶體」是同一池,這個做法一點好處也沒有。
社群配方的解法是讓這張表留在磁碟,每一步只讀當下用到的那幾列,在 CPU 上解量化後送進 GPU。
主權重約 100 GiB 加上這 47 GiB,本來就塞不進 128 GB。

第二,跨兩台機器做張量平行時,注意力的 kernel 是按「每張卡分到幾個頭」預先編譯的。
兩台機器的分法和四台機器不同,把一份為四台寫的 patch 直接拿來用,只會得到一模一樣的錯誤訊息。

二、先選對檢查點

原版引擎只認得它自己支援的量化格式,社群常用的 EXL3 不在其中。加上兩台機器的容量上限,能選的其實很少:

候選 格式 大小 可否用原版引擎
Mia-AiLab EXL3 2.9bpw3.0bpwcoolbho3k 3bpw EXL3 197 到 400 GiB 否,需要配方自帶的載入器
nvidia NVFP4 與第三方 FP8 原生格式 475 到 491 GiB 格式可以,但兩台放不下
LibertAI REAP-256E(384 個專家剪到 256) 專家 MXFP4、其餘 FP8 196 GiB 可以

只有最後一個同時滿足格式與容量。代價是品質,作者在模型卡上明白寫出剪枝讓文字困惑度上升約 14.1%、
圖片說明上升 8.9%,而且該配方本身把上下文限制在 32K。這兩點在動手前就要想清楚,因為它們決定了移植成功之後,這個配方能不能真的拿來用。

還有一個細節容易漏掉:REAP-256E 的倉庫沒有附 Engram 的那兩個分片(編號 47 與 48),要從原始模型取得。
如果機器上已經跑過同一個模型的其他量化版本,那兩個分片通常就在本機,可以直接接進來,省下數十 GB 的下載。

三、移植途中的發現

1. 目錄改名,就讓整檔掛載全部失效

新版把模型程式的目錄改了名,並把 Engram 相關的程式拆成兩個檔案。
原配方的做法是「準備好整個檔案,啟動時覆蓋掉映像裡的同名檔」。目錄一改,這種掛載要嘛掛到不存在的路徑,
要嘛掛上去的是舊版程式,把新版的修正一起蓋掉。後者特別危險,因為它不會報錯。

做法:不要沿用整檔覆蓋。找出原配方的差異(多數專案會附 diff),逐一評估每一段在新版還需不需要,再套到新版檔案上。

2. 自動套用的模糊比對,會把程式碼放到錯的地方

套 patch 工具在上下文對不上時,會降低比對嚴格度去盡量套上。其中一段就這樣被塞進了另一個函式的 return 敘述中間:

            alignment=(
                576
                if uses_fp8_ds_mla_layout and not _use_v41_mxfp8_kv_record()
        if _dsv41_indexer_sm12x():            # 被塞進來的兩行
            return DeepseekV4IndexerSM12xBackend
                else 512
            ),
        )

這段之所以被抓到,是因為它剛好造成語法錯誤。如果落點再偏一點,變成語法合法但語意錯誤,就會一路帶到執行期,最後表現成某個難以解釋的品質問題。

解法
允許模糊比對,但每一個勉強套上的段落都要仔細看過,套完務必做一次語法檢查。順帶一提,同一個 patch 引用的類別在新版也被改了名,要改成繼承新的基底類別。

3. 有些 patch 已經不必存在

原配方有一段是強制改用某支 top-k kernel,因為預設的那支在這張卡上會超出共用記憶體。
新版把這個選擇做成了正式參數,一個啟動旗標就能指定。

解法
移植前要先查上游收了什麼。已經收進去的就刪掉,做成可設定項的就改用參數。
這樣的好處是,每刪掉一個 patch,之後每次改版就少一份維護成本。

4. 上游還沒合併的修正,可以只取需要的部分

vLLM #57028(zhao6300 提出,截至 2026-09-23 仍未合併)
處理的正是這張卡的分頁幾何問題。它的修改和原配方有一段重疊,但另外還帶了兩個空批次的保護。
我的做法是重疊的部分沿用原配方(因為那份已經在實機跑過),另外兩個保護從上游 PR 取用。

解法
把「已經被實機驗證過」和「上游認為正確」當成兩個不同的來源,逐段挑選,不做二選一。

5. kernel 的實體化和平行度綁在一起

注意力 kernel 依「每張卡的頭數」預先產生。兩台機器分到的頭數是四台的兩倍,而函式庫沒有內建這個尺寸。
原配方附了修改原始碼的 patch ,但映像裡有預先編譯好的產物,會讓即時編譯整個跳過, patch 形同不存在。

解法
把預編目錄用空目錄遮蔽,逼它走即時編譯,編完後用符號表確認目標 kernel 真的存在。
不要看到建置成功就當作完成,因為建置本來就會成功,只是產物是舊的。

四、Engram 讀磁碟:怎麼確認自己沒寫錯

這段是移植裡唯一需要重寫的部分。原本的實作和舊版程式糾纏在一起,新版的結構反而更清楚,查表的類別已經把怎麼配置權重與怎麼取用權重拆成可以覆寫的方法,所以磁碟模式可以做成一個選項,不去動共用的程式路徑。

真正的問題是怎麼知道自己在 CPU 上重寫的解量化,和 GPU 上那支 kernel 算出來的完全一樣?

解法是寫一支對照測試,用真的安全張量檔當輸入,同時跑兩條路徑,比較輸出:

  • 兩種分片切法,確認每張卡只讀自己那段列,而且起始位移正確。
  • 補位的頭與「不屬於這張卡」的列,兩條路徑都要寫出零。
  • 重複的 id 與無效 id,確認去重之後還能還原回原本的順序。

驗收標準訂在逐位元相同,不是誤差夠小。這類查表沒有浮點累加順序的問題,本來就該完全一致。
如果只要求接近,就會把真正的缺陷放過去。實測兩個切片都逐位元相同,這才算可信。

值得一提的是,這個模式必須關掉 CUDA graph,因為每一步都要回到主機讀檔。程式裡直接在圖擷取時丟出明確的錯誤,不讓它默默產生錯誤結果。

五、移植結果

移植後的組態

官方正式版映像,加上九個檔案的修改與一支自行編譯的 kernel,兩台機器張量平行,以 RoCE 直連,32K 上下文,關閉 CUDA graph,開啟投機解碼。

啟動情形

主權重 48 個分片從網路檔案系統載入 22.6 分鐘,投機解碼的草稿模型再掃一輪約 44 分鐘,加上剖析與暖機,合計約 69 分鐘可服務。KV 池 372,529 個 token。

功能全部 pass

  • 對數機率是有限值(這項是用來抓 NaN 的。輸出變成 NaN 時,內容看起來仍然像正常文字,只有這裡會顯示出來)
  • 兩萬六千 token 的埋針測試取回正確
  • 工具呼叫與結果回填正確
  • 繁體中文短文與一萬一千 token 長文,簡體字零個,語意正確
  • 思考模式開啟時推理正常、答案正確

效能
單路解碼每秒 9 到 10 個 token,八路併發總吞吐每秒 31.5 個,暖啟動首個 token 延遲 0.92 秒。解碼期間 GPU 使用率 83% 到 96%,屬於計算受限。這個數字只有配方作者公布值的一半左右,原因沒有再追。

六、方法學

數字若不附條件就沒有意義,這節把這次量測的條件寫清楚,方便判讀與重現。

軟硬體

項目
機器 兩台 GB10,各 128 GB 統一記憶體,以 200 Gb 等級網路直連,NCCL 走 RoCE v2
引擎 官方映像 vLLM 0.30.0(aarch64),torch 2.13.0+cu130,FlashInfer 0.6.18.post1
驅動 580.178.04。與配方作者的基準(580.173.02)不同,所以跨配方比較時這是一個未受控的變因
權重 REAP-256E,固定於撰稿時的最新修訂版,Engram 分片取自原始模型
啟動參數 張量平行 2、32K 上下文、最大併發序列 8、單批 4096 token、GPU 記憶體比例 0.90、關閉 CUDA graph、投機解碼 k=5、純文字模式、思考預設關閉

測試腳本的來源

  • 正確性用的是配方作者附的驗證腳本,未修改。它刻意用預設請求形狀(不帶任何選用旗標)測一次,
    因為只在自訂形狀下測會漏掉預設行為的問題。
  • 效能與品質用的是我自己既有的一組腳本,和先前量測同一台機器上其他配方時用的是同一份,
    所以本文的對照數字(例如 EXL3 路線的每秒 26 到 37 個 token)是同工具、同提示、同取樣條件下得到的。

測試條件

  • 解碼速度:400 token 的繁體中文散文生成,溫度 0,串流計時,併發 1、2、4、8 各兩輪,
    取每一路的解碼速率與總吞吐。單路數字另外再單獨重測三輪,因為併發測試會在同一個伺服器上留下排隊效應。
  • 首個 token 延遲:同一段 7,813 token 的提示連送三次,第一次為冷、後兩次為暖。
    這台的前綴快取會命中,所以暖啟動的數字不能拿來當冷啟動用,兩者要分開報。
  • 針取回:在 13,835 與 26,030 token 的長文中埋入隨機代碼,要求原樣取回,溫度 0。
  • 中文品質:短文與 11,126 token 長文,溫度 0 與 1.0 各一次,機械統計輸出中的簡體字數量,
    再人工讀過語意。這裡不用「和參考答案逐字相同」當標準,因為長提示下分歧本來就是預期行為。
  • 系統面:解碼期間同時取樣 GPU 使用率與虛擬記憶體統計,用來分辨「計算受限」與「被磁碟或置換拖住」。
    這次確認是前者:GPU 使用率 83% 到 96%,置換換入接近零。

這次量到的限制,以及沒有做的事

  • 單路解碼在不同輪次落在每秒 7.4 到 15.8 個 token,區間不算窄。文中引用的 9 到 10 是重測後的中位水準,但樣本數少,應該當作數量級而不是精確值。
  • 只跑了一組啟動參數。記憶體比例與 KV 大小都沒有調校過,而就緒後可用記憶體僅剩約 2 GiB,
    這很可能就是速度只有作者公布值一半的原因之一,但沒有驗證,所以不把它寫成結論。
  • 沒有自行量測困惑度,品質數字引用自模型作者。
  • 每項測試各跑一次或兩次,沒有做統計顯著性。
  • 這是實驗環境,不是正式服務,量測期間機器上沒有其他工作負載。

七、那麼,要不要用這個配方呢 ?

不建議當正式服務用,理由有三個:

  1. 上下文只有 32K,長對話用不了。
  2. 剪枝有品質代價,模型作者自己量到的文字困惑度上升 14.1%。
  3. 在同樣兩台機器上,EXL3 路線的單路解碼是每秒 26 到 37 個 token,這次移植的結果只有其三分之一到一半。

那為什麼還要做?因為它回答了一個原本大家只能猜的問題,也就是當上游雖然正式支援了這個模型,但在這張卡上還沒到開箱即用,缺的是雙機的 kernel 實體化與分頁幾何修正。等上游把這兩塊補上,這份移植就是現成的起點。

在那之前,這篇實作,是實務上跑這條路線時,用戶會碰到缺什麼的具體證據。

八、可以參考的心得

  1. 先確認格式與容量,再談移植。格式不被原版引擎支援的檢查點,patch 再多也沒用。
  2. 用差異,不要用整檔。整檔覆蓋在改版時會把上游修正一起蓋掉,而且失效時未必看得出來。
  3. 每一段勉強套上的 patch 都要複查,並在套完立刻做語法檢查。
  4. 移植前先查上游收了什麼。已收進去的刪掉,做成參數的改用參數。
  5. 重寫的計算要有逐位元的對照測試,而且測試要涵蓋邊界,分片切法、補位、無效值、重複值。
  6. 建置成功不等於 patch 生效。預編產物會讓原始碼層級的修改完全失效,要用符號表之類的方式確認。
  7. 先寫下這次移植成功之後要拿它做什麼。這次的答案是不取代現有服務,而這個答案在動手前就能推得出來。寫下來,可以避免最後被沉沒成本推著走。

致謝與來源

這次移植站在幾份公開工作上,沒有它們不可能在一個晚上完成:

  • Libertai/dsv41-flash-vllm-2x-spark:兩台機器的完整配方,
    本文移植的對象。它的啟動器、記憶體看門狗與那支驗證腳本(用對數機率抓 NaN 的點子就出自這裡)我都直接沿用。
    權重是 LibertAIDAI/DeepSeek-V4.1-Flash-REAP-256E
    剪枝方法與品質數據作者都公開得很完整,包含對自己不利的部分。
  • tonyd2wild/DeepSeek-V4.1-Flash-vLLM-DGX-Spark
    GB10 這張卡的 patch 集源頭(四台機器的配置),包含 Engram 讀磁碟、分頁幾何與 top-k 的修正。
    該專案把每個修正拆成獨立目錄,各自附上差異、離線測試與筆記,是這次能逐段評估而不是整包照抄的關鍵。
  • MiaAI-Lab/DeepSeek-v4.1-Flash-EXL3-2x-DGX-Sparks
    另一條路線(EXL3 量化),本文用它當速度對照。它在同樣兩台機器上的單路解碼是每秒 26 到 37 個 token。
  • coolbho3k/DeepSeek-v4.1-Flash-2x-DGX-Spark
    同期出現的第三條路線,把 KV 放進顯示保留區換到更長的上下文,本文沒有實測,列在這裡供比較。
  • vLLM #57028:本文取用了其中的空批次保護。

社群配方的價值不只在能跑,更在於作者願意寫下限制。上面幾份都清楚標明了自己的代價,方便我們實作時能評估。

本 repo 以 Apache-2.0 釋出,與 vLLM 一致,各來源的歸屬見 NOTICE

附錄:這次移植的變更紀錄

Day 26 之後談變更管理時會用 ITGC 的形狀寫變更單,這一節先用同一個形狀把這次移植記下來。前六列是移植的規模,後四列是變更單一定要有而技術文章常常漏掉的部分。

項目 內容
變更前的基線 社群配方 Libertai/dsv41-flash-vllm-2x-spark,對應 vLLM 2026-09-11 nightly,配方作者的驅動基準 580.173.02(待填:配方 repo 當時的 commit)
變更目標 官方映像 vLLM 0.30.0(aarch64),torch 2.13.0+cu130,FlashInfer 0.6.18.post1,本機驅動 580.178.04
原配方 patch 7 個。直接沿用 3 個,人工修正後沿用 1 個,完全重寫 2 個(Engram 讀磁碟與載入器過濾),因上游已支援而刪除 1 個
另從上游未合併 PR 取用 vLLM #57028 中 3 個檔案的片段,截至 2026-09-23 該 PR 仍未合併
自行編譯的 GPU kernel 1 支,以符號表確認目標實體化存在
最終修改檔案數 9 個,約 520 KB,全部以差異檔記錄在 patches/
驗證方式 功能驗收五項全過(對數機率有限值、26K 針取回、工具呼叫、繁中長短文零簡體字、思考模式)。Engram 的 CPU 解量化與 GPU kernel 逐位元對照,兩種切片皆相同。效能與方法學見第五、六節與 RESULTS.md
回退路徑 拉回官方映像、不掛載 files/ 下任何檔案,即回到未修改的 vLLM 0.30.0。若要回到可服務狀態,改用既有的 EXL3 配方
變更後的決策 不上正式服務,理由見第七節。此組合保留為「上游還缺什麼」的證據與日後的起點
耗時 移植與驗證約 3 小時,不含兩次載入的等待(約 69 分鐘可服務)

明天

Day 11 是 DGX Spark 的節點基線。今天的移植在一台已經跑過多個配方的機器上做,Engram 分片直接從本機接進來、驅動版本與配方作者不同,這些都是「機器目前是什麼狀態」沒有被記錄下來的結果。明天從 DGX OS 的初始設定、帳號與 SSH 加固開始,把兩台機器整成一樣的、可以描述的狀態,之後 Day 12 的守護與 Day 19 的指標收集才有一致的對象可以量。


系列文章與程式碼索引:onprem-ops-30days

本日程式碼:ivanusto/ @

前一個系列:Day 27|雙 DGX Spark GB10 合體與分工合作的部署與實測


上一篇
Day 9|容量規劃:用模型與硬體搭配矩陣決定地端節點與儲存規格
系列文
地端機房的三十天維運:開源服務封裝、GPU 節點守護與可稽核的變更管理10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言