~/blog/run-119b-moe-on-3x-2080ti

洋垃圾跑大模型 · part 1

[洋垃圾跑大模型 #1] 一台洋垃圾三張老卡,怎麼跑得動 119B 的大模型

cat --toc

TL;DR

一台約 5 萬台幣的洋垃圾機(二手 EPYC + 三張改裝 2080 Ti,共 66G VRAM)跑得動 119B 的 Mistral Small 4,74 tok/s。訣竅:MoE 的 expert 大多在睡,把睡著的丟去便宜的 RAM 給 CPU 算,吐字只慢 2.4 倍。⚠️ 多卡卸載有個坑:卸「前 N 層」單卡沒事、三卡直接 OOM。

白話版:大模型不是塞不進便宜顯卡,是你沒讓它「輪班上工」

AI 模型越大通常越聰明,但也越吃「顯卡記憶體」(VRAM,顯卡上專用的高速記憶體)。一張家用顯卡大概 24G、我這種二手改裝卡一張 22G,聽起來一顆上百 GB 的大模型根本沒得玩。

但有一類模型叫 MoE(混合專家):它裡面養了一大群「專家」,每吐一個字只叫醒其中一小群出來算,其他都在睡(我這篇測的那顆,是 128 個專家裡每次只叫 4 個)。既然大部分專家隨時都在睡,那就沒必要讓它們全擠在昂貴的顯卡記憶體裡——把睡著的丟去便宜、量大的一般記憶體(就是你電腦裡那種 RAM),要用的時候再叫 CPU 去算就好。

於是一台二手零件湊出來、大概五萬塊的機器,就跑得動別人以為要專業卡才跑得動的大模型。這篇講的就是這招怎麼運作、慢多少(我拿一顆小的先量給你看),還有我自己踩到的一個多卡坑。


前言

去年開始,我下班就在家修修補補,當個垃圾佬撿二手零件湊機器。這台是其中最誇張的一台:二手企業級 EPYC 主機、八通道塞滿 128G 記憶體、外加三張改裝過的 2080 Ti 22G(魔改把原本 11G 的老卡加到 22G),全部加起來大概台幣五萬。

一開始只是想拿它跑跑圖、養幾個本地小模型。裝到一半腦裡冒出一句:「反正記憶體這麼大,不如塞塞看最新的大模型?」——然後就有了這個系列。這是第一篇,先講最核心的一件事:這台便宜機到底憑什麼跑得動百億級的大模型。 下一篇再來選,哪一顆值得留下來當常駐。

目標:在只有 66G 顯卡記憶體的洋垃圾機上,跑 100B+ 的量化大模型

先把目標講清楚。三張 2080 Ti 合起來 66G 顯卡記憶體,聽起來不小,但現在動輒 100B、200B、甚至 300B 參數的開源大模型一狗票,光權重就上百 GB,66G 怎麼看都不夠塞。

我要的不是「勉強跑個 demo」,是跑得動、還能實際拿來用:速度別慢到不能忍、開機就在、不用每次都連雲端燒 API 的錢。

好處:一顆常駐自己家、不燒 API 的大模型

跑得起來的好處很直接:你有一顆放在自己家、電費級成本的大模型,可以接進自己的工具流程、丟工作給它,資料完全不出門。不用每一句都送去 OpenAI 或 Anthropic 計費,也不用擔心哪天 API 漲價或碰到 rate limit。

對我這種喜歡自己搭一整套 agent 的人來說,這才是重點:大腦握在自己手上。

怎麼達成一:119B 剛好塞得進三張卡,74 tok/s,沒有魔法

第一件事其實沒什麼特技。我拿來當主力測的是 Mistral Small 4——Mistral 2026 年 3 月出的 119B MoE 模型,每吐一個字只用到 128 個專家裡的 4 個,真正在算的大約只有 6.5B。量化到 Q3 之後大約 55G,三張卡湊出的 66G VRAM 剛好塞得下

用 llama.cpp 的 layer-split 把模型的層拆給三張卡(-sm layer),各分一段,全部進 VRAM。實測吐字 74 tok/s——一顆掛著 119B 名號的模型,每步其實只算約 6.5B,這也是它在三張十二年前架構的老卡上還能有這速度的原因。

這一段的重點是:只要塞得下,就沒魔法。 量化壓到 66G 以內,分給三張卡,跑起來。難的是下一段——塞不下的時候怎麼辦。

怎麼達成二:塞不下就讓 expert 睡去 RAM,吐字只慢 2.4 倍

想再往上跑 200B–300B,或是想把其中一張卡騰出來做別的事(例如生圖),顯卡記憶體就真的不夠了。這時候 MoE 的特性就派上用場。

機制:MoE 每吐一個字,只會用到一小部分 expert(專家),其他都不算。既然大部分 expert 當下都用不到,就用 llama.cpp 的 --override-tensor(簡稱 -ot)把這些 routed expert 的權重丟去 RAM、交給 CPU 算,VRAM 只留真正在動的部分。八通道 DDR4-2133 的記憶體頻寬實測約 105 GB/s(理論約 136),撐得起這種串流。

MoE expert offload 的記憶體分配:三張 2080 Ti 的 66G 顯卡記憶體(616 GB/s)只放 attention 跟當下在算的權重,睡著的 expert 全丟去 128G 一般記憶體(105 GB/s)給 CPU 算,要用時才串流過去——洋垃圾跑大模型系列 #1。

代價要量、不要猜。 我沒有直接拿大模型硬跑,而是先拿一顆好操作的 26B MoE 做對照,量出「全塞顯卡」跟「expert 丟 CPU」到底差多少——先押注、再實測,這是我做 benchmark 的老規矩:

跑法吐字 (tok/s)相對速度
全部塞顯卡記憶體1131.0×(基準)
routed expert 全丟 CPU47.62.4×
只丟一半的層69.1慢 1.6×

把 expert 從顯卡丟到 CPU 算的代價,26B 實測:全塞 VRAM 113 tok/s、丟一半層 69.1(慢 1.6×)、expert 全丟 CPU 47.6(慢 2.4×);短對話幾乎無感,讀 prompt 慢更多約 5.3×——洋垃圾跑大模型系列 #1。

三個數字,一個結論:把 expert 丟去 RAM,吐字只慢 2.4 倍,老 EPYC 當 expert 引擎其實夠格。 兩個提醒:

  • 讀 prompt(prefill)那段慢更多,大約 5.3 倍——因為 prefill 是一次算一大批,CPU 在這裡最吃虧。所以這招最適合短來短往的判斷型對話,不適合餵超長文件。
  • 丟一半不會剛好慢一半(丟一半是 69.1,不是 113 跟 47.6 的中點)——offload 的代價不是線性的,別用「丟越多線性越慢」去估。

debug:同一個卸載指令,單卡沒事、三卡直接 OOM

真正卡我最久的不是速度,是把上面那顆 119B 從「三卡全速」改成「兩卡跑模型、第三張卡讓出來生圖」的時候。

兩張卡塞不下整顆 119B,得把一部分 expert 丟去 CPU。我很自然地下了「把前 N 層的 expert 丟去 CPU」這種指令——-ot "blk\.(前面幾層)\.ffn_.*_exps.*=CPU"。8 層、9 層、10 層、11 層,一路加上去,全部 OOM(記憶體爆掉)載入失敗。

奇怪的是:我明明一直在增加「丟去 CPU」的層數,llama.cpp 想在第二張卡上配的 buffer 卻固定釘在 24,835 MiB 不動——那比一張卡的 22.5G 還大,當然爆。丟再多層,它要配的量就是不降。

這行數字就是破案關鍵。三張卡是用 layer-split 分工的——模型的層,早就被拆給不同張卡了。我卸掉的「前 N 層」剛好全部落在第一張卡的管區,所以第一張卡越來越鬆,第二三張卡一根寒毛都沒動,該爆還是爆。

說穿了,我以為我在幫整台機器減重,其實我只在幫第一張卡減重,另外兩張卡在旁邊看戲。

多卡卸載的坑:三張卡用 layer-split 各自分到一段層(GPU0 層 0–20、GPU1 層 21–41、GPU2 層 42–62),卸「前 N 層」的 expert 全落在 GPU0 的管區,只有 GPU0 鬆了、有空間;GPU1/GPU2 原封不動、照樣 OOM。解法是把卸載的層分散到各卡的 layer 範圍——洋垃圾跑大模型系列 #1。

解法不是卸「前 N 層」,是按每張卡各自分到的 layer 範圍、分散去挑層卸——每張卡都被拿掉一些 expert,VRAM 用量才會一起降。改成分散卸之後,兩卡版終於站穩,吐字 44–49 tok/s,第三張卡順利讓出來生圖。

收穫

最花時間的不是「怎麼卸」,是「卸到哪張卡」。 單卡的直覺(想省記憶體就卸前面幾層)搬到多卡直接失效,而且錯誤訊息只跟你說 OOM,不會告訴你「你卸的層根本不在那張爆掉的卡上」。真正戳破它的是那個「怎麼加層都不降」的固定數字——一個不動的數字,比一堆會動的數字更會說話。

可以搬去別的地方用的一招:代價要拿小的先量,再外推大的。 我沒有拿 200B 的模型盲試,而是先用一顆 26B 量出「expert 丟 CPU = 慢 2.4 倍、prefill 慢 5.3 倍」的係數,之後每一顆更大的模型都用這組係數先估、再驗。省下的是好幾個小時的盲試。

通用原則一句話: 便宜硬體跑大模型,關鍵從來不是「顯卡記憶體夠不夠」,是「你懂不懂哪些權重根本不用進顯卡」——MoE 的 expert 大多時候在睡覺,而睡著的東西沒必要睡在最貴的床上。

收工前的四條 checklist

  1. 先算模型量化後的大小,對得上總顯卡記憶體就直接塞。 119B 的 Q3 大約 55G,三張 22G = 66G 塞得下,layer-split 全進顯卡,不用 offload。
  2. 塞不下再開 expert offload。 MoE 專用:-ot "ffn_.*_exps.*=CPU" 把 routed expert 丟一般記憶體;代價大約吐字慢 2.4 倍、讀 prompt 慢 5.3 倍。
  3. 多卡卸載一定要分散到各卡的 layer 範圍。 卸「前 N 層」只會鬆到第一張卡;盯「怎麼加層都不降」的那張卡,它才是真正爆的地方。
  4. 記憶體頻寬決定 offload 好不好用。 八通道 DDR4(約 105 GB/s)撐得起;雙通道桌機記憶體會慢很多,別直接套我的數字。

這是「洋垃圾跑大模型」系列第一篇。跑得動是一回事,該留哪一顆當常駐大腦又是另一回事——下一篇我會把 119B、230B、295B 幾顆一起擺上檯面比,講為什麼我最後留了比較小的那顆。工具本身:llama.cpp


常見問題

一張 32G 的顯卡都塞不下的大模型,66G 顯卡記憶體怎麼跑得動?
兩個層次。第一,119B 的模型量化到 Q3 大約 55G,三張 2080 Ti 22G 湊出 66G 顯卡記憶體剛好塞得下,不用什麼特技,74 tok/s。第二,再大的模型(200B–300B)顯卡記憶體真的不夠,就靠 MoE 的特性:這種模型每吐一個字只會用到一小部分「專家」,所以可以把不常算的專家權重丟去一般記憶體(RAM),讓 CPU 去算,顯卡只留真正在動的部分。66G 顯卡記憶體 + 128G 一般記憶體加起來的權重池,遠比單看顯卡大。
把 expert 丟去 RAM 給 CPU 算,會慢多少?
我拿一顆 26B 的 MoE 實際量過:全部塞顯卡記憶體時 113 tok/s,把所有 routed expert 丟去 CPU 算之後掉到 47.6 tok/s,吐字慢大約 2.4 倍。但讀 prompt(prefill)那段慢更多,大約 5.3 倍——因為那是一次要算一大批,CPU 吃虧。結論:短對話幾乎無感,長 prompt 才會痛。這台 EPYC 是八通道 DDR4,記憶體頻寬 約 105 GB/s,撐得起這種串流,老伺服器當 expert 引擎其實夠格。
為什麼同一個 offload 指令,單卡跑得動、三卡卻 OOM?
因為三張卡是用 layer-split 分工的——模型的層早就被拆給不同張卡了。你下「把前 N 層的 expert 丟去 CPU」這種指令,卸掉的前 N 層剛好全落在第一張卡的管區,所以第一張卡鬆了、第二三張卡一點沒動,照樣爆掉。實測 llama.cpp 一直想在第二張卡上配 24,835 MiB 的 buffer(比 22.5G 的卡還大、當然 OOM),不管你把幾層丟去 CPU 這個量都不降,就是這個原因。解法是別卸「前 N 層」,要按每張卡各自負責的 layer 範圍分散去挑層卸。
這台洋垃圾機到底是什麼規格?
二手企業級 EPYC 7402(24 核)+ 128G 八通道 DDR4 ECC + 三張改裝 2080 Ti 22G(魔改把原本 11G 的卡加到 22G),PCIe 3.0,總價大約台幣 5 萬。三張卡合計 66G VRAM,八通道記憶體頻寬實測約 105 GB/s(理論約 136)。這台就是拿來跑大模型實驗的:跑得動就好、不追求最快,平常閒置自動關機,要用再遠端喚醒。

接著讀

不想錯過新文章?

訂閱我確保不漏接!

隨時一鍵退訂。