macOS swap が解放されない理由と対処法|M1 Pro

プログラミング

環境情報・著者紹介

本記事は macOS Sequoia 15.7 / ComfyUI 0.3.6 / Python 3.12 で実測した内容に基づいています。著者は生成 AI(FLUX、Stable Diffusion、Llama など)をローカルで運用しており、M1 Pro で VRAM と swap のトレードオフを日常的に観測しています。


はじめに

M1 Pro 32GB の MacBook Pro で FLUX.1 schnell(画像生成モデル)をローカル実行した日、不思議なことが起きた。

  1. ComfyUI 起動、python プロセス 17GB、swap 7GB まで増える
  2. 生成 1 枚終わって ComfyUI 終了、python プロセスは消える
  3. Activity Monitor を見ると swap がまだ 7GB のまま
  4. 新しいワークロードを始める前から memory pressure がオレンジ色
  5. 次の生成が swap 競合で遅くなる

「使われ終わった swap は勝手に解放される」という Linux 的な直観だと説明がつかない。で、調べたら macOS の仮想メモリ設計はそもそも そういう挙動に寄っていない と分かった。

この記事は M1 Pro で生成 AI / LLM を運用していて「なんか最近遅い」と感じている人向けに、macOS の swap の特性と対処法を書き残す記録。


再現手順(簡単にできる)

Terminal で以下を実行:

sysctl vm.swapusage

実際の出力例(FLUX.1 schnell fp8 実行後):

vm.swapusage: total = 7168.00M  used = 6234.45M  free = 933.55M  (encrypted)

メモリを食うアプリ(Xcode / Docker Desktop / ComfyUI 等)を全部終了させてから同じコマンドを叩くと、used が大きく減らないまま維持されるケースがほとんど。Linux の swapoff -a && swapon -a に相当する操作は macOS には存在しない。


macOS の仮想メモリ設計

macOS の swap は Linux とは設計思想が違う。

dynamic_pager

macOS では dynamic_pager というデーモンが swap ファイルを動的に管理します。詳細は man dynamic_pager または Apple Developer Documentation – Memory Usage Performance Guidelines を参照してください。

  • /private/var/vm/swapfile0, swapfile1, swapfile2 … と順に作っていく
  • 初期ファイルは環境やメモリ圧力により数百 MB ~数 GB、需要に応じて追加作成される(Sequoia 15.7 での観測では 1 ファイル約 1 ~ 2GB)
  • メモリ需要に応じて swap ファイルを増やす
  • 逆に swap ファイルは簡単には削除しない

ls -lh /private/var/vm/ で swap ファイルが何枚できているか確認:

sudo ls -lh /private/var/vm/

swapfile0    1.0G
swapfile1    2.0G
swapfile2    2.0G
swapfile3    1.0G
swapfile4    1.0G

合計 7GB ぶん swap が作られている状態の例。大型モデルを複数回実行すると、さらにファイルが増えることもあります。

なぜ解放しないのか

macOS の思想として「swap ファイルを削除して I/O を発生させるより、ディスクを消費したまま置いておくほうが安全」という判断があります。具体的には:

  • swap を削除するには、そこに退避されたページを全部物理メモリか別の swap に戻す必要がある
  • その移動自体が重い I/O を発生させる
  • 再度メモリ需要が来たら作り直す羽目になる
  • それなら放置が合理的

この設計は 常時メモリに余裕がある前提 だと問題ありません。しかし、M1 Pro 32GB で生成 AI ワークロードを回すと「常時ギリギリ」になるため、放置された swap が次のワークロードと競合します。

Compressed Memory も絡む

macOS にはもう一つ compressed memory という仕組みがあります。

  • よく使うアプリのメモリページを圧縮して物理メモリ内に保持する
  • swap に書き出すより高速
  • 実効メモリを拡張する効果

Activity Monitor の「メモリ」タブで「圧縮」として表示されている部分です。これも 終了したアプリのメモリを即座には解放せず、しばらく圧縮状態で保持 します(次起動を速くするため)。

結果として M1 Pro で AI ワークロードを回した後は以下のような状態になります:

物理メモリ 32GB
├── アプリ使用       8GB
├── ファイルキャッシュ 5GB
├── 圧縮メモリ       7GB   ← 終了アプリの残骸
└── 空き            12GB

swap 7GB(解放されず)

「空きは十分あるじゃん」と思っても、圧縮メモリが多い状態で大きいプロセスを起動すると swap に追い出される確率が上がります。


観測方法

Memory Pressure の色

Activity Monitor の「メモリ」タブ下部に表示される graph:

  • 緑:健全(swap ほぼ未使用)
  • オレンジ:警告(重い swap 発生中)
  • 赤:危険(重大な性能劣化、システム反応鈍化)

FLUX 生成中に画面を開いたまま見ると、物理メモリ使用 + 圧縮メモリ + swap で段階的にオレンジに染まっていく様子が分かります。

vm_stat でページイン / ページアウトを見る

vm_stat 1

1 秒ごとにメモリ統計を表示。実際の出力例(FLUX 生成中):

Pageins:           245672
Pageouts:          189456
Swapins:           67890
Swapouts:          45123
Compressor pages:  1984756

注目する行:

  • Pageins:swap → 物理メモリへ読み戻す(遅くなる)
  • Pageouts:物理メモリ → swap へ退避(1 回目だけ重い)
  • Swapins / Swapouts:実際に swap ファイルを触った回数

生成中に Pageins / Swapins が継続的に増えていれば、物理メモリが足りず swap から読み戻している = 遅い状態 です。

具体的数字の例

FLUX.1 schnell fp8(8ビット浮動小数点フォーマット、fp16 より小さい)モデル(17GB)を M1 Pro 32GB で生成したときの Activity Monitor スナップショット(実測値):

python3.12:        17.57 GB    (プロセス)
メモリ使用済み:    27.11 GB / 32 GB
圧縮メモリ:        7.59 GB
swap 使用:         7.09 GB
Memory Pressure:   オレンジ

生成時間:          1 枚 203 秒

モデルを FLUX schnell GGUF Q5_K_S(Griptape 量子化フォーマット、UNET 8GB + T5 GGUF 3GB + fp16 weights expand)に変えたら:

python3.12:        13.07 GB    (-4.5GB)
メモリ使用済み:    22 GB / 32 GB
圧縮メモリ:        2.73 GB     (-4.9GB)
swap 使用:         7.23 GB     (減らない!)
Memory Pressure:   緑

生成時間:          1 枚 173 秒(-15%)

重要な観察:swap は変わっていない(OS が保持し続けた)が、物理メモリの余裕ができたので生成時間は 15% 短縮されました。ただし速度の差が 15% に留まったのは、compute は物理メモリを小さくしただけでは縮まらない から(量子化で weight が小さくなっても演算時間は大差ない)。


対処法

1. 再起動が一番確実

身も蓋もありませんが、再起動すると swap ファイルは全部作り直し になって 0 にリセットされます。週 1 で再起動する運用にすれば十分です。

2. sudo purge(効果限定的)

sudo purge

ファイルキャッシュを flush するコマンド。swap には効きません。圧縮メモリも大部分は残ります。実行しても体感改善は少ないため、期待値を上げないでください。

3. dynamic_pager の shutdown / restart(非推奨)

# dynamic_pager を止めて swap ファイル削除(危険)
sudo launchctl unload /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist
# /private/var/vm/ の swapfile を削除
# dynamic_pager を再起動
sudo launchctl load /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist

やればできますが、System Integrity Protection(SIP)の範囲に触る可能性 があります。SIP が有効な場合はこの操作自体が実行できません(エラーで拒否されます)。SIP を無効化してまで行う価値はありません。加えて、swap 中のページがメモリに戻っていない状態で止めるとシステム不安定になるリスクもあります。おすすめしません。

4. ワークロード設計で避ける(最も実用的)

これが一番実用的で推奨される方法です。

モデル選択の工夫

  • GGUF 量子化モデル を優先(元の fp32 や fp16 の 30~50% 程度のサイズに圧縮)
  • fp8 フォーマット(8ビット浮動小数点、画質低下は最小限)や Q5_K_M フォーマット(Llama.cpp の標準量子化、バランス型)を選ぶ
  • フルサイズ fp16 ではなく、量子化済みモデルを活用

運用上の工夫

  • ComfyUI 系は --lowvram 相当の設定で modular load(モデル層ごとに逐次読み込み)
  • 複数の重いプロセスを同時に走らせない(LLM + 画像生成 + dev tool = 地獄)
  • 1 タスク終わったら一呼吸置いて swap が落ち着くのを待つ(効果は薄いが、心理的安定性がある)

5. 素直にメモリを増やす

Apple Silicon の現行ラインナップでは:

  • M4 Pro:最大 48GB(ここから swap が激減)
  • M4 Max / M4 Ultra:64GB / 128GB / 192GB

生成 AI を本気で触るなら M4 Pro 48GB 構成、または M4 Max 64GB 以上を検討する価値があります。ワークロードが 32GB 以内に収まっている人は問題になりませんが、複数モデルの並行実行や長時間連続実行を考えるなら 48GB 以上を強く推奨します。

Apple Store:MacBook Pro M4 Pro 最大 48GB でご確認ください。


LLM / 画像生成ワークロードでの観察

M1 Pro 32GB で以下を実測した体感。表内のモデルは ComfyUI デフォルト設定で実行した結果です。

モデル / ワークロード メモリ使用 初回実行 3 回目実行 備考
FLUX schnell fp8 17GB 203 秒 230 秒 swap 競合で遅延
FLUX schnell Q5_K_S GGUF 13GB 173 秒 178 秒 物理メモリ内収束、安定
SD 3.5 medium 12GB 328 秒 340 秒 fp16 weights デフォルト
Llama 3.1 70B Q4_K_M 38GB OOM Q4_K_M は Ollama 経由

表の注釈:

  • SD 3.5 medium は ComfyUI デフォルト設定・fp16 weights を使用
  • Llama 3.1 70B は Ollama 経由の Q4_K_M(Quantized 4-bit K-means、量子化フォーマット)で計測
  • FLUX GGUF は UnicoRN の GGUF 量子化版を使用
  • 全てのモデルは無限置換テキスト生成ではなく、単一生成(画像 1 枚またはテキスト 1 セッション)の時間

初回と 3 回目で差が出ているのは 連続実行で swap が蓄積していく ためです。物理メモリに収まるワークロード(13GB)は初回と 3 回目がほぼ同じ。収まらない(17GB)はじわじわ遅くなります。

つまり「物理メモリに 1 ワークロード分まで収める」ことが実用性能を決める最大要因です。量子化は compute 時間より「swap しないためのメモリ削減」として効くことが多いです。


まとめ

  • macOS の swap は 積極的には解放されない。Linux 的な挙動を期待してはいけません
  • sysctl vm.swapusagevm_stat 1 で観測できます
  • 対処は再起動が一番確実。purge は効果薄です
  • ワークロードを物理メモリ内に収める のが最大の予防策です
  • 生成 AI を本気でやるなら M4 Pro 48GB / M4 Max 64GB 以上を検討する価値があります

この挙動を知っていれば「最近遅い」の原因が切り分けられます。特に複数の重い AI 系アプリを並走させている人は、一度 swap 使用量を sysctl vm.swapusage で見て、再起動前後で比較してみると納得がいくでしょう。

swap が Linux と違う設計になっている理由は合理的だし、常時 swap を消す必要もありません。ただ 30GB のメモリで 30GB 分のワークロードを走らせる 人たちにとっては、その設計が裏目に出やすいというだけの話です。


参考文献・外部資料


関連記事・おすすめ読み物


シェア・フィードバック

この記事が役に立ったら、X(旧 Twitter)でシェアしてください。「#macOS #生成AI #メモリ管理」などのハッシュタグを添えると、同じ悩みを持つ人に届きやすくなります。

質問や改善提案は、お気軽にコメントまたは DM でお寄せください。

コメント