Phase Unload
The nudge Apple Silicon ComfyUI needs to actually free VRAM
- value
- model
- value
Phase Unload is a passthrough node with one job: when it runs, it calls unload_all_models(), empties the cache, and passes your data through unchanged. That reads as trivial. It isn't, because it's the missing second half of a Sync Barrier workflow - and the half that actually makes the thing fast on Apple Silicon.
Here's the failure mode this exists for. In a batched multi-prompt graph you've got several CLIP encodes, then several samplers, then several VAE decodes. Without a barrier, Comfy's memory manager keeps juggling: CLIP loads, gets "Unloaded partially" as UNet loads, reloads for the next branch, gets evicted again… minutes of waste per run, and it's a real, commonly-reported ComfyUI behavior - the log line shows up in plenty of "why is my generation suddenly 10 minutes" threads. A barrier stops the ordering chaos, but on macOS it's not enough, because Comfy's auto-eviction treats macOS "available" system RAM as free VRAM. Models that should be evicted just stay resident into the next phase. Phase Unload forces the boundary that eviction won't.
How it works
The node's unload() takes your value, then either:
- unloads all loaded models via
model_management.unload_all_models(), or - if you pass something to the optional
modelinput and it's in memory, evicts that model preferentially first.
Then it runs soft_empty_cache, a gc.collect(), and empties CUDA and MPS caches where they exist. The output is your value echoed straight through - this is a wire you can drop inline without re-routing anything.
Placement is the whole game
- After a barrier, on every wire into the next phase. Each
out_i→ Phase Unload → sampler/VAE. One unload on a single branch does not gate the others - the other branches will barrel ahead with stale models. - Do not hang one off an individual save node. With many saves in the graph it can fire mid-phase, which is exactly the thrash you're trying to kill.
- Do not use it in split-phase workflows at all. If you're running CLIP, diffusion, and VAE as separate
/promptcalls (separate graphs), an unload node races the next graph's loader. There you free memory after each phase finishes withPOST /freeinstead:
POST /free
{"unload_models": true, "free_memory": true}
That's the "Free memory" button in the GUI. Barriers still help inside a single graph; unloads belong with the barrier, not in the gap between graphs.
The inputs
value(required, wildcard*) - whatever you're passing through. It accepts anything, because it touches nothing.model(optional, wildcard*) - a specific loaded model to prefer for eviction. You'll rarely need this; leave it empty to unload everything.
One output, value. It's a passthrough, that's the point.
Install
Part of Model Phase Sync: Manager → search "Model Phase Sync", or:
comfy node install model-phase-sync
Manual install:
cd ComfyUI/custom_nodes
git clone https://github.com/josetseph/ComfyUI-ModelPhaseSync.git
Restart ComfyUI; it's under model_phase_sync. No dependencies, no model downloads, MIT.
Gotchas
- It's not free - unloading means reloading next phase. If your logs aren't showing thrash, you don't need it; on a beefy NVIDIA card with headroom, the barrier alone often suffices. This node is the macOS/unified-memory fix, and on MPS it's effectively required.
- "Unloaded partially" in your log is the diagnostic to look for. Clean run means one load per phase; if you see it repeating, the unload (or its placement) is wrong.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| value | * | — | |
| modelopt | * | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| value | * | — |