昨天我們看到一顆 26B 的模型直接吃整頁文件會怎麼壞
今天要講的是另一半:就算你想用更大的模型去救它,你的硬體也不會答應
而且不是「跑不動」那種不答應
是跑得動,但慢到你不想用那種不答應 😅
| 項目 | 數字 |
|---|---|
| 晶片 | GB10 Grace Blackwell Superchip |
| 統一記憶體 | 128 GB LPDDR5X(CPU + GPU 共享) |
| 記憶體頻寬 | 273 GB/s |
| AI 算力 | 最高 1 PFLOP(FP4,sparse) |
| CUDA capability | 12.1(sm_121) |
| OS | Ubuntu,arm64 |
| 上市 | 2025-10-15,US$3,999 |
| 現價 | 2026-02-23 調漲至 US$4,699(記憶體漲價) |
💡Tip: 上面每一行我都標得出來源,但我要你只看其中一行。不是 128GB,不是 1 PFLOP。是 273 GB/s。這 30 天裡有一半的架構決策都是這個數字逼出來的。
先講一個我自己搞錯過的觀念
我剛拿到這台的時候,滿腦子都是「128GB 欸,那我是不是可以跑 70B 的模型」
可以。裝得下。
然後你會發現它慢到你懷疑人生
原因在 LLM 推論的 decode 階段——就是「一個字一個字吐出來」的那個階段——它是 memory-bound,不是 compute-bound
白話講:
每吐一個 token,GPU 都要把模型權重從記憶體搬到計算單元一次
算得多快不重要,因為它大部分時間都在等資料搬過來
所以單序列的生成速度,物理上限大約就是:
理論上限 tok/s ≈ 記憶體頻寬 ÷ 每個 token 需要讀取的權重量
這條式子很粗糙,但它抓的是對的東西
而我這台的分子是 273 GB/s
對照一下:
| 裝置 | 記憶體 | 頻寬 |
|---|---|---|
| DGX Spark(GB10) | 128GB LPDDR5X | 273 GB/s |
| RTX 5090 | 32GB GDDR7 | 1,792 GB/s |
| H100 SXM5 | 80GB HBM3 | 3,350 GB/s |
我的頻寬大概是 5090 的 1/6、H100 的 1/12。
關鍵差別在記憶體類型:GB10 用的是 LPDDR5X(就是手機/筆電那種低功耗記憶體),不是顯卡的 GDDR7、更不是資料中心的 HBM
這就是「128GB 統一記憶體」的代價。 你能用消費級的價格拿到 128GB,是因為它用的是便宜、省電、但慢的記憶體
(來源標註:H100 的 3.35TB/s 是 NVIDIA 官方規格頁逐字寫的;5090 的 1,792 GB/s 官方頁沒有直接列出 GB/s,是多個二手來源一致的數字,也跟 GDDR7 × 512-bit 的規格反推吻合)
128GB 買到的是「裝得下」,不是「跑得快」
我 vault 裡當時寫下的一句話是:
容量不缺,缺的是自己設的限制;真正的代價是速度(頻寬)不是容量。
來把上面那條式子套進真實數字。這段值得你跟著算一遍,因為你換到自己的顯卡上也是同一套算法
我跑的是 gemma-4-26b,它是 MoE(Mixture of Experts) 架構:
權重總量 = 25.2B × 0.5 byte = 12.6 GB
理論上限 = 273 GB/s ÷ 12.6 GB ≈ 21.7 tok/s
21.7 tok/s。
那是什麼概念?大概就是你盯著螢幕看它一個字一個字慢慢爬的速度
活躍權重 = 3.8B × 0.5 byte ≈ 1.9 GB
理論上限 = 273 GB/s ÷ 1.9 GB ≈ 143 tok/s
差了 6.6 倍
我今天為了寫這篇,實際跑了 7 次量測(不同長度的輸出,取 completion tokens ÷ 端到端耗時):
| 測試 | completion tokens | 耗時 | tok/s |
|---|---|---|---|
| 短輸出 | 141 | 3.40s | 41.5 |
| 中輸出 | 358 | 7.78s | 46.0 |
| 中輸出 | 387 | 8.11s | 47.7 |
| 長輸出 | 459 | 9.52s | 48.2 |
| 長輸出 | 710 | 14.53s | 48.9 |
| 長輸出 | 737 | 15.18s | 48.5 |
| 超長輸出 | 1464 | 30.15s | 48.6 |
穩定落在 48.5 tok/s 左右(短輸出那次偏低是因為 prefill 跟網路往返還沒被攤薄掉,輸出越長越收斂)
把三個數字放在一起看:
| tok/s | |
|---|---|
| 情境 A:Dense 假設(讀全部權重)的理論上限 | 21.7 |
| 實測 | 48.5 |
| 情境 B:MoE 只讀活躍權重的理論上限 | 143 |
實測是 Dense 上限的 2.2 倍 → MoE 的稀疏讀取確實有效,它真的沒在讀全部權重
但實測只有 MoE 上限的 34% → 中間有一大段損耗
那 66% 去哪了?attention 層是 dense 的(每個 token 都要全讀)、KV cache 要來回搬、MoE 的 router 每步都要算、kernel 效率本身也不是 100%。我沒有拆到那麼細,這裡只能說「式子給你的是天花板,不是預期值」
💡Tip: 這條式子的正確用法是算天花板。如果你算出來 143,實測 48,那還有調校空間;但如果你算出來 21,那就不用調了,換架構或換量化才是唯一的路。這就是昨天講的「調參救不回來」的量化版本。
我原本這篇是要寫「實測 150 tok/s,跟理論 143 幾乎貼合,完美驗證」的
因為我的舊筆記裡確實記著「decode 速度正常,約 150 tok/s」
然後我今天重測,只有 48.5。
差了三倍
那筆舊記錄我已經沒辦法還原它當時的條件了——而且更糟的是,那則筆記的主機型號本身我就記錯過一次(寫成 5090,後來 2026-08-04 驗證才發現那台其實就是 GB10)
所以我選擇:以今天實測為準,舊數字不引用。
💡Tip: 這件事的教訓比那個數字重要——你自己的舊筆記也是二手資料。尤其是那種「順手記一下」的效能觀察值,沒有記錄當時的量化格式、context 長度、batch 大小、有沒有其他服務在搶記憶體,事後就沒有任何解釋力了。Day 26 講記錄設計的時候,我會回來講這一刀該怎麼記才不會白記。
這才是今天的重點。我把這台機器逼我做的決定整理成一張表
| 硬體限制 | 逼出來的架構決策 |
|---|---|
| 頻寬 273 GB/s 是硬瓶頸 | 單序列速度撞物理上限,調參救不回來,只剩量化跟推測解碼兩條路(Day 4、Day 5) |
| Dense 模型在這台上太慢 | 優先選 MoE 架構,或接受 Dense 模型只用在慢速但精準的區塊(Day 9、Day 10) |
| CPU/GPU 共享同一塊記憶體 | 多個推論服務會互相排擠,實務做法是「一次只起一個」 |
| 統一記憶體沒有獨立 VRAM 概念 | 不要用 --kv-cache-memory-bytes 自設容量上限,那是自己給自己設瓶頸 |
| sm_121 太新 | PyTorch 會對 sm_121 噴 warning,套件相容性要逐個試 |
| arm64 架構 | 很多預編譯的 wheel 沒有 arm64 版,得自己編或找對的 image |
--kv-cache-memory-bytes 的坑這條我特別想講,因為它是**「用 x86 顯卡的直覺去操作統一記憶體」**的典型錯誤
我一開始寫了 --kv-cache-memory-bytes 3G,想說先限制一下免得吃太多
然後算一下就發現不對勁:
KV cache ≈ 79 KB / token(fp8)
64K context 的單一 request = 65536 × 79 KiB ≈ 4.9 GiB
我設 3G,連一個 64K 的 request 都裝不下。
💡Tip: 那個
79 KB / token本身是換算估算值(跟層數、head 數、KV dtype 都有關),所以這篇跟後面幾天所有 KV cache 的數字,請當量級看,不要當精確值。我統一用 GiB(1024 進位)算,因為記憶體慣例是這樣。差個 3–5% 不影響「裝不裝得下」的判斷,但差一個數量級就會。
這行的正確做法是整行刪掉,讓 gpu-memory-utilization 自己去分配
💡Tip: 順便講一個更常見的誤解——
--gpu-memory-utilization是總預算(weights + activations + KV cache + CUDA graph 全部算在內),不是「只給 KV cache 的比例」。方向搞反的話你會一直往錯的方向調。
還有一次更蠢的。我設 --gpu-memory-utilization 0.3,想說 128GB 的 30% 也有 36GB 應該夠了吧
啟動直接失敗
因為是統一記憶體,別的服務吃掉的量會直接從你的預算裡扣。x86 上你有獨立 VRAM,這事不會這樣發生
後來的保守值:降到 0.18–0.20,或如果確定整台機器只跑這一個服務,設 0.5–0.7
Gemma 4 的 config.json 裡寫 max_position_embeddings: 32768
原生 context 就是 32K。
但我的文件情境要跑到 64K 以上(一份年報章節動輒好幾萬 token)
解法是開 YaRN rope scaling:
rope_scaling:
rope_type: yarn
factor: 2.0
32K × 2.0 = 64K
而我今天去問了一下現在線上這個服務實際吃到多少:
curl -s http://10.0.0.220:8004/v1/models -o models.response.json
curl -s http://10.0.0.220:8004/version -o version.response.json
// models.response.json(節錄)
{
"id": "gemma-4-26b",
"root": "/models/gemma4",
"max_model_len": 131072
}
// version.response.json
{ "version": "0.19.1.dev6+g6d4a8e6d2" }
131072,也就是 128K。
比我筆記裡寫的 64K 又多了一倍——顯然我後來又調過一次但沒回去更新筆記 😅(同上一節的教訓,我的筆記也是二手資料)
順便你也看到我跑的是 vLLM 0.19.1.dev6,dev 版。這件事 Day 4 講版本衝突的時候會解釋為什麼
💡Tip: 注意我這兩行
curl都加了-o把回應存成檔案,沒有只讓它印在終端上。這是我自己吃過虧才養成的習慣——寫文章、寫報告的時候,「我記得我查過」跟「這是回應存檔」是兩件完全不同的事。這篇稿子在校對階段,就有一段因為只跑在終端裡沒落檔,被判定成「無法核實」打回來重做。跑過的每個測試都留檔,成本幾乎是零,省下的是之後的爭論。
💡Tip: 這裡有個實務提醒——
max_model_len開得大不代表你該用得滿。KV cache 是按實際 context 長度吃記憶體的,128K 的單一 request 光 KV cache 就要 將近 10 GiB(按前面 79 KB/token 換算)。開大是為了不被擋住,不是為了每次都塞滿。
順便講一個真的發生過的低級錯誤:我有一次 max_model_len 多打了幾個零,寫成 131072000
服務當然起不來
但更糟的是它的錯誤訊息不會告訴你「你多打了零」,它會噴一串記憶體相關的錯,讓你往完全錯誤的方向查
我的實際環境,給你對照用:
| 項目 | 值 |
|---|---|
| 主機 | gx10@10.0.0.220 |
| GPU | NVIDIA GB10(不是 5090,我的舊筆記記錯過一次,Day 5 會講這個教訓) |
| 推論框架 | vLLM |
| 服務埠 | 8004(gemma-4-26b) |
| 模型格式 | NVFP4(ModelOpt export) |
| Python 環境 | native venv,不是 NGC container |
因為版本打架:
transformers 鎖在 <5
transformers >= 5.5.0
在容器裡處理這個衝突比自己開 venv 還麻煩,所以我直接走 native venv
💡Tip: 如果你看到這裡想說「那我用 Ollama 不就好了」——我試過。這台上 host 層的 Ollama 跑
gemma4:26b會 crash(journalctl顯示 llama runner segfault,exit status 2),疑似跟 vLLM container 同時佔用 unified memory 有關。這也是統一記憶體的副作用之一。
我知道這篇通篇都在講「不能」
但我想講的其實是反過來的事
如果我有一台 8 卡 H100,我根本不會想做這 30 天要做的東西。
我會直接丟一顆最大的模型上去,一次吃整頁,跑得又快又準,然後這系列就沒什麼好寫的了
正是因為 273 GB/s 這個數字擋在那邊,我才被迫去想:
這些問題的答案,最後就長成了 Harness。
架構不是設計出來的,是被限制逼出來的
明天我們就從第一個「被逼出來的選擇」開始:量化