ComfyUI "Expected All Tensors to Be on the Same Device"? Read the Parentheses, Then Fix It (2026)
TL;DR: RuntimeError: Expected all tensors to be on the same device means one tensor in a PyTorch operation sat in system RAM (cpu) while the rest sat in VRAM (cuda:0). In ComfyUI that almost always traces to one of four causes: a buggy custom node, a model split across CPU and GPU by memory management, a core-node bug in brand-new model support, or a multi-GPU box picking two different cards. The traceback — not the error line — tells you which one you have.
What you’ll be able to do:
- Decode the error in under a minute: the parenthetical (
addmm,cat,index_select) names the operation, and the file path in the traceback names the responsible code - Split the fix correctly between “update this custom node” and “change how ComfyUI manages VRAM” instead of reinstalling everything
- Recognize the two 2026-specific traps: memory flags that no longer do what old tutorials claim, and day-one nodes for new models that ship with device bugs
Honest take: This error looks scary because it comes with a 40-line Python traceback, but it’s one of the most mechanical fixes in the ComfyUI troubleshooting family. The file path in the last few traceback frames answers 90% of the diagnosis. Read that path before you touch a single flag.
What the error actually says
The full message always has three parts, and all three carry information:
RuntimeError: Expected all tensors to be on the same device,
but found at least two devices, cuda:0 and cpu!
(when checking argument for argument mat1 in method wrapper_CUDA_addmm)
Part one is the complaint: a single PyTorch operation received inputs living on different devices. Part two names the devices — usually cuda:0 (your GPU’s VRAM) and cpu (system RAM), occasionally cuda:0 and cuda:1 on multi-GPU machines. Part three, the parenthetical, names the exact operation that failed, and it’s worth decoding because it narrows down what kind of thing was left behind on the wrong device:
| Parenthetical | Operation | What it usually means | Real example |
|---|---|---|---|
mat1 in method wrapper_CUDA_addmm | Matrix multiply in a linear layer | A model layer’s input stayed on CPU while the weights are on GPU (or vice versa) | ControlNet input in issue #5336 |
wrapper_CUDA_cat | Tensor concatenation | Two intermediate results produced on different devices are being joined | Latent + mask concat in issue #11412 |
wrapper_CUDA__index_select | Embedding / index lookup | The index tensor (often token IDs) is on CPU while the embedding table is on GPU | Gemma prompt enhancer in LTXVideo issue #325 |
argument weight in method wrapper_CUDAnative_layer_norm | Layer norm | A normalization layer’s weights were offloaded mid-model | Reported in issue #5902 |
None of this is ComfyUI-specific — it’s PyTorch refusing to compute across memory pools, because there’s no implicit copy between system RAM and VRAM. Something was supposed to call .to(device) and didn’t. The question is whose code.
Step 1: read the file path, not the error line
Scroll up through the traceback and look at the last three or four File "..." frames before the RuntimeError — the same discipline that solves ComfyUI’s IMPORT FAILED errors. The frames fork the whole diagnosis:
- Path contains
custom_nodes/<something>→ the bug is in a custom node. Fix by updating or patching that node (Fix A below). Don’t waste time on launch flags. - Path is inside
comfy/orcomfy_extras/→ the bug is in core ComfyUI or triggered by its memory management. Fix by updating ComfyUI or changing how models are offloaded (Fixes B and C).
Two real tracebacks show the fork. A crash in AnimateDiff pointed at custom_nodes/ComfyUI-AnimateDiff-Evolved/animatediff/motion_module_ad.py line 1112 — a positional-encoding tensor (self.pe) left on CPU, issue #372, fixed in that repo’s PR #382. A crash during LoRA training pointed at comfy/weight_adapter/lora.py line 51 (weight = w + scale * diff.reshape(w.shape)) — core code, issue #10940, nothing a node update could touch.
If you’re on the desktop app and can’t see the terminal, open the log from Help → Open Logs Folder — the traceback is written there in full.
Fix A: the traceback names a custom node
This is the most common case and the easiest. Custom nodes ship device bugs constantly because their authors test on one memory configuration (usually a big card where nothing gets offloaded) and your machine runs another.
- Update the node in ComfyUI Manager (Manager → Custom Nodes Manager → Update). The AnimateDiff-Evolved crash above was a one-line class of fix — move
self.peto the input’s device — merged within days. If the node has an open issue matching your traceback, the update very likely contains the patch. - Check the node’s issue tracker for your exact error string. The LTXVideo Gemma prompt-enhancer bug (issue #322) came with a community patch that adds explicit
.to(device)calls ingemma_encoder.py— posted in the issue itself before any release carried it. - Bisect if you’re not sure which node is responsible: disable all custom nodes (Manager → “Disable All” or launch with
--disable-all-custom-nodes), confirm the workflow runs, then re-enable in halves. One user in issue #11673 did exactly this and proved the crash was core, not custom — which is also useful information, because it moves you to Fix C instead of chasing node updates that won’t help.
A subtlety worth knowing: the crash can surface inside core files even when a custom node caused it, because the node handed a CPU tensor into core code. If the frames alternate between custom_nodes/ and comfy/, treat the custom node as the suspect first.
Fix B: the model got split between CPU and GPU
ComfyUI’s memory management offloads models — or parts of them — to system RAM when VRAM is tight. That machinery is what makes 12GB and 16GB cards run FLUX and video models at all, but every offload boundary is a place where a device mismatch can happen when some code forgets the model isn’t fully resident. The tells:
- The error appears on big workflows (multiple ControlNets, video models, LoRA stacks) but not simple ones. Issue #3179 reproduced it with two chained ControlNets and combined conditioning on an RTX 4070 — and, tellingly, the first run worked and the second failed, because the models were cached between runs in a different offload state.
- The error appears with
--lowvram-style flags or on cards where the startup log shows models loading partially. Issue #11673 hit it running LTX 2 text-to-video specifically in lowvram mode, where the text encoder’slearnable_registersstayed on CPU whilehidden_stateswent to GPU.
Three things to try, in order:
1. Update ComfyUI first. Offload-boundary bugs get fixed continuously in core; the current release is v0.30.0 (August 3, 2026, per the releases page) and several of the issues linked in this article only exist on older builds.
2. Stop forcing stale memory flags. The 2026 builds changed what the classic flags do, and old tutorials haven’t caught up. ComfyUI enabled dynamic VRAM management by default on NVIDIA in spring 2026 (covered in our Comfy Desktop guide), and the current --lowvram help text now reads: “Doesn’t do anything if dynamic vram is enabled. If dynamic vram isn’t being used this option makes the text encoders run on the CPU.” Read that twice — --lowvram today is either a no-op or an instruction to deliberately put the text encoder on CPU, which is exactly the split some buggy code paths can’t handle. If your launch script carries --lowvram --novram --disable-smart-memory accumulated from three years of copy-pasted advice, strip them all and let the dynamic manager decide.
3. If you have the VRAM, keep everything resident with --gpu-only. Its help text: “Store and run everything (text encoders/CLIP models, etc… on the GPU).” No offload boundary, no boundary bugs. The cost is real — text encoders and VAE now permanently occupy VRAM your latents needed, so on a 12GB card this often just converts the device error into an out-of-memory error (that one is a different article: CUDA out of memory fixes). On 24GB cards like a used RTX 3090 it’s a legitimate everyday setting for SDXL-class workflows. Note it’s not bulletproof against genuine node bugs: the reporter in issue #11412 was already running --gpu-only on a 48GB L40 and still crashed, because the node built a fresh CPU tensor and concatenated it into GPU data — no flag can fix code like that.
For the specific works-once-then-fails pattern (#3179 above), the state that breaks the second run lives in ComfyUI’s model cache. A server restart always clears it — that’s the universal reset, not a fix. --disable-smart-memory (“Force ComfyUI to agressively offload to regular ram instead of keeping models in vram when it can”) is worth testing for this pattern because it forces models to a clean state between runs instead of reusing the cached split — at the price of reloading models every run.
Fix C: brand-new model, core node, day-one bug
A pattern we keep seeing across the 2026 issues: the error clusters around whatever model family shipped in the last few weeks. LTX 2 video (#11673), Z-Image function ControlNets (#11412, on ComfyUI 0.5.1 with PyTorch 2.9.1+cu128), and the then-new LoRA trainer node with Lumina2 (#10940, ComfyUI 0.3.75) all threw this exact error in core code paths. New architectures mean new offload paths, and the ones nobody at HQ tested on your VRAM tier ship broken.
When the traceback points at core files and the workflow uses a model added to ComfyUI recently:
- Update — same-week fixes are common for day-one model bugs.
- Search the ComfyUI issue tracker for your traceback’s file name (e.g.
embeddings_connector.py). If an issue exists, subscribe; if not, file one with the full traceback and your startup log — device bugs are usually quick fixes once a maintainer can see which tensor is stranded. - As a bridge, change the conditions that trigger the offload: run the model at a smaller resolution or batch, use the fp8 checkpoint variant instead of fp16 (half the weight memory often means no partial load at all), or rent headroom for the one-off job — a RunPod pod with a 48GB card sidesteps every lowvram code path while your fix lands upstream.
Fix D: multi-GPU boxes — cuda:0 and cuda:1
If your error names two CUDA devices rather than CUDA and CPU, ComfyUI (or a custom node) put half the pipeline on each card. Vanilla ComfyUI doesn’t split single workflows across GPUs well, and most “multi-GPU” custom nodes manage the split themselves — badly, sometimes. The reliable configuration is one ComfyUI instance pinned to one card with the built-in flag, whose help text reads: “Set the ids of cuda devices this instance will use, as a comma-separated list (e.g. ‘0’ or ‘0,1’). All other devices will not be visible.”
python main.py --cuda-device 0 # this instance sees only GPU 0
python main.py --cuda-device 1 --port 8189 # second instance on GPU 1
Two pinned instances on two ports gives you real parallel throughput without any cross-device tensor traffic. (CUDA_VISIBLE_DEVICES=0 as an environment variable does the same thing one layer lower.)
The same error outside ComfyUI
You’ll hit the identical message in plain PyTorch and Hugging Face transformers scripts, and the diagnosis transfers: with device_map="auto", accelerate shards the model across GPU and CPU, and your inputs must follow the model — inputs = tokenizer(...).to(model.device) is the one-liner that fixes the overwhelming majority of scripted cases. Anything you build by hand (masks, index tensors, freshly-created torch.zeros) starts on CPU unless you say otherwise. That’s the exact class of bug behind most of the ComfyUI node cases above, just in your own code.
30-second diagnosis table
| What the traceback shows | Cause | Fix |
|---|---|---|
custom_nodes/<name>/... in the frames | Custom node device bug | Update/patch that node; bisect with --disable-all-custom-nodes (Fix A) |
| Core files + lowvram flags or partial model loads | Offload split | Update ComfyUI; strip stale memory flags; --gpu-only if VRAM allows (Fix B) |
| First run works, second run fails | Cached partial-offload state | Restart to confirm; test --disable-smart-memory; update (Fix B) |
| Core files + a model released in the last month | Day-one core bug | Update; check/file an issue; fp8 or smaller res as a bridge (Fix C) |
cuda:0 and cuda:1 | Multi-GPU split | Pin with --cuda-device 0 (Fix D) |
| Your own Python script | Inputs not moved to model device | inputs.to(model.device) |
And confirm what you’re actually running before reporting anything — the version line is printed at every startup:
$ python main.py
ComfyUI version: 0.30.0
...
Device: cuda:0 NVIDIA GeForce RTX 3090 : cudaMallocAsync
If that first number is months old, update before debugging further; you may be chasing a ghost that’s already fixed.
FAQ
Does this error mean my GPU is failing or my install is corrupt? No. It’s a logic bug — some code path forgot to move a tensor between system RAM and VRAM. Reinstalling ComfyUI only “fixes” it when the reinstall happens to update the buggy component; updating directly is faster and diagnosable.
Why does my workflow run fine the first time and crash on the second run?
ComfyUI caches models between runs to skip reloading. If a model was partially offloaded during the first run, the cached state can leave tensors stranded on the wrong device for the second. Restarting clears it; updating ComfyUI or testing --disable-smart-memory addresses it (issue #3179 documents the pattern).
Will --gpu-only always fix it?
Only when the cause is offload splitting and everything fits in VRAM. It trades this error for an out-of-memory error on small cards, and it can’t fix a node that creates fresh CPU tensors mid-pipeline — that crashed even on a 48GB L40 running --gpu-only.
Is --lowvram still a recommended flag in 2026?
Mostly no. With dynamic VRAM management enabled by default on NVIDIA, the current help text says the flag does nothing — and where dynamic VRAM isn’t active, it forces text encoders onto the CPU, which is one of the classic triggers for this exact error. Let the default manager work unless you have a measured reason not to.
The error names cuda:0 and cuda:1 — can I just remove one GPU?
No need. Pin the instance with --cuda-device 0 so the second card is invisible to it. If you want both cards working, run two pinned instances on different ports rather than letting one workflow straddle the pair.
Sources
- Issue #3179: device error with two ControlNets, first run works — ComfyUI GitHub
- Issue #5336: Flux ControlNet mat1 addmm error on RTX 3060 — ComfyUI GitHub
- Issue #10940: TrainLoraNode device mismatch in lora.py — ComfyUI GitHub
- Issue #11412: ZImage ControlNet cat error under —gpu-only — ComfyUI GitHub
- Issue #11673: LTX 2 lowvram embeddings_connector crash — ComfyUI GitHub
- Issue #372 + PR #382: AnimateDiff-Evolved self.pe fix — Kosinkadink GitHub
- Issue #322: LTXVideo Gemma encoder device fix — Lightricks GitHub
- comfy/cli_args.py — current flag help text, ComfyUI GitHub
- ComfyUI releases page (v0.30.0, Aug 3 2026) — GitHub
Last updated August 4, 2026. Error behavior verified against the issue threads and flag help text linked above; ComfyUI memory management changes frequently — check the current release notes before applying flags.
Was this article helpful?
Thanks for the feedback — it helps improve future articles.
Need hands-on help?
I offer 1-on-1 technical consulting for local AI setup, GPU selection, and AI coding tool configuration — same topics covered on this site.
Book a session — $49 / hour →