★ Reading this for free? Get 20 structured AI courses + per-chapter AI tutor — the first chapter of every course free, no card.Start free in 30 seconds
Image Generation

Expected All Tensors on the Same Device: ComfyUI Fix

August 23, 2026
13 min read
Local AI Master Research Team

Want to go deeper than this article?

Free account unlocks the first chapter of all 25 courses — RAG, agents, MCP, voice AI, MLOps, real GitHub repos.

📚AI Learning Path

Generating images locally? Take it further. From FLUX and ComfyUI setup to building real image pipelines and apps. First chapter free, no card.

Start free
Or own it for life — Lifetime $149, pay once

A cuda:0 and cpu mismatch in ComfyUI is almost never something you configured wrong. It is a node whose weights were pushed to system RAM while its inputs stayed on the GPU, so the fastest unblock is to stop the offload: restart with --disable-dynamic-vram, or with --gpu-only if the model genuinely fits your card. Both switch off the dynamic memory manager that moved the parameter. The dtype pairs are a separate family with a separate fix — Input type (float) and bias type (c10::BFloat16) means a tensor was produced outside the model's cast path, and the node that produced it decides which flag helps.

The reason this error is so frustrating is that the message tells you nothing about the cause. PyTorch raises it the moment two tensors meet on different devices, so the same eleven words come out of the VAE decoder, a text encoder, a LoRA trainer, an upscale node and a frame interpolator. The string is generic; the bug is not.

What follows is a lookup from the rest of your traceback line — the file, the line number, the wrapper method PyTorch names in the parentheses — to the specific defect on record. Every row below is tied to a numbered issue in the ComfyUI tracker so you can check whether yours has been fixed since. Flag help text is quoted verbatim from comfy/cli_args.py on master (ComfyUI v0.33.1, released 13 August 2026).

What Does the Device Mismatch Error Actually Mean?

There are three distinct exceptions people call "the device mismatch error", and separating them is most of the diagnosis:

RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!
RuntimeError: Input type (float) and bias type (c10::BFloat16) should be the same
ValueError: Expected a cuda device, but got: cpu

The first is a device mismatch: two tensors, two pieces of hardware. The second is a dtype mismatch: same device, incompatible number formats. The third is not a tensor operation failing at all — it is a capability probe being handed a CPU device it was never guarded against.

They share one trigger. All three appear when ComfyUI decides part of a model does not fit in VRAM and places it in system RAM instead. That is the same subsystem behind ComfyUI's out-of-memory behaviour after a DynamicVRAM update — except that here, rather than refusing to allocate, it succeeds at loading and then crashes when the split model tries to do arithmetic across the gap.

One detail worth knowing before you read your own traceback: the device order in the message is not meaningful. Both cuda:0 and cpu and cpu and cuda:0 appear in the tracker for the same class of bug, and PyTorch simply lists what it found.

Reading articles is good. Building is better.

Free account = 20+ free chapters across 25 courses, with a per-chapter AI tutor. No card. Cancel anytime if you ever upgrade.

Which Mismatch Pair Do You Have?

Start here. Read the exception line, not the node name, and take the branch that matches.

Your exception line containsFamilyWhat it meansGo to
found at least two devicesDeviceA parameter or buffer stayed on CPU while activations went to GPUThe device table below
Input type ... and bias type ... should be the sameDtypeA tensor entered a layer without passing through ComfyUI's cast pathThe dtype table below
Input type (torch.cuda.HalfTensor) and weight type (torch.HalfTensor)Device, disguisedSame dtype, different device — torch.cuda.HalfTensor vs torch.HalfTensorThe dtype table, last row
Expected a cuda device, but got: cpuNeitherAn unguarded CUDA capability probe received a CPU deviceThe cuda-device section

That third row catches a lot of people. torch.cuda.HalfTensor and torch.HalfTensor are the same precision — the only difference is the cuda in the middle. It is a device bug that phrases itself as a dtype bug, and chasing precision flags for it will waste an evening.

Device Mismatch: Which Node Broke, by Traceback Line

Match the file and line in your stack trace, not the wording of the message.

Traceback detailNode / workflowRoot cause on recordIssue
comfy/ldm/minimax/vae.py line 312, wrapper_CUDA_catVAEDecode, MiniMax H3 videoself.register_tokens left on CPU by partial VAE offload. Patching that line then exposes self.scale1 / self.scale2 at line 293 in addcmul_#15251, closed
qwen35.py line 215, inside GatedDeltaNetQwen3.5 text generation, larger variantsself.A_log and self.dt_bias are plain nn.Parameter tensors with no cast_to_device, unlike conv1d.weight alongside them#15193, open
comfy/ldm/cascade/stage_c_coder.py line 37Stable Cascade Super Resolutionself.mean and self.std sit on the default CPU device and are never moved. Reporter launched with --disable-smart-memory --novram#3012, open since March 2024
nodes.py line 356, pixels[:,:,:,i] *= mVAEEncodeForInpaintThe mask and the pixel tensor end up on different devices#2556, open since January 2024
wrapper_CUDA_clamp_Tensor, argument minTextGenerate, Gemma 4 templateCrash at the TextGenerate node on the stock llm_gemma4_text_gen workflow#14084, open
wrapper_CUDA_addmm, argument mat1Third-party WanVideoKsampler with WanVideoBlockSwapReporter's Wan 2.1 1.3B graph fails while the 14B graph on the same machine works — the smaller model is the one that breaks#8417, open
tensors is on cpu, different from other tensors on cuda:0ZImageFunControlnetLaunch args were --fast --reserve-vram 0 --gpu-only on a 48GB L40, so this one is not a VRAM shortage#11412, open
No file named, node type TrainLoraNodeLoRA training inside ComfyUIReporter's summary: "Train lora with a model, use gpu/ram or cpu/ram, but it uses gpu/cpu/ram which break the node"#10940, open

Two things to take from that table.

First, the pattern is identical across all eight. A model class holds some tensor that ComfyUI's offload machinery does not know about — a registered buffer, a normalisation constant, a plain nn.Parameter — and that tensor stays wherever it was while everything around it moves to the GPU. Issue #15193 states the mechanism plainly: those parameters "lack explicit device casting during the forward pass," while conv1d.weight in the same module "is properly handled using comfy.model_management.cast_to_device."

Second, several of these are not about running out of memory. The ZImageFunControlnet report came from a 48GB L40 with --gpu-only set. The Stable Cascade report is from 2024 and still open. If you have plenty of VRAM and still get this, you are looking at an unmanaged tensor, not a capacity problem, and adding VRAM flags will not help.

Issue #15251 is the clearest single write-up if you want to understand the shape of the bug. The reporter reproduced it deliberately by running MiniMax H3 on an 8GB card to force partial CPU offload of the video VAE, and recorded both crashes in sequence:

File "comfy/ldm/minimax/vae.py", line 312, in forward
    h = torch.cat([h, self.register_tokens.expand(B, -1, -1), torch.zeros_like(h[:, 0:1, :])], dim=1)
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 tensors in method wrapper_CUDA_cat)

-- and after patching that line --

File "comfy/ldm/minimax/vae.py", line 293, in forward
    x = x.addcmul_(self.attn(comfy.rmsnorm.rms_norm(x, self.norm1.weight, self.norm1.eps), rotary_pos_emb), self.scale1)
RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!

That "and after patching that line" is the useful part. Fixing one unmanaged tensor exposes the next one. If you are hand-patching, expect to do it more than once.

Dtype Mismatch: Input Type and Bias Type Should Be the Same

Different family, different fix. Here the tensors are on the same device but in incompatible number formats, and the culprit is usually a tensor that was produced by something outside the model — a spectrogram transform, a different upscaler, a custom node — and handed straight to a layer whose weights are in another precision.

Exact stringNode / workflowRoot cause on recordIssue
Input type (float) and bias type (c10::BFloat16) should be the sameLTX audio VAE encode and decodeAudioPreprocessor.waveform_to_mel() uses torchaudio.transforms.MelSpectrogram, which "always returns float32," and feeds it to an autoencoder loaded in bf16#14811, open
Same string, at causal_audio_autoencoder.py line 160LTX audio VAE launched with --bf16-vaeReporter's title is the whole question: "ltx audio vae encoder error with --bf16-vae. Why now only fp32?"#13550, open
Input type (struct c10::BFloat16) and bias type (struct c10::Half) should be the sameLTX 2.3 audio VAE, image-to-videoAdding --force-fp32 changes the message to Input type (float) and bias type (struct c10::Half) but does not fix it. Text-to-video on the same install works#13921, open
Input type (struct c10::Half) and bias type (float) should be the sameImageUpscaleWithModel, via spandrelReporter's repro is a chain: load an image, upscale it with SeedVR2, then upscale that result again with the core node. Their expectation was that it "work with UpscaleImageWithModel regardless where the image comes from"#9958, open
Input type (float) and bias type (c10::half) should be the sameSam2Segmentation, a custom node, on Apple MPSReporter set SAM2Model precision to FP16 and switched CUDA to MPS on an M1#6612, open
Input type (torch.cuda.HalfTensor) and weight type (torch.HalfTensor) should be the sameFrame Interpolate, FILMNet and IFNetfree_memory() could evict the interpolation model back to CPU immediately after it was loaded#13583, fixed

The audio VAE rows deserve a note, because they explain why the fix is not simply "add a precision flag." Issue #14811 documents the mechanism in detail: the mel spectrogram is computed by torchaudio and comes back float32 regardless of what precision the model is in, then goes into a bf16 encoder. The report is explicit about why the video VAE is immune — "comfy.sd.VAE.encode() has dtype casting logic via cast_bias_weight, but the audio VAE's internal mel computation bypasses that path entirely." The proposed fix is a cast inside AudioVAE.encode(), not a launch flag. That matches #13921, where the reporter tried forced fp16, bf16 and fp32 in turn and none of them worked.

The upscale row is the one you are most likely to hit in an ordinary image workflow. If you chain two upscalers, the first one can hand the second a half-precision image while the second model's weights are fp32. The workaround that costs you nothing is to break the chain: save and reload between the two upscale steps, or move the core ImageUpscaleWithModel node before the third-party one. Our guide to local AI image upscaling covers what each upscaler family expects at its input.

And the last row is the good news case. Issue #13583 was fixed by PR #13698, "fix: Proper memory estimation for frame interpolation (CORE-150)", merged on 4 May 2026. The PR description names the exact defect: "load_model_gpu + separate free_memory could evict the just-loaded interp model back to CPU, causing a CUDA/CPU mismatch on forward. Replaced with a single load_models_gpu(memory_required=...) call." If you are on a build older than that, updating is the whole fix.

Reading articles is good. Building is better.

Free account = 20+ free chapters across 25 courses, with a per-chapter AI tutor. No card. Cancel anytime if you ever upgrade.

Why Does CPU Offloading Cause This At All?

Because offloading is per-module, and the modules do not all cooperate.

When ComfyUI decides a model will not fit in VRAM, it does not simply give up. It moves part of the model to system RAM and streams weights across as they are needed. For a layer that ComfyUI knows about, this is handled — the weight is cast to the execution device on the way in. For a tensor the model class created itself and never registered through that path, nothing moves it. The activations arrive on cuda:0, the forgotten constant is still on cpu, PyTorch refuses, and you get the traceback.

That is why the error shows up mid-generation rather than at load time. Loading succeeds. Sampling succeeds until it reaches the one line that touches both.

It is also why the failure correlates with small cards and large models without being caused by them. The 8GB reproduction in #15251 works because 8GB forces the split. The 48GB reproduction in #11412 works anyway, because the offending tensor was never managed on any card. If you are sizing hardware around this, our Wan VRAM requirements by GPU and running FLUX on a low-VRAM GPU pages cover what actually fits where — but note that more VRAM removes the trigger, not the bug.

Expected a cuda Device But Got cpu: The Third Variant

This one is a ValueError, not a RuntimeError, and it has a genuinely different cause worth understanding because the reasoning generalises.

Issue #15607 reports ValueError: Expected a cuda device, but got: cpu from the MiniMaxMusic3TextEncode node on the default MiniMax Music 3 workflow. A commenter traced it to an unguarded capability probe in comfy_kitchen/flash_attention.py, where is_available() checks whether the system has CUDA but never checks the device it was handed, then calls torch.cuda.get_device_capability(device) — which raises when that device is the CPU. The full call path is in the thread: nodes_minimax_music.pysd.pytext_encoders/minimax_music.pyldm/minimax_music/ar.pytext_encoders/llama.pyflash_attention.py.

The arithmetic explains who gets hit. A commenter's log reports the text encoder loading at 8759.61 MB, which is 8759.61 / 1024 = 8.55 GiB for that component alone. On a 6GB or 8GB card there is no way to keep it resident, so ComfyUI places it on the CPU — and that placement is what trips the probe.

The important practical finding in that thread is what does not work: "--cuda-device 0 does not change the placement, and --lowvram makes it more likely, not less." That is worth repeating, because --lowvram is the reflex fix people reach for and it is exactly backwards here. Anything that pushes more of the model to system RAM increases the chance of landing on the unguarded path.

The same commenter reported measuring roughly 25 seconds per token when the encoder does run on CPU, versus 8 minutes 43 seconds for a 30-second piece with the quantised w4a8 text encoder on the GPU. If your model ships a smaller quantised text encoder, that is the real answer on a small card — the guard only converts a crash into a very slow success.

Which Flag Should You Actually Change?

Help text below is quoted verbatim from comfy/cli_args.py on master at v0.33.1. Check your own build with python main.py --help, because this file has changed repeatedly across the v0.3x series.

FlagVerbatim help textUse it for
--disable-dynamic-vram"Disable dynamic VRAM and use estimate based model loading."The first thing to try. Reverts to the pre-2026 loader
--gpu-only"Store and run everything (text encoders/CLIP models, etc... on the GPU)."Removes the offload entirely, if the model fits
--highvram"By default models will be unloaded to CPU memory after being used. This option keeps them in GPU memory."Keeps models resident between runs
--lowvram"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."Almost never. Read that help text twice
--novram"When lowvram isn't enough."Diagnosis only — it maximises offloading
--disable-smart-memory"Force ComfyUI to agressively offload to regular ram instead of keeping models in vram when it can."Not a fix here. It increases offloading
--cpu-vae"Run the VAE on the CPU."When the crash is in the VAE decode specifically
--disable-async-offload"Disable async weight offloading."When the offload itself is suspected of racing
--reserve-vram"Set the amount of vram in GB you want to reserve for use by your OS/other software..."Reducing it buys headroom before the split happens

Three of those deserve emphasis.

--lowvram is inert on a default install. Its own help text says so: it "doesn't do anything if dynamic vram is enabled," and dynamic VRAM is the default. Every tutorial telling you to add --lowvram to fix a memory problem predates that change.

--novram and --disable-smart-memory make this class of bug more likely, not less. Both increase how much of the model lives in system RAM. Note that the Stable Cascade report in #3012 was filed by someone running --disable-smart-memory --novram — they were, in effect, maximising the conditions for the crash.

The gating logic is readable in the source. enables_dynamic_vram() in comfy/cli_args.py returns true unless you pass --disable-dynamic-vram, --highvram, --gpu-only, --novram or --cpu. That is why people who tried --novram sometimes report the problem "fixed" — they disabled dynamic VRAM as a side effect, not because --novram did anything directly.

Practical order to work through:

  1. Update ComfyUI first. Several of the rows above are already fixed, and #13583 in particular needs nothing but a newer build.
  2. --disable-dynamic-vram. One flag, reverts the loader, tells you immediately whether the memory manager is involved.
  3. --gpu-only, if the model fits. No offload means no split means no mismatch.
  4. --cpu-vae, if the traceback is inside the VAE. Moving the whole decode to CPU is consistent even when it is slow, because it puts everything on one device.
  5. Only then start changing precision flags, and only for the dtype family. For the device family they are irrelevant.

What If a Custom Node Is in the Traceback?

Read the file paths in your stack trace before doing anything else. If any line contains custom_nodes, the core-ComfyUI tables above may not apply to you.

Issue #8417 is the cautionary example: the traceback runs through custom_nodes\ComfyUI-WanVideoKsampler\nodes.py and custom_nodes\ComfyUI_smZNodes\smZNodes.py on the way to the crash. Two third-party packages, both patching the sampler, on a workflow that uses block swapping — which is itself a form of manual offloading. That is a different problem from a missing cast_to_device in a core model class, and it is fixed in a different repository.

The test is the same one ComfyUI's own bug template asks for: disable all custom nodes and see whether the error survives. If it does, the tables above are your page. If it does not, bisect your node packs — and if the node fails to load at all afterwards, our guide to reading a ComfyUI IMPORT FAILED message covers finding the real error underneath.

Third-party block-swap and offload nodes are especially worth suspecting, because they are doing by hand exactly the thing that causes this bug when the core code does it automatically.

How Do You Tell a Fixed Bug From a Live One?

Click the issue number before you rearrange a workflow around a workaround. This class of bug churns fast — some of these were opened and closed within a day.

#15251 is a good illustration of why the status alone is not enough. It was opened on 3 August 2026 and closed on 4 August 2026. But the thread's last exchange is a commenter saying "Fixed in the recent update," followed by another user replying "Which update? I have a same problem." A closed issue is evidence that someone believed it was fixed, not proof that your build contains the fix.

Contrast that with #13583, which is closed and names its fix: PR #13698, merged 4 May 2026, with the ComfyUI collaborator who wrote it saying "This should be the proper fix for the root of the issue" and the original reporter confirming the retest. That is a closure you can rely on.

The general rule: a closed issue with a linked merged PR is a real fix. A closed issue with a "seems fixed now" comment is a lead. Either way, note your ComfyUI version — the release, not "latest" — before and after you update, so you can tell whether anything actually changed.

Honest Limitations

  • We did not reproduce any of these on our own hardware. Every file path, line number, error string and quoted sentence above comes from the linked GitHub issues, pull requests or from comfy/cli_args.py, and each is attributed where it appears. We are not going to invent a reproduction on an L40 or an RTX PRO 2000 we do not own.
  • Most of these issues are open and will move. #15193, #14811, #13921, #13550, #11412, #10940, #9958, #8417, #6612, #3012 and #2556 were open at the time of writing. Check before you build a workaround.
  • Line numbers drift. comfy/ldm/minimax/vae.py line 312 is where the crash was in the reporter's build. In yours it may be a different line in the same function. Match the function and the operation, not the number.
  • Some root-cause analyses in the table are user-contributed, not maintainer-confirmed. #15607 and #15193 in particular contain detailed diagnoses written by commenters. They are well-argued and quote real source, but they are not the same as a merged fix, and we have flagged them as reports rather than as settled fact.
  • The custom-node surface is unbounded. We cannot enumerate every third-party node that mishandles device placement. The disable-everything test is the only reliable way to rule that out.

FAQ

What causes "Expected all tensors to be on the same device" in ComfyUI?

A tensor inside the model — usually a registered buffer, a normalisation constant or a plain nn.Parameter — stayed in system RAM while the activations around it went to the GPU. It happens when ComfyUI splits a model across VRAM and RAM because the model does not fit. Issue #15193 describes the mechanism directly: the offending parameters "lack explicit device casting during the forward pass," unlike neighbouring weights that go through comfy.model_management.cast_to_device.

Will --lowvram fix a device mismatch error?

No, and it can make it worse. ComfyUI's own help text for the flag reads: "Doesn't do anything if dynamic vram is enabled." Dynamic VRAM is the default, so on a normal install the flag is inert. In the one case where it does change behaviour, a commenter on issue #15607 reported that --lowvram "makes it more likely, not less," because it pushes more of the model onto the CPU. Try --disable-dynamic-vram instead.

Is "Input type and bias type should be the same" the same bug?

No. That is a dtype mismatch, not a device mismatch — the tensors are on the same hardware but in different number formats. The usual cause is a tensor produced outside the model's cast path, such as the float32 mel spectrogram fed into a bf16 audio VAE documented in issue #14811. Precision flags sometimes help and sometimes only change the wording of the error; in #13921 the reporter tried forced fp16, bf16 and fp32 and none of them worked.

Why does the error only appear partway through a generation?

Because loading succeeds. The model is placed across VRAM and RAM without complaint, and the crash only fires when execution reaches the specific operation that touches a GPU tensor and a CPU tensor together. That is also why it is reproducible on the same workflow at the same step every time, rather than intermittently.

Does more VRAM fix it?

It removes the trigger, not the bug. On a card large enough to hold the whole model there is no split, so the unmanaged tensor never gets stranded. But issue #11412 records this error on a 48GB L40 with --gpu-only set, so a large card is not a guarantee.

I get "Expected a cuda device, but got: cpu" instead. Same fix?

Different cause. That is a ValueError from a CUDA capability probe that was handed a CPU device without a guard, documented in issue #15607 for the MiniMax Music 3 text encoder. The placement is still the trigger — a component too large for your VRAM gets put on the CPU — but the failure is in the probe, not in an arithmetic op. Where a vendor ships a smaller quantised text encoder, using it keeps the component on the GPU and avoids the path entirely.

Sources

🎯
AI Learning Path

Generating images locally? Take it further.

From FLUX and ComfyUI setup to building real image pipelines and apps. First chapter free, no card.

Or own it for life — Lifetime $149 $599, pay once
Once your hardware is sorted

Go from one-off images to a real workflow

The Local Image Generation course covers ComfyUI, SDXL and FLUX properly — plus 24 more courses on running AI on your own hardware.

$149 once unlocks everything, forever — about $0.27/chapter for life. Prefer to spread it out? Pro is $79/year (saves 27%) or $8.99/month.
Secure checkout by Lemon Squeezy — your card never touches this siteInstant access the moment you payFirst chapter of every course is free — try before you buy

Liked this? 20 full AI courses are waiting.

From fundamentals to RAG, agents, MCP servers, voice AI, and production deployment with real GitHub repos. First chapter free, every course.

Reading now
Join the discussion

Local AI Master Research Team

Creator of Local AI Master. I've built datasets with over 77,000 examples and trained AI models from scratch. Now I help people achieve AI independence through local AI mastery.

Build Real AI on Your Machine

RAG, agents, NLP, vision, and MLOps - chapters across 25 courses that take you from reading about AI to building AI.

Want structured AI education?

25 courses, 519+ chapters, from $9. Understand AI, don't just use it.

AI Learning Path
More on Local Image Generation
See the full Run FLUX.1 Locally guide.

Comments (0)

No comments yet. Be the first to share your thoughts!

📅 Published: August 23, 2026🔄 Last Updated: August 23, 2026✓ Manually Reviewed

Ready to Go Beyond Tutorials?

20 structured courses with hands-on chapters - build RAG chatbots, AI agents, and ML pipelines on your own hardware.

🎯
AI Learning Path

Go from reading about AI to building with AI

20 structured courses. Hands-on projects. Runs on your machine. Start free.

Or own it for life — Lifetime $149 $599, pay once

Was this helpful?

LM

Written by the Local AI Master Team

The team behind Local AI Master

We build Local AI Master around practical, testable local AI workflows: model selection, hardware planning, RAG systems, agents, and MLOps. The goal is to turn scattered tutorials into a structured learning path you can follow on your own hardware.

✓ Local AI Curriculum✓ Hands-On Projects✓ Open Source Contributor
📚
Free · no account required

Grab the AI Starter Kit — career roadmap, cheat sheet, setup guide

No spam. Unsubscribe with one click.

🎯
AI Learning Path

Generating images locally? Take it further.

From FLUX and ComfyUI setup to building real image pipelines and apps. First chapter free, no card.

Or own it for life — Lifetime $149 $599, pay once
Free Tools & Calculators