Qwenベースの画像生成モデルをローカルで動かそうとして、VRAM不足に悩んでいる方も多いと思います。

今回は Diffusers の transformer_qwenimage.pyQwenTransformer2DModel)を改造してTransformerブロックをCPUにオフロードする ことで、8GBのGPUでも動かすことに成功したので、その方法の一端を共有します。


問題:Transformerブロックが多すぎてVRAMが足りない

Qwenの画像生成モデルは内部に大量のTransformerブロックを持っています。すべてをGPU(CUDA)上に乗せたまま推論しようとすると、VRAM 8GBでは当然アウトです。

解決策は単純で 「使うときだけGPUに持ってきて、終わったらCPUに戻す」 という逐次オフロードです。


改造内容:forward() メソッドのブロックループ部分

transformer_qwenimage.pyforward() メソッド内、Transformerブロックをループ処理している箇所を以下のように変更します。

変更前(すべてのブロックがGPU常駐)

for block in self.transformer_blocks:
    encoder_hidden_states, hidden_states = block(
        hidden_states=hidden_states,
        encoder_hidden_states=encoder_hidden_states,
        encoder_hidden_states_mask=encoder_hidden_states_mask,
        temb=temb,
        image_rotary_emb=image_rotary_emb,
        joint_attention_kwargs=attention_kwargs,
    )

変更後(CPUオフロード対応版)

# まずすべてのブロックをCPUに逃がしてVRAMをクリア
for block in self.transformer_blocks:
    block.to('cpu')
torch.cuda.empty_cache()

# ブロックを1つずつGPUに持ってきて処理し、終わったらCPUへ
for block in self.transformer_blocks:
    if self.is_cuda:
        block.to("cuda")

    encoder_hidden_states, hidden_states = block(
        hidden_states=hidden_states,
        encoder_hidden_states=encoder_hidden_states,
        encoder_hidden_states_mask=encoder_hidden_states_mask,
        temb=temb,
        image_rotary_emb=image_rotary_emb,
        joint_attention_kwargs=attention_kwargs,
    )

    block.to('cpu')
    torch.cuda.empty_cache()

ループの末尾に block.to('cpu')torch.cuda.empty_cache() を追加してキャッシュをクリアしていますが不要かもしれません。


コードの仕組みを図で理解する

┌─────────────────────────────────────────────────────┐
│                    GPU (VRAM 8GB)                   │
│                                                     │
│  hidden_states ──▶ [ Block N ] ──▶ hidden_states   │
│                       ↑                             │
│                  .to("cuda")                        │
│                   (処理前)                           │
└─────────────────────────────────────────────────────┘
         ↕ .to('cpu') / .to("cuda") を繰り返す
┌─────────────────────────────────────────────────────┐
│                   CPU (RAM)                         │
│                                                     │
│  Block 0, Block 1, Block 2, ..., Block N (待機中)   │
└─────────────────────────────────────────────────────┘
CPUオフロードの動作イメージ:使うブロックだけGPUに移動する

追加で必要な設定:is_cuda フラグ

コード中に self.is_cuda が登場します。これはモデルクラスの初期化時(__init__)に自前で設定したフラグです。GPUが使用可能かどうかを判定するために使っています。

# __init__ 内に追記
self.is_cuda = torch.cuda.is_available()

あるいはモデルロード後に外から設定しても構いません。

model.is_cuda = torch.cuda.is_available()

速度とトレードオフ

CPUオフロードには当然デメリットもあります。

通常(GPU常駐) CPUオフロード
VRAM使用量 ❌ 非常に多い ✅ 1ブロック分のみ
推論速度 ✅ 速い ⚠️ 遅くなる(PCIe帯域が律速)
動作可否(8GB環境) ❌ OOM ✅ 動く

速度は落ちますが、CPUだけの場合よりは5倍ほど早くなります。


まとめ

transformer_qwenimage.pyforward() に手を加えるだけで、VRAM 8GB環境でもQwenベースの画像生成モデルが動くようになります。

Diffusers の公式APIである enable_model_cpu_offload() はモデル全体に対して動作しますが、今回のようにブロック単位で細かく制御する方法はより柔軟で、特に独自モデルや改造モデルに有効です。

同じ問題で詰まっている方の参考になれば幸いです。


動作確認環境
GPU: VRAM 8GB
ライブラリ: PyTorch + Diffusers
対象ファイル: transformer_qwenimage.pyQwenTransformer2DModel

またこの実験ではQwen-Image-Edit-2511-SDNQ-uint4-svd-r32 モデル(uit4ビット 量子化)を使用しています。最も安定動作する計量版Qwenモデルかと思います。