昨天結尾丟了一個問題沒答:DeepSeek-V4-Flash 的 3-bit 權重要 104GB,而 M4 Max 有 128GB,看起來剛剛好——真的剛剛好嗎?
要回答它,得先處理一個更基本、但幾乎沒人講清楚的問題:那 128GB 統一記憶體,GPU 到底能用多少?
你可能會想,統一記憶體嘛,CPU 跟 GPU 共用同一鍋,128GB 當然就是 128GB。我原本也這樣想,直到在 llama.cpp 的啟動 log 裡看到一行沒人提醒過我的字:recommendedMaxWorkingSetSize。
**Metal 不會把 128GB 全部當成 GPU 的正常工作預算。**它給出一條建議線,社群量測大約是總記憶體的 75%——128GB 的機器約 96GB。更妙的是連這個比例都會變:我在自己的 M4 Max 上實際印出來,是 107.52 GiB,佔 84%。Apple 從頭到尾沒公開過計算公式,也沒把這件事寫進任何規格表,所以我叫它潛規則。

圖 1:128GiB 是同一池統一記憶體,但 Metal 會替 GPU 的 working set 畫出一條建議線——而且那裡不只一條線。
統一記憶體的「共用一鍋」是真的,但 GPU 要用的資源仍然有 residency 與 working set 要管。macOS 也不可能讓 GPU 無限制吃掉整池——kernel、WindowServer、你開著的瀏覽器都活在同一鍋裡。
於是 Metal 提供了 MTLDevice.recommendedMaxWorkingSetSize。這裡要看清楚 Apple 自己怎麼定義它,因為很多文章寫錯了:
Returns an approximation of how much memory this device can use with good performance. Performance may be improved by keeping the total size of all resources and heaps less than this threshold, beyond which the device is likely to be overcommitted and incur a performance penalty.
關鍵字是 approximation 跟 performance penalty。**這不是「GPU 最多只能配置這麼多」,而是「超過這裡,效能就要開始付代價」。**至於怎麼算出來的,一個字都沒提。比例是社群自己量出來的:在高記憶體的 Apple Silicon 上約為統一記憶體的 75%。
而這條線早就不是裝飾了。新版 llama.cpp 預設 --fit on,啟動前會先拿它當預算,把你沒指定的參數調到塞得下為止,每張裝置預設留 1024 MiB 餘裕。
我拿一顆 1B 的 BF16 故意開 -c 4000000 試,它的盤算是這樣:
projected to use 131319 MiB of device memory vs. 110100 MiB of free device memory
cannot meet free memory target of 1024 MiB, need to reduce device memory by 22243 MiB
context size set by user to 4000000 -> no change
- MTL0 (Apple M4 Max): 14 layers, 107533 MiB used, 2566 MiB free
successfully fit params to free device memory
那個 110100 MiB 就是 recommendedMaxWorkingSetSize,除以 1024 正好 107.52 GiB。因為 context 是我親手指定的,它不動;改成把上 GPU 的層數從 17 砍到 14,剩下 3 層丟回 CPU——Day 01 講過的那種 GPU 等 CPU 的接力慘劇,現在是預設行為,而且它不會問你。
Ollama 那邊也是同一條線,只是它有自己一套 memory estimation,把 weights、KV、graph 分開估再決定 offload 幾層。
**同一條線——llama.cpp 拿它做 device-memory fitting,Ollama 拿它做自己的 offload 預算。**兩邊都不會因為超線就拒絕啟動,但兩邊都會默默改掉你以為自己設定好的東西。加 -fit off 才會回到舊行為:只印一行 warning,然後硬幹到配置失敗為止。
不用背任何比例,你的機器自己會說。存成檔案跑一次,需要 Xcode Command Line Tools,xcode-select --install 裝一次就好:
cat > /tmp/vram.swift <<'EOF'
import Foundation
import Metal
guard let dev = MTLCreateSystemDefaultDevice() else { print("No Metal device"); exit(1) }
let ram = ProcessInfo.processInfo.physicalMemory
let gib = { (n: UInt64) in String(format: "%.2f", Double(n) / 1_073_741_824) }
let pct = { (n: UInt64) in String(format: "%.1f%%", Double(n) / Double(ram) * 100) }
print("GPU: \(dev.name)")
print("實體記憶體: \(gib(ram)) GiB")
print("建議 working set: \(dev.recommendedMaxWorkingSetSize) bytes = \(gib(dev.recommendedMaxWorkingSetSize)) GiB (\(pct(dev.recommendedMaxWorkingSetSize)))")
print("單 buffer 上限: \(dev.maxBufferLength) bytes = \(gib(UInt64(dev.maxBufferLength))) GiB (\(pct(UInt64(dev.maxBufferLength))))")
EOF
swift /tmp/vram.swift
比例它會直接幫你算好,你不用自己拿 bytes 去除。
已經在跑 llama.cpp 的人更省事,連模型都不用準備:
llama-cli --list-devices
# MTL0: Apple M4 Max (110100 MiB, 110100 MiB free)
那個 110100 MiB 是同一個數字,除以 1024 正好 107.52 GiB。想看原始那行 log 就跑 llama-bench,別用 llama-server——後者印完不會退出,你得自己按 Ctrl-C:
llama-bench -m model.gguf -p 1 -n 1 -r 1 2>&1 | grep -i recommended
# ggml_metal_device_init: recommendedMaxWorkingSetSize = 115448.73 MB
我的機器印出來是這樣:
| 項目 | 回報值 | 佔 128 GiB |
|---|---|---|
recommendedMaxWorkingSetSize |
115,448,725,504 bytes = 107.52 GiB | 84.0% |
maxBufferLength |
86,586,540,032 bytes = 80.64 GiB | 63.0% |
| 社群通則(較早版本、機型混雜) | 約 96 GiB | 75% |
前兩列為本機實測,M4 Max 128GB / macOS 26.2 (25C56),llama.cpp build 10360、ggml 0.19.0。社群 75% 通則見 ivanopcode devnote,原文寫「~75% of unified RAM」。
**先擋一個誤會:80.64 GiB 不是「整顆模型最多只能 80.64 GiB」。**它限制的是單一 MTLBuffer 能配置多大,而一個模型會被拆成多個 buffer——所以 97 GiB 的模型照樣可以整顆上 GPU。兩條線管的是不同的事。
同樣 128GB,社群量測的老答案是 96GiB,我印出來是 107.52 GiB。這不是誰量錯了。
把數字反推一下會發現,它一點都不隨機:
recommendedMaxWorkingSetSize = ceil (0.84 × hw.memsize ÷ 16384) × 16384
maxBufferLength = floor(0.63 × hw.memsize ÷ 16384) × 16384
兩式都跟回報值 byte 級完全吻合,而且精確落在 16 KiB 的頁邊界上。所以我這台不是「大約 84%」,是乾淨的 0.84。
但話只能說到這裡。這是一條能完整解釋這台機器的候選規律,不是 Apple 的公式——一個資料點能被某條式子解釋,跟 Apple 內部真的這樣算,是兩件事。我手上也只有這一台 M4 Max,所以 75% 和 84% 的差距到底來自 macOS 版本、晶片代還是記憶體容量,我分不出來。我唯一能確定的是:75% 並不是所有組合都成立。
所以下面那張表是照 0.84 外推的預測,不是量測,印出來對不上就以你的為準。
真的對不上反而是有用的資訊:把你印到的 bytes 除以 hw.memsize,你就有了自己那台的比例。之後每次估容量都用它,比抄任何一篇文章的數字都準。

圖 2:照 0.84 推,各容量該印出什麼數字。這是給你一個該期待的量級,不是保證。
順便把規矩立在這裡:**VRAM 與 KV 一律用 GiB,Hugging Face 上的檔案大小是十進位 GB,兩者相減之前先換算。**同一個 115,448,725,504 bytes,上面的 Swift 腳本印成 107.52 GiB,llama.cpp 的 log 卻印成 115,448.73 MB,因為它除的是 1e6。別背比例要自己印,印出來還要看清楚單位。這條規矩 Day 09 算 KV 預算時就要用上。
現在可以回答開頭那個問題了。DeepSeek-V4-Flash-0731 的 UD-IQ3_XXS 是 104.21 GB,換算成 97.05 GiB。
照社群的老比例,建議線只有 96 GiB,差 1.05 GiB——這顆模型你會直接放棄。照我實際印出來的 107.52 GiB,權重進得去,紙面上還剩 10.47 GiB。
注意這 10.47 GiB 不能整包當成 KV 預算:runtime、compute 與 graph buffer 都要從裡面分。權重塞得下不等於跑起來舒服,真正能開多長的 context,Day 09 把 KV 與 overhead 的帳補齊之後才算得出來。
11.52 GiB 的比例誤差,聽起來像小數點後面的事——但 DeepSeek 的量化階梯剛好有一階落在中間。

圖 3:五個量化階,兩條線。背錯比例的代價不是少一點餘裕,是放棄一顆其實跑得動的模型。
再往上一階就有趣了。UD-IQ3_S 是 108.10 GiB,只比建議線高 0.58 GiB——它最適合拿來測 wired_limit。但就算把門檻抬到 112 GiB,扣掉權重也只剩 3.9 GiB 給 KV 與 runtime 分,這是極限實驗,不是我會推薦的日用配置。至於 UD-Q3_K_XL 的 119.40 GiB,抬到 120 只剩 0.6 GiB,不用想。
如果模型真的比這條線大,macOS 留了一個後門,把 GPU 可 wire 的上限直接抬高:
# 先看目前值,預設是 0,代表交給 OS 決定
sysctl iogpu.wired_limit_mb
# 社群在 Sonoma(14)之後普遍用這個名字,而單位其實是 MiB
# 下例設為 112 GiB(114688 = 112 × 1024),替 IQ3_S 那階留路
sudo sysctl -w iogpu.wired_limit_mb=114688
# 設完一定要印一次確認,這步不能省,理由見下
swift /tmp/vram.swift
# 更早的版本則曾用另一個名字,單位是 bytes
# sudo sysctl -w debug.iogpu.wired_limit=<bytes>
# 還原:設回 0 即可,不必重開機
sudo sysctl -w iogpu.wired_limit_mb=0
有人在 64GB 的 M1 Max 上設 iogpu.wired_limit_mb=57344,也就是 56GB,硬是把預設上限拉高。設定即時生效——反過來說重開機後就還原。
設完再印一次,回報值會跟著動,而且動法很直白:它直接變成你設的數字,不再乘 0.84。
我印出來的對照是這樣(中間那欄是我打錯一位數的意外收穫,後面會說):
iogpu.wired_limit_mb |
0(預設) | 11468 |
|---|---|---|
recommendedMaxWorkingSetSize |
107.52 GiB | 11.20 GiB |
maxBufferLength |
80.64 GiB | 80.64 GiB(不動) |
兩件事值得記住。
第一,那個 0.84 只在你不插手的時候生效。11468 × 1048576 = 12,025,069,568,跟回報值 byte 級完全相等——順帶把單位也釘死了,名字寫 _mb,吃的其實是 MiB。一旦你手動設值,OS 就完全照你說的辦。
**第二,maxBufferLength 紋風不動。**兩條線裡只有一條抬得動,另一條是硬的。
但四個警語,一句都不能省:
114688 打成 11468,少一位數,建議線當場從 107.52 GiB 掉到 11.20 GiB,比預設低了快十倍——系統一聲不吭,照設。所以上面那行 swift /tmp/vram.swift 不是禮貌性的複查,是必要步驟。
圖 4:手動設值之後,Metal 的建議值直接等於你設的數字——包括你不小心往下設的時候。
至於 70B 要不要動它?不用。Llama-3.3-70B 的 Q4_K_M 檔 42.52 GB,換算 39.60 GiB,加上 context 的 KV cache(這筆帳 Day 09 有專章教你精算),離 96 GiB 的老預設線都還很遠,離 107.52 GiB 更遠。預設值之下,70B Q4 在 128GB 的 Mac 上綽綽有餘,這正是這台機器的賣點,什麼都不用調。
換到桌子另一邊,4070 Ti 的世界簡單得多:板上就是 12 GiB 實體 VRAM。實際 free VRAM 還是會被 driver、display 與其他 GPU 工作占掉一些,數字會浮動。
但實體容量本身就是那 12 GiB,不會再從整池系統記憶體切出一份 working set 預算給你。裝不下就是 CUDA out of memory,清清楚楚,童叟無欺。
實務上引擎通常會先用部分 offload 救你——那就是 Day 01 的接力慘劇,真的硬要整顆塞進 VRAM 才會 OOM。但它也沒有彈性:不存在任何 sysctl 能變出第 13 GiB。
這是兩種記憶體哲學。隔離(獨立 VRAM)邊界硬、行為可預測,撞牆時痛得明明白白。共享(統一記憶體)上限高、彈性大,代價是 GPU 得跟整個作業系統搶同一鍋飯,而分配規則是不透明的。
本系列參照組 T3 的 RTX PRO 6000 單卡就有 96GB VRAM,數字剛好跟 Mac 的老預設線一樣——但那是隔離哲學的 96GB,沒有人跟它搶。NVIDIA 那邊想讓多個工作共享一張卡,反而得靠一整套工具階梯,那是 Day 20 的主題。
回頭看 Day 01 那張表:546 對 504,頻寬同一量級;但容量這格,Mac 的 Metal 建議工作預算是 107.52 GiB,對上 4070 Ti 單卡的 12 GiB,將近九倍,就算把 T2 雙卡的 24 GiB 加總也還有四倍半。容量是 Mac 的主場,這場我們已經確認了。
那速度呢?尺我們做好了(Day 02)、壓測方法有了(Day 03)、模型挑好了(Day 06)、記憶體規則今天也摸清了——萬事俱備。
任何一台 Apple Silicon 的 Mac,8GB 也能印,數字小一點而已。sysctl 那段需要管理者權限,動手前請把四個警語讀完。N 卡讀者今天旁聽,明天就是你的主場。
recommendedMaxWorkingSetSize 是那條隱形的線,Apple 定義它是「效能開始付代價」的門檻、不是容量上限。而且線不只一條,maxBufferLength 管的是單一 buffer,不是第二個總容量。--fit on 會砍 context 或砍 offload 層數,Ollama 有自己的 memory estimation。想看到純 warning 的舊行為要 -fit off。iogpu.wired_limit_mb 一旦手動設值,Metal 的建議值就直接等於它、不再乘 0.84,但 maxBufferLength 不受影響;四個警語不能省:非官方支援、OS 更新後行為可能變、留 8-16GB headroom、它也可以往下設所以務必印一次確認。明天,Day 08:第一組正面對照,同一顆模型、同一份 prompt,MacBook 與 4070 Ti 到底誰快?這週磨的尺、認的模型、摸清的記憶體規則,全部上場。
咱們明天見。