跑本地 LLM 的核心 invariant 跟雲端不一樣:Mac 是 shared resource、不是 dedicated GPU。雲端 inference server 跑進 dedicated container、結束 instance 自然回收所有資源;本地推論伺服器跑在你日常用的 Mac、跟 統一記憶體 共享同一塊容量,忘記管理會 silently 吃光 RAM、磁碟、port、最後讓系統變慢甚至 swap。

本篇紀錄三個 dimension(RAM / 磁碟 / port)的觀察工具跟釋放姿勢、對比 Ollama 跟 ComfyUI 兩種典型 lifecycle、加上實測釋放數字。對應 0.7 隱私資料流原理「每個 hop 都要 audit」這條思維——資源管理也是 hop 級的 audit、不是「裝完就忘」。

驗證日期:2026-05-12 環境:macOS 14、Apple Silicon、Ollama 0.23.2、ComfyUI 0.21.0、SDXL base 1.0

為什麼這事重要

雲端 inference:

1Container start → load model → serve requests → container stop → 所有 RAM / 磁碟 / port 自動回收

本地 inference:

1brew services start → load model on demand → serve → ??? → 你忘記 stop
2                                              → RAM / 磁碟一直被佔
3                                              → 下次重開機才釋放

具體會踩到的問題:

  • RAM:18 GB SDXL 模型載入後不會自動卸、即使 ComfyUI idle、Python process 仍占 RAM
  • 磁碟ollama pull 累積、~/.ollama/models/blobs 半年可長到 50 GB+、不主動清不會減
  • Port:上次 crash 的 ollama serve 進程沒乾淨清、port 11434 還占著、下次啟動報「address already in use」
  • GPU / Metal:模型載入後 Metal context 佔住、跟其他 GPU-using app(影片剪輯、遊戲)競爭

三個 dimension + 觀察工具

Dimension觀察指令看什麼
RAMvm_stat | head -5Pages free(每 page 16 KB)、空閒越多越好
RAM(per process)Activity Monitor 或 ps aux | sort -k6 -rn | head哪個 process 佔最多記憶體
磁碟df -h ~ | tail -1系統 volume 剩餘
磁碟(per dir)du -sh ~/.ollama/models/blobsLLM models 累積量
Portlsof -i :11434誰在 listen 該 port
Processps aux | grep -i ollama | grep -v grepOllama / ComfyUI / Python 跑哪幾個
Ollama loaded modelsollama ps哪些 model 在 RAM、size、idle timer

實測:剛 kill 完 ComfyUI(SDXL + Python venv)後、vm_stat 看到 free pages 從 619K 變 1090K(每 page 16 KB)、約 +7.5 GB RAM 釋放——這就是 SDXL + ComfyUI process 一直占的記憶體量。

Ollama 的 lifecycle(auto-unload 模式)

Ollama 走「按需 load / idle unload」設計:

1brew services start ollama          → daemon 啟動、沒 model 載入、RAM 占用 ~200 MB
2                                     port 11434 listening
3ollama run gemma3:4b "hello"        → 把 model 載入 RAM (~4-5 GB)
4                                     立刻 generate response
5                                     model 留在 RAM
6(idle 5 分鐘、無新 request)         → Ollama 自動 unload model
7                                     RAM 釋放、daemon 仍跑著
8ollama run gemma3:4b "next"         → 重新 load model(~5-10 秒)、generate
9brew services stop ollama           → daemon 結束、port 釋放

關鍵參數 OLLAMA_KEEP_ALIVE(環境變數、預設 5m):

 1# 看當前 loaded models
 2ollama ps
 3# NAME         ID              SIZE      PROCESSOR    UNTIL
 4# gemma3:4b    a2af6cc3eb7f    5.5 GB    100% Metal   4 minutes from now
 5
 6# 啟動時調 keep_alive(持續佔 RAM 直到 ollama 重啟)
 7OLLAMA_KEEP_ALIVE=-1 brew services restart ollama
 8
 9# 啟動時讓 model 用完立即 unload
10OLLAMA_KEEP_ALIVE=0 brew services restart ollama

選 keep_alive 的 trade-off:

設定RAM 占用首字延遲適合場景
0最低(generate 完立即釋放)高(每次都重 load)偶爾用、RAM 緊張
5m(預設)中(活躍用占住、閒 5 分鐘後釋放)低(活躍期不重 load)大多場景
-1高(永久占住)最低整天頻繁用、RAM 充裕

主動 unload 指令

1# 把 idle 的 model 立刻從 RAM 卸掉、但 daemon 仍跑
2curl -s http://localhost:11434/api/generate \
3  -d '{"model": "gemma3:4b", "keep_alive": 0}'
4
5# 或關掉整個 daemon
6brew services stop ollama

ComfyUI 的 lifecycle(持續占用模式)

ComfyUI 走完全不同模式:model 載入後一直在 RAM、直到 server process 結束。沒有 auto-unload 機制。

 1python main.py                      → ComfyUI server start、port 8188 listening
 2                                     RAM ~3 GB(Python venv + 框架)
 3第一次 Queue Prompt (用 SDXL)        → 載入 sd_xl_base_1.0.safetensors (~6 GB)
 4                                     RAM 跳到 ~9-10 GB
 5                                     generate 完成、model 留在 RAM
 6連續多張生成                          → 維持 ~9-10 GB、沒 unload
 7idle 1 小時                          → 仍 ~9-10 GB(沒 timer)
 8切到 ControlNet workflow             → 多載 ControlNet model (~2 GB)、ComfyUI 自動 swap
 9                                     RAM 暫升、SD 部分可能被 evict 到 disk
10Ctrl+C / pkill                       → process 結束、RAM 完全釋放

要釋放 ComfyUI 占的 RAM、唯一方法是結束 server

 1# 找 PID
 2ps aux | grep "ComfyUI/main.py" | grep -v grep
 3
 4# 優雅關(讓它 cleanup)
 5pkill -INT -f "ComfyUI/main.py"
 6
 7# 強制 kill(如果上面沒反應、最多等 5 秒再強制)
 8pkill -KILL -f "ComfyUI/main.py"
 9
10# 確認 port 釋放
11lsof -i :8188 | head -3

實測:M4 Pro 32GB、SDXL base 載入後 ComfyUI process 占 ~8 GB RAM;pkill -9vm_stat 顯示 free pages 增加 ~470K page(7.5 GB 釋放)。

為什麼 Ollama 跟 ComfyUI 設計不同

因素Ollama 設計ComfyUI 設計
主要使用模式API 服務、IDE plugin 透過 HTTP 用互動 GUI、user 連續調 prompt
Model 切換頻率高(不同任務換不同 model)低(一次 session 通常一個 model)
User 期待的 latency低首字延遲(IDE 補完場景)高 throughput(連續生圖)
結論Auto-unload 釋 RAM 給其他 model持續載入避免重複 load 浪費

兩種設計都 valid、適合不同使用模式。理解差異後就知道 ComfyUI 一直占 RAM「不是 bug」、是設計選擇。

跟其他本地 server 對比

ServerAuto-unload主動 unload 指令占 RAM 觀察
Ollama有(5 分鐘 idle)keep_alive: 0 或 stop daemonollama ps
LM Studio無(GUI 主動關閉 model 才釋)GUI Eject ModelActivity Monitor
llama.cpp llama-serverkill processlsof -i :8080
ComfyUIkill processps aux | grep ComfyUI
oMLX有(per model 可配)API endpointserver log

結論:只有 Ollama 跟 oMLX 內建 auto-unload、其他都要手動釋放。GUI server(LM Studio)通常給 user 一個「Eject」按鈕、CLI server 通常要 kill process。

標準釋放程序

寫 code 完一天結束、要釋放所有資源、按下表順序操作:

 1# 1. 確認當前狀態(記下要還回去多少 RAM)
 2vm_stat | head -3
 3df -h ~ | tail -1
 4ollama ps
 5ps aux | grep -E "ollama|ComfyUI|llama-server" | grep -v grep
 6
 7# 2. 釋放當前載入的 LLM models(Ollama)
 8brew services stop ollama
 9# 或保留 daemon、只 unload model:
10# curl -s http://localhost:11434/api/generate -d '{"model": "<your model>", "keep_alive": 0}'
11
12# 3. 結束 ComfyUI / 其他 GUI server
13pkill -INT -f "ComfyUI/main.py" 2>/dev/null
14pkill -INT -f "llama-server" 2>/dev/null
15sleep 5
16# 強制(如果上面沒清乾淨)
17pkill -KILL -f "ComfyUI/main.py" 2>/dev/null
18pkill -KILL -f "llama-server" 2>/dev/null
19
20# 4. 驗證所有 port 釋放
21lsof -i :11434 -i :1234 -i :8080 -i :8188 -i :8000 2>&1 | head
22
23# 5. 確認釋放量
24vm_stat | head -3
25# free pages 該明顯增加

容易出錯的「釋放方式」

  • killall Python:會 kill 所有 Python process、包括其他 dev tool(如 jupyter、Django)。用 pkill -f "ComfyUI/main.py" 等明確 pattern。
  • rm -rf ~/.ollama:會清掉所有 model registry、下次要重 pull 全部 model。Cleanup 用 ollama rm <model> 才精準。
  • brew uninstall ollama:直接卸載 Ollama 本身、過 reinstall 麻煩。Stop service 就夠。
  • 重開機釋放:work 但太重、會中斷其他工作。用 process-level 操作即可。

磁碟長期累積管理

Models 一旦 pull~/.ollama/models/blobs、不主動 rm 不會減少。半年累積可長到 50 GB+。

Ollama models 只是磁碟大戶之一。整台 Mac 突然被吃光、要從哪裡查起的全機診斷順序(先排除快照浮動、再用實際佔用值逐層找大戶),見 macOS 磁碟空間診斷流程——那篇的佔用大戶表也會把 ollama 列為其中一項、再連回本篇的專屬清理 idiom。

觀察累積

 1# Ollama models 總占用
 2du -sh ~/.ollama/models/blobs
 3# 4.1G    /Users/tarragon/.ollama/models/blobs
 4
 5# 逐 model 看大小
 6ollama list
 7# NAME                       ID              SIZE      MODIFIED
 8# gemma4:e4b                 c6eb396dbd59    9.6 GB    Less than a second ago
 9# nomic-embed-text:latest    0a109f422b47    274 MB    3 hours ago
10
11# ComfyUI checkpoints 累積
12du -sh ~/.ollama ~/Projects/ComfyUI/models 2>/dev/null
13# 4.2G    /Users/tarragon/.ollama
14# 7.0G    /Users/tarragon/Projects/ComfyUI/models

清理策略

 1# 刪掉很久沒用的 model
 2ollama rm <model-tag>
 3
 4# 一次清掉所有 Ollama models(保留 daemon)
 5ollama list | tail -n +2 | awk '{print $1}' | xargs -I {} ollama rm {}
 6
 7# 看 ComfyUI checkpoints 哪些可清
 8ls -lh ~/Projects/ComfyUI/models/checkpoints/
 9
10# 手動刪不要的 .safetensors(小心、不能 undo)
11rm ~/Projects/ComfyUI/models/checkpoints/<old-model>.safetensors

磁碟管理 idiom

定期(每月或磁碟剩 < 20% 時)做:

  1. du -sh ~/.ollama ~/Projects/ComfyUI/models 看當前累積
  2. ollama list 看哪些 model 沒在用(看 MODIFIED 欄、太舊的考慮刪)
  3. 刪實驗用的 model、保留 daily-driver
  4. ComfyUI checkpoints 同樣 review

Port / Process 排錯

啟動報「address already in use」

 1# 找誰占
 2lsof -i :11434
 3# COMMAND  PID  USER   ...   NAME
 4# ollama   xxx  ...    ...   TCP localhost:11434 (LISTEN)
 5
 6# 看是不是 zombie process
 7ps aux | grep $(lsof -ti :11434 | head -1)
 8
 9# 清掉
10kill -9 $(lsof -ti :11434)
11
12# 或重啟 service(會自動清舊 instance)
13brew services restart ollama

Ollama daemon 掛了不知道

 1# 健康檢查
 2curl -s http://localhost:11434/api/version
 3
 4# 沒回應、看 service 狀態
 5brew services list | grep ollama
 6
 7# 沒在跑、重啟
 8brew services start ollama
 9
10# 看 log
11tail -50 /opt/homebrew/var/log/ollama.log

ComfyUI 看似跑著但 Queue 不動

1# 看 stdout / stderr log
2tail -30 /tmp/comfyui.log  # 如果啟動時 redirect 到 log
3
4# 看是不是 GPU / Metal stuck(極少見、但 SDXL 大量並發可能踩到)
5# 解法:kill + 重啟
6pkill -9 -f "ComfyUI/main.py"

完整排錯流程跟「先確認哪一層壞」見 1.7 排錯方法論

觀察記憶體佔用:實測對照

跑這幾步紀錄 baseline → load model → kill 的 RAM 變化:

 1# Baseline
 2vm_stat | grep "Pages free"
 3# Pages free:                              1090076.   ← ~17 GB free
 4
 5# 啟動 Ollama + load 4B model
 6brew services start ollama
 7ollama run gemma3:4b "hello"
 8ollama ps
 9# NAME       SIZE     PROCESSOR    UNTIL
10# gemma3:4b  5.5 GB   100% Metal   4 minutes from now
11
12vm_stat | grep "Pages free"
13# Pages free:                               750000.   ← 跌 ~5 GB(model 載入)
14
15# 額外啟動 ComfyUI + load SDXL
16nohup python main.py > /tmp/comfyui.log 2>&1 &
17# 在 GUI 上 Queue Prompt 跑一次 SDXL generation
18vm_stat | grep "Pages free"
19# Pages free:                               280000.   ← 再跌 ~7.5 GB(SDXL 載入 + Python venv)
20
21# kill 全部
22brew services stop ollama
23pkill -9 -f "ComfyUI/main.py"
24sleep 3
25vm_stat | grep "Pages free"
26# Pages free:                              1090000.   ← 回到 baseline

每 page 16 KB、所以 free pages 數字 × 16 KB = 實際 free RAM bytes。

自動化釋放:launchd / shell alias

寫個 shell function 一鍵 cleanup:

 1# 加進 ~/.zshrc
 2llm-cleanup() {
 3  echo "[*] Stopping Ollama..."
 4  brew services stop ollama 2>/dev/null
 5
 6  echo "[*] Killing ComfyUI..."
 7  pkill -INT -f "ComfyUI/main.py" 2>/dev/null
 8  sleep 3
 9  pkill -KILL -f "ComfyUI/main.py" 2>/dev/null
10
11  echo "[*] Killing other model servers..."
12  pkill -KILL -f "llama-server" 2>/dev/null
13  pkill -KILL -f "lm-studio-server" 2>/dev/null
14
15  echo "[*] Verifying ports..."
16  for p in 11434 1234 8080 8188 8000; do
17    lsof -i :$p 2>/dev/null | head -2
18  done
19
20  echo "[*] Free RAM:"
21  vm_stat | grep "Pages free"
22}

完事打 llm-cleanup 一鍵釋放、不用記每個 process 怎麼 kill。

何時這篇會過時

不會過時的部分

  • RAM / 磁碟 / port 三個 dimension 是長期 invariant、用什麼 LLM server 都成立。
  • 「Mac 是 shared resource、需要主動管理」這個 framing。
  • Ollama 跟 ComfyUI 兩種典型 lifecycle 對比(auto-unload vs persistent)。
  • 觀察工具(vm_statlsofpsdu、Activity Monitor)是 macOS 系統 API、不會 deprecate。
  • 標準釋放程序、自動化 shell function 模式。

會變的部分

  • 具體 model size / RAM 占用數字(隨模型架構演化)。
  • OLLAMA_KEEP_ALIVE 等具體環境變數名(Ollama API 演化)。
  • ComfyUI 可能加 auto-unload feature(社群有 issue 在討論)。

讀的時候若指令跑不過、先 --help 看當前版本 flag;釋放 RAM 的「kill process」這個機制本身永遠成立。

跟其他 hands-on 章節的關係

整體心法:本地 LLM 工作流跟雲端不一樣、要主動管理 lifecycle、不能裝完就忘。