技術 約10分で読めます

ComfyUIアプデ後にQwen Image Editが10分かかるようになった原因を特定した

以前の記事ではM1 Max 64GBでQwen Image Editを動かして約80秒で生成できていた。

ところがComfyUIをアップデートしたら、同じ環境で10分近くかかるようになった。起動オプションを変えれば直るかと思ったが、問題はそこではなかった。

環境

  • Mac Studio M1 Max 64GB
  • macOS 26.3 (Tahoe)
  • Python 3.12.12
  • PyTorch 2.10.0
  • ComfyUI v0.16.4

最初の疑い:—gpu-only

Qwen記事を書いた時点での起動コマンドは python main.py --fp16-vae だけだった。その後Illustriousの速度改善で --gpu-only を追加したエイリアスになっていた。

python main.py --gpu-only --fp16-vae

--gpu-only はApple Siliconの統合メモリでは不要(CPU/GPUで同じ物理メモリを共有するため)なので外したが、サンプリング時間はほぼ変わらなかった。起動オプションの問題ではない。

ベンチマークで発覚:BF16がMPSで2倍遅い

色々試しても改善しないので、MPS上のmatmul性能を直接ベンチマークした。

import torch, time
device = torch.device('mps')
a = torch.randn(4096, 4096, dtype=torch.bfloat16, device=device)
b = torch.randn(4096, 4096, dtype=torch.bfloat16, device=device)
# ... (10回の平均)
dtype4096x4096 matmul相対速度
FP1614.5ms1.0x(最速)
FP3215.9ms1.1x
BF1630.3ms2.1x(最遅)

BF16はFP16の2倍遅い。 しかもFP32より遅い。PyTorch 2.4.1でも同じ結果だったので、バージョンの問題ではなくMPSのBF16実装自体が遅い。

M1/M2/M3チップにはBF16のハードウェアアクセラレーションがなく(ネイティブ対応はM4から)、ソフトウェアエミュレーションで処理しているためと考えられる。

Qwen Image Editの拡散モデルは model weight dtype torch.bfloat16 で動いている。つまり全演算がこの2倍遅いBF16で走っている。

FP16への強制とその結果

--fp16-unet でモデルをFP16に強制してみた。

FP16で生成した結果(真っ黒)

結果は真っ黒だった。macOS 14.5以降、PyTorchのMPSバックエンドにはFP16 Attentionで出力がNaNになるバグがある。ComfyUIは以下のコードで対策している。

# comfy/model_management.py
def force_upcast_attention_dtype():
    macos_version = mac_version()
    if macos_version is not None and ((14, 5) <= macos_version):
        upcast = True
    if upcast:
        return {torch.float16: torch.float32}  # FP16 AttentionをFP32にupcast

FP16モデル → AttentionだけFP32にupcast → 正しい画像が出る…はずだが、これが一部のモデル(特にCheckpointLoaderSimpleでAIOモデルを読んだ場合)ではupcastが効かず黒画像になるケースがあった。

原因の特定:ComfyUIのコミット調査

git logを掘って、関連するコミットを2つ見つけた。

コミット1: 96d891cb(2025-02-24)

# 変更前:全Attention dtypeをFP32にupcast
return torch.float32

# 変更後:FP16のみFP32にupcast、BF16はそのまま
return {torch.float16: torch.float32}

コミット2: 6e28a464(2025-06-10)

# 変更前:macOS 14.5〜15.xだけupcast(16以降は除外)
if (14, 5) <= macos_version < (16,):

# 変更後:macOS 14.5以降すべてupcast
if (14, 5) <= macos_version:

最初は「BF16 AttentionのFP32 upcastが廃止されたから遅くなった」と考えたが、実際にBF16→FP32 upcastを復活させてみたら逆に1.6倍遅くなった(5:29 vs 3:59)。Attentionの中間テンソルが巨大なため、FP32への変換コストとメモリ帯域消費の増加がボトルネックになる。matmulベンチマークの数字がそのまま反映されるわけではない。

つまり以前速かった理由はFP32 upcastではない。おそらく以前はモデル自体がFP16で実行されていた。FP32 upcastは黒画像防止のためであって、速度の源ではなかった。

ComfyUIのアップデートの中で、Qwen Image Editのデフォルト推論dtypeがFP16からBF16に変わったタイミングがあり、それがM1〜M3のBF16ハードウェア非対応と噛み合って速度が落ちた、というのが最も整合する説明になる。

全テスト結果

分割モデル(qwen_image_edit_2511_bf16.safetensors / 38GB)

設定Attention1stepサンプリング(4step)画像
--fp16-vaesub-quadratic (BF16)~51秒3:59OK
--fp16-vae --fp16-unetsub-quadratic (FP32 upcast)~38秒2:33OK
--fp16-vae --fp16-unet --use-split-cross-attentionsplit (FP32 upcast)~128秒計測断念-
--fp16-vae + upcast無効化sub-quadratic (FP16)~25秒1:40

BF16で正常に生成できた画像

AIOモデル(Qwen-Rapid-AIO-NSFW-v16 / 26GB FP8)

FP8モデルはMPSで非対応なので、ロード時にBF16に変換するパッチが必要。

変換先1stepサンプリング(4step)画像
FP16~25秒1:59
BF16~55秒3:40OK

AIO BF16で生成した画像

GGUF(qwen-image-edit-2511-Q8_0.gguf / 20GB)

設定1stepサンプリング(4step)画像
--fp16-vae~48秒3:12ボケる

GGUF Q8_0で生成した画像(ボケている)

現状の最善設定

python main.py --fp16-vae --fp16-unet

分割モデル(qwen_image_edit_2511_bf16.safetensors)で使用。FP16 linear + FP32 Attention upcastの組み合わせで、サンプリング2:33

以前の80秒には戻らないが、10分からは大幅改善。

AIOモデルを使う場合のパッチ

FP8モデルはMPSで直接動かないため、ロード時にBF16に変換する必要がある。以下の2ファイルを修正する。

comfy/sd.py

load_state_dict_guess_config 関数内、weight_dtype の直後に追加:

# MPS does not support float8 types - convert FP8 weights to BF16 upfront
if model_management.is_device_mps(load_device):
    fp8_types = model_management.FLOAT8_TYPES
    converted = False
    for k in sd:
        if sd[k].dtype in fp8_types:
            sd[k] = sd[k].to(torch.bfloat16)
            converted = True
    if converted:
        weight_dtype = comfy.utils.weight_dtype(sd, diffusion_model_prefix)
        logging.info("Converted FP8 weights to BF16 for MPS compatibility")

comfy/model_management.py

cast_to 関数の先頭に追加:

# MPS does not support float8 types - cast to bf16 on CPU first
if device is not None and is_device_mps(device) and weight.dtype in FLOAT8_TYPES:
    if dtype is None or dtype in FLOAT8_TYPES:
        dtype = torch.bfloat16
    weight = weight.to(dtype=dtype)
    copy = False

注意:FP16に変換すると黒画像になる。必ずBF16にすること。

comfy/supported_models.py

QwenImageクラスの supported_inference_dtypes にFP16を追加(--fp16-unet を使う場合に必要):

supported_inference_dtypes = [torch.bfloat16, torch.float16, torch.float32]

AI各社に聞いてみた結果

この問題についてGemini、ChatGPTにも聞いてみた。全提案を実機検証した結果:

Geminiの提案

提案実際の結果
--force-fp16黒画像になる(MPS FP16 Attentionバグ)
--use-split-cross-attention2.5倍遅くなる(128秒/step)
--fp32-vae不要。--fp16-vae で正常動作
--disable-smart-memoryMac SHAREDモードでは効果なし
GGUF Q4モデル速度変わらず品質劣化
TORCH_MATH_DISABLE_SDPA=1PyTorchに存在しない環境変数(創作)

3回やり取りしたが、実測で再現可能な改善は得られなかった。

ChatGPTの提案

ChatGPTの分析はMPS/BF16の制約をより正確に反映していた。特に「M1〜M3のBF16ハードウェア非対応」と「ComfyUIのコミットが原因」の指摘は的を射ていた。

唯一の具体的提案は「BF16 AttentionもFP32にupcastすれば速くなる」だったが、前述の通り実測では逆に1.6倍遅くなった。matmulベンチマークでは速くなるはずが、Attentionの巨大な中間テンソルのメモリ帯域消費が支配的になる。理屈と実測は一致しないことがある。

「Flux系だから遅い」ではない

ComfyUI上でQwen Image Editは model_type FLUX と認識される。ここから「Flux系だから遅い」と思いがちだが、それは不正確。

  • FLUX.1 Kontext: 12Bパラメータの rectified flow transformer
  • Qwen Image Edit: 20Bパラメータの MMDiT

同じ”flow系編集モデル”でも規模が全然違う。Qwen Image Editは”flow系だから遅い”のではなく、20B級モデルとMPS/BF16の相性が悪い。ボトルネックはモデル系列ではなくdtype制約。

まとめ

問題原因
生成が10分かかるMPSのBF16演算がFP16の2倍遅い(M1〜M3はBF16ハードウェア非対応)
FP16にすると黒画像macOS 14.5以降のMPS FP16 Attentionバグ
以前は速かったComfyUIアプデでQwen Image Editの推論dtypeがFP16からBF16に変わった

ComfyUI + PyTorch MPSの組み合わせでは、現時点で --fp16-vae --fp16-unet の2分半が限界。

次の検証先:MLX

今回の問題の多くはQwen Image Edit自体よりPyTorch MPS経路に起因している。Apple Silicon専用フレームワークであるMLXに切り替えれば、BF16エミュレーションの遅さもFP16 Attentionバグも回避できる可能性がある。

実際にMLXコミュニティではQwen-Image の8bit量子化版が公開されており、M-series Macで約8.5秒/stepという数字が報告されている。またmfluxではQwen Image Editサポートのissueが立っている。

ただし「MLXなら絶対速くなる」ではなく、ComfyUIのワークフローと同じ品質・編集精度が出るかは別問題。現実的なアプローチとしては、ComfyUIは全体のハブとして残しつつ、Qwen Image EditだけMLX系ランタイムで別系統ベンチを取る形が良さそう。

いずれにしても、Macでまだ打てる有効手の中ではMLXがかなり上位。実際に試してみた。

mflux実測結果

mflux v0.17.2でQwen Image Edit 2509を実行。同一のM1 Max 64GB環境、4step。

# インストール
uv tool install mflux

# 実行(8bit量子化)
mflux-generate-qwen-edit \
  --image-paths input.png \
  --prompt "Change to summer clothes" \
  --steps 4 --guidance 1.0 \
  --quantize 8 \
  --output output.png
設定stepsサンプリング画像
BF16フル(58GB)44:08ボケる
8bit量子化42:18ボケる
4bit量子化42:12崩壊
8bit量子化2010:44OK

ただし4stepの結果は品質が使い物にならなかった。ComfyUI側ではLightning LoRA(4step最適化)+ ModelSamplingAuraFlow + CFGNormといったノードが組まれているが、mfluxのCLIにはこれらに相当する機能がない。LoRAなし4stepで品質が出るわけがない。

20step Q8にすると品質は実用レベルになるが、10:44かかる。

mflux 4step Q8(ボケる)

mflux 4step Q8 髪色変更(ボケる)

mflux 4bit(完全崩壊)

ComfyUIカスタムノードで統合

mfluxをCLIではなくComfyUIのノードとして使えるようにした。subprocess でmfluxを呼び出す薄いラッパー。

LoadImage → MfluxQwenImageEdit → SaveImage

mflux 20step Q8 ComfyUIノード経由(冬服→夏服)

20step Q8でキャラの造形を維持したまま冬服→夏服に変換できた。PyTorch MPS経路を完全に回避しているので、黒画像もBF16の遅延もない。

Lightning LoRA適用で4stepの品質問題を解決

mfluxはLoRAの外付け適用に対応している。Rapid-AIOがベイクしているLightning LoRAを推論時に適用すれば、4stepでも品質が出るはず。

mflux-generate-qwen-edit \
  --image-paths input.png \
  --prompt "Change to summer clothes" \
  --steps 4 --guidance 1.0 \
  --quantize 8 \
  --lora-paths "Qwen-Image-Edit-Lightning-4steps-V1.0-bf16.safetensors" \
  --lora-scales 1.0 \
  --output output.png

mflux Lightning LoRA + 4step Q8(冬服→夏服)

LoRA適用で品質が劇的に改善。 720レイヤー全マッチ、サンプリング2:28。LoRAなし4stepのボケとは別物で、キャラ維持しつつ冬服→夏服にきちんと変わっている。

全体比較

ランタイムモデル設定時間画像
ComfyUI (PyTorch MPS)Edit 2511 BF16--fp16-vae / 4step+LoRA3:59OK
ComfyUI (PyTorch MPS)Edit 2511 BF16--fp16-vae --fp16-unet / 4step+LoRA2:33OK
mflux (MLX)Edit 2509 Q84step LoRAなし2:18ボケる
mflux (MLX)Edit 2509 Q820step LoRAなし10:44OK
mflux (MLX)Edit 2509 Q84step + Lightning LoRA2:28OK

mflux + Lightning LoRA + 8bit量子化がComfyUIとほぼ同等の速度(2:28 vs 2:33)で、PyTorch MPS経路を完全に回避。 黒画像もBF16の遅延も踏まない。

現状Edit 2509のみの対応だが、mfluxのEdit 2511対応が来れば2511のLightning LoRAも使えるようになり、さらに改善の余地がある。カスタムノードでComfyUIのワークフローに組み込めば、UIを作り直す必要もない。

最終結論

ComfyUI(最善)mflux + LoRA
時間2:332:28
品質OKOK
黒画像リスクFP16で踏むなし
BF16性能低下ありなし
パッチ必要3ファイルなし
モデルEdit 2511Edit 2509

速度はほぼ同等だが、mflux側はPyTorch MPS経路の問題を回避できる。ComfyUIはアプデのたびにパッチ当て直しが必要になるリスクもある。Edit 2509と2511の編集精度差は実際の用途で比較が必要。

参考