RTX 5070 Tiでローカル画像生成:SD-Turboを1・2・4・8 stepで実測

環境: RTX 5070 Ti 16GB / WSL2 / PyTorch 2.13.0+cu130 / Diffusers 0.40.0

SD-TurboをRTX 5070 Tiで動かしました。512×512画像の生成は、読み込みとウォームアップを済ませた状態で約0.09〜0.25秒でした。

同じプロンプトと乱数シードで、ノイズを取り除く処理の回数を1・2・4・8へ変えています。回数を増やすと時間は延びますが、出てきた画像が順番によくなるわけではありませんでした。生成画像を並べて確認します。

1・2・4・8ステップの結果

ステップ 中央値 5回範囲 ピーク allocated
1 0.092秒 0.088~0.096秒 3,120.5 MiB
2 0.117秒 0.112~0.117秒 3,120.5 MiB
4 0.166秒 0.155~0.188秒 3,120.5 MiB
8 0.254秒 0.249~0.257秒 3,120.5 MiB
SD-TurboをRTX 5070 Tiで1・2・4・8ステップローカル生成した比較
SD-TurboをRTX 5070 Tiで1・2・4・8ステップローカル生成した比較

4ステップの生成結果です。

SD-TurboをRTX 5070 Tiで4ステップ、乱数シード 42により生成した大学AI研究室の画像
SD-TurboをRTX 5070 Tiで4ステップ、乱数シード 42により生成した大学AI研究室の画像

画像を目視すると、ステップを増やすほど単調に品質が上がるとは言えません。SD-Turboは少ステップ向けにdistillされたモデルで、8ステップは「高品質設定」と同義ではありません。

実際にダウンロードしたもの

Python パッケージをWSL venvへ入れました。

source /opt/ai-lab/venv/bin/activate
pip install diffusers transformers accelerate safetensors sentencepiece

実行バージョンです。

diffusers     0.40.0
transformers  5.15.1
accelerate    1.14.0
safetensors   0.8.0
torch         2.13.0+cu130

モデルはDiffusersがstabilityai/sd-turboから取得し、Hugging Face キャッシュへ保存しました。初回は12 ファイルの取得に約31秒、処理の流れ読み込みを含むスクリプト上の読み込み時間は33.9秒でした。

2回目はキャッシュから読み、読み込み時間2.63秒です。画像1枚の0.1~0.3秒だけを見ても、初回ダウンロードとモデル読み込みを含むuser experienceは説明できません。

処理の流れをGPUへ載せる

pipe = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/sd-turbo",
    torch_dtype=torch.float16,
    variant="fp16",
)
pipe = pipe.to("cuda")

FP16を使うとFP32より重みメモリを減らせます。今回のtorch_dtype引数には将来dtypeへ変更するdeprecation 警告が出ました。警告を記録し、バージョンを固定しています。

DiffusersのAutoPipelineはモデル configから適切なtext-to-image 処理の流れを選びます。この実行ではStableDiffusionPipelineが構成されました。

プロンプトを固定する

全条件で同じ英語プロンプトを使いました。

A small university AI laboratory desk,
a black GPU workstation beside notebooks with neural network diagrams,
warm morning light, realistic editorial photograph,
no text, no logo

比較中にプロンプトを変えると、ステップ差とプロンプト差を分離できません。negative プロンプトは使っていません。

no text, no logoと書いても完全な保証にはなりません。実際の比較画像にはmonitor上に文字のような模様があります。生成モデルは指示を論理constraintとして実行するわけではありません。

guidance スケールを0にする

生成callです。

image = pipe(
    prompt,
    num_inference_steps=4,
    guidance_scale=0.0,
    height=512,
    width=512,
    generator=generator,
).images[0]

SD-Turboのモデル cardは1~4ステップの高速生成とguidance_scale=0.0を例示しています。普通のStable Diffusionでよく見る7.5をそのまま使わず、モデル固有の推奨条件へ合わせました。

モデル系列が違えば適切なスケジューラ、ステップ、guidanceも変わります。「Stable Diffusionの設定」を一括りにできません。

乱数シードはCUDA Generatorへ渡す

generator = torch.Generator(device="cuda").manual_seed(42)

diffusion モデルはrandom noiseから生成を始めます。乱数シードを固定すると初期noiseを固定でき、ステップ数だけを変える比較がしやすくなります。

global torch.manual_seedだけに頼らず、生成call専用Generatorを作りました。各測定replicateでも乱数シード 42から作り直しています。

ウォームアップを測定から外す

最初の動作確認回では1ステップが0.790秒、2ステップが0.127秒でした。1ステップの方が大幅に遅いのは不自然です。

原因は最初のcallにCUDA initializationやカーネル setupが含まれたためです。そこで乱数シード 999で1回ウォームアップし、その後各ステップを5回測り直しました。

pipe(prompt, num_inference_steps=1, generator=warmup_generator)

for _ in range(5):
    torch.cuda.synchronize()
    started = time.perf_counter()
    image = pipe(...)
    torch.cuda.synchronize()
    samples.append(time.perf_counter() - started)

この修正で1ステップ中央値は0.092秒になりました。GPU ベンチマークではウォームアップとsynchronize()が不可欠です。CUDA operationは非同期なので、同期せずCPU clockだけ測ると処理完了前にtimerが止まります。

ステップ数と時間はほぼ増える

1 step  0.092 s
2 steps 0.117 s
4 steps 0.166 s
8 steps 0.254 s

1→8ステップでUNet 評価回数は8倍ですが、処理全体時間は2.76倍でした。テキスト encoding、VAE 復号、Python callなどステップ数に依存しない固定コストがあるためです。

画像/秒へ単純換算すると次です。

1 step  約10.84 images/s
2 steps  約 8.56 images/s
4 steps  約 6.04 images/s
8 steps  約 3.93 images/s

これはバッチ 1、512×512、同じ処理の流れを読み込み済みの処理速度です。モデル読み込み、PNG保存、web deliveryは含みません。

ピークメモリがステップ数で変わらなかった理由

全条件のtorch.cuda.max_memory_allocated()は約3,120.5 MiBでした。

denoising ステップを増やしても、同じ配列の形のlatentとモデル重みを順番に再利用します。計算回数は増えても、同時に保持するテンソルの最大構成が同じならピークは増えません。

一方、resolutionやバッチを増やすと中間出力テンソルが大きくなり、ピークメモリは増えます。ステップ数は主にtime、resolutionとバッチはtimeとメモリの両方へ効く、という違いです。

この3,120 MiBはPyTorch allocatorのallocated ピークです。ドライバコンテキストや他プロセスを含むnvidia-smi値とは一致しません。

1・2・4・8ステップを目視比較する

全画像は同じ乱数シード 42ですが、スケジューラ trajectoryがステップ数で変わるため別画素になります。

1ステップでもdesk、monitor、rackらしい構図は出ています。2~4ステップではobjectの輪郭や配置が変わり、8ステップでは色の強いmonitorや本が増えました。

ただし「8ステップが最高」と客観評価していません。プロンプト adherence、美観、生成ファイルは人によって判断が変わります。1 プロンプト×1 乱数シードだけではquality ベンチマークになりません。

定量化するなら次が必要です。

  • 複数プロンプトカテゴリ
  • 各プロンプト複数乱数シード
  • human preference blind テスト
  • CLIPなどのtext-image 配置の整列
  • 生成ファイルや文字崩れのannotation
  • 生成失敗率

この記事の画像は、実行条件差を人間が確認するqualitative 根拠です。

同じ乱数シードは画素単位で一致した

4ステップ、乱数シード 42を同じプロセスで再実行しました。PIL imageの画素 byte列へSHA-256を計算します。

hashlib.sha256(image.tobytes()).hexdigest()

2回とも次のhashでした。

deb671cd10d3abd2a64944305de5011efbec9ba74efefd161c2eb704f2f62d9e

画素単位で完全一致です。PNG ファイルサイズも両方409,308 bytesでした。

乱数シード 43は別hashになりました。

30959d7387faa9fcc42bb055a8f59702a0bac98b742983c17a2769078f3ca058

ただしPyTorch、Diffusers、CUDA、スケジューラ、GPU 構造を変えると、同じ乱数シードでも同一画素にならない可能性があります。乱数シードは実験条件の一部であり、普遍的な画像IDではありません。

safety checker 警告をどう扱ったか

処理の流れ読み込み時、safety checkerが無効であるという警告が出ました。公開serviceでunfiltered resultをそのまま配信しないようDiffusersが注意しています。

今回は自分のoffline experimentで、プロンプトは研究室の机です。生成6枚を保存後に目視確認し、公開する比較gridと4-step画像に不適切内容や実在人物の描写がないことを確認しました。

不特定userのプロンプトを受けるserviceなら、content policy、input/output フィルタ、rate limit、audit ログ、人間確認、モデルライセンス確認が必要です。

ダウンロード元とライセンスを記録する

ローカルで動くことと、自由に再配布できることは同じではありません。モデル cardとライセンスを確認し、モデル IDを結果JSONへ保存しました。

{
  "model": "stabilityai/sd-turbo",
  "dtype": "float16",
  "resolution": [512, 512],
  "guidance_scale": 0.0
}

研究成果へ画像を使う場合は、モデルライセンスだけでなくデータセット由来のrisk、生成物の扱い、所属機関のruleも確認します。

再現手順

以下のコマンドでは、作業フォルダーを ~/ai-experiments と表記します。実際の保存先に合わせて読み替えてください。フォルダー内の work/ 以下の配置はそのまま使います。

source /opt/ai-lab/venv/bin/activate

cd ~/ai-experiments/work/ai_lab
python -u experiment_local_image_generation.py

生成されるファイルは次のとおりです。

work/ai_lab/experiment_local_image_generation.py
work/ai_lab/results/local_image_generation.json
work/ai_lab/generated_images/sd_turbo_steps1_seed42.png
work/ai_lab/generated_images/sd_turbo_steps2_seed42.png
work/ai_lab/generated_images/sd_turbo_steps4_seed42.png
work/ai_lab/generated_images/sd_turbo_steps8_seed42.png
work/ai_lab/generated_images/sd_turbo_steps4_seed42_repeat.png
work/ai_lab/generated_images/sd_turbo_steps4_seed43_different_seed.png
work/figures/sd-turbo-local-steps.png

JSONには読み込み時間、プロンプト、バージョン、GPU、各5回の未加工の秒、中央値、ピーク allocated、画素 hash、PNG サイズを保存しています。

生成は速いが、最初の1枚は待つ

読み込み後の生成は短時間でしたが、初回ダウンロードには31秒、キャッシュ済みでもモデルの読み込みに2.63秒かかりました。少数の画像だけを作るなら、こちらの待ち時間も効きます。

同じ環境・乱数シードでは画素単位の一致を確認できました。ステップ数を増やした画像は、細部が変わる一方で一様によくなるわけではありません。使う画像を見ながら回数を選べる速さではありました。

関連記事