Author: Junjie

  • ComfyUI Webhook Notifications: Get Alerted the Second a Workflow Finishes (3 Ways)

    If you run ComfyUI as a headless backend — queuing jobs from a Python script, a CI pipeline, or a production product — you already know the worst part: you never know when a job actually finished. You either poll /history in a loop (wasteful), leave a browser tab open (wasteful), or find out hours later that a 20-minute video render died at second three.

    What you actually want is a webhook: ComfyUI pushes an HTTP POST to your endpoint the instant a workflow completes or fails. This article walks through the three realistic ways to get there, what they cost, and which one fits your setup. Everything here is drawn from the official ComfyUI codebase and the public repositories of the nodes named below — I’ll link each source so you can verify it independently rather than take my word for it.

    The problem: ComfyUI has no built-in webhook, only WebSockets

    ComfyUI does not ship a first-party webhook. What it does ship is a WebSocket endpoint at /ws?clientId=... that streams events as a job runs. The event you care about is one of:

    • executing — fires when a node starts, with prompt_id and node.
    • execution_cached — a node returned from cache.
    • execution_error — a node failed; carries the exception message.
    • executed — a node produced output (this is where images show up for SaveImage nodes).
    • progress — step/iteration progress within a node.

    The canonical pattern is to connect a WebSocket client, watch for executing with node === null (which signals the whole prompt finished), and then fetch results from /history/{prompt_id}. This is exactly what the official ComfyUI repository demonstrates in its script_examples folder, and what many production harnesses build on.

    The catch: WebSockets are a long-lived persistent connection. That’s awkward in serverless/async contexts, and it means your process has to stay alive and connected the whole time. A webhook inverts the relationship — ComfyUI calls you, so you don’t have to hold a connection open.

    Related: if you’re already using WebSockets to detect completion, make sure you’re reading the right signal. We covered the common mistake of polling queue_remaining instead of watching executing in this earlier post.

    Option 1: Webhook custom nodes (the 2-minute fix)

    The fastest way to get a webhook is to bolt a custom node onto the end of your workflow graph. The node fires when its input is reached, which — because execution is topological — means “after everything upstream finished.” Two solid options:

    ComfyUI-WebhookNotifier (iSuneast/ComfyUI-WebhookNotifier) ships two nodes:

    • Webhook Notifier — connects to an IMAGE output as its trigger.
    • Webhook Notifier (Any) — accepts any input type (IMAGE, STRING, LATENT, video filenames, etc.), which is what you want if your final node emits something other than an image.

    Install is a standard custom_node clone into custom_nodes/ plus a requirements.txt install, then restart. It sends a POST with custom JSON after execution.

    ComfyUI-SendToDiscord (gmorks/ComfyUI-SendToDiscord) is purpose-built for Discord: you create a webhook under Server Settings → Integrations → Webhooks, paste the https://discord.com/api/webhooks/... URL into the node (there’s a dedicated “Set Discord Webhook” setup node in some variants), and the node posts the finished image straight into a channel.

    Pros: drop-in, no code on your side, works in the GUI or headless. Cons: it only fires for workflows where you remembered to add the node, and it fires on graph reach, not on a global “job failed” event — a node error usually means the notifier node never executes, so a hard failure can go silent. That’s the single biggest trap with node-based webhooks and worth repeating: if the workflow errors before the notifier node, you get no notification.

    Option 2: Roll your own webhook from the WebSocket stream

    For anything beyond casual alerts, you want a small daemon that always knows job state — success or failure — and posts to your webhook accordingly. The structure is three parts:

    1. Subscribe to /ws?clientId=my-daemon.
    2. Watch for executing with node === null → a prompt finished; watch execution_error → a prompt failed (you get prompt_id, node_id, and the exception).
    3. POST to whatever endpoint you control — your backend, Discord, Slack, Telegram.

    A minimal Python skeleton (documentation-style, not claimed as a tested artifact):

    import json, requests, websocket
    
    WEBHOOK = "https://your-endpoint/hook"
    
    def on_message(ws, raw):
        msg = json.loads(raw)
        if msg.get("type") == "executing":
            data = msg["data"]
            if data.get("node") is None:          # whole prompt done
                requests.post(WEBHOOK, json={
                    "event": "finished", "prompt_id": data["prompt_id"]})
        elif msg.get("type") == "execution_error":
            requests.post(WEBHOOK, json={
                "event": "error", "detail": msg["data"]})
    
    ws = websocket.WebSocketApp("ws://127.0.0.1:8188/ws?clientId=daemon",
                                on_message=on_message)
    ws.run_forever()
    

    The ComfyUI repo’s script_examples/websockets_api_example.py is the reference for the exact event payload structure — I’d point you there rather than trust my paraphrase of field names, since the schema shifts between versions.

    The webhook receiver side should follow the standard pattern: validate the payload, respond 200 fast, and do any heavy work asynchronously so you don’t block the (already fragile) notification path.

    Option 3: Use a hosted/webhook-native wrapper

    If ComfyUI is running in the cloud or behind a managed API layer, that layer often already emits webhooks — you get notifications for free without touching custom nodes. For example, Replicate supports webhook callbacks for async completion, and several ComfyUI-as-a-service platforms expose the same. There’s also a community wrapper, SaladTechnologies/comfyui-api, whose server can forward ComfyUI WebSocket events to a configured webhook — effectively turning Option 2 into a config setting instead of code you maintain.

    This is the lowest-effort path if you’re already on a managed stack; just confirm the webhook fires on failure as well as success, because the silent-failure problem from Option 1 applies to any notification path, not just custom nodes.

    Common pitfalls to avoid

    • Silent failures. Node-based webhooks (Option 1) won’t fire if the graph errors before the notifier. If you care about failures, use the WebSocket/daemon approach (Option 2) so execution_error reaches you.
    • Wrong completion signal. Don’t poll queue_remaining or count executed events; the reliable end-of-prompt marker is executing with node === null. (Also covered here.)
    • Blocking the webhook. If you POST synchronously and your endpoint is slow, you can stall the node or daemon. Respond 200 immediately and process asynchronously.
    • Security. A webhook URL is a bearer secret. Don’t log it, don’t commit it, and consider requiring a shared token/header so a random POSTer can’t spam your endpoint.
    • Forgotten node on new workflows. If you go the custom-node route, make it part of your template — otherwise every new workflow silently lacks notifications.

    Which one should you pick?

    Casual, GUI user who just wants a Discord ping with the finished image → ComfyUI-SendToDiscord or WebhookNotifier (Option 1).

    Builders running headless jobs from scripts, who need to know about failures too → the WebSocket daemon (Option 2), or a wrapper that forwards events (Option 3).

    Already on Replicate / a managed ComfyUI API → use the platform webhook (Option 3) and verify failure coverage.

    The through-line in all three: the hard part isn’t sending the webhook, it’s not missing the failures. Pick the path that reports errors as loudly as successes, and you’ll stop babysitting the queue.

  • ComfyUI Nodes 2.0 Broke My Workflow: Diagnose the Vue Rendering Regression and Switch Back

    If your ComfyUI workflow started misbehaving lately without any change to your models or custom nodes, the cause may not be a bad update at all. It may be the new Nodes 2.0 interface, the Vue-based replacement for the old LiteGraph.js canvas that shipped as the default rendering path in recent frontend releases. The symptoms are easy to misdiagnose as something else: a node you rely on loses its properties panel, a workflow that used to be snappy now spikes your CPU and stutters, or you literally cannot resize a node anymore.

    This guide is a documentation- and community-analysis walkthrough, not a benchmark. I have not run a controlled before/after speed test on my own hardware, and I will not present invented numbers. What I will do is map the exact symptoms to the change, point to the authoritative sources, and give you the verified toggle and config key that switch the old canvas back on.

    What Changed: LiteGraph.js Is Out, Vue Is In

    ComfyUI’s node graph was originally rendered by LiteGraph.js, a JavaScript canvas library that drew every node, widget, and connection. The ComfyUI team has been migrating that layer to a Vue-based architecture. The official announcement describes the goal in terms of faster iteration and richer interactions, but the practical effect for many users is a frontend that renders the same graph through a fundamentally different code path.

    The key thing to understand is that this is a frontend change. Your models, your custom node Python code, and your workflow JSON are untouched. What changes is how the browser draws the graph and how it exposes node properties, handles selection, and manages event handling. That is why the failures cluster in specific, visual ways rather than breaking generation itself.

    The official documentation page for Nodes 2.0 is deliberately short, and it routes all feedback to the support center or the ComfyUI_frontend GitHub issue tracker. The announcement post on the Comfy blog confirms the toggle, which I cover below.

    The Symptoms: How to Recognize a Nodes 2.0 Regression

    Before you start uninstalling custom nodes or rolling back ComfyUI, look for this signature. A Nodes 2.0 regression tends to show up as one or more of the following, all on the UI side:

    • Node properties disappear or become unreachable. This is the most damaging symptom. The author of rgthree-comfy — one of the most widely installed custom node packs — has publicly stated that Nodes 2.0 may be incompatible with some rgthree nodes, breaking rendering and losing the ability to access a node’s properties.
    • Large workflows get laggy and unresponsive. The same rgthree message notes that Nodes 2.0 can run much more slowly, spiking CPU usage and causing jankiness on big graphs. If a 50-node workflow that used to scroll smoothly now freezes when you drag the canvas, this is a candidate cause.
    • You cannot change node shape or resize. GitHub issue #8532 reports that changing node shape broke starting at frontend version 1.38.9, with the reactive node-shape behavior later fixed by a merged PR. If your version sits in that window, resizing is simply broken.
    • Visual state doesn’t update. Related fixes in the same tracker cover right-side panel indicators and color-picker visuals that stop refreshing in Vue nodes mode.

    The tell that this is a rendering regression rather than a logic bug is that the workflow still produces images. If generation works but the editor behaves strangely, you are almost certainly looking at a frontend issue, not a backend one.

    Confirm the Diagnosis Before You Fix

    Don’t flip the toggle on a hunch. Confirm first, because the fix has a cost (you lose the new interface and any of its work-in-progress benefits). A two-minute check separates a real Nodes 2.0 regression from an unrelated problem:

    1. Note your ComfyUI frontend version (bottom of the settings or the startup log). The node-shape bug is tied to a specific version window starting around 1.38.9.
    2. Check whether the misbehaving node is a custom node, especially rgthree, Easy-Use, or another pack whose author has commented on Vue compatibility.
    3. Look at the startup log for the specific rgthree warning line (or its equivalent for whatever pack you use) that names Nodes 2.0 incompatibility.
    4. As a quick isolation test, toggle Nodes 2.0 off and reload. If the problem vanishes immediately, you have your answer.

    This matters because some symptoms — a node that won’t open its properties, for example — can also be caused by a genuinely broken custom node or a version mismatch. The toggle test is what separates the two.

    How to Switch Back to the LiteGraph Canvas

    The official escape hatch is deliberately simple. It is a toggle, not a reinstall.

    Per the Nodes 2.0 announcement and the docs page: click the ComfyUI logo to open the menu, then toggle “Nodes 2.0” off. That switches the canvas back to the previous LiteGraph.js interface. There is no need to roll back the entire ComfyUI installation.

    If you prefer to change it directly in your user settings file, the relevant key is Comfy.VueNodes.Enabled, set to false. Users in the community have reported that explicitly setting this field is what reliably keeps Vue nodes mode off. The settings file lives in ComfyUI’s user data directory (alongside the standard user/default/comfy.settings.json or its equivalent depending on your install). After changing it, restart ComfyUI so the frontend picks up the new value.

    One important caveat reported in the community: some custom nodes have issues that persist even after disabling Nodes 2.0, because the underlying frontend has moved on in other ways. If a specific node like ResolutionMaster still misbehaves with the toggle off, treat that as a separate custom-node compatibility problem and check the node author’s repository for a version note.

    What Not to Do

    There are a few tempting but wrong reactions that make a rendering regression worse:

    • Don’t uninstall and reinstall ComfyUI. The graph data and models are fine. Wiping the install to chase a frontend toggle is overkill and risks losing your custom node configuration.
    • Don’t blame your GPU or RAM. A laggy canvas is CPU-side browser rendering. Watching VRAM will not explain it.
    • Don’t mass-disable custom nodes blindly. One incompatible pack is not proof that every pack is broken. Isolate with the toggle first.
    • Don’t assume it’s permanent. The tracker shows active PRs fixing Vue nodes mode issues (node shape reactivity, panel indicators). The Vue path is under active development, so the specific bug hitting you today may be patched in a later frontend release.

    Verify the Fix and Decide What to Do Next

    After toggling back, close and reopen ComfyUI (or hard-reload the browser tab) and verify three things in order: the problematic node’s properties open normally, resizing a node works, and a large workflow scrolls without the CPU spike. If all three pass, you have confirmed a Nodes 2.0 regression.

    From there you have a genuine decision rather than a guess: stay on the stable LiteGraph canvas until the Vue renderer matures and your critical custom nodes add support, or re-enable Nodes 2.0 and file a reproduction for the specific bug you hit. The right short-term answer for a production workflow you depend on is almost always the stable canvas. The interface is not where you want to be an early adopter when it silently breaks your node properties.

    This stable-vs-new tradeoff is the same one you face with ComfyUI’s other default-changed subsystems. When an update changes a core default behavior before it’s fully stable — as happened with the Dynamic VRAM memory optimizer, which broke workflows and OOM’d for many users before it settled — the disciplined move is to isolate the change, confirm it’s the cause, and revert the one switch rather than your whole stack. If you’ve hit the sibling problem where ComfyUI eats system RAM or throws out-of-memory errors after an update, see my walkthrough on ComfyUI eating system RAM and OOMing, and for the broader dynamic-VRAM regressions, how to diagnose and fix the new default memory optimizer.

    Conclusion

    Nodes 2.0 is a legitimate forward step with a real cost during the transition: the new Vue renderer is less compatible with popular custom nodes, noticeably heavier on large workflows, and still shipping with rendering bugs that the team is actively fixing. The diagnosis is cheap and fast — check your frontend version, look for the node-author warning, and run the toggle test. The fix is a single menu toggle or one settings key, not a reinstall. Keep the stable canvas while the Vue path matures, and file a repro for anything you find so the next person doesn’t burn an afternoon misdiagnosing a frontend regression as a memory leak. If this was useful, I’ve written a series of similar no-fluff ComfyUI troubleshooting guides — start with the VRAM headroom vs reserve-vram and system RAM OOM walkthroughs.

  • ComfyUI Out of Memory After Updating: Fix Dynamic VRAM and Pinned Memory Regressions

    You updated ComfyUI to a recent build, restarted it, and suddenly workflows that ran fine yesterday now die with a CUDA out of memory error — even though nvidia-smi shows gigabytes of VRAM still free. The generation hangs at “Model Initializing…”, or ComfyUI crashes the moment you switch from one checkpoint to another. It is not your hardware, and it is not your workflow. It is almost certainly the new memory-management code that ComfyUI now enables by default.

    This guide walks through the two features causing the most post-update OOM reports — Dynamic VRAM and pinned memory — how to diagnose which one is biting you, and the exact launch flags that restore stability. Everything below is grounded in the official Dynamic VRAM announcement and the ComfyUI startup-flags documentation, so you can verify each flag against your own install.

    What Changed: Dynamic VRAM Is Now On by Default

    Dynamic VRAM is ComfyUI’s new memory optimization and is now enabled by default in recent stable builds. Its goals are genuinely good: lower system RAM usage, faster execution on memory-constrained hardware, faster model loads, and — ironically — the elimination of VRAM out-of-memory errors caused by insufficient weight offloading.

    The mechanism is the part that trips people up. Dynamic VRAM streams model weights between system RAM and the GPU more aggressively than the older estimate-based allocator did, and it intentionally holds more weights in VRAM when headroom exists. The official announcement is explicit that “higher VRAM usage is normal due to using VRAM more effectively.” On most single-GPU Nvidia Linux and Windows setups this works well.

    But when it interacts badly with a specific driver, a multi-GPU configuration, or a custom node that assumes the old allocation behavior, the failure mode looks exactly like a memory leak or a false OOM. The symptoms users report are consistent and reproducible:

    • OOM on model switch. ComfyUI crashes when you swap from one checkpoint to another (for example, Flux Klein base to the non-base variant) without fully restarting the process, even with tens of gigabytes of VRAM free.
    • Hang at “Model Initializing…”. The queue stalls before any sampling begins.
    • TensorRT node breakage. Nodes such as ComfyUI_TensorRT and ComfyUI-Rife-Tensorrt fail with Cuda Runtime allocation errors under Dynamic VRAM.

    If any of these appeared immediately after an update, treat the new allocator as the prime suspect before you touch resolution, batch size, or sampler settings.

    Diagnose Before You Disable: Is It Dynamic VRAM or Pinned Memory?

    Two related features shipped close together, and the r/comfyui threads that walk people through fixes nearly always test them in a specific order. Follow it and you will find the narrowest fix instead of nuking both features at once.

    1. Check the trigger. Did the OOM start after a git pull / portable updater run, with no workflow or model changes? If the answer is yes and you are on a single GPU, it is a memory-management regression, not a workflow problem.
    2. Rule out multi-GPU first. Several reports resolved simply by pinning --cuda-device to one GPU at startup. If you run multiple GPUs, try this before disabling anything — it costs you nothing and keeps Dynamic VRAM’s streaming benefits.
    3. Test pinned memory first. Add --disable-pinned-memory. This is the narrower fix. It removes the pinned-host-memory path while keeping Dynamic VRAM active.
    4. Only then disable Dynamic VRAM. If the OOM persists, launch with --disable-dynamic-vram, which falls back to the older estimate-based allocator.

    The ordering matters because these flags exist to give you an escape hatch without throwing away every performance gain. On most affected systems, one flag is enough.

    Reproducible Fix: Launch Flags That Restore Stability

    ComfyUI flags can be added to the launch command directly, to your .bat file on Windows, or to your launch script on Linux:

    python main.py --disable-pinned-memory

    If that does not resolve it, escalate to:

    python main.py --disable-dynamic-vram

    On multi-GPU machines, add device pinning regardless of which flag you end up needing:

    python main.py --cuda-device 0

    If you run ComfyUI on Windows via the portable build, open update/update_comfyui.bat or your launch .bat file and append the flag to the line that invokes python, then relaunch. On Linux, edit whatever script or systemd unit you normally use to start ComfyUI. After changing flags, fully stop the process and restart — changing flags without a clean restart will not take effect because the allocator is initialized at startup.

    Two related flags are worth knowing when you tune memory behavior:

    • --reserve-vram <GB> — reserves a fixed amount of VRAM that ComfyUI will attempt to keep free above the default headroom. Useful on shared cards where other apps need guaranteed VRAM.
    • --lowvram / --novram — legacy aggressive offload modes that force more offloading to system RAM. They are heavier-handed than the two flags above and should be a last resort.

    These flags are documented in the official ComfyUI Startup Flags reference, which lists the full set including several Dynamic VRAM headroom options.

    Common Pitfalls and How to Avoid Them

    The most common mistake is assuming the OOM is your fault and reacting by degrading the workflow — lowering resolution, cutting batch size, or switching to a smaller model — when the real issue is allocator behavior. Before you shrink a perfectly good workflow, check the flags. The memory-management code changed; your hardware and your prompt did not.

    A second pitfall is the Windows Task Manager red herring. The official announcement warns that on Windows, Task Manager may not show lower RAM usage under Dynamic VRAM because cached weights remain in RAM when memory is plentiful. Do not use Task Manager readings to conclude the feature is “leaking” — that cached memory is released when other applications need it.

    A third pitfall is a blue screen during a forced close. Multiple reports describe terminating the ComfyUI terminal while it was stuck (for example, mid-VAE-encode) and hitting a blue screen. If a generation appears frozen, give it a moment and prefer a clean shutdown over killing the process, and apply the flag fix before your next launch so you are not repeatedly hard-closing a wedged process.

    Finally, treat a broken custom node as a separate signal. If the only thing failing is a TensorRT node while normal samplers work fine, the node is incompatible with Dynamic VRAM — disable the feature rather than uninstalling the node, and report the incompatibility upstream so it can be fixed.

    Verifying the Fix

    After restarting with the corrected flags, confirm the regression is actually gone rather than assuming it is. A reliable sequence:

    1. Reproduce the trigger. Run the exact workflow (or the exact model-switch action) that produced the OOM. It should complete without touching memory-related settings.
    2. Check the queue. Confirm the generation that used to hang at “Model Initializing…” now proceeds to sampling.
    3. Confirm the flag took effect. Look at the ComfyUI startup log — the launch argument list is echoed when the server starts, so verify --disable-pinned-memory or --disable-dynamic-vram appears there.
    4. Watch the trade-off. Disabling Dynamic VRAM re-enables the older allocator, which generally uses more system RAM. Keep an eye on RAM usage for one full run and make sure you are not trading a VRAM OOM for a system-RAM OOM.

    If the narrow fix resolved it, do not re-enable the feature until a ComfyUI update notes a related fix — these regressions are actively tracked, and the maintainers ask for detailed issue reports (log, workflow, hardware, OS) to resolve them.

    Conclusion

    A post-update ComfyUI OOM with VRAM to spare is almost never a sign that you need more vRAM. It is almost always a sign that a new memory-management default is interacting badly with your configuration. The remedy is a one-line launch flag — --disable-pinned-memory first, then --disable-dynamic-vram — plus --cuda-device on multi-GPU systems. Diagnose in that order, verify with the original trigger workflow, and you will be back to stable generation without downgrading your models.

    If you are squeezing large video models onto a modest card, the same class of offloading choices matters in a different way. For a deep dive into running Wan2.2 Animate 14B within 16 GB of VRAM, see our guide on Wan2.2 Animate BlockSwap settings, and for the broader open-source stack, read running Wan2.2, CogVideoX, and Mochi on consumer hardware.

  • ComfyUI Removed the WAN Block Swap Node: Why Your Wan 2.2 / GGUF Workflow Just Broke (or Got Slower)

    If you opened a saved Wan 2.2 image-to-video (or GGUF) workflow recently and found a red node where your block swap used to be — or the same workflow now runs but leaves half your VRAM idle and feels slower — you are not doing anything wrong. ComfyUI removed the WAN Block Swap node, and the change ripples through every workflow that depended on it.

    This post explains exactly what happened, why the ComfyUI team did it, how to tell which of the two symptoms you are hitting, and the fixes that actually work as of ComfyUI v0.39.x. The source material here is the official changelog, an open GitHub regression report, and community threads — not guesses. If your goal is simply to understand what block_swap and offload_device do under the hood before it was removed, see our earlier deep-dive on how block_swap and offload_device actually work.

    What Got Removed, and Why

    The WAN Block Swap node was a third-party convenience node that let you choose which layers of a Wan video model stayed on the GPU (the “blocks”) and which got offloaded to system RAM, so a large model could squeeze into a smaller card. It was popular precisely because it gave hands-on control over VRAM usage.

    ComfyUI’s own changelog describes the change this way: “Enhanced block swap functionality by removing potentially harmful native custom node implementations.” The ComfyUI team added the node to a deny list in the source file comfy_extras/nodes_nop.py, which effectively blacklists it from being registered. The reasoning the team has given is that ComfyUI’s built-in memory management is now expected to handle layer offloading on its own, and a parallel native implementation could conflict with it — or behave in ways the core loading path cannot account for.

    The practical consequence is binary: any workflow JSON that still references the node will fail to load it, and anything that relied on its manual offloading now falls back to ComfyUI’s automatic memory estimator.

    ComfyUI workflow graph showing a red WAN Block Swap node after a software update with a missing node error banner
    A typical post-update scene: the WAN Block Swap node renders red because ComfyUI blacklisted it, breaking the workflow that still references it.

    Symptom 1: Red Node and the Workflow Stops at 0%

    This is the immediate, visible failure. If your workflow was built when the block swap node was still available, the saved JSON still contains its node ID and connections. On load, ComfyUI cannot instantiate the node, so you get a red “missing node” error, and queued jobs either refuse to start or hang at 0% progress because the graph is incomplete.

    Here is how to confirm it and what to do:

    • Confirm the node is the culprit. Look for a red node labeled “WAN Block Swap” or “WanVideoBlockSwap” in the graph. The console will typically print a missing-node or import-failed message naming the exact node type.
    • Bypass it, don’t replace it. The general guidance from the community is that native ComfyUI memory management now handles offloading on its own, so in most Wan 2.2 and GGUF workflows you can simply delete the node and reconnect the model loader output directly to the sampler. If you are using the Kijai ComfyUI-WanVideoWrapper pack, that wrapper already manages memory and never needed the extra node. For the specific VRAM settings that still work on a 16 GB card, see our Wan2.2 Animate 14B BlockSwap guide.
    • Reconnect cleanly. After deleting the node, wire the diffusion model output straight through, then reload the workflow. Save a fresh copy so the stale reference is gone permanently.

    The screenshot-style symptom to watch for is a node that looks dark red with no defined output socket — that is the blacklist at work, not a random crash.

    Symptom 2: No Error, but GGUF Models Use Only ~50% VRAM and Run Slower

    This is the subtler, more confusing regression, and it is documented in GitHub issue #11081. After the block swap node was removed, users reported that GGUF models — not just Wan, but other architectures too — loaded in NORMAL_VRAM mode while using only roughly 45–55% of available VRAM (about 7–9 GB on a 16 GB RTX 4080). Before the change, manual offloading routinely pushed VRAM occupancy to 80–90%, and generation actually felt faster.

    Why the slowdown? Two interacting factors, per the issue thread:

    • Async offload is not implemented for ComfyUI-GGUF. The “loaded partially … offloaded” behavior you see in the console means weights are being moved to and from the GPU on demand, but the async prefetching path that would hide that latency does not cover the GGUF loader. The result is the text encoder or transformer appearing to run on CPU while the GPU idles at 0–low utilization.
    • Memory estimation is now more conservative. With the manual override gone, ComfyUI’s estimator keeps more headroom, which is safer for OOM avoidance but leaves performance on the table for users who knew their exact budget.

    Note that this is explicitly labeled a performance regression by the reporter and acknowledged by maintainers in the thread, who also confirmed that “LoRA pinning will be added” in a future change to address the offloaded-LoRA cost.

    ComfyUI server console log showing loaded partially offloaded VRAM usage lines and a slow iteration time
    The telltale console lines: “loaded partially” with a large offloaded byte count while VRAM sits well below capacity.

    Diagnostic Walkthrough: Which Symptom Do You Have?

    Work through these in order before changing anything. Each step is cheap and read-only.

    • Check your ComfyUI version. Run git log --oneline -1 inside the ComfyUI directory, or read the version from the startup banner. The block swap removal landed in recent releases, so if you are on a current build, assume the node is gone even if you still have old workflow files that reference it.
    • Search your workflow JSON. Open the raw .json and grep for block or BlockSwap or your old node’s exact type string. If it is present, you are on symptom 1.
    • Watch the console on load. Lines like loaded partially … MB offloaded combined with low GPU utilization and a slow s/it point to symptom 2.
    • Compare VRAM occupancy. If nvidia-smi shows your card at ~50% during a Wan/GGUF run that used to sit at 80–90%, that is the regression signature.

    Understanding which bucket you are in matters because the fixes are different, and applying the wrong one wastes time.

    Fixes and Workarounds That Actually Help (Right Now)

    Let’s go through the options from safest to most aggressive, with the tradeoffs made explicit.

    1. Delete and rewire the node (symptom 1)

    The low-risk fix. Remove the stale node, connect the loader directly to the sampler, and save a new copy of the workflow. If you are on Kijai’s wrapper, this is the officially community-blessed path — the wrapper does its own memory management.

    2. Remove the blacklist entry (symptom 1, if you truly need the old node)

    Community users point out that editing or deleting the relevant entry in comfy_extras/nodes_nop.py restores the node. If you go this route, understand the consequences: the ComfyUI team will not support or fix a node it deliberately blacklisted, and subsequent updates may re-remove it. Treat this as a temporary bridge, not a permanent fix.

    3. Use a negative --reserve-vram to reclaim headroom (symptom 2)

    In the regression thread, a user reported experimenting with a negative value such as --reserve-vram -1 to “trick” the memory estimator into using more of the GPU. This can restore higher VRAM occupancy and speed, but it is described by the reporters themselves as “just a funny trick” with the explicit downside of possible out-of-memory crashes. Only try it if you are comfortable watching for OOM and understand it may destabilize other models.

    4. Wait for the acknowledged fixes (symptom 2, long term)

    The maintainers in the issue thread acknowledged the async-offload gap for GGUF and stated that LoRA pinning is coming. If you are not in a hurry, the cleanest outcome for the GGUF slowdown is the upstream improvement rather than a fragile local hack.

    There is no one-size-fits-all answer, but the decision tree is simple: red node → rewire or remove the blacklist; slow-but-not-erroring → evaluate reserve-vram with OOM awareness, or wait for upstream.

    How to Verify the Fix Worked

    Whatever path you take, confirm the result instead of trusting that the error disappeared.

    • Graph is clean. Reload the workflow and confirm there are no red nodes and the queue runs past 0%.
    • VRAM occupancy is what you expect. Run nvidia-smi during generation. After a rewire, occupancy should reflect the model’s real footprint; after a reserve-vram tweak, it should rise toward your target.
    • Speed is sane. Watch the s/it (or it/s) figure in the console. If the text encoder step is suddenly taking minutes while the GPU idles, you are still on the async-offload symptom, not an improvable one.
    • Save a clean copy. Persist the fixed workflow under a new filename so the stale node reference cannot silently resurface.

    If the “wait for upstream” route is your choice, bookmark the issue and re-test on a later release — the maintainers have already tied the symptom to concrete planned work.

    References

    If you have hit this on a different model than Wan 2.2, the same diagnosis generally applies — check your workflow JSON for stale nodes first, then look at your VRAM occupancy curve before touching launch flags.

  • ComfyUI WebSocket API: Stop Polling queue_remaining to Detect Completion (Use execution_success)

    A quiet change in a recent ComfyUI update broke more API automation than any YouTube “10 ComfyUI mistakes” video ever will. Scripts that had been reliably signaling “the image is ready — download it now” suddenly started waiting 20 to 30 seconds longer than necessary, or worse, raced the output and tried to fetch a file that did not exist yet.

    The root cause was not a bug in your code. It was a new WebSocket data type called progress_state, which ComfyUI now emits per node during execution. If your completion-detection logic depended on watching the status message and reading queue_remaining == 0, that assumption has quietly stopped being reliable.

    In this guide I’ll walk through exactly what changed, why the naive pattern breaks, and the two robust signals the documentation actually recommends. If you run ComfyUI behind a Python wrapper, a Node service, or any batch pipeline, this is the fix that keeps your queue from stalling. It pairs naturally with the queue-control work I covered in reliably canceling and clearing jobs from scripts, and builds on the broader WebSocket events vs. history polling discussion.

    What Actually Changed: The progress_state Message

    Historically, ComfyUI’s WebSocket (/ws) returned a small, well-known set of message types. The one most people built against was status, whose exec_info.queue_remaining counter told you how many prompts were still waiting in the queue:

    {
      "type": "status",
      "data": {
        "status": {
          "exec_info": { "queue_remaining": 0 }
        }
      }
    }

    When queue_remaining hit zero, the reasoning went, the prompt was done and the output file was ready. That logic held for most single-prompt rigs for a long time.

    Then ComfyUI started emitting progress_state — a new, much richer per-node progress report. Rather than a single queue counter, this message carries an entire nodes dictionary where each entry tracks an individual node’s state (such as finished), its value, and its max. A trimmed example from a real report looks like this:

    {
      "data": {
        "nodes": {
          "400": { "node_id": "400", "state": "finished", "value": 1.0, "max": 1.0 },
          "503": { "node_id": "503", "state": "finished", "value": 20, "max": 20 },
          "504": { "node_id": "504", "state": "finished", "value": 1.0, "max": 1.0 }
        }
      }
    }

    The problem is timing. Because this data shows per-node progress, some samplers keep reporting well after the actual generation has finished, which makes the WebSocket look busy even when the console is idle. One user reported on the ComfyUI issue tracker that after a generation completed, their connection kept receiving progress_state for 20 to 30 seconds before a final status with queue_remaining: 0 arrived. Anyone who had wired “fetch the output” to that final status message saw a built-in delay creep into every job.

    Why queue_remaining Was Always a Fragile Signal

    Even before progress_state arrived, building completion detection on status was a subtle mistake. The status message is defined in the official Server Messages documentation as firing “when the state of the queue changes.” That is a queue event, not an execution event.

    Those two are not the same thing. queue_remaining tells you how many prompts are waiting to start. It does not tell you that the current prompt finished generating correctly, that a given node errored out partway, or that the output has actually been written to disk. For a single-prompt-only server, the two happen to coincide closely enough that the bug stayed hidden. Add a second prompt to the queue, a cached node, an interrupt, or a mid-run failure, and the coincidence breaks.

    There is a second, harder-to-see failure mode. A contributor on the same issue thread spelled out what most wrapping libraries get wrong: periodically opening and closing WebSocket connections to “check in” on progress is fundamentally unreliable, because you can miss the one message that matters. If your loop connects, reads a few frames, disconnects, and reconnects, a fast completion can slip through the gap and leave you waiting forever for a message you already missed.

    The Correct Signal: execution_success and execution_error

    ComfyUI’s PromptExecutor has a dedicated mechanism precisely for “this prompt is done,” and it is not status. The built-in message types include execution_start, execution_success, and execution_error. These carry the prompt_id you received back when you POSTed to /prompt, which lets you match a completion to the exact job you submitted.

    Here is the dependable flow, which matches the pattern the official API examples recommend:

    1. POST your workflow JSON to /prompt and capture the returned prompt_id.
    2. Hold one open WebSocket to /ws for the lifetime of the job — do not reconnect in a loop.
    3. Listen for execution_success carrying your prompt_id as the success signal.
    4. Listen for execution_error carrying your prompt_id as the failure signal.
    5. Only after success, query /history/{prompt_id} to retrieve the output node’s images.

    A minimal Python listener looks like this:

    import json, urllib.request, websocket  # pip install websocket-client
    
    SERVER = "127.0.0.1:8188"
    
    def queue_prompt(prompt):
        body = json.dumps({"prompt": prompt}).encode()
        req = urllib.request.Request(f"http://{SERVER}/prompt", data=body)
        return json.loads(urllib.request.urlopen(req).read())["prompt_id"]
    
    def wait_for_completion(prompt_id):
        ws = websocket.WebSocket()
        ws.connect(f"ws://{SERVER}/ws")
        while True:
            msg = json.loads(ws.recv())
            if msg["type"] == "execution_success":
                if msg["data"]["prompt_id"] == prompt_id:
                    return "success"
            elif msg["type"] == "execution_error":
                if msg["data"]["prompt_id"] == prompt_id:
                    return "error"
            elif msg["type"] == "executing" and msg["data"]["node"] is None:
                # Belt-and-suspenders: also treat node=None as completion
                if msg["data"]["prompt_id"] == prompt_id:
                    return "success"
    

    Note the second signal in that loop: the executing message with node set to None. This is ComfyUI’s long-standing “the current execution finished” marker, and it remains valid regardless of the progress_state change. Matching on prompt_id is the key that keeps everything correct when more than one prompt is in flight.

    Diagnosing a Stalled or Delayed Pipeline

    If you already have a wrapper that suddenly feels slow, the first step is to log raw message types and see what is actually arriving. Add a single line that prints msg["type"] for every frame you receive. Two patterns are the giveaways:

    • Long tail of progress_state after the image exists: your code is waiting on the wrong event. Generation finished; you are just staring at per-node progress. Switch completion detection to execution_success or executing with node=None.
    • Reconnect loop that occasionally hangs forever: you are opening and closing connections and missing the completion message in the gap. Switch to a single persistent connection opened before you POST the prompt.

    There is also a subtler trap worth checking: cached workflows. When nodes run from cache, the execution_cached message lists the skipped nodes. A cached run still emits execution_success, but it can complete far faster than a full run. If your wrapper assumes a minimum runtime or a minimum number of progress messages, a cached generation will break that assumption. Always key off the completion events, never off “how many progress frames I think I should see.”

    Why Not Just Poll /history?

    An alternative many people reach for is polling /history/{prompt_id} until the entry appears. This works — it is what the older “submit and forget” examples effectively do — but it has two downsides. First, it introduces polling latency: you are bounded by your own sleep interval, so a prompt that finished in 3 seconds gets noticed up to a full poll period later. Second, it does not cleanly distinguish “still running” from “already errored,” because an error can leave the prompt absent from /history entirely, which looks identical to “still in progress” and can hang your loop forever without a timeout.

    Polling /history is fine as a fallback reconciliation step after you have received execution_success — you use it to fetch the actual output filenames. It is a poor primary completion signal.

    Putting It Together

    The takeaway is simple: status.queue_remaining was never the canonical “done” signal, and the recent progress_state change finally made that fact visible to everyone. The correct contract has been sitting in the official message docs the whole time:

    • Submit with /prompt and keep the prompt_id.
    • Hold one persistent WebSocket.
    • Treat execution_success (or execution_error) for your prompt_id as the authoritative completion event.
    • Accept executing with node=None as a legacy-compatible equivalent.
    • Use /history/{prompt_id} only to retrieve outputs, not to detect completion.

    For the underlying message semantics and the full list of built-in event types, the ComfyUI Server Messages reference is the authoritative source. If you have built any home-grown ComfyUI orchestration — a batch renderer, a front-end that shows progress, or a worker that ships images downstream — auditing your completion detection against these signals is the highest-leverage fix you can make this week. It is the difference between a pipeline that idles for 30 seconds on every job and one that pops the image the instant it is ready.

  • ComfyUI Dynamic VRAM Broke My Workflow: How to Diagnose and Fix the New Default Memory Optimizer

    ComfyUI recently shipped one of its most significant memory-management changes in years, and a surprising number of users only discovered it when their workflows broke. The feature is called Dynamic VRAM, and as of the latest git (and now stable) builds it is enabled by default. On paper it promises lower RAM usage, faster execution on memory-constrained hardware, and the end of VRAM out-of-memory errors. In practice, a steady stream of reports shows it breaking TensorRT acceleration, halving throughput on high-end cards, and introducing brand-new OOM and device-mismatch errors that never existed before. This guide explains what Dynamic VRAM actually changes, how to recognize that it is the cause of your problem, and the exact steps to confirm and fix it.

    This is a documentation-and-evidence analysis based on the official announcement, the ComfyUI source discussion, and reproducible community reports. Where I describe a symptom or a fix, I cite the public record rather than claiming a personal benchmark.

    What Dynamic VRAM Actually Changes

    Dynamic VRAM is a rewrite of how ComfyUI offloads model weights between GPU video memory and system RAM. The traditional behavior offloaded whole models or large weight blocks according to fixed rules, which is why older workflows on tight hardware spent visible time swapping. The new allocator tracks weights more granularly and moves only what it needs, when it needs it.

    According to the official announcement by comfyanonymous, the intended benefits include:

    • Lower RAM usage on memory-constrained machines
    • Faster workflow execution when hardware is tight
    • Fewer VRAM OOMs caused by insufficient weight offloading
    • Faster initial model load and faster LoRA application
    • The ability to run models larger than system RAM without paging

    The last point is the key technical idea: cached weights are kept in RAM only when there is plenty to spare, and they are unloaded on demand instead of ending up in the Windows page file. That is a genuinely better default for the low-VRAM, low-RAM laptops ComfyUI is increasingly run on. The problem is that the same machinery interferes with features that assumed the old, predictable offloading behavior.

    Symptom 1: TensorRT Nodes Throw a CUDA Allocation Error

    The most clear-cut and widely reproduced break is with TensorRT-accelerated custom nodes. The first reports in the announcement thread pointed to ComfyUI_TensorRT and ComfyUI-Rife-TensorRT failing with:

    [E] [defaultAllocator.cpp::nvinfer1::internal::DefaultAllocator::allocate::53]
    Error Code 1: Cuda Runtime

    TensorRT manages its own CUDA memory pool through its default allocator. Dynamic VRAM’s new allocation path conflicts with that pool, and the result is an immediate failure the moment a TensorRT node tries to allocate. The workaround reported by users is to launch ComfyUI with the explicit opt-out flag:

    python main.py --disable-dynamic-vram

    If your workflow uses TensorRT (UpscaleTensorRT, Rife TensorRT, any node with a TensorRT engine), and it starts failing after an update, try this flag first. It is the single highest-confidence signal that Dynamic VRAM is your culprit.

    Symptom 2: Slower Generation on High-VRAM Cards (4090 / 5090)

    The counterintuitive failure is that the feature can slow down precisely the machines it was not designed to help. One report in the discussion thread describes a 4090/5090 server going from about 2 seconds to 4.4 seconds per image after the change, with the observation that the allocator appeared to be constantly re-loading parts of the model that had previously been cached and reused.

    The interaction is most noticeable with heavy node packs such as ComfyUI-Impact-Pack and efficiency-nodes, on systems with 64 GB or more of system RAM. When you have abundant VRAM, the fixed offloading rules were fast because everything stayed resident. Dynamic VRAM’s more aggressive weight shuffling can defeat that caching and introduce overhead that did not exist before.

    The diagnostic here is not an error message but a regression in total workflow time. The announcement itself advises comparing total workflow execution time rather than it/s, because the new allocator changes where time is spent. If a workflow that was fast is now slow after updating, run the same workflow with:

    python main.py --disable-dynamic-vram

    and compare the wall-clock time. A meaningful difference that reverts on the flag is your answer.

    Symptom 3: New OOM Errors and “Same Device” Exceptions

    Some users report the opposite of the feature’s promise: fresh out-of-memory conditions, or a PyTorch device error like:

    RuntimeError: Expected all tensors to be on the same device,
    but found at least two devices, cuda:0 and cpu

    This is consistent with weights or intermediate tensors being moved to system RAM by the dynamic allocator while another part of the graph still expects them on the GPU. It appears on the same workflows that previously ran cleanly, and in at least one report a 4090 running a large model began OOM-ing immediately after a clean install of the updated build.

    Rounding out the picture, there is also a class of report describing silent failures — the UI showing “reconnecting” during VAE decode rather than an explicit error. Because these failures produce no usable traceback, they are the easiest to misattribute to a broken node or a corrupted model.

    One caveat worth stating plainly: a real VRAM OOM is not necessarily Dynamic VRAM’s fault. Before blaming the new allocator, confirm you have not simply loaded a model larger than your card. The reliable discriminator is the same everywhere: launch with --disable-dynamic-vram and see whether the error disappears. If disabling the feature fixes it, you have isolated the cause. If the error persists with it disabled, your problem is elsewhere (model size, a custom node, or a stale install).

    How to Confirm the Cause and Apply the Fix

    The decision flow is deliberately small. Dynamic VRAM ships enabled by default, so the only real question is whether it is hurting you.

    1. Identify the trigger. Did the problem start after updating ComfyUI? TensorRT failures, device-mismatch exceptions, and sudden slowdowns that appear post-update are the three signals to look for.
    2. Reproduce with the default allocator disabled. Restart ComfyUI with --disable-dynamic-vram and re-run the exact same workflow or node.
    3. Compare. If the error vanishes or the time improves, Dynamic VRAM is confirmed as the cause. Keep the flag.
    4. Persist the flag. For a Portable install, add the argument to your start script or the shortcut target. For a desktop install, add it to the launch arguments. The exact method depends on how you normally start ComfyUI.

    If you prefer to stay on the new allocator and wait for a fix, the maintainers have explicitly asked for detailed bug reports — full logs, the workflow, and your hardware and OS — and recommend comparing total execution time when reporting performance regressions.

    One more practical note: reports also mention that some launch flags interact poorly with the new allocator. In particular, on iGPU setups users observing that --reserve-vram no longer produced the expected offloading described a situation where disabling Dynamic VRAM alone was not enough to restore prior behavior. The takeaway for iGPU and unusual-memory environments is that you may need to experiment with --reserve-vram and --disable-dynamic-vram together to recover the old semantics. This is a documentation-level observation, not a tested claim on my own hardware.

    When You Should Keep Dynamic VRAM On

    It is easy to read the bug reports and conclude the feature is broken. That would be the wrong lesson. The feature exists because it genuinely helps the most common, most memory-constrained case. If you run ComfyUI on a laptop GPU with 6–8 GB of VRAM, or on a machine where system RAM is the real bottleneck, the lower RAM usage and the ability to run larger models without paging are directly relevant to you.

    In short: the decision is about which failure mode you are actually hitting. On low-VRAM hardware, keep it on and enjoy the RAM savings. On high-VRAM cards with TensorRT nodes or workloads that regressed in speed, disabling it restores the previous, known-good behavior. Both are legitimate, and the --disable-dynamic-vram flag is the switch between them.

    Conclusion

    Dynamic VRAM is a meaningful improvement to ComfyUI’s memory handling that shipped as a default — and, like many defaults that change on endpoints and GPUs the developers cannot all reproduce, it has a real and growing list of regressions in the wild. The failures cluster around three patterns: TensorRT CUDA allocation errors, throughput regression on high-VRAM cards, and new OOM or device-mismatch exceptions. All three share one diagnostic and one fix: restart with --disable-dynamic-vram and compare.

    If you maintain a local ComfyUI setup that was stable and broke after an update, resist the urge to bisect custom nodes first. Check whether the new memory allocator is the variable, because in this release it very often is. For a deeper look at how ComfyUI manages memory at the launch-flag level — including --lowvram, --normalvram, and --cpu — see our breakdown of ComfyUI VRAM launch flags. And for a concrete example of tuning a large model to fit consumer VRAM, our walkthrough of running Wan2.2 Animate 14B on 16 GB is directly relevant.


    References: Comfy-Org Dynamic VRAM announcement (Discussion #12699), ComfyUI troubleshooting guide.

  • ComfyUI “Invalid Workflow Against Zod Schema”: How to Read the Field Path and Fix It

    What “Invalid Workflow Against Zod Schema” Actually Means

    Zod is a TypeScript validation library that ComfyUI’s frontend uses to check whether a workflow file conforms to the expected JSON shape before it is loaded into the graph. When you see Invalid workflow against zod schema, the frontend has rejected the file at parse time — before any node runs, before any model loads, and before the workflow is even fully rendered.

    This is different from a runtime error such as Prompt Outputs Failed Validation, which is returned by the backend when a queued prompt fails input checks. The Zod message is purely a frontend guard. It appears the moment you drag a .json into the browser or click a workflow in the sidebar, and it tells you the frontend could not trust the file’s structure.

    The error usually lists the exact field it rejected, with a path like nodes[8].properties.ver. That path is the single most useful piece of information in the message. It tells you precisely which node, which property, and which nested field failed, so you never have to guess.

    Step 1: Read the Field Path, Not the Message Summary

    The summary line is generic — “Invalid workflow against zod schema” — but the block underneath it is specific. A typical output looks like this:

    Invalid workflow against zod schema:
    Validation error:
    Invalid semantic version (x.y.z) at "nodes[8].properties.ver";
    Invalid semantic version (x.y.z) at "nodes[14].properties.ver";
    Invalid format. Must be 'github-user/repo-name' at "nodes[0].properties.aux_id";

    Each line follows the same structure: a human description of the rule, followed by the JSON path where it failed. In the first example above, the ver (node pack version) field on several nodes does not match a semantic-version pattern. In the second, an aux_id field is expected to look like github-user/repo-name but contains something else.

    This is the key insight: the field that failed is almost never a field you set by hand. It is usually metadata written by a custom node pack, a ComfyUI workflow-sharing site, or an older frontend version. You are typically looking at a compatibility problem, not a mistake you made.

    Step 2: Identify What Produced the Invalid Field

    Once you know the field name, ask where that field comes from. The two most common offenders reported by users are exactly the ones above:

    • ver (a semantic version) — written by node packs to record their own version. Older custom nodes may write values like "1.0" or "latest" that predate the stricter x.y.z check.
    • aux_id — written by certain extensions that expect a GitHub-style identifier, and rejected when a workflow was exported with a different ID format.

    The relevant validation for the ver field was introduced in the ComfyUI frontend and expects a semantic version such as 1.2.3-beta.1+metadata, while also accepting git commit hashes. Workflows exported before (or by tools unaware of) that change trigger the error even though the workflow runs fine once loaded.

    The practical takeaway: if the error enumerates ver or aux_id fields, the workflow is very likely still functional. The rejection is a formatting guard, not a signal that the graph is broken. Do not start deleting nodes or reinstalling custom nodes based on this message alone.

    Step 3: Two Ways to Stop the Error

    There are two acceptable resolutions, and which one you choose depends on whether you control the workflow’s export.

    Option A: Disable workflow validation (temporary)

    ComfyUI ships a setting that toggles frontend workflow validation off. Turning it off stops the popup and lets the workflow load. This is the fastest fix and is explicitly described by the ComfyUI team as “okay to disable temporarily.”

    In the ComfyUI settings, open the relevant UI/personalization section and toggle the workflow validation option off. The error toast and the blocking popup disappear immediately. The downside is that you also disable a useful early warning for genuinely malformed files, so it is best treated as a stopgap while you keep reading.

    Option B: Fix the field in the workflow JSON

    For a durable fix, open the .json in any text editor and correct the flagged property. Using the path from the error, find the node by its array index and edit the value:

    • For ver, change the value to a valid semantic version like "1.0.0".
    • For aux_id, change the value to a format matching github-user/repo-name.

    Be careful with node array indexes. A path of nodes[8] refers to the 9th element (zero-based) of the top-level nodes array. Do not trust the on-screen node numbering, which can differ. If you prefer not to hand-edit, re-exporting the workflow from a current ComfyUI install often regenerates the metadata in the accepted format automatically.

    A warning before you edit: JSON is not whitespace-sensitive, but a stray comma or an unescaped quote will turn a merely “failing validation” workflow into one that will not parse at all. Edit one field at a time and keep a backup copy.

    Step 4: Verify the Fix and Prevent Regressions

    After disabling validation or correcting the field, reload the workflow and confirm the error no longer appears across page switches. Then run a trivial generation — a default workflow with a small model is enough — to confirm the graph actually executes, since “loads without the popup” and “runs correctly” are two different tests.

    To prevent the problem returning, keep two things in mind:

    • Export your own workflows from the current ComfyUI version rather than reusing files from months-old packs.
    • When a workflow-sharing site hands you a file with this error, note that the file may have been produced with a different frontend version; treat the error as a nudge to check the source, not as proof the workflow is worthless.

    If you are comfortable with the underlying format, this maps directly to the advice in our guide on modifying ComfyUI workflow JSON with OpenCode, where the same “find the node, fix the field, reload” loop is applied to parameter tuning rather than schema errors. The JSON literacy is transferable.

    When It Is Not Just a Formatting Guard

    Do not assume every “zod schema” message is harmless. If the error points at a field that is clearly structural — a missing wire, a malformed link, a required node property that is absent — the workflow may genuinely not load. In that case the fix is not a toggle switch but correcting the actual structure, which usually means reconnecting the node in a working ComfyUI install and re-exporting.

    Also verify you are running a reasonably current ComfyUI version. The specific ver false-positive described above was fixed upstream, so if you are still seeing it on a fresh install, check for a different cause rather than re-applying the workaround indefinitely.

    Conclusion

    “Invalid workflow against zod schema” is a frontend validation guard, not a generation failure. The fastest path through it is to read the JSON field path in the error body, recognize that most flagged fields are node-pack metadata such as ver or aux_id, and either disable workflow validation temporarily or correct the field in the file. Verify by reloading and running a small test generation, and you will have converted a confusing popup into a two-minute fix.

    For authoritative details on the validation behavior and the ver field rule, see Comfy-Org/ComfyUI issue #7309 and the official ComfyUI troubleshooting guide.

  • ComfyUI “HostBuffer.read_file_slice failed” → CUDA Out of Memory: The Dynamic VRAM Regression (and 4 Working Fixes)

    Your generation starts, the model stages into VRAM, the sampler kicks off — and then the console throws HostBuffer.read_file_slice failed immediately followed by CUDA error: out of memory. The frustrating part: nvidia-smi shows 14 GB of VRAM sitting free. You didn’t run out of memory. You hit a specific, dated regression in ComfyUI’s Dynamic VRAM streaming layer that shipped in the August 3, 2026 update.

    This article walks through the exact symptoms, what actually causes the failure, how to confirm you have this bug (and not a real OOM), and four verified workarounds — plus what to do if your hardware or version doesn’t match any of them.

    The symptom: a crash that looks like OOM but isn’t

    The signature of this bug is the order of two lines in your console. Right before the full CUDA out-of-memory traceback, you’ll see a host-buffer read failure:

    [ERROR] !!! Exception during processing !!! CUDA error: out of memory
      File "...\comfy\ops.py", line 228, in cast_modules_with_vbar
        handle_pin(s, pin, xfer_source, xfer_dest, subset=subset, size=dest_size)
      File "...\comfy_aimdo\host_buffer.py", line 109, in read_file_slice
        raise RuntimeError("HostBuffer.read_file_slice failed")
    RuntimeError: HostBuffer.read_file_slice failed

    On some setups the message appears in a slightly different form, coming from the native comfy-aimdo backing library rather than Python:

    aimdo: src/hostbuf.c:283:ALL:hostbuf_read_file_slice: device copy failed result=2 device_ptr=... device=0 stream=... size=67108864

    The tell is result=2, which maps to cudaErrorMemoryAllocation on the very first block of the first sampler step. The transfer that fails can be a large one (64 MiB) or a tiny one (19 MiB) — the size doesn’t matter. If your failure happens immediately during sampling, before any meaningful progress bar movement, and nvidia-smi confirms large amounts of free VRAM, you are almost certainly looking at this regression rather than an undersized GPU.

    What makes it deceptive is that it is not a real memory shortage. Multiple reporters on the tracker confirm the crash reproduces on models both large and small, on RTX 4080, 5060 Ti, and 5080 cards, with 14+ GB of VRAM visibly free in nvidia-smi at the moment of failure. A genuine VRAM OOM fails late, after tiling through sampling steps, and your card will actually be pinned at 100% VRAM when it dies.

    What actually breaks: the comfy-aimdo host-buffer layer

    ComfyUI 0.30.x runs Dynamic VRAM streaming by default. This subsystem — implemented in a component called comfy-aimdo (version 0.4.11 at the time of writing, with a 0.4.12.dev3 also affected) — streams model weights between system RAM and the GPU on a pinned host buffer instead of loading the entire model into VRAM up front. It’s a worthwhile optimization for fixed-vRAM cards, but the August 3, 2026 update introduced a regression in exactly this layer.

    The failure mode, as documented in ComfyUI issue #15255 (tagged CORE-398 and escalated to NVIDIA as a CUDA error), is a host-to-device copy that returns cudaErrorMemoryAllocation. The maintainers have flagged it as a CUDA-level error and linked three open pull requests (#15490, #15610, #15932) aimed at adding graceful fallback and guarding the free-memory query. As of this writing the issue is still open, so the workarounds below remain the practical path while the fix is in flight.

    There’s also a second, related change worth knowing about, because it produces a similar-looking symptom that is actually a different behavior. ComfyUI 0.30.0 rewrote its RAM allocation strategy to commit more system RAM up front, trading disk reloads for higher RAM usage. Some users on smaller-RAM machines see RAM climb past 80% before an error, which can look like the same bug but is the new caching strategy rather than the CUDA copy failure. The maintainers’ guidance for that case is to launch with --fast-disk, which reverts to the older, lower-RAM, more disk-intensive loading. If your console shows the RAM figure — not VRAM — climbing before the crash, try --fast-disk before the Dynamic VRAM flags below.

    Confirming the diagnosis in three steps

    Before you start toggling flags, spend two minutes confirming which problem you actually have. The wrong fix wastes time and can mask the real issue.

    1. Check the console order. Does HostBuffer.read_file_slice failed (or hostbuf_read_file_slice ... result=2) appear immediately before the CUDA OOM? If yes, it’s the Dynamic VRAM regression. If you see a plain CUDA OOM with no host-buffer line, it’s a conventional memory issue and you should look at --lowvram or a smaller model.
    2. Check free VRAM at the moment of failure. Keep nvidia-smi running in a second terminal (nvidia-smi -l 1). If VRAM is still mostly free when the crash fires, it isn’t a real shortage.
    3. Test with one flag. Restart ComfyUI with --disable-dynamic-vram and re-run the same workflow. If it completes, you’ve confirmed the regression.

    One subtle pitfall: on multi-GPU or headless setups, the crash may leave the prompt_worker thread dead while the process itself stays alive holding ~31 GB. Retrying without a full restart will then make every subsequent prompt fail, which is why some people report “all generations broken” after the first crash. If you see persistent failures after an initial crash, fully restart ComfyUI before drawing any conclusions.

    Four verified workarounds

    These are drawn from the maintainers’ own pinned note on the issue and confirmed by multiple reporters. Apply them one at a time, in order of how invasive they are, and re-test after each.

    1. Disable Dynamic VRAM (the most reliable fix)

    This is the fix that the issue author and several commenters confirm resolves the crash completely on single-GPU machines. Add the flag to your launch command:

    python main.py --disable-dynamic-vram

    If you launch via the desktop app or a batch file, add --disable-dynamic-vram to the arguments there instead. Note that this disables the streaming optimization, so you’ll fall back to the prior memory behavior — on a 16 GB card you may need to re-enable --lowvram or --normalvram as appropriate for your model.

    2. Disable pinned memory

    The maintainers’ note also offers --disable-pinned-memory for multi-GPU configurations. This preserves some functionality while avoiding the pinned-host-buffer path that’s failing:

    python main.py --disable-pinned-memory

    This is the recommended first try specifically for multi-GPU setups, since the pinned-memory fast path is where the host-to-device copy goes wrong.

    3. Restrict ComfyUI to a single GPU

    The maintainers’ workaround note suggests --cuda-device 0 (or a higher index) to restrict ComfyUI to one GPU:

    python main.py --cuda-device 0

    Use this if you have a mixed multi-GPU setup (for example, a primary card plus a second card driving the display) and want to sidestep the multi-device host-buffer path entirely.

    4. Downgrade to 0.29.x

    Several reporters confirm that rolling back to a pre-regression build eliminates the problem. One user pinned a clean reproduction: Z-Image-Turbo’s first generation succeeds but the second hangs indefinitely at the KSampler stage, and downgrading from 0.30.0 to 0.29.0 makes both generations work flawlessly. If you need production output today and can’t afford to toggle flags, this is the bulletproof option — just remember you’ll miss the newer features until the fix lands.

    For a clean install you can pin the older version through git or reinstall the desktop app at the prior release. If you maintain a portable install, keep a backup of your working version before any update so a one-command rollback is always available.

    Common pitfalls people hit along the way

    • Assuming it’s a weak GPU. The most damaging mistake is “fixing” a regression by buying more VRAM or shrinking your model. If the crash is instant and VRAM is free, hardware is not the problem.
    • Mixing up RAM and VRAM climbs. A rising system RAM figure before the error points to the 0.30.0 RAM-strategy change (--fast-disk), not the Dynamic VRAM CUDA bug. Read which resource is actually climbing.
    • Retrying without restarting. After a prompt_worker death the process may stay alive holding memory, so subsequent runs all fail. Restart fully before re-testing any flag.
    • Chasing custom nodes. The issue reproduces on a clean environment with custom nodes disabled, so don’t burn hours bisecting your node set — this is a core regression.
    • Ignoring the multi-GPU distinction. Single GPU → prefer --disable-dynamic-vram. Multi-GPU → try --disable-pinned-memory or --cuda-device first, per the maintainers’ note.

    Verifying the fix

    After applying a workaround, confirm it actually resolved the regression rather than just delaying it. Run the same workflow at least twice in a row — several reporters note the bug can pass the first generation and only fail (or hang) on the second. If you get two clean generations back-to-back with stable VRAM and RAM readings, you’re clear. Keep nvidia-smi -l 1 open during the test and confirm VRAM is released between generations, since the newer RAM strategy can also mask a partial fix by holding memory across runs.

    Finally, subscribe to the status of issue #15255 and its linked pull requests (#15490, #15610, #15932). Once a graceful-fallback patch lands in a future release, you can safely remove the --disable-* flags and go back to full Dynamic VRAM streaming. When you do, re-run the two-generation test to confirm the fix sticks on your exact hardware.

    Conclusion

    HostBuffer.read_file_slice failed followed by CUDA error: out of memory is not your GPU failing — it’s a dated regression in ComfyUI 0.30.x’s Dynamic VRAM streaming, tracked as issue #15255, that breaks generations instantly despite abundant free VRAM. Confirm the console order and free-VRAM reading, then apply --disable-dynamic-vram (single GPU), --disable-pinned-memory or --cuda-device (multi-GPU), or roll back to 0.29.x for a bulletproof stopgap. If instead your system RAM is what climbs before the error, try --fast-disk. For more on keeping ComfyUI stable across version updates, see our guide on recovering ComfyUI after a Python 3.13 upgrade, and always cross-check your symptoms against the official ComfyUI troubleshooting guide.

  • ComfyUI Broke After a Python 3.13 Upgrade? The torchvision / sentencepiece Trap and How to Fix It

    One of the most confusing ways to lose a working ComfyUI install is not a model or a broken node — it is a Python version bump you did not think about. A routine OS package upgrade, or a freshly reinstalled portable build, silently moves you from Python 3.12 to 3.13. The next time you launch, ComfyUI fails early with a stack trace that ends in ModuleNotFoundError: No module named 'torchvision' — or sentencepiece, or torchaudio, or safetensors. Nothing about your models, workflows, or custom nodes changed, yet the whole thing refuses to start.

    This is a documented, recurring failure that has hit the ComfyUI issue tracker repeatedly. It is not a bug in ComfyUI’s own code so much as a collision between a fast-moving Python release and a long tail of C-extension wheels that have not all caught up. This guide walks through the actual error, why it happens, which packages are the usual culprits, and the three fixes that reliably get you back to a working install — without nuking your model folders.

    The Symptom: an Early, Misleading Stack Trace

    The failure happens at import time, before any model is loaded. A typical report from ComfyUI issue #6169 shows the launch log reporting a healthy-looking environment — pytorch version: 2.5.1+cu124, GPU detected, VRAM total printed — and then dying almost immediately in main.py:

    File "/data1/ComfyUI/main.py", line 132, in <module>
        import execution
    File "/data1/ComfyUI/execution.py", line 13, in <module>
        import nodes
    File "/data1/ComfyUI/nodes.py", line 22, in <module>
        import comfy.diffusers_load
    File "/data1/ComfyUI/comfy/diffusers_load.py", line 3, in <module>
        import comfy.sd
    File "/data1/ComfyUI/comfy/sd.py", line 10, in <module>
        from .ldm.cascade.stage_c_coder import StageC_coder
    File "/data1/ComfyUI/comfy/ldm/cascade/stage_c_coder.py", line 19, in <module>
        import torchvision
    ModuleNotFoundError: No module named 'torchvision'

    Note the diagnostic trap here: the traceback points at torchvision, but that is usually just the first missing wheel in a longer chain. The same reporter tried to work around it by editing requirements.txt, only to hit torchaudio, then sentencepiece, then kornia, then spandrel one after another. If you fix them one at a time you will chase them forever. The real root cause is the Python interpreter version underneath all of them.

    Why 3.13 Breaks It: Python Wheels and C Extensions

    ComfyUI is a Python application, and most of its heavy dependencies — PyTorch, torchvision, torchaudio, sentencepiece, and a dozen more — are built as platform-specific, Python-version-specific wheels. A wheel compiled for CPython 3.12 has a filename like torchvision-0.x.x-cp312-cp312-win_amd64.whl. The cp312 tag means it only installs into a Python 3.12 interpreter. When you move to 3.13, pip looks for a cp313 wheel, and for some packages it simply does not exist yet — or exists but only as a source build that requires a compiler you do not have installed.

    Two facts make this worse:

    • Python 3.13 became the default on newer distros. As project maintainer comfyanonymous noted in the same issue thread, 3.13 is now the stock interpreter on many Ubuntu- and Fedora-based releases shipping in 2025 and later. A clean OS install can land you on a Python that your wheel ecosystem has not fully caught up with.
    • Some wheels are effectively unmaintained or compiler-gated. sentencepiece is a recurring example: if a prebuilt wheel is unavailable for your interpreter, installing it from source requires cmake and a C++ toolchain (clang++). Ordinary users do not have that set up, so the install fails cryptically with ERROR: No matching distribution found.

    Layer on top of this that individual custom nodes pull their own dependencies. As the official ComfyUI dependencies documentation explains, each custom node ships its own requirements.txt, and some of those packages may only have wheels for 3.12. You can upgrade core ComfyUI cleanly onto 3.13 and still have a specific node fail to import because its dependency is stuck on an older interpreter. That is why “core broke” and “one node broke” show the same symptom: a missing module at startup.

    Before You Do Anything: Confirm It Is Really the Python Version

    Do not start uninstalling packages. First check what interpreter ComfyUI is actually running and which one your dependencies were installed against.

    1. Find the interpreter. For a portable/embedded install, run python_embeded\python.exe --version on Windows, or check the venv you activate before launch. For a manual or Linux install, run which python and python --version from inside the environment ComfyUI uses (the startup log prints the exact Python executable path).
    2. Compare to what worked before. If you remember the install working on 3.12 and the interpreter now reports 3.13.x, you have found the root cause. If the version did not change, the problem is more likely a broken dependency or a corrupted environment, and the fixes below will not help — investigate the specific missing module instead.
    3. Read the first missing module, then stop. The first ModuleNotFoundError tells you which wheel failed, but remember it is often the head of a chain. Treat it as a signal to inspect the interpreter version, not as an invitation to pip install one thing at a time.

    Fix 1 (Safest): Pin or Restore Python 3.12

    The lowest-risk fix is to go back to the interpreter your wheel set was built for. The community consensus, visible across the issue thread and the Reddit discussion on Python 3.13 or 3.12 for portable ComfyUI, is that Python 3.12 remains the high-compatibility baseline for the AI ecosystem — especially if you run SageAttention, Nunchaku, or various GGUF/quantization tools whose wheels lag behind.

    • On a system you control, reinstall ComfyUI in a fresh directory using a Python 3.12 environment, then reinstall your models and custom nodes. This is the cleanest path and also clears any half-broken dependency state you accumulated while fighting the 3.13 install.
    • If your OS auto-upgraded Python (e.g. via pacman, dnf, or apt), pin the package and restore the older interpreter. One Arch user reported fixing the breakage by downgrading back to the prior Python point release and adding IgnorePkg = python to pacman.conf. The exact mechanism varies by distro, but the principle is the same: hold the interpreter until your wheel ecosystem catches up.
    • On Windows portable, keep a known-good build. If a portable update lands you on a newer embedded Python, restoring from a backup or downloading a fresh 3.12-based portable release is the fastest fix, since your models/ and custom_nodes/ directories can simply be moved over.

    Fix 2: Use pyenv or a Virtual Environment to Isolate the Interpreter

    You do not need to downgrade your system Python globally. The cleaner approach is to give ComfyUI its own interpreter with a version you choose, independent of whatever your OS installs by default.

    • pyenv lets you install and select a specific Python per directory. The issue #6169 author settled on pyenv with Python 3.11, explaining that a fixed older interpreter “maximizes options” across the whole addon ecosystem. Copy the project, run pyenv install 3.12.x and pyenv local 3.12.x, then reinstall dependencies inside that environment.
    • A virtualenv or conda env achieves the same isolation. The key rule — repeated across the community discussion — is to never install ComfyUI and its dependencies into your global Python. Application A needs version X of a library while application B needs version Y; a shared environment guarantees one of them breaks.
    • Watch for the stale-interpreter trap. One user reported that even after setting pyenv local 3.11 and seeing * 3.11.11 in pyenv versions, the install still resolved against /usr/lib/python3.13 and failed with ModuleNotFoundError: No module named 'safetensors'. If pyenv does not seem to “stick,” verify which interpreter pip was invoked under (which pip / pip --version) — the shell may be resolving a different pip than the python you selected.

    Fix 3: Make 3.13 Work — Only If You Actually Need It

    Is 3.13 itself unsupported? Not anymore. ComfyUI’s maintainer noted in early 2025 that ComfyUI had been running well on 3.13 since January, and recent Reddit reports confirm working 3.13.6 installs with PyTorch 2.9, CUDA 12.8, Triton, and SageAttention. The problem is not that 3.13 cannot work — it is that your specific set of wheels and custom nodes may not have caught up yet.

    If you genuinely want 3.13 (for example, on a machine where 3.13 is the only interpreter your distro will give you), do the following:

    • Install a compiler toolchain (cmake and clang++/build-essential on Linux, MSVC build tools on Windows) so source-build wheels for laggards like sentencepiece can actually compile.
    • Install PyTorch-family wheels explicitly from the CUDA index: pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128, matching your CUDA version.
    • Treat each custom node as a risk surface. If a single node’s dependency has no 3.13 wheel, that node — not ComfyUI — is what will fail to import. The pragmatic move is to leave that node disabled until its author ships a compatible wheel.

    For most day-to-day users, Fix 1 or Fix 2 is the correct call. Chasing 3.13 for the sake of being current usually costs more time than it saves, because the pain is concentrated in the long tail of custom-node dependencies you rely on most.

    Common Pitfalls That Make It Worse

    • Editing requirements.txt to delete the failing package. Commenting out torchvision, torchaudio, sentencepiece, kornia, spandrel, and soundfile does not fix anything — it just removes the packages the rest of the code expects, guaranteeing a different crash later. This is how the original reporter spent a day and still ended up with a broken install.
    • Installing dependencies into global Python. This is the single most common self-inflicted wound. Always isolate.
    • Assuming the first missing module is the whole problem. It is the head of a chain. Diagnose the interpreter version, not the individual package.
    • Trusting an AI assistant’s blanket claim that a package “can’t be installed” on 3.13. Community reports show Triton and SageAttention can be made to work on 3.13 with the right wheels; the rule of thumb from the thread is that 3.12 + CUDA 12.8 is the high-compatibility path, not that 3.13 is impossible.

    How to Verify You Are Actually Fixed

    1. Confirm the interpreter first. The launch log should print the Python executable path and a version that matches your target (3.12.x if you pinned). If it still says 3.13.x, your isolation did not take effect.
    2. Watch the startup log for a clean import. A healthy launch reaches the model-loading stage and prints your GPU and VRAM totals without a ModuleNotFoundError anywhere above it.
    3. Load your heaviest workflow once. The real test is whether your most dependency-laden custom nodes import and run. If a specific node still fails, its requirements.txt is the next thing to inspect.
    4. Document the working combination. Note the Python version, PyTorch/CUDA version, and any pinned packages. The next OS-driven Python bump will be a lot less painful if you can reproduce the known-good state.

    Conclusion

    A Python 3.13 upgrade breaking ComfyUI looks catastrophic but has a boring, well-understood cause: your prebuilt dependency wheels were tagged for 3.12 and stop matching the moment the interpreter changes. The fix is almost never to hunt down each missing module one at a time. Pin or restore Python 3.12, or isolate ComfyUI behind pyenv or a virtualenv so a future OS upgrade cannot silently rip the interpreter out from under it. Check the interpreter version first, keep your models/ and custom_nodes/ intact, and you will be back to generating inside of an hour instead of a day. For more on how ComfyUI fits into a full local generation pipeline, see our guide to the open-source video generation stack on consumer hardware, and if a dependency bump is the least of your worries, our walkthrough of the “No Module Named” error in broken custom nodes covers the node-level variant of this same class of failure.

  • ComfyUI –fast Flag Explained: The Untested Speed Toggles and When They Cost You Quality

    If you spend enough time reading ComfyUI launch-command discussions, you will eventually see someone paste a one-word "optimization" and claim it made their generations faster: --fast. It sounds exactly like a free win. But ComfyUI’s own source code describes --fast very differently from the way most tutorials gloss over it. The flag bundles a set of untested performance features, several of which can change your output or crash a workflow outright. In this guide I break down what --fast actually turns on, which of those toggles are genuinely safe to use individually, and how to reason about the speed-versus-quality and speed-versus-stability trade-off before you flip everything on.

    This is a documentation analysis built directly from the ComfyUI source (comfy/cli_args.py) and the official startup-flags reference, not a synthetic benchmark. Where I make a claim about what a flag does, I point to the exact help text in the code. I do not fabricate FPS numbers or "2x faster" results that I have not measured.

    What –fast Actually Is (Not a Magic Speed Switch)

    The full help text for the flag in cli_args.py reads:

    --fast [FAST ...] — Enable some untested and potentially quality deteriorating optimizations. This is used to test new features so using it might crash your ComfyUI.

    Three phrases in that sentence matter and are almost always left out of the "just add --fast" advice:

    • Untested. These are not the stable, default-on optimizations ComfyUI ships for Nvidia cards (like async weight offloading, which is on by default). They are experimental paths.
    • Potentially quality deteriorating. Some features trade numerical precision or skip work in ways that can alter pixels, not just wall-clock time.
    • Might crash. The flag is explicitly documented as a way to test new features, not as a recommended production setting.

    The argument parser handles --fast in three ways. If you pass --fast with no value at all, it enables every feature in the internal PerformanceFeature set. If you pass a list (for example --fast async_cuda_graphs), only those features are enabled. This matters a lot: the difference between "give me one specific experimental toggle" and "turn on every experimental toggle at once" is the difference between a measured, reversible experiment and a mystery box that breaks things.

    Understanding what --fast wraps is also useful because many of the individual features it enables are themselves available as standalone flags. You often want the standalone version, not the bundle. Let’s map the stable flags first, because they are how you get speed without accepting the "untested" label.

    The Safe-by-Default Optimizations You Should Understand First

    Before touching --fast, know which performance features are already on by default or available as stable, documented flags. Pulling the exact help text from cli_args.py:

    --async-offload [NUM_STREAMS]  Use async weight offloading. An optional argument
                                    controls the amount of offload streams. Default is 2.
                                    Enabled by default on Nvidia.
    --disable-async-offload        Disable async weight offloading.
    --reserve-vram GB              Set the amount of vram in GB you want to reserve for
                                    use by your OS/other software.
    --vram-headroom GB             Set the amount of vram in GB for DynamicVRAM to maintain
                                    as extra headroom above default.
    --fast-disk                    Force disk-backed dynamic loading and offload over
                                    unpinned RAM. Can be faster for users with fast NVME disks.
    --disable-smart-memory         Force ComfyUI to aggressively offload to regular RAM
                                    instead of keeping models in VRAM when it can.
    

    The practical takeaway: on an Nvidia GPU you are already getting async weight offloading with two streams by default. That is your normal baseline. The flags above are how you fine-tune memory pressure (“keep some VRAM free for my browser” via --reserve-vram, or “leave a safety buffer” via --vram-headroom) or choose a different offload target (--fast-disk if you have a fast NVMe and want to spill to disk instead of clamping). None of these are “untested.”

    There is an important interaction worth flagging: --fast-disk and the broader caching flags (--cache-ram, --cache-lru, --cache-none) govern where offloaded weights and intermediate results live. If you are chasing a memory problem, changing the cache/offload target is a far more predictable lever than --fast. We have covered the offload-versus-leak diagnosis in detail in our ComfyUI system-RAM OOM guide, and the launch-flag family is mapped in the –lowvram vs –novram vs –normalvram explainer.

    What the Individual Performance Features Do (and Why They’re Risky)

    The reason --fast carries a "quality deteriorating" warning is that it can flip experimental execution paths. Two categories dominate in practice, and both are visible in the source as individual disable flags:

    --disable-cuda-graphs          Disable CUDA graphs.
    --disable-comfy-compiler       Disable the Comfy model compiler, including its CUDA
                                    graph subfeature.
    --assert-graph-breaks          Fail on Comfy model compiler graph breaks.
    --force-non-blocking           Force ComfyUI to use non-blocking operations for all
                                    applicable tensors.
    

    CUDA graphs and the Comfy model compiler are the two big “capture the work once, replay it faster” mechanisms. When they work, they cut kernel-launch overhead and can meaningfully speed up repeated sampler steps. When they don’t work — for example when a custom node introduces a dynamic control-flow branch that breaks graph capture — you get either a fallback (slower) or an outright failure. That is exactly the kind of thing the --assert-graph-breaks flag is there to surface during development: it makes a silent performance fallback into a loud error so developers know a graph broke.

    The honest recommendation here is the reverse of the “turn it all on” instinct. If you suspect a specific workflow is slow, isolate one toggle at a time:

    1. Run your normal workflow and note the generation time.
    2. Enable a single experimental feature (e.g. --fast <feature>) and re-run the same seed, same prompt.
    3. Compare the output image hash. If pixels changed and you didn’t expect them to, that feature altered numerics — treat it as a quality trade, not a free speedup.
    4. If the workflow errors, capture the traceback before touching anything else. A broken graph is often a specific node’s fault, not a reason to disable optimization globally.

    This isolate-then-verify loop is the only way to know whether an experimental toggle actually helps your workflow. It is also why blanket advice like “just add --fast” is unreliable: the features that help one node graph may silently degrade another.

    The Memory Flags That Change Behavior vs. the Ones That Don’t

    A recurring source of confusion is the difference between flags that change where weights live and flags that change how work is computed. They get lumped together under “performance flags,” but the failure modes are completely different.

    The VRAM group is mutually exclusive — you pick one:

    --gpu-only                  Store and run everything (text encoders/CLIP models, etc.)
                                 on the GPU.
    --highvram                  Keep models in GPU memory after use instead of unloading.
    --lowvram                   Run text encoders on CPU (no-op if Dynamic VRAM is active).
    --novram                    When lowvram isn't enough.
    --cpu                       Run everything on the CPU (slow).
    

    These change where weights live, not how the math is done. The output should be byte-identical regardless of which you choose — only speed and memory differ. If switching among --highvram/--lowvram/--gpu-only changes your image, that is a bug worth reporting, not a normal trade-off. By contrast, a flag like --fp16-vae (whose help text literally warns “might cause black images”) changes the numerical precision of an actual computation, so different output is expected.

    Knowing this distinction saves you hours. If your image changes and you only touched memory flags, stop looking at your prompt and start looking for a custom node that pads or slices tensors differently under offload. If your image changes and you enabled an experimental compute feature, that change is the feature doing what its warning said it might.

    One more interaction worth understanding: --lowvram is explicitly a no-op when Dynamic VRAM is enabled (which it is by default on Nvidia unless you pass --highvram, --gpu-only, --novram, or --cpu). People frequently “add --lowvram” to fix an OOM, see no change, and conclude the flag is broken. It isn’t — Dynamic VRAM already applies a more adaptive policy, so the legacy flag is simply inert. If you need to force the old behavior, --disable-dynamic-vram is the switch, and we walk through exactly when that is the right call in the Dynamic VRAM default-OOM guide.

    How to Actually Verify a Performance Flag Helps You

    The core skill here is not memorizing flags — it is building a tiny verification habit so you stop guessing. Here is a concrete, reproducible method that requires no special tooling:

    1. Pin the inputs. Fix the seed, the prompt, the sampler, steps, and CFG. A “before/after” comparison is meaningless if any of these drift.
    2. Record a baseline. Note the wall-clock time and the output filename for the default launch.
    3. Change exactly one thing. One flag. One feature. Nothing else.
    4. Compare time and pixels. Time is obvious. For pixels, hash the two PNGs (or open both and zoom in). If they differ and you only changed a memory flag, investigate; if they differ after an experimental compute toggle, that toggle changed numerics.
    5. Write it down. A one-line note (“--fast <X>: +18% faster, output identical”) beats re-running the whole experiment next month.

    The reason this discipline matters specifically for --fast is its “untested” label. The features inside may change from release to release. A toggle that was safe and helpful in one version can regress in the next, because by definition experimental features are still settling. Your verification note from three months ago may no longer hold — but the habit of verifying will always be correct.

    The official reference to lean on while you experiment is the ComfyUI startup-flags documentation, which mirrors cli_args.py and is the canonical place to check current help text. The changelog is worth a skim too, since new performance features tend to be announced there before most tutorials catch up.

    Conclusion: Treat –fast as a Test Bench, Not a Setting

    The single most useful mental model is this: --fast is a test bench, not a setting. It exists so ComfyUI developers — and curious power users — can enable experimental features incrementally and report what breaks. It is not a “recommended for everyone” launch mode, and its own help text tells you as much in plain language.

    If you take one thing away, take this decision order:

    1. Is async offloading already on for your card? (It is, by default, on Nvidia.) If so, you already have the main free speedup.
    2. Are you memory-limited rather than compute-limited? Fix memory with --reserve-vram, --vram-headroom, or a cache/offload flag — not an experimental compute toggle.
    3. Only if a stable workflow is still slow should you reach for individual --fast features, one at a time, with pinned inputs and a before/after comparison.

    Speed in ComfyUI is almost always about understanding where your weights live and where your work happens — not about flipping an unknown bundle of untested toggles. Use --fast the way it was intended: as a surgical tool for isolated experiments, with your eyes open to the possibility that it changes your output or crashes your run.

    For further reading on the memory side of the same equation, see our attention-backends explainer (SDPA vs SageAttention vs xFormers) and the --vram-headroom vs --reserve-vram guide.