画像生成AIなんもわからん人がRadeon RX 9060 XTでComfyUIの最初の一枚を生成するまで

ゲーム用に買ったRX9060XTで画像生成AIを動かしてみたくなったのでやってみた。

ざっくりとした流れはWSL2でUbuntu 24.04を立ち上げ、ROCm周りを整備してGPUを認識、ComfyUIに必要なものを追加して最初の1枚を生成していく・・・といったところ。 Linuxは多少の心得があるけど画像生成AIについては本当になんも知らない状態から始めたので要らないものが含まれているかも。

前提

  • OS:Windows11
    • WSL2利用可能
    • AMD Software: Adrenalin Editionインストール済み
    • Windows Kitインストール済み
  • GPU: AMD Radeon RX 9060 XT 16GB

1. WSL2 に Ubuntu 24.04 をインストール

PowerShellで実行。

wsl --install -d Ubuntu-24.04

Windows11 標準の wsl コマンドで、ディストリビューションを指定してインストール。 ユーザ名とパスワードは適当に設定。

2. ROCm 導入用パッケージ(amdgpu-install)の取得

以降はインストールしたUbuntuで。

cd /tmp
wget https://repo.radeon.com/amdgpu-install/7.2.1/ubuntu/noble/amdgpu-install_7.2.1.70201-1_all.deb
sudo apt install ./amdgpu-install_7.2.1.70201-1_all.deb
sudo apt update

AMD公式リポジトリから ROCm 7.2.1 用の amdgpu-install パッケージ(debパッケージ)をダウンロードしてインストール。これにより apt から ROCm 関連パッケージを取得できるリポジトリ設定が追加される。

3. ROCm 本体のインストール

sudo amdgpu-install -y --usecase=rocm --no-dkms

WSL特有のポイント。

  • --usecase=rocm: グラフィックドライバ一式ではなく、ROCmランタイム・ライブラリ群のみをインストール対象に絞る。
  • --no-dkms: WSL上のLinuxはハードウェアに直接アクセスするドライバを持たず、GPUアクセスは Windows 側のドライバが WSL 用の DXG(DirectX Graphics)インターフェースを通じて仲介する構成になる。そのため WSL 環境ではカーネルドライバ(DKMSモジュール)のビルド・組み込みは不要かつ不可能であるため、--no-dkms で明示的にスキップしている。

4. ビルドツール一式の導入

sudo apt update
sudo apt install -y git build-essential cmake pkg-config wget gpg

この後の librocdxg のソースビルドに必要な cmakebuild-essential などをまとめて導入。

5. librocdxg のビルド・インストール

cd ~
git clone https://github.com/ROCm/librocdxg.git
cd ~/librocdxg
export win_sdk='/mnt/c/Program Files (x86)/Windows Kits/10/Include/10.0.28000.0'
mkdir -p build
cd build
cmake .. -DWIN_SDK="${win_sdk}/shared"
make
sudo make install
sudo ldconfig
 
find /usr /usr/local /opt -name "librocdxg.so*"

librocdxg は、WSL上のROCmランタイムが Windows 側の DXCore/DXG 経由でGPUを検出・利用できるようにするための橋渡しライブラリ。ビルドには Windows SDK のヘッダが必要なため、WSL からは /mnt/c/... 経由で Windows 側にインストール済みの Windows Kits のパスを win_sdk としてcmakeに渡している。ここのインストールパスはWindows側にインストールしたWindows Kitのパスに合わせる必要があるので適宜変更のこと。ビルド後 make install でシステムにインストールし、ldconfig で共有ライブラリキャッシュを更新、最後に find でインストール先を確認。

6. WSL/ROCm用の環境変数スクリプトを作成

cat > ~/rocm-wsl-env.sh <<'SH'
export HSA_ENABLE_DXG_DETECTION=1
export PATH=/opt/rocm/bin:$PATH
export LD_LIBRARY_PATH=/opt/rocm-7.2.1/lib:/usr/local/lib:/usr/lib:$LD_LIBRARY_PATH
export LD_PRELOAD=/opt/rocm-7.2.1/lib/libhsa-runtime64.so.1
 
unset HSA_TOOLS_LIB
unset ROCP_TOOL_LIB
unset ROCM_PROFILE_CONFIG
unset ROCPROFILER_LOG_LEVEL
SH

以降の作業で毎回 source して使う共通環境変数をまとめたスクリプト。

  • HSA_ENABLE_DXG_DETECTION=1: HSAランタイムに対して、WSL特有のDXG経由でのGPU検出を有効化するフラグ。これがWSL上でROCmがGPUを見つけるための肝。
  • PATH / LD_LIBRARY_PATH: ROCmの実行ファイルと共有ライブラリを優先的に参照させる設定。
  • LD_PRELOAD=.../libhsa-runtime64.so.1: HSAランタイムのライブラリを明示的に先読みさせ、システム側の別バージョンなどと競合しないようにしている。
  • 後半の unset 群: ROCmプロファイリングツール関連の環境変数を解除している。これらが残っていると意図しないプロファイラの読み込みでエラーや不要なオーバーヘッドが発生することがあるため、クリーンな状態にする。

7. Python環境の準備とGPU認識確認

sudo apt install -y python3 python3-pip python3-venv
source ~/rocm-wsl-env.sh
/opt/rocm/bin/rocminfo | grep -E "gfx|Name|Marketing"
  Name:                    AMD Ryzen 5 5600 6-Core Processor
  Marketing Name:          AMD Ryzen 5 5600 6-Core Processor
  Vendor Name:             CPU
  Name:                    gfx1200
  Marketing Name:          AMD Radeon RX 9060 XT
  Vendor Name:             AMD
      Name:                    amdgcn-amd-amdhsa--gfx1200
      Name:                    amdgcn-amd-amdhsa--gfx12-generic

Python本体・pip・venvを導入した上で、先ほどの環境変数スクリプトを読み込み、rocminfo を実行してGPUがROCmから正しく認識されているかを確認。ここで Radeon RX 9060 XT に対応する gfxアーキテクチャ名や製品名(Marketing Name)が出力されれば、WSL越しにGPUが認識できている状態になっている。

8. PyTorch(ROCm版)とComfyUIの導入

python3 -m venv ~/venvs/comfyui
source ~/venvs/comfyui/bin/activate
 
python -m pip install --upgrade pip
 
python -m pip install \
    torch torchvision torchaudio \
    --index-url https://download.pytorch.org/whl/rocm7.2
 
mkdir -p ~/AI
cd ~/AI
git clone https://github.com/comfyanonymous/ComfyUI.git
cd ComfyUI
 
grep -vE '^(torch|torchvision|torchaudio)$' requirements.txt > requirements-rocm.txt
python -m pip install -r requirements-rocm.txt

venvを作成し、まず ROCm版の torch / torchvision / torchaudio を専用インデックス(rocm7.2)からインストール。その後 ComfyUI 本体をクローンし、requirements.txt から torch 系の行だけを grep -v で除外した requirements-rocm.txt を作成してインストールしている。 これはrequirements.txt をそのまま pip install すると、PyPI標準の(CUDA向け)PyTorchで上書きされてしまい、せっかく入れたROCm版PyTorchが無効になってしまうため。torch系を事前に除外しておくことで、GPU認識に使うROCm版のビルドを保ったまま依存関係の解決を図る。

9. ComfyUIの起動

cd ~/AI/ComfyUI/
source ~/venvs/comfyui/bin/activate
source ~/rocm-wsl-env.sh
python main.py

venvを有効化し、ROCm用の環境変数を読み込んだ状態で main.py を実行し、ComfyUIを起動。起動後はWindows側に戻り、ブラウザで http://127.0.0.1:8188 にアクセス。

テンプレート選択画面が出るがこれはいったん閉じる。

画面上でCtrl + vし以下のJSONを貼り付け。 ここではStable Diffusion 1.5の公式モデルv1-5-pruned-emaonly-fp16.safetensorsを利用。


テンプレートJSONスクリプト(クリックして表示)

{
  "last_node_id": 9,
  "last_link_id": 9,
  "nodes": [
    {
      "id": 4,
      "type": "CheckpointLoaderSimple",
      "pos": [-380, 200],
      "size": [315, 98],
      "flags": {},
      "order": 0,
      "mode": 0,
      "outputs": [
        {"name": "MODEL", "type": "MODEL", "links": [1]},
        {"name": "CLIP", "type": "CLIP", "links": [3, 5]},
        {"name": "VAE", "type": "VAE", "links": [8]}
      ],
      "properties": {"Node name for S&R": "CheckpointLoaderSimple"},
      "widgets_values": ["v1-5-pruned-emaonly-fp16.safetensors"]
    },
    {
      "id": 6,
      "type": "CLIPTextEncode",
      "pos": [10, 150],
      "size": [425, 180],
      "flags": {},
      "order": 1,
      "mode": 0,
      "inputs": [{"name": "clip", "type": "CLIP", "link": 3}],
      "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [4]}],
      "properties": {"Node name for S&R": "CLIPTextEncode"},
      "widgets_values": ["beautiful scenery nature glass bottle landscape, , sunflower, purple sky, detailed"]
    },
    {
      "id": 7,
      "type": "CLIPTextEncode",
      "pos": [10, 380],
      "size": [425, 180],
      "flags": {},
      "order": 2,
      "mode": 0,
      "inputs": [{"name": "clip", "type": "CLIP", "link": 5}],
      "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [6]}],
      "properties": {"Node name for S&R": "CLIPTextEncode"},
      "widgets_values": ["text, watermark"]
    },
    {
      "id": 5,
      "type": "EmptyLatentImage",
      "pos": [10, 600],
      "size": [315, 106],
      "flags": {},
      "order": 3,
      "mode": 0,
      "outputs": [{"name": "LATENT", "type": "LATENT", "links": [2]}],
      "properties": {"Node name for S&R": "EmptyLatentImage"},
      "widgets_values": [512, 512, 1]
    },
    {
      "id": 3,
      "type": "KSampler",
      "pos": [470, 200],
      "size": [315, 262],
      "flags": {},
      "order": 4,
      "mode": 0,
      "inputs": [
        {"name": "model", "type": "MODEL", "link": 1},
        {"name": "positive", "type": "CONDITIONING", "link": 4},
        {"name": "negative", "type": "CONDITIONING", "link": 6},
        {"name": "latent_image", "type": "LATENT", "link": 2}
      ],
      "outputs": [{"name": "LATENT", "type": "LATENT", "links": [7]}],
      "properties": {"Node name for S&R": "KSampler"},
      "widgets_values": [156680208700286, "randomize", 20, 8, "euler", "normal", 1]
    },
    {
      "id": 8,
      "type": "VAEDecode",
      "pos": [820, 200],
      "size": [210, 46],
      "flags": {},
      "order": 5,
      "mode": 0,
      "inputs": [
        {"name": "samples", "type": "LATENT", "link": 7},
        {"name": "vae", "type": "VAE", "link": 8}
      ],
      "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9]}],
      "properties": {"Node name for S&R": "VAEDecode"}
    },
    {
      "id": 9,
      "type": "SaveImage",
      "pos": [1060, 200],
      "size": [210, 270],
      "flags": {},
      "order": 6,
      "mode": 0,
      "inputs": [{"name": "images", "type": "IMAGE", "link": 9}],
      "properties": {"Node name for S&R": "SaveImage"},
      "widgets_values": ["ComfyUI"]
    }
  ],
  "links": [
    [1, 4, 0, 3, 0, "MODEL"],
    [2, 5, 0, 3, 3, "LATENT"],
    [3, 4, 1, 6, 0, "CLIP"],
    [4, 6, 0, 3, 1, "CONDITIONING"],
    [5, 4, 1, 7, 0, "CLIP"],
    [6, 7, 0, 3, 2, "CONDITIONING"],
    [7, 3, 0, 8, 0, "LATENT"],
    [8, 4, 2, 8, 1, "VAE"],
    [9, 8, 0, 9, 0, "IMAGE"]
  ],
  "config": {},
  "extra": {},
  "version": 0.4
}


コードを貼り付けるとテンプレートが表示され、CheckpointのLoaderと画面右上にはエラーが表示される。原因は単純でJSON中で指定したモデルがライブラリ内に存在しないから。
これを解消するため自分でモデルをダウンロードして所定のパスに配置する。

huggingface.co
格納先は~/AI/ComfyUI/models/clip。 ブラウザでダウンロードして格納するのは面倒なのでUbuntuで直接wgetしたい。公開サイトのDownloadボタンをクリックすると配信用のCDNにリダイレクトされるので、そのリンクをコピーしてwgetする。リンクには有効期限があるので注意。

wget -O ~/AI/ComfyUI/models/checkpoints/v1-5-pruned-emaonly-fp16.safetensors 'DownloadボタンをクリックしてリダイレクトされたURL'


ComfyUIを再起動。

python main.py


エラーが消えている事を確認。

実行するボタンをクリックすると画像が生成される。お疲れさまでした。


まとめ

WSL2上でRadeon GPUをROCm経由で使うポイントは大きく3つ。

  1. amdgpu-install --usecase=rocm --no-dkms で、WSLでは不要なカーネルドライバ部分を除いてROCmランタイムのみを導入する。
  2. librocdxg をビルドし、HSA_ENABLE_DXG_DETECTION=1 を設定することで、WSLのDXG経由のGPU検出をROCmランタイムに認識させる。
  3. ComfyUIのPython環境構築では、ROCm版PyTorchを先に入れた上で、requirements.txt からtorch系を除外してから残りの依存関係をインストールし、CUDA版PyTorchで上書きされるのを防ぐ。

依存関係が本当にシビアでちょっとバージョンが違うともりもりエラーを吐いたりするので、環境の更新などは当面取り組むつもりないです。 苦労したくない人や最新環境にどんどん更新したい人はGeForce系でやりましょう。