Blog

  • ComfyUI Eating System RAM and OOMing: Dynamic VRAM and the PyTorch 2.8 Fix

    Over the last few weeks, a specific class of ComfyUI problem has exploded across Reddit and GitHub: workflows that ran fine a month or two ago now silently balloon system RAM, spill into the page file, and die with an out-of-memory error. The user is on 32GB of RAM and a 16GB GPU, the model is one they have run a hundred times, and yet a 9GB model suddenly consumes 30GB+ of RAM plus another 28GB of page file. The culprit is not a single bug but a collision of two independent changes landing around the same time: the new Dynamic VRAM memory allocator that rolled out in ComfyUI, and a memory regression reported against PyTorch 2.8. This article explains what each of them does, how to diagnose which one is biting you, and the concrete fixes that are actually working for people.

    The Symptom: RAM Spikes on Workflows That Used to Fit

    The canonical report comes from a Reddit user who had 32GB of RAM and 16GB of VRAM. After a round of ComfyUI updates (pulled to get Qwen image-edit support), their Wan workflows that had worked two months prior began crashing with OOM immediately. Qwen image edits worked once and then OOM’d on the next run, forcing a full restart between each image. A 9GB model running alongside an fp8 T5 XXL was eating 30GB+ of RAM plus 28GB of page file — more than a much larger Flux model consumed under Forge. The user’s resolution, documented in their update, was to downgrade to PyTorch 2.7.1 while keeping the latest ComfyUI, after which the RAM issue disappeared entirely.

    The key diagnostic clue in almost every report is the same: the RAM usage is out of proportion to the model size, and it grows across runs rather than dropping back after a generation completes. If your task manager shows committed memory climbing toward your physical RAM limit and then the page file spiking right before the crash, you are almost certainly looking at one of the two causes described below — not a “your GPU is too small” problem.

    What Dynamic VRAM Actually Changes About Memory

    In late 2026, ComfyUI shipped a new memory system called Dynamic VRAM, built on an internal project the team calls the AI Model Dynamic Offloader (aimdo). It is a custom PyTorch VRAM allocator designed to do on-demand offloading of model weights instead of predicting memory needs up front. The official Comfy blog announcement is the best single reference for the internal mechanics, and it is worth reading before you touch any flags.

    Three things changed that matter for the RAM-OOM problem:

    • A new safetensors loader. Files are now opened and mapped to uncommitted, file-backed memory rather than deep-copied into the PyTorch model. The weights are assigned by pointer to uncommitted memory. This is why model loader nodes complete almost instantly in Dynamic VRAM mode.
    • Models are no longer unloaded from VRAM back into RAM. Previously, unloading a model to free VRAM meant writing it back to system RAM — and in multi-model workflows this is exactly what caused RAM exhaustion and page file churn. Under Dynamic VRAM, the VRAM is simply freed and the model returns to the uncommitted load state.
    • Weights stay cached in RAM if you have plenty of it. The official discussion announcing Dynamic VRAM spells this out explicitly: Windows Task Manager may not show lower RAM usage, because cached weights are deliberately kept in RAM to speed things up. The critical difference from before is that these cached weights should never end up in the page file and should be unloaded to make room when other applications need the memory.

    So if you are on a recent ComfyUI build and your RAM appears high but your system is otherwise stable, part of that “high” usage may simply be the new caching behavior working as intended. The page-file spike is the real red flag — that is the behavior Dynamic VRAM was supposed to eliminate, and if you still see it, something else is wrong.

    The PyTorch 2.8 Regression Is a Separate, Identifiable Problem

    Running in parallel with the Dynamic VRAM rollout is a memory regression tracked against PyTorch 2.8. GitHub issue #9259 collects reports of OOM problems that appear specifically after installing the stable PyTorch 2.8 build, with users confirming that the same workflow is fine on 2.7.x and broken on 2.8. The most frequently cited fix in that thread and in the Reddit reports linked above is the same: pin PyTorch back to 2.7.1 while keeping ComfyUI current.

    Disentangling “Dynamic VRAM is misbehaving” from “PyTorch 2.8 is leaking” matters, because the fixes are different. If your RAM problem started immediately after a PyTorch version bump rather than a ComfyUI update, the PyTorch downgrade is the more targeted move. If it started after a ComfyUI update on an already-current PyTorch, the Dynamic VRAM flags are your first stop.

    How to Diagnose and Fix the RAM OOM

    Work through these in order. Each step is cheap and reversible.

    Step 1: Confirm it is a page-file / committed-memory crash

    Open your OS memory monitor (Task Manager on Windows, free -h or htop on Linux) and watch a known-bad workflow run. Note whether committed memory or the page file climbs toward the physical limit right before the crash. A model using RAM out of proportion to its on-disk size, combined with page-file growth, points at unloaded/cached-model memory rather than a genuinely undersized GPU.

    Step 2: Try disabling Dynamic VRAM

    Launch ComfyUI with the --disable-dynamic-vram flag. This is the officially documented workaround for several known problems — notably TensorRT custom nodes such as ComfyUI_TensorRT and ComfyUI-Rife-Tensorrt crash under the new allocator, and some users on high-RAM servers (64GB+) report roughly a 2x slowdown because weight caching that previously worked is no longer effective. If your RAM OOM disappears with the flag, you have isolated the cause to Dynamic VRAM.

    Step 3: If the regression tracks PyTorch, downgrade to 2.7.1

    If the problem appeared after a PyTorch 2.8 install and is unaffected by the Dynamic VRAM flag, pin PyTorch back. On a portable install the relevant command is a version-pinned pip install (pip install torch==2.7.1 — verify your CUDA build string against the official PyTorch instructions) rather than an unpinned upgrade. The Reddit report and issue #9259 both confirm this resolves the RAM spike for affected users.

    Step 4: Verify with a controlled re-run

    After each change, run the exact same workflow that crashed and compare the total workflow execution time (not it/s) and peak committed memory. The Comfy team explicitly asks users reporting Dynamic VRAM performance problems to compare total execution time, because the per-iteration rate can look worse while the end-to-end run is unchanged or better.

    Update First, and Don’t Chase Two Ghosts at Once

    Before any of the flag surgery above, make sure you are on the current ComfyUI build. Dynamic VRAM was, at the time it was first announced, only enabled by default in the latest git version, and several issues discussed in the rollout thread were fixed in subsequent releases. The team has been explicit that they want detailed bug reports — full logs, workflow, hardware, and OS — for any remaining issues. If you are running an older build, you may be debugging behavior that has already been patched.

    It is also easy to confound the two causes. A user who updates ComfyUI (pulling in both Dynamic VRAM and a newer PyTorch at once) cannot tell which change broke them without isolating one variable. Change one thing at a time — flag first, then version pin — and record the peak memory after each step. That discipline is the difference between a 20-minute fix and a week of flailing.

    What This Means for Local AI Hardware Decisions

    The Dynamic VRAM feature is genuinely good news for people on memory-constrained hardware — the official framing is that it makes it possible to run models larger than your RAM without paging, and to avoid the “VRAM OOM because weights were not offloaded enough” failure mode. If you have been sizing a build around physical RAM as the hard ceiling for model size, the allocator’s on-demand offloading changes that calculus. For a deeper look at squeezing large models onto limited VRAM, see our earlier writeup on running Wan2.2 Animate 14B on 16GB VRAM with BlockSwap.

    That said, the rollout has real rough edges. The slowdown reports on high-RAM machines and the TensorRT breakage are reminders that a new allocator interacts with every custom node differently. If you run a production workflow that depends on TensorRT or on a large in-RAM weight cache for speed, treat Dynamic VRAM as an opt-in during this transition window rather than trusting the default silently — and keep the --disable-dynamic-vram escape hatch in your launch script until you have measured your own numbers.

    Conclusion

    If ComfyUI has started eating your system RAM and OOMing on workflows that used to fit, you are almost certainly hitting one of two things: the transition to the Dynamic VRAM allocator (whose new caching can look like a leak but whose page-file behavior is what really matters) or the PyTorch 2.8 memory regression. Confirm which one by watching committed memory and page file, then apply the targeted fix — --disable-dynamic-vram for the allocator, a pin back to PyTorch 2.7.1 for the regression. Update to the latest build first, keep your launch flags in version control, and change one variable at a time. None of this requires a bigger GPU.

    References

  • ComfyUI API: How to Reliably Cancel, Interrupt, and Clear Jobs Queued From Scripts

    Once you take ComfyUI out of the browser and drive it from a script, a simple job that you expected to take thirty seconds can turn into a queue full of runaway video renders. The HTTP API exposes five different ways to stop work, and they operate on different parts of the system. Calling the wrong one does nothing visible, or worse, interrupts a job you actually wanted to keep. This guide walks through each endpoint, what it does to your queue, and the race condition that made job cancellation buggy until recently.

    The Three Jobs States Every Endpoint Hinges On

    Before choosing an endpoint, you need to know which of three states a prompt is in. ComfyUI tracks a prompt from the moment you submit it until it lands in history, and each state is controlled by a different route.

    • Running — the prompt is currently executing on the GPU. Exactly one prompt can be running at a time in the default single-GPU server.
    • Pending — the prompt is sitting in the queue, waiting for its turn after the running prompt finishes.
    • Finished — the prompt has completed (or errored) and moved into history.

    This distinction matters because the endpoint that stops a running prompt is completely different from the endpoint that removes a pending one. A prompt does not magically move between these buckets when you call the wrong route — it just keeps going.

    You can see a snapshot of the first two states at any time with GET /queue. The response body has two lists: queue_running (the current prompt) and queue_pending (everything waiting). Each entry carries the prompt’s UUID, which is the handle you’ll use for every cancellation call below.

    /interrupt Stops Only What Is Running

    POST /interrupt is the bluntest tool and the one most people reach for first. In its simplest form, it stops whatever prompt is currently executing:

    import requests
    
    requests.post("http://127.0.0.1:8188/interrupt")
    

    That global interrupt halts the active prompt mid-sampling and moves it into history in an interrupted state. It does nothing to the pending queue — the next queued prompt starts running immediately after the interrupt completes. If that is not what you want, you must also clear the pending queue (see the next section).

    The newer and safer form accepts a prompt_id in the JSON body. This was added in PR #9607 specifically to close a race condition: without it, a timed interrupt could land on a different prompt that happened to start in the gap between your queue snapshot and your interrupt call. When you pass a prompt_id, the server checks whether that exact prompt is the one currently running and only interrupts if it is:

    requests.post(
        "http://127.0.0.1:8188/interrupt",
        json={"prompt_id": "1f2a3b4c-..."},
    )
    

    If the prompt is no longer running, the server logs “prompt is not currently running, skipping interrupt” and does nothing — which is exactly the behaviour you want for a targeted cancel. The trade-off is that this only ever touches the running prompt; it cannot reach anything pending.

    /queue Removes Pending Jobs and Can Wipe Everything

    For anything waiting in line, the route is POST /queue. It accepts a JSON body with either of two keys.

    To remove specific pending prompts, pass a list of prompt_id values under delete:

    requests.post(
        "http://127.0.0.1:8188/queue",
        json={"delete": ["id-1", "id-2"]},
    )
    

    To clear the entire pending queue in one shot, use clear:

    requests.post(
        "http://127.0.0.1:8188/queue",
        json={"clear": True},
    )
    

    A critical detail: /queue operates only on the pending list. If a prompt has already started running, deleting it from the queue does not stop the GPU work — the running prompt is not in the pending list at all. This is the single most common mistake in automation scripts: you call /queue delete on a prompt that just started, the call returns 200, and nothing appears to happen because the prompt is now running, not pending. To be safe, treat /queue delete and /interrupt as a pair rather than alternatives.

    /history Cleans Up Completed and Interrupted Records

    Once a job is finished — whether it completed normally, errored, or was interrupted — it lives in history. History entries are no longer on the GPU and do not block anything, but they still occupy server memory and clutter the GET /history output your script reads back. ComfyUI gives you a POST /history route to tidy up:

    # delete specific history entries
    requests.post(
        "http://127.0.0.1:8188/history",
        json={"delete": ["id-1"]},
    )
    
    # wipe all history
    requests.post(
        "http://127.0.0.1:8188/history",
        json={"clear": True},
    )
    

    The body shape mirrors /queue exactly, which makes the API easy to remember: delete accepts a list of ids, clear nukes everything. The key conceptual point is that /history is housekeeping, not cancellation. Do not reach for it when a job is still running or pending; it will not stop anything — it only removes records after the fact.

    The New /api/jobs/cancel Endpoint Is the Upgrade You Want

    For a long time, the “cancel one job no matter what state it’s in” use case had no clean answer. You had to snapshot /queue, figure out whether your target prompt_id was running or pending, then branch into /interrupt or /queue delete. The race condition in the middle was the source of the feature request captured in discussion #8835 — deleting a job from the frontend could silently fail because the job had already started running between your click and the request.

    ComfyUI now ships a state-agnostic, idempotent cancellation route that collapses all of that logic:

    requests.post("http://127.0.0.1:8188/api/jobs/1f2a3b4c-.../cancel")
    

    Under the hood it resolves the prompt’s state and does the right thing automatically: it interrupts if the job is running, dequeues if it is pending, and returns a benign {"cancelled": false} if the job already finished or the id is unknown. Because it is idempotent, you can safely fire it at a job you are no longer sure is still in flight without risking an error or interrupting the wrong thing.

    There is also a batch variant for multi-job teardown:

    requests.post(
        "http://127.0.0.1:8188/api/jobs/cancel",
        json={"job_ids": ["id-1", "id-2", "id-3"]},
    )
    

    The batch version is best-effort and idempotent too: each well-formed id is cancelled if it is running or pending, while finished or unknown ids are treated as no-ops rather than errors. A batch that is entirely no-ops still returns 200 with {"cancelled": false}. This is the endpoint to standardise on for any “cancel everything I just submitted” workflow.

    One compatibility note worth checking against your install: all of these routes are also available under an /api prefix (/api/interrupt, /api/queue, /api/history, and /api/jobs/.../cancel). The server auto-registers both the unprefixed and prefixed paths, so either spelling works — but if you reverse-proxy ComfyUI behind a gateway that routes on the /api prefix, using the prefixed forms keeps traffic on the correct path. The /api/jobs/* cancellation routes specifically live under the /api prefix.

    A Careful Cancel Routine That Never Interrupts the Wrong Job

    Putting the pieces together, here is a small, defensible pattern for cancelling one job by its prompt_id without collateral damage:

    import requests
    
    BASE = "http://127.0.0.1:8188"
    
    def cancel_job(prompt_id: str) -> bool:
        r = requests.post(f"{BASE}/api/jobs/{prompt_id}/cancel")
        r.raise_for_status()
        return r.json().get("cancelled", False)
    

    For a teardown in a script that might have queued several jobs, snapshot the id list first, then cancel the whole batch and clear history afterward:

    ids = [...]  # prompt_ids you submitted
    requests.post(f"{BASE}/api/jobs/cancel", json={"job_ids": ids})
    requests.post(f"{BASE}/history", json={"clear": True})
    

    Two habits will save you from most “the cancel didn’t work” reports. First, always capture the prompt_id from the POST /prompt response — it is the only stable handle across state transitions. Second, after cancelling, poll GET /queue and GET /history to confirm the prompt actually left the queue and did not silently restart, rather than trusting the HTTP 200 from the cancel call alone. See our guide on websocket events and /history polling for a deeper look at confirming job completion by state.

    Conclusion

    ComfyUI’s job-control API is small but easy to fumble if you conflate the five routes. Remember the mapping: /interrupt stops the single running prompt (optionally targeted by prompt_id), /queue deletes pending jobs or clears the whole backlog, /history deletes finished records, and the newer /api/jobs/{id}/cancel (plus its batch form) collapses state detection into one idempotent call. For new automation, prefer /api/jobs/{id}/cancel and keep the targeted /interrupt form for the narrow case where you must be certain you are stopping a specific in-flight prompt. Validate against the official ComfyUI server routes documentation and the exact version you have installed, since these cancellation helpers have landed progressively across recent releases.

  • ComfyUI “No Module Named” After pip install: The Two-Python Trap (venv vs python_embeded)

    The Symptom: You Installed It, But ComfyUI Still Says “No Module Named”

    You find a custom node on GitHub, follow the README, open a terminal, and run pip install some-package. The install completes with a green “Successfully installed” message. You restart ComfyUI, reload your workflow, and the node still fails with the same red error: ModuleNotFoundError: No module named 'some_package'.

    It feels like gaslighting. The package is clearly on your machine—you just watched pip download and install it. The reason it still fails is almost never the package itself. It’s that the python (or pip) you ran in the terminal is not the same Python interpreter that ComfyUI actually uses. ComfyUI runs in an isolated environment, and every custom node executes inside that same environment. Install into the wrong interpreter and nothing changes inside ComfyUI, no matter how many times you reinstall.

    This article walks through how to find out which Python ComfyUI is really using, how to install a dependency into the right one, and how to prove the fix worked. The steps are grounded in a real Linux setup I maintain, where this exact two-Python trap bites on a regular basis.

    Why There Are Two (or More) Pythons on Your Machine

    ComfyUI does not run on your operating system’s default Python. Since the project pulls in high-memory native libraries—PyTorch, CUDA, and friends—it deliberately isolates itself so those dependencies don’t fight with anything else you have installed. Depending on how you installed ComfyUI, that isolation takes one of a few concrete forms:

    • Portable (Windows): ships a self-contained python_embeded folder, typically at ComfyUI_windows_portable\python_embeded\python.exe.
    • Manual / comfy-cli install: creates a venv (Python virtual environment) or conda env, often at ComfyUI/venv/bin/python or inside a conda env named something like comfyenv.
    • Desktop app: manages its own bundled Python per-instance, so there is no separate interpreter for you to target at all.

    The official manual install guide is explicit about why this matters: a virtual environment prevents ComfyUI’s dependencies from conflicting with other system packages. There is a direct consequence users often miss, spelled out in the dependencies documentation: every custom node’s requirements.txt must be installed into ComfyUI’s own Python, not the default one on your PATH.

    Concrete Evidence From a Real Install

    On my own server, the mismatch is easy to reproduce. The system-wide interpreter is Python 3.12:

    # which python3
    /usr/bin/python3
    # python3 --version
    Python 3.12.3
    # python3 -c "import torch; print(torch.__version__)"
    2.13.0+cu130

    But the ComfyUI process was launched with a completely different interpreter inside a virtual environment:

    # ps aux | grep main.py
    ... /home/kings/ComfyUI/venv/bin/python /home/kings/ComfyUI/main.py --listen 0.0.0.0 --port 18188
    
    # /home/kings/ComfyUI/venv/bin/python --version
    Python 3.11.15
    
    # /home/kings/ComfyUI/venv/bin/python -c "import torch; print(torch.__version__, torch.version.cuda)"
    2.12.0.dev20260407+cu128 12.8

    These are two genuinely different torch builds on the same machine: system Python has 2.13.0+cu130 while the venv has 2.12.0.dev+cu128. Run pip install some-package in a plain terminal and it lands in /usr/local/lib/python3.12/dist-packages. ComfyUI, however, only ever imports from /home/kings/ComfyUI/venv/lib/python3.11/site-packages. The two never touch. Every “installed but still missing” error on a venv or portable install is this exact split.

    Step 1: Find the Python ComfyUI Actually Uses

    Before installing anything, identify the interpreter ComfyUI was started with. The most reliable signal is the running process, not whatever python happens to be on your PATH.

    On Linux / macOS

    ps aux | grep "[m]ain.py"

    The first token of the result is the interpreter. If it’s /path/to/ComfyUI/venv/bin/python, you have a venv. If it’s a conda path like /home/user/miniconda3/envs/comfyenv/bin/python, you have conda. If it’s a bare python3 or /usr/bin/python3, ComfyUI is running on your system interpreter (unusual for manual installs, but possible).

    On Windows (Portable)

    The portable build won’t typically show a separate PATH-based python. Instead, check for the python_embeded folder:

    dir ComfyUI_windows_portable\python_embeded\python.exe

    If that file exists, it is the interpreter—not the python in your C:\Python3xx or whatever else is on your PATH.

    You can also make ComfyUI tell you directly. A one-liner that prints the live interpreter avoids all guessing, and works regardless of OS:

    import sys; print(sys.executable)

    Run it from inside ComfyUI itself (for example, via a quick temporary custom node or the Python console in some builds) and it prints exactly which binary is executing your nodes.

    Step 2: Install Into the Right Environment

    Once you know the interpreter, install the missing dependency with that Python’s -m pip. The -m pip form is important: it guarantees the pip you invoke belongs to the exact interpreter you named, rather than depending on whichever pip script your shell resolves first.

    Venv (manual / comfy-cli install)

    # Linux / macOS
    /home/kings/ComfyUI/venv/bin/python -m pip install some-package
    
    # Windows (manual venv)
    ComfyUI\venv\Scripts\python.exe -m pip install some-package

    You do not need to activate the venv first. Pointing at its Python directly with -m pip is more precise than source venv/bin/activate followed by a bare pip install, because activation is state that can silently expire in a new terminal while the explicit path cannot.

    Portable (python_embeded)

    cd ComfyUI_windows_portable
    python_embeded\python.exe -m pip install some-package

    This is the exact pattern the official dependencies doc recommends for custom-node requirements, with the general form:

    python_embeded\python.exe -m pip install -r ComfyUI\custom_nodes\YourNode\requirements.txt

    Conda env

    conda activate comfyenv
    python -m pip install some-package

    With conda, make sure the active comfyenv is the one ComfyUI was launched under. A second, forgotten conda env is just another flavor of the same two-Python trap.

    Step 3: Prove the Fix (Don’t Just Restart)

    After installing, verify the package is importable from ComfyUI’s own interpreter before you restart and reload the workflow. This is the step most people skip, and it’s the difference between a fix and another round of guess-and-check.

    # Linux / macOS venv
    /home/kings/ComfyUI/venv/bin/python -c "import some_package; print(some_package.__version__)"
    
    # Windows portable
    python_embeded\python.exe -c "import some_package; print(some_package.__version__)"

    If the import prints a version without a ModuleNotFoundError, the package is now visible to ComfyUI. If it still errors, you installed into the wrong interpreter and the above command will tell you instantly—without the slower loop of restarting ComfyUI and re-running the workflow each time. Only after the import succeeds do you restart ComfyUI, then reload the node. Going forward, the maintainable habit is: on a manual install, always put a copy of that exact <venv>/bin/python -m pip install ... line in your notes next to the node name, so the next dependency has the correct target already written down.

    Common Pitfalls That Make the Trap Worse

    Several habits quietly convert this into a recurring problem rather than a one-time fix.

    • Running sudo pip install. Besides bypassing the environment, sudo often resets PATH and HOME, so the pip that runs is the system one, not the venv’s. It also writes into system-owned directories, which is the exact contamination a venv exists to prevent.
    • Trusting pip --version instead of python -m pip --version. The bare pip on your PATH may belong to yet another interpreter. The -m pip form removes the ambiguity.
    • Installing into the venv after ComfyUI is already running, then rebuilding the node without a real restart. Python caches imported modules; custom nodes import at startup. A dependency installed while ComfyUI is up will not be picked up until the process restarts.
    • Mixing PyTorch versions. If a custom node requires a newer torch than the venv has, running pip install torch inside the venv can silently downgrade/upgrade the very build ComfyUI depends on, breaking every other node. Prefer nodes whose requirements match ComfyUI’s own torch, and check pip show torch in the venv before and after.
    • Editing the wrong requirements.txt. Each custom node ships its own requirements.txt inside custom_nodes/<Node>/. Installing ComfyUI’s top-level requirements.txt does nothing for a node’s missing third-party package.

    Conclusion

    The “installed but still missing” error is almost never a broken package. It’s an environment boundary: ComfyUI runs an isolated Python (a venv, conda env, or portable python_embeded), and a bare pip install lands in your system interpreter instead. The fix is a three-step loop that takes about a minute:

    • Find the real interpreter with ps aux | grep main.py (or by locating python_embeded).
    • Install with -m pip through that exact interpreter, never a bare pip or sudo pip.
    • Prove it with a live import in ComfyUI’s own Python before restarting, so a failed fix surfaces in seconds instead of a full restart cycle.

    Once you’ve internalized “install into the interpreter ComfyUI uses, not the one on my PATH,” a whole class of custom-node import errors stops being a mystery. If you’re still stuck after confirming the package imports in the correct environment, the problem moves to the node itself—and that’s a different investigation, one worth tackling separately.

    For more on diagnosing broken custom nodes generally, see our walkthrough of ComfyUI “No Module Named” errors and how to diagnose broken custom nodes.

  • ComfyUI Headless API: Reclaim ~25% VRAM by Dropping the Browser UI

    Why Your VRAM Looks Full Even When ComfyUI Is Idle

    Open ComfyUI, load a checkpoint, generate one image, and then just sit there. Open nvidia-smi in a second terminal. The VRAM is still nearly as full as it was during the generation — even though nothing is running.

    That residual occupancy is not a leak. It is the ComfyUI browser frontend working exactly as designed. When the web UI starts, it keeps the models you loaded — the diffusion model, the text encoder, and the VAE — resident in GPU memory so that the next “Queue Prompt” click starts instantly. For an interactive workflow where you tweak a prompt and re-render every few seconds, that caching is a feature.

    But if you drive ComfyUI as a headless backend — submitting jobs over HTTP and not touching the browser at all — that always-on residency is pure waste. It is memory you are paying for while getting nothing back.

    How much exactly? A first-party test by Andrew Zhu on an RTX 5090 measured peak VRAM at 18 GB when running Krea-2-Turbo through the browser, dropping to 13.5 GB for the identical generation driven through a plain Python headless pipeline. That is a ~25% reduction, same model, same image quality (source). On a 16 GB card, 4+ GB is very often the difference between a workflow that fits and one that throws CUDA out of memory.

    The Two Modes: Browser UI vs. Headless Server

    ComfyUI is, at its core, an HTTP server. The node graph editor you see in a browser tab is just one client that talks to that server. The official documentation is explicit about this: “ComfyUI runs as an HTTP server on your machine by default. You can call it programmatically to submit workflows, upload files, download outputs, and monitor progress” (ComfyUI server documentation).

    That means the memory-hungry browser tab is completely optional. You can run the exact same backend in headless mode — no browser, no frontend — and submit the very same workflows as JSON over the HTTP API.

    The reason the browser wastes memory specifically comes down to *when* models get freed. There are two distinct behaviors:

    1. Browser UI: models stay resident in VRAM after a job completes, because the frontend is waiting for your next interaction.

    2. Headless API: you control the lifecycle. Once a workflow finishes and you move on, you can unload models (or let a force-offload node push them back to system RAM / disk) before submitting the next job.

    It is the same insight behind every serious local-AI deployment: system memory offloading is a *failsafe*, not an operating mode you want to lean on. The weight held in VRAM between runs is capacity you could be using for higher resolution, larger batches, or a bigger model.

    Step-by-Step: Convert a Working Workflow to Headless API Format

    Here is the reproducible path. It assumes you already have a working ComfyUI workflow in the browser and want to serve it without the UI.

    Step 1 — Export your workflow in API format

    The workflow JSON the API accepts is *not* the same file the browser saves. The browser’s “save” format stores UI metadata — node positions, colors, group sizes — that the API has no use for. The API format strips all of that out, leaving a smaller graph that is what /prompt actually consumes. The official docs describe this distinction in the Workflow API Format page.

    To convert: open your .json in the frontend via File → Load, then export with File → Export Workflow (API). Do not hand-convert the save format; the node catalog between the two formats diverges in ways that are easy to get wrong. (If your nodes “vanish” after submitting, that is almost always a save-format file being fed to the API — see this workflow.json vs workflow_api.json walkthrough.)

    Step 2 — Start the server in headless mode

    Launch ComfyUI with the frontend disabled:

    
    python main.py --listen 127.0.0.1 --port 8188
    

    You do not need a special --headless flag for memory purposes — headless here means “don’t open a browser tab,” not a separate binary. What matters is that *you* never keep the frontend open against this instance. Do not navigate to http://127.0.0.1:8188 in a browser. Submit everything over HTTP.

    For extra insurance, add your normal VRAM hygiene flags. If you are on a memory-constrained card, --disable-smart-memory prevents ComfyUI from preemptively loading more than it needs, and --reserve-vram carves out headroom for the OS. These flags are covered in detail in this VRAM launch-flag explainer.

    Step 3 — Submit the workflow over HTTP

    Post the API-format workflow to the /prompt endpoint as JSON with a "prompt" key wrapping your graph, plus a unique client_id:

    
    curl -X POST http://127.0.0.1:8188/prompt \
      -H "Content-Type: application/json" \
      -d '{"prompt": <your-api-format-graph>, "client_id": "headless-runner"}'
    

    A successful call returns a prompt_id. You then poll /history/{prompt_id} until the status flips to completed, then download the output images/videos from the returned filename fields. The full run-job-then-poll loop — which is exactly what you need for a reliable headless harness — is broken down step by step in this ComfyUI API automation harness guide.

    Step 4 — Unload between jobs

    This is the part that actually gives you the VRAM back. In an interactive browser session, the models never leave. In a headless loop, *you* decide when they do. Options:

    • Use a force_offload=True option on model-loading nodes (many custom-node video/image suites expose this) so weights return to system RAM immediately after the node runs.
    • Explicitly free VRAM between batches by letting the Python process that owns the next request manage the lifecycle, rather than keeping a single long-lived session.

    Either way, the point is the same: the browser’s “keep it warm” behavior is the thing you are opting out of.

    Common Pitfalls When You Go Headless

    Pitfall 1 — Submitting save-format JSON. The single most common “why did my nodes disappear” error. Always export via *Export Workflow (API)*, never the plain save file.

    Pitfall 2 — Expecting /prompt to be synchronous. It is not. /prompt enqueues the job and returns immediately with a prompt_id. If your script assumes the response body contains finished images, it will read garbage. You must poll /history or listen on the WebSocket (/ws?clientId=...).

    Pitfall 3 — Keeping a browser tab open “just to check progress.” The moment you load the frontend against your headless instance, you re-introduce the resident-model behavior you just removed. Monitor via nvidia-smi or the WebSocket events, not the web UI.

    Pitfall 4 — Forgetting the VRAM flags on a 16 GB card. Going headless helps, but it does not make a 24 GB checkpoint fit in 16 GB by itself. Headless reclaims the *idle* residency; it does not shrink the *peak* footprint of a single large forward pass. Combine headless mode with your model’s own offload/quantization path — for Wan 2.2 specifically, BlockSwap and offload_device are the levers, covered in this Wan 2.2 Animate 14B on 16GB VRAM guide.

    How to Verify the Reclaim Worked

    Do not trust the memory back until you measure it. Run nvidia-smi (or gpustat) at three checkpoints under your *headless* setup:

    1. Before submitting — right after the previous job completed and your script moved on. VRAM should be visibly lower than what it was mid-generation.

    2. Mid-generation — the peak. This tells you the true working-set size of one forward pass.

    3. After the next job completes — confirms the residency did not creep back up.

    The number you care about is the *idle* delta: browser-mode idle minus headless idle. That is the memory you reclaimed — on the RTX 5090 test it was roughly 4.5 GB, and on a 16 GB consumer card the same effect can turn a borderline OOM workflow into a stable one.

    Conclusion

    ComfyUI is an HTTP server first and a visual editor second. The browser tab is genuinely useful for prototyping — but once a workflow is stable, keeping the frontend open means handing a quarter of your VRAM to a UI you are not using. Export the API-format JSON, launch the server without a browser, submit over /prompt, and take back the idle residency by unloading between jobs. It is the single lowest-effort VRAM win available on memory-constrained hardware, and it costs nothing but a few lines of Python.

  • ComfyUI “Invalid workflow against zod schema”: Fix the Semantic Version Validation Error

    You download a popular workflow from Civitai, drag the JSON file into ComfyUI, and immediately get a toast that reads something like Invalid workflow against zod schema with a wall of text about Invalid semantic version (x.y.z) at "nodes[8].properties.ver". Nothing actually breaks — the workflow still runs — but the warning reappears every time you switch pages or reload, and it is maddening enough that users have spent entire days searching for a way to silence it.

    This error is real, it is well understood, and the fix is specific. It also happens to be a different problem from the better-known Prompt Outputs Failed Validation error, which is a server-side rejection when your API prompt does not match a node’s schema. This guide covers the frontend workflow-file validation instead: what Zod is doing, which fields trigger it, why it usually fires on nodes you did not write, and exactly how to make it go away without hiding a genuine problem.

    What the Zod Schema Validation Error Actually Is

    Zod is a TypeScript schema-validation library that ComfyUI’s frontend uses to check that a workflow file conforms to the expected structure before it is loaded into the graph editor. When you open a workflow, the frontend validates a set of rules: node types must be known, connections must reference real slots, and certain property fields must match a specific format. If a rule fails, Zod raises a collection of errors, and ComfyUI surfaces them all at once in the warning banner.

    The two fields that cause the overwhelming majority of these warnings are ver and aux_id, both of which live under a node’s properties object. They are not fields most users ever edit by hand, which is exactly why the error feels so confusing: the file loads fine, the graph renders fine, and yet the validator keeps complaining about data you never knowingly changed.

    The Two Fields That Trigger It

    The Semantic Version Field (ver)

    The ver property is meant to hold the version of the node pack that produced a given node. The validator expects it to be a valid semantic version string, matching a regex that is roughly:

    /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-z-]+(?:\.[\da-z-]+)*))?(?:\+([\da-z-]+(?:\.[\da-z-]+)*))?$/

    This means a valid value looks like 1.2.3, 1.2.3-beta.1, or 1.2.3+metadata. Git commit hashes are also tolerated. The problem is that some custom node packs write a ver value that does not satisfy this pattern — a bare 1.0, a date string, a v1.2.3 with a leading v, or simply null. Any one of these makes Zod flag that specific node, and because the error is reported per-node, a single malformed value can produce a long list of Invalid semantic version entries, one for every node that inherited the bad value.

    The Auxiliary ID Field (aux_id)

    The aux_id property is expected to be a GitHub repository reference in the form owner/repo. The validator enforces the pattern github-user/repo-name. Some workflows ship node properties where aux_id is empty, is a bare username, or contains a full URL, and any of those triggers a separate set of Invalid format. Must be ‘github-user/repo-name’ errors.

    Why This Happens on Workflows You Downloaded

    The single most important thing to internalize is that these errors are almost never caused by anything you did. The offending values are baked into the node definitions by the custom-node authors when they export the workflow. A node pack that writes a non-semver ver string produces a workflow that loads fine inside that pack’s own authoring environment but trips the stricter validator in a newer ComfyUI frontend. In other words, the workflow was always slightly malformed; a recent frontend update simply started enforcing rules that were previously ignored.

    This is corroborated by the official issue tracker. ComfyUI issue #7309 documents exactly this pattern, with users reporting that the error appears on workflows they downloaded and disappears entirely after they disable validation. The validation for the ver field was introduced in frontend PR #2751, and the maintainers later noted the over-eager matching had been relaxed in current versions.

    Step by Step: Diagnose First, Then Decide

    Before you toggle anything, read the message closely, because the fix depends on which field is failing.

    1. Capture the full error text. Do not skim it. The warning lists the exact property path — for example nodes[8].properties.ver — which tells you both the node index and the offending field.
    2. Confirm it is ver or aux_id. Semantic-version and aux_id failures are the two dominant cases. If the message points at a different field, your workflow has a different structural problem and disabling validation may hide something real.
    3. Check whether execution is actually affected. In the vast majority of reports, the workflow runs normally despite the warning. If your graph queues and produces output, the validation is purely a frontend nuisance, not a blocker.
    4. Open the workflow JSON. If you want to fix the data at the source, locate the node by index and inspect its properties.ver or properties.aux_id value. You will typically see a value that clearly fails the pattern described above.

    How to Fix It

    Option 1: Correct the Field in the Workflow File

    The cleanest fix is to normalize the offending value in a text editor. Open the .json workflow, find the node, and change ver to a valid semantic version such as 1.0.0, or change aux_id to a proper owner/repo string. Save and reimport. This keeps validation enabled, which means any future structural problem will still be caught.

    Option 2: Disable Workflow Validation

    ComfyUI exposes a setting to turn off this validation entirely. In the Settings dialog, find the Workflow validation toggle and set it to disabled. This is the step users in issue #7309 confirmed removes the error immediately. The official troubleshooting guidance states it is acceptable to disable validation temporarily while you investigate. The caveat is the same as before: with validation off, genuinely broken workflows will no longer warn you, so treat this as a stopgap for workflows you trust rather than a permanent global default.

    Option 3: Update ComfyUI and the Affected Node Packs

    Because the overly strict matching was relaxed in later releases, simply being on a current frontend often clears the error. If a specific custom node is the source, update that pack from ComfyUI Manager so its exported workflows carry a compliant ver value going forward.

    Common Pitfalls

    • Confusing this with prompt_outputs_failed_validation. That is a server-side rejection of an API prompt with a valid execution record boundary, not a frontend file-import warning. The fixes do not overlap; see our guide on finding and fixing the invalid node in a failed prompt for that separate error.
    • Editing the wrong file. You may have both workflow.json and workflow_api.json saved. The Zod validation applies to the graph file you import into the editor, not the API-format export.
    • Assuming a leading v is fine. v1.2.3 fails the semver regex even though it looks reasonable to a human. Use 1.2.3.
    • Disabling validation and forgetting. If you later open a workflow with a real missing-node problem, you will not get the warning, and you may misdiagnose the resulting red nodes as something else. Re-enable validation once you have moved past the noisy workflows.

    Verification and Conclusion

    After applying a fix, reload the workflow and watch the banner. A corrected ver/aux_id value should eliminate the warning for the nodes that previously triggered it while keeping validation enabled. If you chose to disable validation, confirm the error is gone and then deliberately re-enable it on a workflow you know is clean to verify you have not masked a real issue.

    The Invalid workflow against zod schema error is, in the end, a data-hygiene signal rather than a runtime failure. The values that fail are usually inherited from custom-node exports, the validator was made stricter by a frontend change, and the practical fix is either a one-field edit in the JSON or a single settings toggle. Understanding the difference between this warning and the much more serious prompt-validation rejection is the key to not wasting a day on a problem that never affected your generation in the first place.

    For full context on how ComfyUI recommends ruling out custom-node issues, consult the official ComfyUI troubleshooting documentation and the dedicated custom-node troubleshooting guide.

  • ComfyUI Flux LoRA strength_clip Does Nothing: Why Your LoRA Strength Knob Is Half-Broken (and How to Check)

    You load a Flux LoRA, wire it into the Load LoRA node, and start dragging the strength_clip slider to tune the effect. Nothing changes. The image is identical from 0.0 to 1.0. You assume the LoRA is broken, delete it, or hunt for a different LoRA of the same character or style.

    In most cases the LoRA is fine. The real problem is a misunderstanding of what the two sliders actually control, and a quirk of how Flux LoRAs are usually trained. This article breaks down the “strength_model” vs “strength_clip” split, explains why strength_clip commonly appears to do nothing on Flux, and gives you a concrete way to check a LoRA instead of guessing.

    The Two Knobs: strength_model vs strength_clip

    A LoRA (Low-Rank Adaptation) is a set of small weight patches applied on top of existing models. In ComfyUI’s Load LoRA node, a single LoRA file can carry patches for two different models at once: the diffusion model (the UNet or, for Flux, the DiT transformer) and the text encoder (the CLIP model).

    That is why there are two strengths. The official ComfyUI FAQ puts it plainly:

    These separate values control the strength that the LoRA is applied separately to the CLIP model and the main MODEL. In most UIs adjusting the LoRA strength is only one number and setting the lora strength to 0.8 for example is the same as setting both strength_model and strength_clip to 0.8.

    • strength_model scales how strongly the patch affects the diffusion model. This is the heavy hitter — it changes how latents get denoised, which is where most of a style or character LoRA lives.
    • strength_clip scales how strongly the patch affects the text encoder. It changes how your prompt gets interpreted into conditioning, which usually produces a much subtler effect.

    When both parts exist in the LoRA, both sliders should do something. But here is the key insight: a LoRA does not have to contain both. Whether a slider does anything depends entirely on what the person who trained the LoRA actually trained.

    Why Flux LoRAs Usually Ignore strength_clip

    This is the heart of the confusion. ComfyUI issue #8050 documents exactly this scenario: a user got perfect results with the SDXL Load LoRA node, then switched to Flux with a DualCLIPLoader and found that strength_clip had zero effect on the output.

    The answer, from maintainer ltdrdata in that same thread, is that many Flux LoRA models are simply not trained on the CLIP model at all. Flux is a large DiT model with a separate text encoder; when people train a character or style LoRA, they frequently train only the diffusion transformer and leave the text encoder untouched. In that case the LoRA file contains no CLIP patch, and strength_clip scales an empty set of weights — so of course nothing changes.

    This is not a bug. It is a property of the specific LoRA file you downloaded. The distinction matters because it flips your intuition: on SD1.5/SDXL it is common to nudge strength_clip and see a visible shift in composition, but on Flux the same action frequently does nothing because there is nothing there to scale.

    How to Check Whether a LoRA Trained the Text Encoder

    Rather than guessing, you can inspect the LoRA directly. The same GitHub issue points to the LoRA Block Info node from the Impact Pack (ComfyUI-Impact-Pack). It reads the internal keys of a LoRA file and shows you which named modules the patch targets.

    The diagnostic step is simple:

    1. Install or enable the Inspire Pack (Impact Pack) if you do not already have it.
    2. Add a LoRA Block Info node and point it at your LoRA file.
    3. Look at the list of block keys it returns.

    If the keys point only at diffusion-model layers (for Flux, names like diffusion_model.* or transformer blocks), then the text encoder was not trained, and strength_clip will legitimately do nothing. If you see text_encoder.* or CLIP layer names in the list, then a CLIP patch is present and strength_clip should have an effect — and if it still does not, you can start looking for a real problem.

    ComfyUI Load LoRA node with strength_model and strength_clip sliders shown side by side in a dark node graph
    The Load LoRA node exposes two independent strength values because a single LoRA file can patch both the diffusion model and the text encoder.

    Common Traps That Mimic a Dead strength_clip

    Before you conclude the LoRA is malformed, rule out the surrounding mistakes that can also make the entire LoRA appear inert.

    • Base-model mismatch. A Flux LoRA only works on a Flux checkpoint. Loading a Flux LoRA against an SDXL or SD3.5 checkpoint produces garbage or no visible effect, on both strength knobs. Confirm the checkpoint family matches the LoRA.
    • Wrong loader node. Older tutorials show a generic Load LoRA node, but Flux workflows often need the LoRA wired into the correct model path after the CLIP loader. If the LoRA node is not actually in the diffusion-model chain, strength_model will also look dead.
    • Trigger words. Some LoRAs require an activation token in the prompt. If the token is missing, the LoRA may produce only a faint or zero response regardless of strength. Check the LoRA’s model page on Hugging Face or Civitai for required trigger words.
    • Strength set to an extreme. Values far above 1.0 (say 1.5+ on Flux) can blow out the latent and wash the image out, which reads as “the LoRA broke” when it actually worked too hard.

    None of these are specific to strength_clip, but all of them are easy to mistake for “this knob does nothing.” Isolate the variable: the only way to know strength_clip specifically is inert is to hold everything else constant and sweep only that one value.

    A Reproducible Isolation Test

    Set strength_model to a fixed, sensible value (usually 1.0), then render the same prompt at strength_clip = 0.0 and again at 1.0. Keep the seed, sampler, steps, CFG, and checkpoint identical.

    • If the two images are pixel-identical: the LoRA has no CLIP patch (or a negligible one), and strength_clip is doing exactly what it should — nothing. This is the expected Flux outcome for many files.
    • If the images differ: a CLIP patch exists and is active. Now the knob works, and you can tune it as intended.

    This two-render test takes a couple of minutes and removes the ambiguity completely. It is the same principle as the maintainers’ advice in the linked issue — check whether the text encoder was trained before assuming the UI is broken.

    When a CLIP Patch Exists But Still Does Nothing

    There is a rarer case worth covering: the LoRA does contain text_encoder.* keys, yet strength_clip still appears to have no visible effect. Two explanations are common.

    First, the CLIP patch may be visually subtle. Text-encoder patches shift conditioning, which often only nudges composition or style rather than producing an obvious “different image.” A slider that feels dead at a glance may still be changing the embedding vectors; compare the conditioning or inspect outputs at a fine granularity before declaring it inert.

    Second, a genuine wiring error can leave the loaded CLIP model disconnected from the LoRA. In Flux workflows that use a DualCLIPLoader (Flux uses two text encoders, CLIP-L and T5), the LoRA must be applied to the correct one. If the patch targets CLIP-L but is applied to a path that never reaches the prompt conditioning, the CLIP weight never takes effect. Trace the model chain and confirm the LoRA output actually reaches the sampler’s conditioning input.

    The official guidance is worth repeating: for Flux specifically, the ComfyUI_examples LoRA page shows the correct wiring pattern — the LoRA node patches the model output directly, and applying multiple LoRAs is just a matter of chaining additional Load LoRA nodes.

    Conclusion: The Knob Is Not Broken

    When a Flux LoRA ignores strength_clip, the overwhelmingly likely explanation is that the file was trained on the diffusion model only, and the text-encoder strength is scaling a patch that does not exist. This is normal, expected, and confirmable in seconds with the LoRA Block Info node or a two-render isolation test.

    Save yourself the detour: before replacing a “broken” LoRA, check whether it trained the text encoder at all. Tune strength_model for the character or style you want, leave strength_clip at a sensible default when no CLIP patch is present, and reserve real debugging for the cases where a CLIP patch exists and still fails to move the image.

    For the other silent failure that plagues Flux LoRAs — missing trigger words that make an entire LoRA do nothing — see our companion guide on fixing the silent LoRA no-op and missing trigger words.

  • ComfyUI Eats System RAM While VRAM Looks Free: Diagnose the Offload-vs-Leak OOM

    If you have ever watched your NVIDIA card sit at a lazy 30% VRAM while your Linux box grinds to a halt — mouse cursor stuttering, malloc failures, the whole machine frozen — you have hit one of the most confusing failures in local AI: ComfyUI exhausting system RAM while the GPU still looks half empty.

    The error is almost always reported as “CUDA out of memory” or a vanilla std::bad_alloc, which sends people straight to the wrong fix. They lower the image resolution, shrink the batch, or add --lowvram — and nothing improves, because the problem was never on the GPU in the first place.

    This article breaks down the symptom, shows you how to prove which tier of memory is actually dying, and gives you a concrete fix for each case. I write this from a first-hand snapshot of my own T2 machine: a 30GB-RAM box running ComfyUI 0.39.0 with an RTX 5060 Ti (16GB VRAM), which was sitting at 284MB of free RAM and 22GB of swap used while the GPU reported 10.1GB of VRAM free. That is the exact state this article exists to explain.

    The Symptom: VRAM Free, Machine Frozen

    The tell-tale signature is a mismatch between two readings taken at the same moment. Your GPU is fine, and your system is not. Here is what it looked like on my box mid-incident:

    • nvidia-smi / /system_stats: 10.1GB of 16.6GB VRAM free.
    • free -h: total 30Gi, used 30Gi, available 625Mi, swap 22Gi used of 43Gi.
    • ps aux --sort=-%mem: the ComfyUI/main.py process holding ~20.1GB RSS, 64% of all system memory.

    When you see that, stop tuning the sampler. The GPU was never the constraint — ComfyUI is holding model weights in ordinary CPU RAM, and it has run the machine into the swap file.

    The reason this is so common is that ComfyUI’s memory manager is aggressive about offloading. When a model (or a LoRA, a VAE, a text encoder like umt5-xxl, or a full video diffusion checkpoint) does not fit in VRAM at load time, ComfyUI keeps it in system RAM and streams pieces onto the GPU as needed. That is by design and usually correct. The subtle problem is that offloading and leaking produce the same free -h output — a huge RSS number — so you cannot tell which one you are looking at without one more step.

    How to Prove Which Tier of Memory Is Dying

    The single most useful diagnostic is to stop conflating VRAM and RAM, and read both at the same time. Three commands, run on the machine that is actually running ComfyUI:

    1. Read the GPU from ComfyUI’s own API, not from nvidia-smi.

    curl -s http://127.0.0.1:18188/system_stats

    Look at devices[0].vram_total vs vram_free. On my box this returned 16,610,820,096 total and 10,102,938,348 free. The API also reports torch_vram_total/torch_vram_free, which is the pooled-allocator view — the number to trust when a workflow is actively running under cudaMallocAsync.

    2. Read system RAM and swap.

    free -h

    If available is in the hundreds of MB while swap used is climbing into the gigabytes, your system is thrashing.

    3. Find the process responsible.

    ps aux --sort=-%mem | head

    On my box the top line was unambiguous: the venv Python running main.py --listen 0.0.0.0 --port 18188 at ~20GB RSS. If that same process is also the only thing consuming memory, you have a case of offloaded-weights residency; if the RSS climbs after every single queue run and never comes back down, you are looking at a leak.

    The distinction matters because the fixes are opposite. Offloading that simply never releases is a configuration problem (it is holding too much, or it is holding it after you are done). A leak that grows per-run requires either a restart cadence or a bug fix upstream.

    Rule Out a Real Leak Before Trying to “Fix” Offloading

    The healthiest instinct when you see 20GB of RSS is to assume it is a leak, but ComfyUI offloading is specifically designed to look exactly like that. A reliable test is to watch the RSS trend across two identical queue runs with no model changes between them:

    • Run the workflow once, note the RSS after it finishes.
    • Wait 30 seconds, run it again, note the RSS again.

    If RSS returns to roughly the same baseline after each run, you have offload residency — weights are parked in RAM and reused, which is expected. If RSS ratchets upward by the size of the model on every run and never returns, you have a leak, and the correct response is different: check whether a specific custom node is the cause (isolate it in a minimal graph), pin and update that node, and add a periodic restart while you wait for a fix. ComfyUI has an open, long-running issue about memory not being released after generation (Comfy-Org/ComfyUI#8298) that documents exactly this “RAM keeps growing after each run” behavior on high-RAM systems.

    One common trap: people read a full RAM line and assume offloading is not working, then disable offloading entirely. That is backwards. Full RAM with weights parked there is offloading working; the failure only begins when RAM plus swap is exhausted and the kernel starts the OOM killer. The goal is not zero RAM usage — it is staying under the ceiling where the machine remains responsive.

    Fixing the Offload Bloat Without Disabling It

    If you have confirmed offload residency (not a leak), the goal is to shrink the system RAM footprint and give ComfyUI a cleaner release path. In rough order of impact:

    Turn on Dynamic VRAM. ComfyUI’s dynamic VRAM mode improves how the manager moves tensors between VRAM and system RAM, which directly lowers the amount of RAM stuck holding offloaded weights and reduces OOM errors on tight machines. You enable it in the Settings → Memory → Dynamic VRAM toggle, or pass it at launch depending on your version.

    Understand that --lowvram is not the same lever. The launch flags --lowvram, --normalvram, --highvram, and --novram control how aggressively weights are moved onto the GPU, which changes VRAM pressure — but they do not, by themselves, cap how much system RAM an offloaded or partially-loaded model holds. If you are OOM-ing in CPU RAM, changing these flags will not save you and can even make the machine worse by forcing more residency in RAM. I cover the exact behavior of each flag in ComfyUI VRAM Launch Flags Explained.

    Close previews and stale generations. A huge and under-appreciated system-RAM sink is the accumulated preview / history backing store. If your browser tab has been open for hours and every queue prompt has rendered a live preview, that image data is sitting in RAM. Closing the browser tab (or the API client’s websocket that streams previews) and clearing the queue frees a surprising amount.

    Use an explicit release / free-memory node. If you are scripting or chaining many video segments, ComfyUI does not always return offloaded weights to a clean state between segments. Scheduling an explicit memory release — either through your driver script hitting the appropriate endpoint, or by restarting the ComfyUI process on a cadence — is the pragmatic solution most production pipelines settle on. My own Wan2.2 Animate setup on the same hardware leans on BlockSwap and force-offload precisely to keep a 16GB-class video model from thrashing; the full settings are in ComfyUI Wan2.2 Animate 14B on 16GB VRAM.

    Add swap deliberately — or remove the pressure first. A box that is sitting in 22GB of swap is past the point of tuning; it is thrashing. Give it headroom (close Chrome if it is running, stop the local LLM server you forgot about), then come back and re-measure free -h. Swap is a safety net, not a working tier — once you are depending on it for daily generation, every prompt becomes 10× slower.

    Verify You Actually Fixed It

    After applying whatever combination applies to your case, prove it with the same three commands you used to diagnose, and confirm the trend — not just a single lucky reading:

    • free -h shows available comfortably above the model footprint, and swap used is flat or falling, not growing.
    • Re-run the same workflow twice and confirm RSS returns to a stable baseline (offload) instead of ratcheting (leak).
    • /system_stats shows the VRAM pattern you expect, and the UI/API no longer throws “CUDA out of memory” or bad_alloc on a workload that previously died.

    The decisive test is responsiveness. A machine that is genuinely healthy will let you open a terminal and type while a generation is running. If the mouse still crawls during a prompt, you have not freed the system-RAM tier — you have only moved the problem around.

    Conclusion

    “CUDA out of memory” with a half-empty GPU is almost never a GPU problem. It is a system-RAM problem wearing a CUDA costume. The fix is to measure the right tier (VRAM via /system_stats, RAM via free -h, and the culprit via ps), distinguish offload residency from a genuine leak, and then apply the matching lever — Dynamic VRAM for the former, an isolated-node hunt and restart cadence for the latter. Get that right and a 30GB box with 16GB of VRAM goes back to being a perfectly capable local-AI workstation instead of a swap-bound slideshow.

    The memory management behavior is moving fast, so treat everything here as a snapshot to verify against the version you actually run. For current settings and the authoritative explanation of the memory manager, start with the official ComfyUI troubleshooting guide and the project’s memory-not-released issue thread.

  • ComfyUI Wan2.2 Keeps Eating RAM Until It Crashes? The –cache-none Fix, Explained

    There is a specific failure pattern that keeps showing up in ComfyUI threads about Wan2.2, and it is easy to misread. You load the 14B text-to-video or image-to-video workflow, generate one clip, and everything works. You generate a second clip, and RAM usage climbs. By the third or fourth run, the machine is thrashing into swap, the UI freezes, or the whole process dies with no useful error message — or worse, on Linux, the OS hard-freezes because the kernel overcommitted memory and ComfyUI filled it before the OOM killer could react.

    If your first instinct is to blame the model, the BlockSwap node, or your 16GB GPU, you are looking in the wrong place. The model being “too big” causes an out-of-VRAM error at load time. This is different: this is RAM that is never handed back to the OS after a completed generation. It is an execution-cache problem, not a model-size problem.

    Symptom: RAM Is Never Released After a Run

    The clearest description of this exact issue is in ComfyUI issue #9484, titled “RAM not being fully released after WAN 2.2 generation.” The reporter describes running “pretty much any generation” and seeing roughly 30GB of RAM reserved for no obvious purpose, with the leaked footprint growing on each subsequent run until RAM saturates and spills into swap. The timeline matters here: the problem reportedly became common around the time subgraph support landed in the daily builds, which made it easy to attribute the regression to the newer ComfyUI versions rather than to the model.

    Several commenters reproduced it under different hardware. One ran three ComfyUI instances — one per GPU — and watched a 128GB server start to OOM after only the second generation. Another confirmed it on an RTX 4090 with 32GB of system RAM, where ComfyUI would “crash a lot” without the fix. A third reported it on a 64GB machine with an RTX 3090: their Wan2.2 workflows that previously ran clean started breaking repeatedly, and ComfyUI would just stop with no error message, leaving the Windows console waiting on “Press any key to continue.”

    How to confirm it is this issue and not something else. Watch system RAM (not VRAM) across two or three generations. On Linux, run free -h between runs; on Windows, use Task Manager. If available memory drops after every completed run and does not come back up when the queue is empty, you are seeing the leak. The model being loaded and then intentionally kept warm for fast re-use is normal — but an ever-growing footprint that never returns to a baseline is the leak.

    The Real Cause: The Execution Cache, Not the Model

    The confusion that trips most people up is the word “cache.” ComfyUI has two completely separate caching systems, and they get blamed for each other’s problems all the time.

    • Model/diffusion-model cache — keeps loaded checkpoints in VRAM or RAM so you don’t re-read a 16GB file from disk on every run. This is what BlockSwap, --lowvram, and --normalvram interact with.
    • Execution cache — stores the output of individual nodes so that if you only change one parameter downstream, ComfyUI can re-use upstream results instead of re-running them. This is what the --cache-* flags control.

    The Wan2.2 leak lives in the second system. Video generation produces enormous intermediate tensors — latent frames across dozens of timesteps — and the execution cache holds onto them. On an image workflow, a cached latent is a few tens of megabytes and nobody notices. On a 14B video workflow producing dozens of frames, the cached intermediates are gigabytes, and they accumulate because the cache is not being reclaimed after the run completes.

    The official ComfyUI troubleshooting guide documents the lever directly: switching cache strategies. The startup-flags reference states that the cache-mode flags are mutually exclusive — you pick one of --cache-ram, --cache-classic, --high-ram, --cache-lru, or --cache-none. The key distinction, per the docs, is that --cache-none is “less RAM usage, but slower” because it re-executes every node on each run.

    This is why the fix is counter-intuitive. You are not changing how much VRAM Wan2.2 needs. You are telling ComfyUI to stop hoarding the gigabytes of intermediate results it would otherwise hang onto between runs.

    The Fix: Launch ComfyUI with –cache-none

    The fix reported working across hardware in issue #9484 is a single launch argument. Start ComfyUI like this instead of the default command:

    python main.py --cache-none

    On Windows Portable, edit the run_nvidia_gpu.bat (or whichever launcher you use) and add the flag to the python invocation line. On a headless Linux box or Docker setup, add it to whatever starts main.py. After restarting ComfyUI with the flag, run the same two or three Wan2.2 generations and watch free -h: available RAM should return close to the pre-run baseline after each queue drains, instead of ratcheting downward.

    The trade-off you are accepting. --cache-none means ComfyUI re-executes every node on each run, which costs some speed — most noticeable if your workflow already hit the same upstream nodes repeatedly. For a one-shot video generation, the difference is minor because the dominant cost is the diffusion model itself, not re-running a cheap preprocessor. For a long batch or a loop that re-uses upstream results, the slowdown is real. If --cache-none costs you too much speed, the middle ground is --cache-lru N, which caps how many node results are cached — it still bounds memory, but keeps a small hot cache for the nodes you re-touch most.

    One important correction from the issue thread: adding “clear VRAM” (or “Free Memory”) nodes to the workflow does not reliably fix this. Those nodes target VRAM/model cache, not the execution cache that is leaking. Multiple users confirmed the nodes had no effect while --cache-none did. Don’t waste time re-wiring the graph — the lever is a launch flag.

    Linux Users: The Overcommit Freeze Is a Separate Danger

    One comment in issue #9484 describes a failure mode worth flagging for Linux users specifically: the leak is worse on Linux because of memory overcommit. With default kernel settings (vm.overcommit_memory=0), Linux happily hands out more memory than physically exists, and when ComfyUI — already leaking — fills it all, the system hard-freezes before the OOM killer can react. The commenter tried tightening overcommit (sysctl vm.overcommit_memory=2 vm.overcommit_ratio=100) and found it caused ComfyUI to die when loading a second model instead — a different problem, not a fix.

    Net result: on Linux, fix the leak at the source with --cache-none rather than trying to fight it with kernel overcommit settings. If you are running multiple ComfyUI instances per GPU for throughput, this flag is effectively mandatory — the multi-instance OOM report on a 128GB server is the canonical warning case.

    How This Interacts With Wan2.2 Memory Optimization (and Why It’s Not the Same Thing)

    It is worth separating this RAM-leak fix from Comfy’s recent Wan2.2 VRAM optimizations so you don’t conflate the two. The official Wan2.2 memory-optimization update shipped ~10% less VRAM for VAE decoding and a major improvement for the 5B I2V model, plus new template workflows for the 14B models. Those changes reduce peak GPU memory during a run — a different axis entirely.

    You can think of the two levers this way:

    • VRAM (GPU): controlled by BlockSwap (blocks_to_swap), offload_device, quantized fp8 checkpoints, and the newer model-loader memory optimizations. This governs whether a 14B model fits on your card at all.
    • RAM (system): controlled by the execution-cache flags (--cache-none, --cache-lru). This governs whether your machine stays responsive across multiple runs.

    They are not substitutes. A 16GB card can load and run Wan2.2 14B using BlockSwap to page transformer blocks between VRAM and system RAM — that is a solved problem — and still fall over on the third generation because the execution cache is leaking RAM on top of the BlockSwap reservation. You need the VRAM trick and the RAM fix together for a workflow you actually run repeatedly.

    A Repeatable Verification Sequence

    Here is a concrete, reproducible check you can run in a few minutes to confirm whether you are affected and whether the fix took.

    1. Start ComfyUI normally and note system RAM baseline (free -h on Linux, Task Manager on Windows) with an empty queue.
    2. Run the same Wan2.2 generation three times in a row. Record available RAM after each run completes.
    3. If available RAM is shrinking after every run and not recovering, you have the leak.
    4. Restart ComfyUI with --cache-none (or add --cache-lru 8 if speed is a concern).
    5. Repeat the three generations. Available RAM should return close to the baseline after each queue drains.
    6. Confirm the trade-off: leave --cache-none in place if the per-run slowdown is acceptable; otherwise test --cache-lru with a value that keeps memory bounded but preserves your most-hit nodes.

    Conclusion

    If Wan2.2 runs fine once in ComfyUI but steadily eats RAM until the process crashes or your Linux box freezes, the model is almost certainly not the problem — the execution cache is. The fix that has been reported working across 32GB, 64GB, and 128GB systems is a single launch flag: python main.py --cache-none, or --cache-lru N as a bounded middle ground. Keep it separate from the VRAM-side optimizations (BlockSwap, quantized checkpoints) that govern whether the 14B model fits on your GPU in the first place: you generally need both for a workflow you run more than once.

    For a broader look at how the VRAM launch flags and offloading actually behave — the other half of this equation — see our earlier breakdown of ComfyUI VRAM launch flags.

  • ComfyUI “Missing Node” and “Import Failed”: A Complete Diagnostic Walkthrough

    The Symptom: Red Nodes and a Reload That Never Fixes

    You open a workflow you downloaded from Civitai or a friend’s chat, and half the graph is bright red. A banner tells you that nodes are missing, or the ComfyUI Manager reports “Import Failed” in red text next to a plugin name. You click “Install Missing Custom Nodes,” restart, and — sometimes — the red nodes are still there.

    This is far and away the most common support complaint in the ComfyUI community. The official troubleshooting guide opens with a blunt statement that most reported issues actually come from custom nodes, not from ComfyUI core. So before you reinstall anything, you need a reliable diagnostic sequence that tells you which of a handful of distinct failure modes you are actually hitting.

    The good news is that “missing node” is not a single problem. It is a symptom with a small set of well-understood causes, and each cause has a deterministic fix. This article walks through the diagnostic flow I use on a production ComfyUI install (66 custom node packages, running on an RTX 5060 Ti), with the exact commands and files to check at each stage.

    A quick reality check before you start: in many cases the node is not actually missing at all. It is merely failing to load, which ComfyUI reports as “missing” because the name never made it into the node registry. Your first job is to distinguish “not installed” from “installed but failing to import.” These have completely different fixes.

    The One Skill That Matters: Read the Actual Error

    ComfyUI prints import failures to its console (or terminal). The browser banner only tells you a node is missing — it does not tell you why. The real answer is always in the Python traceback written to the terminal where you launched ComfyUI.

    Start ComfyUI from a terminal so you can see that output:

    cd ~/ComfyUI
    python main.py

    Now trigger the failing load (restart, or click “Try fix” in the Manager). Scroll the terminal for a Traceback (most recent call last): block. The final line is the actual error class. Here is what each one means in practice:

    • ModuleNotFoundError: No module named 'torchvision' — a Python dependency is missing. The node package installed, but a library it imports is absent or the wrong version.
    • ImportError: cannot import name 'X' from 'Y' — a version mismatch: the custom node pins an API that changed in a dependency you have already upgraded.
    • OSError ... libcuda.so.1: cannot open shared object file — a native library or GPU driver issue, common on Linux (the official guide lists this under its Linux-specific section).
    • SyntaxError / IndentationError — the node code itself is broken for your current Python version; a plugin written for Python 3.10 behaviors can fail on 3.11+.

    This single step — actually reading the last line instead of the red banner — resolves a surprising number of cases because it tells you whether to install a pip package, pin a version, or touch nothing and just update the node.

    Verify Against the Registry, Not the Workflow File

    A workflow JSON stores names like KSamplerAdvanced or VHS_LoadVideo. When ComfyUI loads it, it looks each name up in its live node registry. You can confirm whether a name genuinely exists on your machine by checking the frontend’s object info endpoint:

    curl -s http://127.0.0.1:8188/object_info | python3 -c "import sys,json; d=json.load(sys.stdin); print('KSamplerAdvanced' in d, 'LoadImage' in d)"

    If a name prints False here, it is categorically not registered — either not installed, or installed but failing to import. If it prints True but the node is still red in the UI, you have a frontend/backend mismatch (the JavaScript side has an old shim), which a browser hard-refresh and a frontend rebuild usually clears.

    Diagnosing the Two “Missing” Cases

    Case 1: Genuinely Not Installed

    The workflow references a node whose package simply is not in custom_nodes/. This is the easy half. You have three install paths:

    • Via ComfyUI Manager (recommended): search the node name, click Install, restart.
    • Manually via git: cd custom_nodes && git clone <repo-url>, then restart.
    • Via git clone + pip install when the node has dependencies: clone, then pip install -r requirements.txt inside the node folder.

    On my own install, I run ComfyUI with a dedicated virtualenv (~/ComfyUI/venv, Python 3.11.15 in my case). Whichever install path you use, the dependencies must go into the same environment that runs ComfyUI, or you will get a ModuleNotFoundError the moment it loads.

    Case 2: Installed but Failing to Import

    This is the case the “Install Missing Custom Nodes” button cannot help, because the folder is already there. ComfyUI scans every directory in custom_nodes/ and tries to import it; an import that throws means the node never registers, and the UI shows it as missing.

    The loader logic is worth knowing because it explains a subtle trap. In ComfyUI’s node loader, a folder whose name ends in .disabled is skipped entirely — it is not imported at all. The same loader also consults a manager function (comfyui_manager.should_be_disabled()) to honor nodes you disabled through the Manager UI. This means:

    • A folder you renamed to MyNode.disabled to turn it off will show up as “missing” for any workflow that still references it — not because it is broken, but because you (or a previous session) disabled it.
    • Leftover junk folders with suffixes like .old-074513 (a timestamp backup pattern) are silently ignored. I actually found one of these on my live machine: a ComfyUI-GGUF.old-074513 directory sitting next to the active ComfyUI-GGUF. It is harmless, but it is also exactly the kind of directory that confuses people about why a node appears in the filesystem yet not in the UI.

    To isolate an import failure, disable everything except the problem node and observe. There are a few concrete actions:

    • Launch with only core nodes — temporarily move suspect folders out of custom_nodes/, or rename them with a .disabled suffix, then restart. If the crash disappears and comes back when you restore one folder, that folder is the culprit.
    • Read the __init__.py traceback — the failing import always prints a stack trace. The offending node folder’s name is usually visible in that trace’s file paths.
    • Check for a stale .disabled or .old-* twin — ls -d custom_nodes/*.old* custom_nodes/*.disabled* will reveal leftover folders that may be shadowing the real one in your mental model.

    The Manager Itself: A Source of “Import Failed”

    One frequently-overlooked cause of “Import Failed” is that ComfyUI-Manager itself is missing or out of date. The red “Import Failed” button people click most often is rendered by the Manager, so if the Manager can’t import the plugin, every plugin it tries to install or enable will report failure downstream.

    On my production machine, the Manager is not installed at all (a deliberate choice — I install custom nodes via git and manage dependencies directly in the venv). That is a completely valid setup, but it means:

    • There is no “Install Missing Custom Nodes” one-click button; missing-node workflows must be resolved by reading the workflow JSON’s custom_nodes list and cloning those repositories manually.
    • The comfyui-manager config directory (user/default/comfyui-manager/) does not exist, so any guide that assumes Manager-side disabling will not map to my environment.

    If your Manager shows persistent “Import Failed” for many nodes at once, the highest-value single action is to update the Manager itself (git pull inside custom_nodes/ComfyUI-Manager), then restart. A stale Manager frequently mis-reports import failures for plugins that import perfectly fine.

    Verification: When Is It Actually Fixed?

    A fix is not complete the moment the red nodes disappear. Confirm at three levels:

    • Registry check — re-run the object_info check above and confirm the node name now returns True.
    • Fresh generation — run the default workflow (or a minimal graph using just that node) end to end and confirm it executes without a “Prompt execution failed” dialog.
    • Console clean — the terminal should show no new Traceback lines on startup. A clean start is the real signal that the import now succeeds.

    If you are reporting a bug upstream, the official guide asks for exactly what a good diagnosis already gives you: the full console error, your list of installed custom nodes, the workflow JSON that reproduces it, and the recent changes you made. Gathering those as you go turns a frustrating “it just won’t load” session into a report that can actually be acted on.

    Conclusion

    “Missing node” and “Import Failed” feel like one problem but are really three: a node that isn’t installed, a node that’s installed but crashes on import, and a node that’s disabled (or shadowed by a stale folder) and thus skipped by the loader. The fix for each is different, which is why blindly clicking “Install Missing” so often fails to change anything.

    The discipline that pays off is embarrassingly simple: read the Python traceback in the terminal instead of the red banner in the browser. That one habit moves you from guessing to diagnosing, and it is the reason the official ComfyUI troubleshooting guide and the dedicated custom-node issues page both lead with “rule out custom nodes first.” If you are automating ComfyUI and want the diagnosis-before-queueing mindset baked into a script, the same principle shows up in my earlier walkthrough of submitting and polling ComfyUI’s /prompt API — check what the system actually reports before you assume the model or node is to blame.

  • ComfyUI –vram-headroom vs –reserve-vram: Why Your VRAM Limit Stopped Working After the Dynamic VRAM Update

    If you updated ComfyUI in mid-2026 and suddenly found that your carefully tuned --reserve-vram 2 flag no longer keeps your GPU from maxing out, you are not alone. There is an open issue on the Comfy-Org tracker titled “--reserve-vram ignored” describing exactly this on an RTX 5090. The reason is not that the flag broke — it is that ComfyUI shipped a new memory subsystem and a new sibling flag, --vram-headroom, and the two now mean very different things.

    This article explains what each flag actually does under the Dynamic VRAM system, how to diagnose which one you need, and how to stop ComfyUI from eating the last few gigabytes your desktop or browser still needs. Everything below is drawn from the official startup-flags reference and the Comfy-Org GitHub tracker; I have not run synthetic benchmarks, and I will not invent any.

    What Changed: Dynamic VRAM Became the Default

    ComfyUI’s Dynamic VRAM optimization — described in GitHub discussion #12699 — is now enabled by default on NVIDIA hardware (and ROCm 7.14+). Its whole job is to load and offload weights on demand instead of keeping everything resident, which is why it “massively reduces RAM usage and generally speeds things up” in the maintainers’ words.

    The side effect is that a lot of older memory flags no longer behave the way they used to. The official startup-flags reference now says outright that --lowvram has “no effect when dynamic VRAM is enabled.” When you pair that with the older flags you relied on, the result is exactly the confusion people report after an update: a workflow that used to fit now blows past the limit, or a flag that used to reserve memory seems to do nothing.

    Two flags matter most for capping VRAM, and they are easy to mix up:

    • --reserve-vram GB — the older flag. Reserves VRAM for the operating system and other software.
    • --vram-headroom GB — the newer flag, added alongside the Dynamic VRAM rework (PR #14480, 2026-06-15). Keeps extra VRAM free above the default headroom.

    What –vram-headroom Actually Does

    According to the startup-flags documentation, --vram-headroom GB (default 0) is the “extra VRAM in GB for dynamic VRAM to keep free above the default headroom.” The crucial detail is the last clause: ComfyUI “tries to keep this much VRAM completely free and unused, even counting VRAM used by other apps.”

    That last point is what makes it different from --reserve-vram. --vram-headroom is designed for the scenario where ComfyUI is not the only thing on the card — you have a browser with hardware acceleration, a desktop compositor, OBS, or a second app that also needs VRAM. By setting, say, --vram-headroom 2, you tell the dynamic allocator to leave 2 GB genuinely free even after accounting for whatever other processes are already holding.

    You launch it the same way as any other startup flag:

    python main.py --vram-headroom 2

    Or, on Windows Portable, add it to your .bat launcher (for example run_nvidia_gpu.bat) the same way you would any other argument.

    Why –reserve-vram Seems Ignored Now

    This is where the frustration kicks in. In open issue #15666, the reporter ran an RTX 5090 (31.5 GB) with --reserve-vram 2 and expected VRAM to never exceed 29.5 GB. Instead they observed the card hit 31.0 GB during model load and initialization, then “settle” to roughly 23.3 GB only after the run completed. Their conclusion — that --reserve-vram is effectively ignored during model load and initialization, then enforced afterward — mirrors what the maintainers and other commenters have begun to confirm.

    One commenter on that thread summarizes the emerging picture well: --vram-headroom may actually allocate its headroom up front to avoid writing into it, which is why Windows can still report near-full VRAM even though ComfyUI is respecting the cap. Either way, the practical takeaway is the same — if your goal is to keep a hard slice of VRAM free during the worst-case moment (model load, when pressure is highest), --reserve-vram alone is no longer the reliable tool it used to be.

    This is a moving target. The bug report is still open and assigned, so behaviors you read about today may change in the next point release. Treat the distinction below as “what the flags are documented to mean now,” not a promise that the underlying allocator has stopped surprising people.

    How to Choose the Right Flag (and Verify It)

    Start by deciding what problem you are actually solving, because the two flags address different ones:

    • You want ComfyUI to leave room for other running apps (a browser, OBS, the desktop) → use --vram-headroom GB. This is the flag built for “I need 2 GB left over for the rest of my system.”
    • You want to reserve VRAM for the OS on a headless or mostly-dedicated box → --reserve-vram GB still has a role, but understand its enforcement may be loose during model load under Dynamic VRAM.
    • You need dynamic VRAM entirely out of the picture → --disable-dynamic-vram, which falls back to estimate-based loading. I covered the broken-workflow and TensorRT OOM aspect of this in a separate post on fixing Dynamic VRAM OOM and broken TensorRT nodes.

    Once you pick a flag, do not trust your memory — verify with a live tool. On Linux, nvidia-smi in a second terminal shows per-process VRAM; on Windows, Task Manager’s GPU view or nvidia-smi in a terminal does the same. Watch the card during model load, not just after the generation finishes, because that is the moment the issue reporter saw the limit being violated.

    A quick diagnostic sequence:

    # 1. Baseline: what is already using VRAM before ComfyUI starts?
    nvidia-smi --query-gpu=memory.used,memory.total --format=csv
    
    # 2. Start ComfyUI with your chosen flag
    python main.py --vram-headroom 2
    
    # 3. Watch peak usage during a real run, not an idle one
    watch -n 1 nvidia-smi

    If peak usage still climbs past your intended ceiling, the honest answer with the current codebase is to fall back to --disable-dynamic-vram and re-test, or to close the other VRAM consumer rather than fight the allocator.

    Don’t Confuse This with Offloading or Pinned Memory

    It is easy to lump every memory flag together, but they address different failure modes. --vram-headroom and --reserve-vram are about governing how much of the card ComfyUI is allowed to hold. They are separate from:

    • Offload behavior — --lowvram, --novram, --gpu-only, which control where models live (and --lowvram is now a no-op under Dynamic VRAM).
    • Pinned-memory OOM — a distinct video-model failure fixed with --disable-pinned-memory, which I documented in a separate article.
    • System RAM pressure — the OOM-killer and swap problem, which is about host memory, not VRAM, and needs different handling.

    Keeping these buckets separate is what prevents you from chasing the wrong flag for an hour while VRAM keeps climbing.

    Conclusion

    The short version: if you updated ComfyUI and your old --reserve-vram cap stopped holding, you are hitting a real, documented behavior change, not a typo in your launch command. The new --vram-headroom flag is the one explicitly designed to keep a hard slice of VRAM free above the default headroom while counting other apps’ usage — but even it has open questions around whether it allocates that headroom up front.

    Before you change anything, decide which problem you have (leave room for other apps vs. reserve for the OS), apply the matching flag, and verify with nvidia-smi during model load rather than after the fact. And because this is an active area of the codebase — the --reserve-vram issue is still open and assigned — re-check the official startup-flags reference after your next update, because these two flags are exactly the kind of thing that changes between point releases.