Radeon 8060S(EVO-X2 / ROCm)でFastWanとWan 14Bを実走 ZLUDAは諦めてTheRockのgfx1151ホイールで動かした
目次
動画生成のローカル実行を、手元のマシンを変えながら何度か試している。M1 Max 64GBで Wan 2.2が2秒に82分、RTX 4060 Laptop 8GBでは FramePack F1が5秒に56分。どちらも「動くけど遅い」で、詰まる場所はGPUよりメモリ側だった。
今度はEVO-X2でやる。VRAM 48GBは、4060の8GBともM1 Maxの共有64GBとも違う。VRAMが壁になることはまずない。代わりに気になるのは、AMD GPUでPyTorchがそもそも動くのか、という一点だ。前回までの2台はCUDAとMetalで、いずれも「PyTorchが動く」のは前提だった。ROCmはそこから疑う。
テスト環境
| 項目 | 値 |
|---|---|
| マシン | GMKtec NucBox EVO-X2 |
| CPU | AMD Ryzen AI Max+ 395 (~3000 MHz) |
| GPU | AMD Radeon 8060S (RDNA 3.5 / gfx1151) |
| メモリ構成 | 64GB UMA(BIOSでVRAM 48GB / System RAM 16GBに分割) |
| OS | Windows 11 Pro 26200 |
| PyTorch | 2.11.0+rocm7.13.0 |
| diffusers | 0.38.0 |
| Python | 3.11.15 (Miniconda) |
ここで先に「UMA」を説明しておく。UMA(Unified Memory Architecture)はCPUとGPUが同じ物理メモリを共有する方式で、Strix HaloではBIOSが64GBを切り分ける。今回はVRAMに48GB、システムRAMに16GB。GPU側は潤沢だが、CPU側のRAMは16GBしかない。この非対称が後半までずっと制約になる。
AMD GPUでPyTorchを動かすまで
ここが今回いちばん時間を食った。やったことを先に並べると、CUDA互換レイヤーは全滅で、AMD公式のWindows向けROCmホイールに行き着いた。順番に3回試している。
flowchart TD
A[PyTorchをROCmで動かしたい] --> B[試行1: ZLUDA + CUDA版PyTorch]
B --> B1[GPU検出はOK<br/>VRAM 53.9GBも見える]
B1 --> B2[全演算が named symbol not found<br/>足し算すら通らない]
B2 --> C[試行2: PyTorch公式ROCmホイール]
C --> C1[Windowsには配布なし<br/>No matching distribution]
C1 --> D[試行3: AMD TheRock gfx1151ホイール]
D --> D1[全演算OK<br/>ZLUDA不要のネイティブROCm]
試行1 ZLUDAでは演算が通らない
最初にZLUDAを試した。ZLUDAはCUDA向けのバイナリをAMDのHIP(ROCmのCUDA相当API)に変換して動かす互換レイヤーで、「CUDA版PyTorchをそのままAMDで」を狙える、はずだった。
GPU検出までは通る。
# zluda_with.exe 経由で実行
CUDA available: True
Device: AMD Radeon(TM) 8060S Graphics [ZLUDA]
VRAM: 53.9 GB
ところが演算に入った瞬間に死ぬ。
RuntimeError: CUDA error: named symbol not found
テンソルの確保(メモリを取るだけ)は通るのに、a + b の足し算すら通らない。HSA_OVERRIDE_GFX_VERSION=11.0.0 でgfx1100にフォールバックさせても変わらなかった。
原因はGPUの世代ではなく、PyTorchの配布形態とZLUDAの相性だった。関わるのはPTXとSASSの違いだ。
| 表現 | 何か | ZLUDAとの関係 |
|---|---|---|
| PTX | NVIDIA GPUの中間表現。仮想的な命令セット(バイトコード相当)で、実行時にドライバが各GPU向けに最終コンパイルする | ZLUDAはこのPTXを読んでHIPに変換する。PTXがあれば動く |
| SASS / ELF | 特定のGPUアーキ向けにコンパイル済みのネイティブ機械語 | ZLUDAは変換できない |
公式PyTorchのCUDAホイールには、PTXが入っていない。プリコンパイル済みのSASS/ELFカーネルだけが同梱されている。ZLUDAは変換元のPTXを見つけられないので、カーネルを呼んだ瞬間に「named symbol not found」になる(ZLUDA issue #626)。gfx1151固有の問題ではなく、この組み合わせでは原理的に動かない。
試行2 公式ROCmホイールはWindowsにない
ZLUDAを捨てて、PyTorch公式のROCmホイールを直接入れにいく。
pip install torch --index-url https://download.pytorch.org/whl/rocm6.2
pip install torch --index-url https://download.pytorch.org/whl/rocm6.3
pip install torch --index-url https://download.pytorch.org/whl/rocm6.4
# → 全滅: "No matching distribution found for torch"
PyTorch公式のROCmホイールはLinux専用で、Windows版は配布されていない。バージョンを変えても出てこない。
試行3 AMD TheRockのgfx1151ホイールで動いた
行き着いたのがAMDの TheRock。ROCmとPyTorchをまとめてビルドして配るプロジェクトで、ここがgfx1151向けのWindows PyTorchホイールを公式に出していた。gfx1151(RDNA 3.5 / Strix HaloのiGPUを指すLLVMのターゲット名)は、Windows上で唯一「Release Ready」扱いのコンシューマGPUになっている。
pip install --index-url https://repo.amd.com/rocm/whl/gfx1151/ torch torchvision torchaudio
# → torch-2.11.0+rocm7.13.0
# → rocm-sdk-core-7.13.0 / rocm-sdk-libraries-gfx1151-7.13.0
# hipcc, amdclang++ なども同梱
検証する。
import torch
print(torch.__version__) # 2.11.0+rocm7.13.0
print(torch.cuda.is_available()) # True
print(torch.cuda.get_device_name(0)) # AMD Radeon(TM) 8060S Graphics
print(torch.cuda.get_device_capability(0)) # (11, 5)
d = torch.device('cuda')
a = torch.tensor([1.0, 2.0, 3.0], device=d)
b = torch.tensor([4.0, 5.0, 6.0], device=d)
print(a + b) # tensor([5., 7., 9.], device='cuda:0')
# fp32 / fp16 / bf16 全テスト通過
足し算が通り、fp16もbf16も通る。ZLUDAなしのネイティブROCmで、torch.cuda のコードがそのまま動く。ここまで来てやっとスタートラインだ。
FastVideoは使えなかったのでdiffusersを直接叩く
当初の狙いはFastWanで、本来は FastVideo フレームワークを使う。が、これがWindows ROCmでは動かない。
| 障壁 | 中身 |
|---|---|
| Triton依存 | fastvideo-kernel が triton>=2.0.0 を要求。TritonはWindows非対応 |
| torch.distributed欠落 | TheRock版PyTorchに torch.distributed が含まれず、importチェーンの早い段階で torch._C._distributed_c10d が無くて死ぬ |
2つめは、1箇所コメントアウトしても次のdistributed importでまた死ぬ、というモグラ叩きになる。フレームワーク全体がWindows ROCmを想定していない。
そこで方針を変える。FastVideo「フレームワーク」を諦めて、diffusersから「モデル」だけを直接呼ぶ。
from diffusers import WanPipeline # これは普通に動く
ここで前提を一つ補っておく。FastWanの速さの正体はVSA(Video Sparse Attention、全フレーム総当たりをやめて疎に注意を計算する手法)で、これが使えるのはH100 / A100 / 4090だけなのは 前回の記事 で確かめた通り。8060SではVSAは使えない。今回はFastWanの「速さ」ではなく、DMD蒸留(Distribution Matching Distillation。多ステップの拡散モデルを数ステップに蒸留する手法で、3ステップで生成できる)という「重み側の性質」だけを使う。だからパイプラインのクラスを標準のWanPipelineに差し替えても3ステップで回る。
FastWan 1.3B(T2V)を回す
モデルを落とす。
from huggingface_hub import snapshot_download
snapshot_download('FastVideo/FastWan2.1-T2V-1.3B-Diffusers')
# 29ファイル、約28GB(大半はテキストエンコーダUMT5)。ダウンロード約5分
model_index.json を見ると本来のクラスは WanDMDPipeline だが、これはdiffusers 0.38.0には入っていない。標準のWanPipelineで代用する。起動時に警告が出る。
Some weights of the model checkpoint were not used when initializing WanTransformer3DModel:
['blocks.*.to_gate_compress.bias', 'blocks.*.to_gate_compress.weight']
DMD固有のto_gate_compress層が標準のWanTransformer3DModelに無いので無視されている。品質への影響は今回は確認していない。
推論パラメータはこう。
| パラメータ | 値 |
|---|---|
| モデル | FastVideo/FastWan2.1-T2V-1.3B-Diffusers |
| 解像度 | 480x480 |
| フレーム数 | 25(≒1秒 @24fps) |
| ステップ数 | 3(DMD蒸留) |
| guidance_scale | 1.0 |
| 精度 | fp16 |
| Attention | SDPA(PyTorch標準のScaled Dot-Product Attention) |
プロンプトは茶髪に赤ネクタイのアニメ調の女の子がピースしてウインク、というもの。出てきた結果。
1秒(25フレーム)が263秒で出た。問題はその内訳だ。
| フェーズ | 時間 |
|---|---|
| モデルロード(CPU) | 25.2秒 |
| GPU転送 | 19.2秒 |
| DiT 3ステップ合計 | 35.5秒(22.2 / 13.0 / 10.2秒、ウォームアップ後に短縮) |
| VAEデコード + 後処理 | ~227.7秒 |
| 生成合計 | 263.2秒(4分23秒) |
拡散の本体であるDiTの3ステップは35.5秒で終わっている。生成時間263秒のうち、実に86%がVAEデコードに費やされている。25フレーム×480×480のラテント(潜在表現)をピクセルに戻すだけのVAE(1.3BのAutoencoderKLWan、Conv3Dベース)に227秒。ここが速度を決めていた。
メモリ側は余裕だった。
| 指標 | 値 |
|---|---|
| VRAM合計 | 53.9 GB |
| VRAM使用(モデルロード後) | 15.7 GB |
| VRAMピーク(生成中) | 20.9 GB |
| システムRAM使用 | 15.0 / 15.6 GB(ほぼ限界) |
VRAMはピーク20.9GBで33GB余る。一方でシステムRAMは15.0 / 15.6GBでほぼ天井。4060の記事で突き止めた「VRAMでなくRAMが壁」と同じ構図がここにもある。ただしUMAなので、モデルをVRAMに載せたあとはRAM側が解放され、ページファイル溢れには至らなかった。
なお生成中にこの警告が出る。
Flash Efficient attention on Current AMD GPU is still experimental.
Enable it with TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1.
AMD GPU向けのFlash Attention(AOTriton実装)はまだ実験的扱いで、デフォルトではSDPAにフォールバックしている。この環境変数を立てればFlash Attentionが使える。後半のI2Vでこれを使うことになる。
Wan 2.1 I2V 14B でかなちゃんを動かす
T2Vが動いたので、次は本命のI2V(image-to-video、静止画を起点に動かす)。前回FramePackで使った、かなちゃんの正面ピース画像を入力にする。

モデルはWan-AI/Wan2.1-I2V-14B-720P-Diffusers、fp16で約28GB。VRAM 48GBには余裕で載る計算だが、ロードで3回壁にぶつかった。
ロードがSegfaultで落ちる
最初は普通にCPUへロードしようとした。low_cpu_mem_usage=Trueを付けても、トランスフォーマーのシャード3/14(6GBあたり)でSegfaultする。クラッシュ箇所は_local_scalar_dense_cpu。16GBのシステムRAMで28GBのモデルをCPU上に展開しきれず、途中で死ぬ。
次にdevice_map="balanced"でGPUへ直接ロードした。これはCPU RAMを経由せず、シャードを直接GPUに割り当てる指定だ。14シャード全部がGPUに載って46.6GB使用。載りはしたが、残りVRAMが7.3GBしかない。
そして832x480x33フレームで生成しようとすると、今度はattentionでOOM。dense attention(SDPA)が29.38GBのバッファを要求し、7.3GBに入らない。
通すための設定
最終的に480x480x33フレーム(約2秒)を通したときの設定はこう。
import os
os.environ["TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL"] = "1" # Flash Attention有効化
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
from diffusers import WanImageToVideoPipeline
pipe = WanImageToVideoPipeline.from_pretrained(
model_id,
torch_dtype=torch.float16,
device_map="balanced",
max_memory={0: "48GiB", "cpu": "12GiB"}, # 一部をCPU側へ振り分ける
)
pipe.vae.enable_tiling() # VAEをタイル分割してメモリ削減
pipe.vae.enable_slicing()
これを通すための工夫は次のとおり。
| 対処 | 効果 |
|---|---|
device_map="balanced" | CPU RAMを経由せずGPU直接ロード。CPU展開時のSegfaultを回避 |
max_memory でCPUに12GiB確保 | テキストエンコーダの一部をCPU側へ分散。GPUロード処理のメモリ圧を下げてSegfaultを解消 |
| Flash Attention(AOTriton)有効化 | attentionバッファが圧縮され、480x480x33fが7.3GBの空きに収まる |
特に2つめが決定打だった。device_map="balanced"だけだと、トランスフォーマー(28GB)をGPUに載せた後、テキストエンコーダ(UMT5、~10GB)のsafetensorsロード中に重み127/242番で必ずSegfaultする。max_memoryでCPU側に12GiBの余地を作ると、GPUロード処理のメモリ圧が下がって通った。Flash AttentionをオフにするとSDPAに戻り、832x480x33fのattention行列29GBが空き7.3GBを超えて不可になる。
結果
| パラメータ | 値 |
|---|---|
| モデル | Wan-AI/Wan2.1-I2V-14B-720P-Diffusers |
| 入力画像 | frame_000.png (608x640) |
| 出力解像度 | 480x480 |
| フレーム数 | 33(≒2秒 @16fps) |
| ステップ数 | 20 |
| guidance_scale | 5.0 |
| 精度 | fp16 |
| フェーズ | 時間 |
|---|---|
| モデルロード(GPU直接) | 60.8秒 |
| DiT 20ステップ | ~768秒(38.4秒/ステップ) |
| VAEデコード + 後処理 | ~47秒 |
| 生成合計 | 815.1秒(13.6分) |
| 指標 | 値 |
|---|---|
| モデルロード後 VRAM | 44.6 GB |
| ピーク VRAM | 45.4 GB |
| システムRAM使用 | 6.2 / 15.6 GB |
14Bは48GBのVRAMに載る。ただしモデルで44.6GB食うので、推論バッファの余地は9GB前後しかない。Flash Attentionでそこに収めて480x480x33fが通った。
ここでBIOSの固定分割が足かせになる。48GB/16GBに振っているせいでCPU側のRAMが16GBしかなく、enable_model_cpu_offload(モデルをCPUに置いて必要分だけGPUに送る省VRAM手法)が使えない。CPUオフロードはCPU側でモデルを展開する瞬間にSegfaultするからだ。32GB/32GBに振り直せばCPUオフロードでGPU全量を計算に回せるが、それはそれで別の検証になる。T2Vと違ってこちらはVRAMもRAMも両方ぎりぎりだった。
これまでとの比較
前回までの2台と今回を並べるとこうなる。
| 環境 | モデル | モード | 解像度 | フレーム | ステップ | 時間 |
|---|---|---|---|---|---|---|
| EVO-X2 8060S(今回 T2V) | FastWan 1.3B | T2V | 480x480 | 25 | 3 | 263秒(4.4分) |
| EVO-X2 8060S(今回 I2V) | Wan 14B | I2V | 480x480 | 33 | 20 | 815秒(13.6分) |
| M1 Max(前々回) | Wan 14B GGUF | I2V | 832x480 | 33 | 20 | 4965秒(82分) |
| 4060 Laptop(前回) | FramePack F1 13B | I2V | 608x640 | 145 | - | 3363秒(56分) |
同じWan 14B I2Vの33フレームで、M1 Maxの4965秒に対しEVO-X2は815秒。解像度が832x480→480x480と下がっているので単純比較はできないが、桁が一つ違う。CPUオフロード前提でVRAMをやりくりしていたM1 Maxと、48GBにフルロードできるEVO-X2の差が出ている。
参考までに、FastWan公式のVSA有効ベンチはH200で81フレームを5秒、4090で21秒。EVO-X2のDiTのみ35.5秒(3ステップ)は4090の~21秒の1.7倍程度で、GPU性能差を考えれば悪くない。FastWanで遅いのはDiTではなくVAEデコードの方だった。
何が速度を決めているか
3台を通して、ボトルネックはモデルやGPUコアの速さではなく、その手前にあった。
| 環境 | ボトルネック |
|---|---|
| 4060 | 26GBのモデルが32GB RAMに収まらずページファイルへ溢れ、DynamicSwapが毎ステップディスクから読み直す。GPU使用率が一桁のまま |
| M1 Max | 64GB共有メモリでCPUオフロードしながら回るので、転送のオーバーヘッドが加わる |
| EVO-X2 | VRAM 48GBにフルロードできてGPUは普通に回る。代わりにFastWanではVAEデコード(Conv3DがROCmで遅い)が時間の86%を占める |
EVO-X2は「モデルがVRAMに丸ごと載る」という点で前の2台より明確に有利だった。VRAM 48GBの余裕は大きい。一方で、AMD GPUのVAEデコードとattentionまわりの最適化はまだ発展途上で、SDPAは実験的、Flash Attentionも実験フラグ前提。動くようになった、というのが今の段階だ。
そしてUMAの固定分割が両刃になる。VRAMに48GB振れば大きいモデルが載るが、CPU側が16GBまで減ってSegfaultとCPUオフロード不可を招く。結局、ワークロードごとにBIOSで割り当てを変えるしかない。
参考
- AMD TheRock — ROCm + PyTorchのビルド・配布プロジェクト
- gfx1151 PyTorchホイール:
pip install --index-url https://repo.amd.com/rocm/whl/gfx1151/ torch - FastVideo / FastWan — FastWanの本家フレームワーク
- ZLUDA issue #626 — 公式PyTorchホイールのPTX非互換