技術 約5分で読めます

ComfyUIのUpscaleがMac MPSで壊れる問題をcontiguousで直した

いけさん目次

AntiGravityで出した640px画像をComfyUIで一括拡大していたら、出力が一部ガチで崩壊した。
最初はアップスケーラモデルの相性だと思っていたけど、実際はもっと手前のメモリレイアウトが原因だった。

2026-04-29追記。ComfyUI を更新したら案の定この修正が上書きされて再発した。
同じ1行を再適用したら直ったが、ついでに上流の PyTorch issue(#169342)が出ていたこと、再発時の再現手順、再発を検知する小さな運用メモを末尾に足した。

何が起きていたか

再現条件は以下だった。

  • Mac(Apple Silicon)
  • ComfyUIの Upscale Image (using Model)
  • Load Image から入った画像をそのままUpscale

同じUpscaleでも、生成フロー末尾の画像は壊れにくいのに、Load Image 起点だけ壊れる。
この差を追ったら、原因箇所がはっきりした。

原因

ComfyUIの画像テンソルは基本 BHWC。Upscaleノード内部ではESRGAN系モデルに渡すため BCHW に並べ替える。

この並べ替え後テンソルが non-contiguous のまま conv2d 側に渡るケースがあり、MPS環境で破綻する。

ポイントは tiled_scale_multidim 側でタイルを narrow() した s_in を、そのまま function(s_in) に渡していたこと。

実際に当てた修正

comfy/utils.py の該当行を次のように変更した。

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

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

この1行で、壊れていた出力が正常化した。

関連Issueはこれ。この記事を書いている時点でまだオープン。
https://github.com/comfyanonymous/ComfyUI/issues/11851

パッチ手順

cd ~/ComfyUI
source venv/bin/activate

# 念のためバックアップ
cp comfy/utils.py comfy/utils.py.bak

# 手修正(エディタで以下を置換)
# ps = function(s_in).to(output_device)
# ->
# ps = function(s_in.contiguous()).to(output_device)

# ComfyUI再起動
python main.py

補足

--force-fp32PYTORCH_ENABLE_MPS_FALLBACK=0 は切り分けとしては有効だった。
ただ、今回の本命は精度設定よりメモリレイアウトだった。

なので「MPSだから壊れる」というより、「non-contiguous入力を踏む経路だとMPSで壊れやすい」のほうが実態に近い。

運用メモ

ComfyUIを更新すると comfy/utils.py の手修正は上書きされる。
更新後に再発したら、同じ1行を再適用する。

同様の壊れ方が Sharpen や他の BHWC -> BCHW 変換ノードで出るなら、permute/movedim 直後に contiguous() を挟むと再発防止しやすい。

再発した(2026-04-29追記)

漫画調モノクロのワークフロー(カラー画像→背景除去(任意)→グレースケール+ハーフトーン+ラインアート抽出→4x-UltraSharpで拡大)を回したら、拡大結果が水平ノイズに化けた。直前の画像処理は正常で、ImageUpscaleWithModel 直後の出力だけが死んでいた。

ノード [42](4x-UltraSharp直後)最終出力 [54]
壊れた拡大出力。横方向のノイズだけが残り、画像内容が消失している壊れた最終合成。ハーフトーンとラインアートが破綻している

ワークフロー全体(FireShotで撮ったもの)はこんな感じ。

ComfyUIワークフロー全景。LoadImage→背景除去→ハーフトーン処理→4x-UltraSharp→ハーフトーン+ラインアートの構成

上流のPyTorch issueが立っていた

2月の時点では ComfyUI 側の issue #11851 しか見つけていなかったが、PyTorch 側にも本丸のbug reportが上がっていた。

pytorch/pytorch #169342 — MPS chunk view + conv produces incorrect results(2025-12提出、2026-04時点でopen)

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

chunk() だけでなく narrow() で作った非連続viewも同じ経路で踏む。ComfyUIの tiled_scale_multidim がタイル切り出しに narrow() を使っているのでドンピシャで該当する。

影響バージョン: PyTorch 2.9.0 / 2.9.1 / 2.10.0。CUDA / CPU では出ない。MPS固有。

関連するPRは #169935 として上がっているが、レビュー中かつ M5 チップ向けの限定対症療法(M5 デバイス検知時のみ contiguous を強制)なので、M1〜M4 ユーザーには上流fixが降ってこない可能性が高い。当面は手元の contiguous() パッチで凌ぐ前提でよい。

再発時の確認手順

入力依存じゃないと確信したいときの最短路。

  1. ワークフローの ImageUpscaleWithModel の出力に PreviewImage を挿す
  2. プレビューが「壊れたテレビの砂嵐」状態なら、上流ではなく拡大ノード自体が壊れている
  3. その状態で comfy/utils.py.contiguous() が残っているか確認

PreviewImage 直挿しはGUIをいじらなくても、PNGのworkflow JSONに直接ノードを足してAPI(POST /prompt)に投げる形でも検証できる。

再発を検知する運用メモ

ComfyUI を更新するたびに毎回踏むのは無駄なので、起動前に grep する一発を ~/.zshrc あたりに置いておくと安全。

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
}

ComfyUI 起動ラッパーを書いている人は、その先頭で comfy_check_patch || return 1 を呼ぶようにしておくと、踏んでから気付くより早い。

パッチ適用後の出力

念のため再パッチ後のビフォーアフター。raw([42]直後)と最終([54])の両方で、画像内容が完全に復元される。

ノード [42](4x-UltraSharp直後)最終出力 [54]
パッチ後の拡大出力。グレースケールの人物が正しく拡大されているパッチ後の最終合成。ハーフトーンとラインアートが正常に乗っている