Tech 5 min read

Fixing Corrupted ComfyUI Upscale Output on Mac MPS with contiguous()

IkesanContents

I was batch-upscaling 640px images from AntiGravity in ComfyUI when some outputs came out completely garbled. I initially suspected an upscaler model compatibility issue, but the real cause turned out to be a memory layout problem much further upstream.

2026-04-29 update: A ComfyUI update predictably wiped this patch and the bug came back. Reapplying the same one-liner fixed it again, and along the way I found the upstream PyTorch issue (#169342), so I appended the recurrence story, the diagnostic shortcut, and a small ops snippet to detect the patch going missing after future updates.

What Was Happening

The conditions to reproduce it were:

  • Mac (Apple Silicon)
  • ComfyUI’s Upscale Image (using Model) node
  • Input image coming straight from a Load Image node

Oddly, images generated within ComfyUI’s own pipeline rarely got corrupted — only Load Image as the source caused the problem. Tracking down that difference made the culprit clear.

Root Cause

ComfyUI image tensors are natively in BHWC format. The Upscale node internally permutes them to BCHW before passing to ESRGAN-family models.

After this permutation, the tensor can end up non-contiguous, and passing it directly to conv2d on MPS breaks things.

The specific issue: tiled_scale_multidim was passing s_in, a narrow() slice, directly to function(s_in) without making it contiguous first.

The Fix

One line changed in comfy/utils.py:

# before
ps = function(s_in).to(output_device)

# after
ps = function(s_in.contiguous()).to(output_device)

That single line fixed the corrupted output.

Related issue — still open at the time of writing:
https://github.com/comfyanonymous/ComfyUI/issues/11851

How to Apply the Patch

cd ~/ComfyUI
source venv/bin/activate

# Back up just in case
cp comfy/utils.py comfy/utils.py.bak

# Edit the file and replace:
# ps = function(s_in).to(output_device)
# with:
# ps = function(s_in.contiguous()).to(output_device)

# Restart ComfyUI
python main.py

Notes

--force-fp32 and PYTORCH_ENABLE_MPS_FALLBACK=0 were useful for narrowing things down, but precision settings weren’t the real issue here — memory layout was.

So “MPS breaks it” isn’t quite accurate. A more precise description would be: “paths that encounter a non-contiguous input tend to break on MPS.”

Maintenance Note

Manual edits to comfy/utils.py get overwritten when you update ComfyUI. If the problem comes back after an update, reapply the same one-liner.

If you see similar corruption from Sharpen or other nodes that do BHWC -> BCHW conversion, inserting .contiguous() right after permute/movedim should prevent recurrence.

2026-04-29 Update: It Came Back

I was running a manga-style monochrome workflow (color image → optional background removal → grayscale + halftone + line art extraction → 4x-UltraSharp upscale) when the upscaled output collapsed into horizontal noise. Everything upstream looked fine — only the ImageUpscaleWithModel output was dead.

Node [42] (right after 4x-UltraSharp)Final output [54]
Broken upscale output. Only horizontal noise survives, the image content is goneBroken final composite. Halftone and line art layers are wrecked

The full workflow (FireShot screenshot):

ComfyUI workflow overview. LoadImage → optional background removal → halftone processing → 4x-UltraSharp → halftone + line art composite

The upstream PyTorch issue exists now

In February I only had ComfyUI’s issue #11851. There’s now a proper upstream report on the PyTorch side too:

pytorch/pytorch #169342 — MPS chunk view + conv produces incorrect results (filed 2025-12, still open as of 2026-04)

tensor views created by chunk() produce incorrect results when passed through convolution operations

It’s not just chunk() — non-contiguous views from narrow() hit the same path. ComfyUI’s tiled_scale_multidim uses narrow() to slice tiles, which lands exactly on this bug.

Affected versions: PyTorch 2.9.0 / 2.9.1 / 2.10.0. CUDA / CPU don’t reproduce it. MPS-only.

The related PR #169935 is up but it’s a narrow workaround that only forces contiguous() on M5 chips. M1–M4 users probably won’t see an upstream fix from that PR, so plan on keeping the local contiguous() patch.

How to confirm the bug quickly

Shortest path to convincing yourself the issue isn’t input-dependent:

  1. Add a PreviewImage node on the ImageUpscaleWithModel output
  2. If the preview is “broken-TV static,” the upscale node itself is broken, not anything upstream
  3. Then check whether comfy/utils.py still has .contiguous() applied

You don’t even need to use the GUI — you can splice a PreviewImage into the workflow JSON inside the PNG metadata and POST it to /prompt directly to verify.

Detecting recurrence after future updates

Hitting this every time ComfyUI updates is a waste, so a one-line grep wrapped in a shell function (e.g. in ~/.zshrc) saves time.

comfy_check_patch() {
  if grep -q 'function(s_in.contiguous())' ~/ComfyUI/comfy/utils.py; then
    echo "[ok] contiguous patch is present"
  else
    echo "[warn] contiguous patch is MISSING — re-apply before running on MPS"
    return 1
  fi
}

If you have a ComfyUI launcher script, prepend comfy_check_patch || return 1 to fail fast instead of finding out after a broken render.

After re-patching

Before/after for the record. With the patch reapplied, both raw ([42]) and final ([54]) outputs come back to normal:

Node [42] (right after 4x-UltraSharp)Final output [54]
Patched upscale output. The grayscale subject is properly upscaledPatched final composite. Halftone and line art are layered correctly