0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GMKTec EVO-X2(Ryzen AI Max+ 395)で Qwen-Image-2.1 を約2倍速化した記録【ComfyUI+ROCm FP8常駐】

0
Posted at

GMKTec EVO-X2(Ryzen AI Max+ 395)で Qwen-Image-2.1 を約2倍速化した記録【ComfyUI+ROCm FP8常駐】

背景

  • Qwen-Image-2.1:AlibabaのQwenチームが2026年9月に公開した7Bのオープンウェイト画像生成モデル。テキストからの生成と参照画像を使った編集を1モデルで扱える。VRAM 13〜15GB程度で動くため、ミドルレンジGPUでもローカル実行できる
  • GMKTec EVO-X2:Ryzen AI Max+ 395(Strix Halo)搭載のミニPC。CPUとGPU(Radeon 8060S)が128GBのLPDDR5x統合メモリを共有しており、大容量モデルを丸ごと載せられるのが強み。一方、デディケイティブGPUに比べて演算性能は控えめで、ROCmのソフトウェア環境もまだまだ整いきっていない
  • 用語:DiT=画像生成の本体(Diffusion Transformer)、VAE=生成後の潜在表現を画像に戻す部品、AOTriton=AMD GPU向けの高速attention実装、GGUF=llama.cpp系の量子化ファイル形式

環境

項目 内容
機種 GMKTec EVO-X2
CPU Ryzen AI Max+ 395(16C32T)
GPU Radeon 8060S(gfx1151・RDNA3.5 iGPU)
Memory 128GB LPDDR5x統合メモリ(CPUとGPUが共有)
OS Ubuntu 26.04.1 LTS
計測条件 1280x720・20steps

課題:1枚5分は遅いのか?

当初の構成は stable-diffusion.cpp(Vulkanバックエンド・GGUF Q8量子化重み)+HTTP互換レイヤーで、1280x720・20stepsで1枚あたり約5分かかった。

なぜVulkan構成だったのか

結論から言うと、Vulkanを選んだのは好みではなく消去法だった。当時のStrix Halo+ROCmはGPUのハングアップ、VAE decode中の停止、96GB割当時のオーバーフローなど不安定な点が多く、安定して使い続けられる状態ではなかった(同機種での苦労記録)。一方、stable-diffusion.cppはROCmを経由せずMesaのRADVドライバからGPUを直接使うため、遅い代わりに安定して完走できた。

diffusers+bf16フル精度の常駐(約31GB)も試したが、メモリ不足と速度の両面で実用にならず、CPUオフロード前提のVulkan+GGUFに落ち着いた。つまり5分という時間は、性能ではなく安定性を優先した結果だった。

5分の内訳(概算)

工程 目安
text encoder(Qwen3-VL 8B・Q4)の読み込み+推論 CPU寄りで数十秒
DiT 7B・20steps(fused attentionなし) 約15秒/step × 20 ≒ 5分の大半
VAE decode(tilingなし) 単発で数十秒

支配的なのはdiffusionのstep単価で、1 step約15秒は「DiT 7Bを最適化されていないカーネルで回した」値そのものだ。NVIDIA勢の秒単位と比べるとずいぶん遅く感じるが、CPUオフロード+最適化なしカーネル+VAE tilingなしの条件なら、Strix Halo・ROCm界隈の「分単位が普通」という報告とも矛盾しない。

ネットの目安を調べると、NVIDIA勢はまったく別世界に見えた。

一方、Strix Halo側の報告は「分単位が普通」というものだった。ただし高速化に成功した事例もあった:

  • kyuz0氏のツールボックス:VAE tilingで3分の1、AOTritonでQwenが約3倍速に(Frameworkスレ・ベンチマーク集)
  • AMD公式ブログ(ROCm 7.2.1・Windowsネイティブ):SDXL 1024pxが18.4秒、Flux.1-dev(完全精度)が77.6秒(ROCm Blog)
  • Qwen 2512を自作アプリ+Q8量子化で34〜35秒/枚まで縮めた報告(r/StrixHalo)

まとめると、5分は故障ではない(この構成では当たり前)が、工夫すれば縮む。方針はこれに決めた。

方針:ROCm FP8 ComfyUI常駐

ここまでで原因は特定済みで、直す点は次の3つだった。

  1. Vulkan+GGUFはGPUメモリを使わず(実測VRAM 0MB)、演算のたびに量子化解除とCPU側へのフォールバックが走る
  2. 高速なattention(AOTriton)が使われない
  3. VAE decodeにtiling(分割処理)が効いていない

そこでPyTorch(ROCm版)上でComfyUIを常駐させ、FP8量子化した重みをGPUメモリに載せたままにしておく方針にした。

実施内容

1. ComfyUI用venv(torchは再利用)

別途、torch(ROCm版)を入れ直すと数GBのダウンロードに加えてバージョンの組み合わせが不安定になる。そこで動作実績のある qwen21-server/.venv(torch 2.12+ROCm 7.13、gfx1151で動作確認済み)を .pth ファイル経由で再利用した。

uv venv -p ~/qwen21-server/.venv/bin/python --seed ~/ComfyUI/.venv
SITE_Q=$(~/qwen21-server/.venv/bin/python -c 'import site; print(site.getsitepackages()[0])')
SITE_C=$(~/ComfyUI/.venv/bin/python -c 'import site; print(site.getsitepackages()[0])')
echo "$SITE_Q" > "$SITE_C/qwen-rocm-torch.pth"
~/ComfyUI/.venv/bin/python -c "import torch; print(torch.__version__, torch.cuda.is_available())"
# reuse torch 2.12.0a0+rocm7.13 True

2. ComfyUI依存の導入(CUDA版torch混入防止)

ComfyUIの requirements.txt をそのまま入れると、PyPIのCUDA版torchに上書きされてしまう。torch / torchvision / torchaudio の3つを除外し、torchsde は依存解決を止める --no-deps で入れる。

grep -v -E "^(torch|torchvision|torchaudio|torchsde)[>=< ]" ~/ComfyUI/requirements.txt > /tmp/comfy-req-no-torch.txt
~/ComfyUI/.venv/bin/pip install -r /tmp/comfy-req-no-torch.txt
~/ComfyUI/.venv/bin/pip install --no-deps torchsde

3. モデル3点(DiT / text encoder / VAE)の配置

使うファイルは DiTにComfy-Org公式配布の int8_convrot 版(後述の「つまずき」2件から得た教訓)、text encoderとVAEはダウンロード済みのキャッシュをsymlinkした。配布元は Comfy-Org/Qwen-Image-2.1 と unsloth/Qwen-Image-2.1-FP8。

ln -sfn <hub>/models--Comfy-Org--Qwen-Image-2.1/.../diffusion_models/qwen_image_2.1_int8_convrot.safetensors \
  ~/ComfyUI/models/diffusion_models/
ln -sfn <hub>/models--unsloth--Qwen-Image-2.1-FP8/.../Qwen-Image-2.1-text_encoder-FP8.safetensors \
  ~/ComfyUI/models/text_encoders/
ln -sfn <hub>/models--Comfy-Org--Qwen-Image-2.1/.../vae/qwen_image_2.1_vae_bf16.safetensors \
  ~/ComfyUI/models/vae/

text encoderは16.7GBあり、通常なら分割ロードを検討する規模だが、統合メモリのおかげで full load: True のまま常駐できた。

4. 起動オプション

env PYTORCH_HIP_ALLOC_CONF=expandable_segments:True \
  TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 \
  MIOPEN_FIND_MODE=FAST MIOPEN_FIND_ENABLE_DYNAMIC=0 \
  ~/ComfyUI/.venv/bin/python ~/ComfyUI/main.py \
  --listen 127.0.0.1 --port 8188 \
  --disable-mmap --gpu-only --disable-smart-memory --cache-none --bf16-vae

5. ComfyUI連携(/prompt API)

自作スクリプトからComfyUIの /prompt API へworkflow JSONを投げる。構成は以下の通りで、新規生成と参照編集ではtext encodeノードだけが変わる:

  • UNETLoader(weight_dtype=default)→ CLIPLoader(type=qwen_image)→ VAELoader
  • 新規生成:CLIPTextEncode/参照編集:TextEncodeQwenImageEdit(+LoadImage)
  • EmptyLatentImage → KSampler(euler・simple・cfg 4.0・20steps)→ VAEDecodeTiled → SaveImage

参照編集では参照画像を /upload/image でComfyUIへ送り、LoadImage 経由で TextEncodeQwenImageEdit に渡す。参照画像の構図・画風を引き継いだ生成になる。

つまずき3件

1. VAEDecodeTiledにtemporal系が必須

このComfyUI版では temporal_size・temporal_overlap が必須入力。未指定だと400が返る。

{"class_type": "VAEDecodeTiled",
 "inputs": {"samples": ["8", 0], "vae": ["3", 0],
            "tile_size": 512, "overlap": 64,
            "temporal_size": 64, "temporal_overlap": 8}}

2. unsloth単体FP8はUNETLoaderで検出不可

Could not detect model type で失敗。unslothの単体ファイルはdiffusers形式のままで、ComfyUIが読める形式(ComfyUI向けに量子化・変換されたconvrot形式)ではない。Comfy-Orgのsplit重み qwen_image_2.1_int8_convrot.safetensors を取得して解決した。

3. TextEncodeQwenImageEditの入力名はprompt

text ではなく prompt が正しい(新schema)。required_input_missing: prompt が出たらここを疑う。

結果

条件 時間 1 stepあたり
従来Vulkan(GGUF Q8) 約300秒/枚 約15秒
ComfyUI FP8・新規 124〜129秒/枚 約6秒
ComfyUI FP8・参照edit 約158秒/枚 約8秒

約2倍から2.4倍に短縮された。テスト用プロンプトで描画品質を確認し問題はなく、参照編集でも参照画像の構図・画風を引き継げることを目視で確認した。

考察

  • メモリ帯域はDGX Spark(273GB/s)とStrix Halo(理論値256GB/s)でほぼ互角。実際に差が出るのは演算ユニットとCUDA環境の成熟度で、同じ条件ならDGX Sparkの方がさらに2〜5倍速い。Strix Haloの強みは速度ではなく容量(128GBの統合メモリに大きいモデルを載せられること)にある
  • 今回の約2倍は「最適化の効いた上限」に達したわけではなく、誤った経路を正しく直した分の伸びにすぎない。残る余地としてはsteps削減(Lightning系4-step LoRAの併用)が最大
  • ROCm環境の再現性が最大の課題だった。動くtorch一式を .pth で使い回す方式が最も安定した

再現手順まとめ

手順スクリプトは本作業用に自作したもので、内容は次の4つに分けられる:

  1. ComfyUI用venvの作成(依存+モデルリンク)を setup_comfyui_qwen.sh にまとめる
  2. 起動オプション付きでComfyUIを常駐させる(start-comfyui-qwen.sh)
  3. /object_info/UNETLoader にDiT名が出ることを確認する
  4. 上記workflow JSONを /prompt に投げ、/history/{prompt_id} の完了を待って /view で取得する(完了待ちポーリングと取得は定型処理なので、共通化しておくと楽)

以上です。EVO-X2でQwen-Image-2.1を回している方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?