GTX 1660 SUPER(VRAM 6GB)に SD1.5 + ControlNet 環境を構築した記録。詰まったのは VRAM 不足ではなく「RAM が ~26GB に跳ね上がる OOM」「fp16 の全黒 NaN 画像」「math SDPA 強制で 3.2x 遅延」の 3 点だった。さらに後から「全ステップ計測値がスピルによる誤計測だった」という誤算も発覚した。原因から修正まで全部書く。
この記事でできること
- VRAM 6GB の GPU で ControlNet が動く SD1.5 構成がわかる
- GTX 16xx 系(TU116)の fp16 NaN 問題と回避策がわかる
- sequential_cpu_offload が「VRAM ではなく RAM を食う」仕組みがわかる
- 最終的な速度: Hyper-SD15 1-step ~5 秒/枚、フル 25-step ~1 分/枚の実測値が得られる
使ったもの
- MSI GeForce GTX 1660 SUPER GAMING X 6GB(Amazonで確認)— 今回の実行 GPU
- CFD販売 DDR4-3200 32GB デスクトップメモリ(Amazonで確認)— RAM 不足 OOM を経験したため 32GB 以上を推奨
- WSL2(Ubuntu 22.04)、Python 3.12、diffusers 0.38
- Realistic Vision V6.0 B1 (noVAE) チェックポイント
- ControlNet(OpenPose / Canny / Depth)+ LCM-LoRA / Hyper-SD15
背景:SDXL の ControlNet を 1 時間 13 分で廃止した
もともと別環境(SDXL ベース)で ControlNet を実装しようとしていた。2026-04-30 21:40 に実装が完走したのだが、22:53 には廃止 commit を打っていた。1 時間 13 分。
原因は RAM だった。SDXL で enable_sequential_cpu_offload を使うと VRAM ピークは ~4GB に収まる。しかし、この仕組みではモデル全体が RAM に常駐する。そこに ControlNet(fp32 で ~5GB)を追加すると:
| 構成 | VRAM ピーク | RAM 常駐 | 結果 |
|---|---|---|---|
| SDXL fp32 + FaceID | ~4GB | ~21GB | ✅(24GB 環境) |
| + ControlNet を追加 | ~4GB(変わらず) | ~26GB ❌ | OOM |
VRAM は問題なかった。ControlNet 追加のコストは RAM 側に出る。「VRAM 6GB に収まっているのになぜ OOM?」という不可解さは、sequential_cpu_offload の仕組みを理解しないと解けない。
sequential_cpu_offload の仕組みを整理する
diffusers の enable_sequential_cpu_offload は UNet の ~700 サブモジュールを 1 つずつ VRAM に転送→推論→RAM に戻す、を毎ステップ繰り返す。モデル全体は RAM に常駐し続ける。CPU↔GPU のピンポンが 1 ステップに 700 回あり、Python GIL で 1 コア 100% 張り付く構造だ。
SD1.5 は全体 ~4GB (fp32)。VRAM 6GB に収まるので sequential_cpu_offload が不要になる。これが SD1.5 別環境を立てた動機だった。
詰まった問題 3 つ
① fp16 で全黒 NaN 画像が出る(GTX 16xx 系の既知問題)
5 構成でマイクロベンチを実行した:
| 構成 | step 時間 | 出力 |
|---|---|---|
| T1: SD1.5 fp16、CN なし、math SDPA | 15.8 s/step | 全黒 1224 bytes(NaN) |
| T3: + ControlNet 1 個、math SDPA | 22.6 s/step | NaN |
| T4: + flash + mem-efficient SDPA | 7.1 s/step | NaN(速くなったが) |
| fp32 に変更(SDPA デフォルト) | 後述 | ✅ 正常生成 |
SDPA の種類を変えても NaN は消えなかった。原因は GTX 1660 SUPER(TU116)が Tensor Core を非搭載であること。attention layer の fp16 softmax で underflow/overflow が起きる。AUTOMATIC1111 が GTX 16xx 向けに --no-half フラグを公式提供しているのと同じ問題だ。fp32 に変えると即解決した。
② math SDPA 強制が 3.2x 遅延の原因だった
T3(22.6 s/step)と T4(7.1 s/step)の差。別環境から流用したコードが原因だった:
# 別環境から引き継いだコード(fp16 NaN 対策として書かれたもの)
torch.backends.cuda.enable_flash_sdp(False)
torch.backends.cuda.enable_mem_efficient_sdp(False)
torch.backends.cuda.enable_math_sdp(True)
このコードを削除して PyTorch のデフォルト自動選択に任せることで 3.2x 改善できる。ただし flash SDPA は fp16/bf16 専用なので、fp32 化すると math kernel に戻る。「強制コードを削除 + fp32 化」が正しい組み合わせだ。
③ 全計測値がスピルによる誤計測だった(後から判明)
環境構築後、vram_peak_mb メタデータを生成画像に出力するようにして計測したところ、fp32 + ControlNet 1 個で VRAM ピークが 6,726 MB に達していた。GTX 1660 SUPER の WSL2 実効上限は ~6,100 MB。
全部スピルしていた。初期の計測値(13.7 s/step / フル 25-step 5分55秒)は全部 WSL2 がホスト RAM にスピルした状態での値だった。
対策は enable_model_cpu_offload(モデル単位、5〜10 回の CPU↔GPU 入れ替え):
| 構成 | --offload none(スピルあり) | --offload model(現行 default) | 改善率 |
|---|---|---|---|
| フル 25-step + 1 CN | 584.7s / VRAM 6,726 MB | 61.5s / VRAM 5,298 MB | 9.5x |
| Hyper-SD15 4-step + 1 CN | 62.9s / VRAM 6,680 MB | 11.1s / VRAM 5,146 MB | 5.7x |
| Hyper-SD15 1-step | 32.4s / VRAM 6,680 MB | 4.9s / VRAM 5,106 MB | 6.6x |
| Multi-CN (2 個) 25-step | 884.6s / VRAM 8,124 MB | 680.1s / VRAM 6,694 MB | 1.3x(まだスピル) |
1 CN 構成では VRAM が 5.1〜5.3GB に収まりスピルなし → 9.5x の高速化。Multi-CN(2 個)は --offload model でも 6.7GB でスピルが残るため改善が限定的だ。
SD1.5 の低 step 蒸留:SDXL Lightning に相当するものは?
SDXL で使っていた Lightning は SD1.5 版が存在しない。ByteDance は SDXL 専用にしかリリースしていないからだ。SD1.5 の対応手法:
- LCM-LoRA(
--lcm): 4-step。~10 秒/枚 - Hyper-SD15 CFG-distilled(
--hyper {1,2,4,8}): 1-step で ~5 秒。negative prompt 無効 - Hyper-SD15 CFG-preserved(
--hyper-cfg {8,12}): 8-step で ~4.5 分。negative prompt 有効、過飽和しにくい
ポーズ探索は --hyper 1(5 秒/枚)で大量に回し、人物写真の仕上げは --hyper-cfg 8(negative prompt 有効)が使いやすい。
2 環境の役割分担(確定版)
| SD1.5 環境 | SDXL 環境 | |
|---|---|---|
| 主な用途 | ポーズ・構図の探索 | 最終出力・FaceID portrait |
| 速度(フル 25-step) | ~1 分/枚(61.5s 実測) | ~16 分/枚 |
| 速度(低 step 蒸留) | Hyper 1-step ~5 秒 | Lightning 8-step ~6-7 分 |
| 解像度 | 512×768 (native) | 1280×720 / 832×1216 |
| ControlNet | OpenPose / Canny / Depth ✅ | ❌(RAM 制約で廃止済み) |
推奨ワークフロー: SD1.5 で --hyper 1(5 秒/枚)を使ってポーズ・構図を大量に試し、確定したら SDXL 環境に参照画像ごと渡して最終出力を得る。SD1.5 の OpenPose 骨格画像は SDXL の ControlNet モデルと latent space が違うので直接は使えないが、元の参照画像を IP-Adapter 入力として渡す形で橋渡しできる。
よくある質問
Q. GTX 1660 SUPER で fp16 を使えないか?
A. 現状は fp32 一択です。TU116 は Tensor Core 非搭載で attention layer の fp16 softmax が underflow/overflow します。AUTOMATIC1111 の --no-half や ComfyUI の --force-fp32 と同根の既知問題です。RTX 3060 以降(Ampere)なら fp16 が使えて flash SDPA で 1-2 s/step が期待できます。
Q. SDXL に ControlNet を追加する方法はないか?
A. FaceID を使わない構成(txt2img + ControlNet のみ)なら RAM ~19GB で動作可能です。FaceID と ControlNet の同時利用は RAM ~26GB を超えるため SD1.5 に分担するのが現実的な選択です。
Q. Multi-ControlNet(2 個同時)はどれくらい時間がかかる?
A. --offload model でも VRAM 6.7GB でスピルが残るため ~11 分/枚です。1 個の時と比べて改善が限定的なのはスピルが完全には解消しないからです。
※本記事の手順・数値は 2026 年 5 月時点の環境(diffusers 0.38、GTX 1660 SUPER、WSL2 + Ubuntu 22.04)で確認しています。ライブラリや GPU ドライバのバージョンが変わると動作が変わる場合があります。動かない場合はコメント欄でお知らせください。
まとめ
GTX 1660 SUPER(VRAM 6GB, RAM 24GB)に SD1.5 + ControlNet 環境を構築した。3 つの落とし穴(fp16 NaN、math SDPA 強制遅延、VRAM スピルによる誤計測)を経て、最終的に Hyper-SD15 1-step ~5 秒/枚・フル 25-step ~1 分/枚が出る構成になった。SDXL 環境(16 分/枚)との使い分けでポーズ探索を高速化できる。
この記事が役に立ったら X でシェアしてもらえると喜びます。
このブログを書いた人のアプリ
本ブログの著者が作った iOS 読書管理アプリ わたしのほんやさん を App Store で公開しています。本棚をシンプルに管理したい方はぜひ。
参考
- diffusers — Hugging Face
- ByteDance/Hyper-SD — Hugging Face
- lllyasviel/ControlNet
- AUTOMATIC1111 Wiki: Install and Run on NVidia GPUs(--no-half の説明あり)
※この記事は Claude Code を使った自動更新を試しています。
0 件のコメント:
コメントを投稿