Stable Diffusion 3.5 Large による Text-to-Image (ソースコードと実行結果)

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する


pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install diffusers huggingface_hub transformers accelerate pillow

HugggingFace トークン取得

https://huggingface.co/settings/tokens にアクセス. HugggingFace トークンの設定.保存の支援ツールは別ページ (https://www/kkaneko.jp/ai/labo/hf.html)で紹介.

Stable Diffusion 3.5 Large による Text-to-Image プログラム

概要

このプログラムは、Stable Diffusion 3.5 LargeおよびSDXL 1.0モデルを使用して、テキストプロンプトから画像を生成する。Hugging FaceのDiffusersライブラリ[4]を使用し、複数の画像生成モデルを操作できる。ユーザーはモデルを選択し、プロンプトを入力することで、画像を生成できる。

主要技術

Stable Diffusion 3.5

Stability AIが2024年に公開した画像生成モデル[1]。Rectified Flowベースの拡散モデルであり、Flow Matchingアルゴリズム[2]を採用している。本プログラムでは、通常版(28ステップ)とTurbo版(4ステップ)の両方に対応している。

SDXL 1.0

Stable Diffusion XL(SDXL)は、高解像度画像生成に対応したモデル[3]である。U-Netアーキテクチャの改良により、1024×1024ピクセルのネイティブ解像度での生成を可能にしている。

Flow Matching Scheduler

SD3.5で採用されているFlowMatchEulerDiscreteSchedulerは、Ordinary Differential Equationを離散的に解く手法である[2]。

技術的特徴

自動デバイス選択とメモリ最適化

プログラムはCUDA対応GPUの有無を自動検出し、適切なデバイスとデータ型を選択する。GPU環境ではbfloat16またはfloat16精度を使用し、CPU環境ではfloat32にフォールバックする。メモリ効率化のため、model_cpu_offload機能とVAEタイリングを有効化し、大規模モデルの実行を可能にしている。

モデル設定の辞書管理

MODEL_CONFIGSディクショナリにより、各モデルの設定を一元管理している。推論ステップ数、ガイダンススケール、スケジューラークラスなどのパラメータを事前定義することで、モデル固有の設定を自動適用する。この設計により、新規モデルの追加が容易になっている。

Hugging Face認証の統合

環境変数HF_TOKENを通じたトークン管理を実装し、ゲート付きモデルへのアクセスを自動化している。model_info APIを使用してアクセス権限を事前確認し、必要に応じてライセンス同意の案内を表示する。

実装の特色

対話形式のインターフェース

ユーザーとの対話を通じて、モデル選択、プロンプト入力、パラメータ設定を行う。各段階で説明を提供し、操作を支援する設計となっている。サンプルプロンプトの提供により、動作確認が可能である。

ネガティブプロンプトの動的制御

モデルごとのネガティブプロンプト対応状況を設定で管理し、SD3.5 Turboのような非対応モデルでは自動的に無効化する。対応モデルではデフォルト値を提供し、ユーザーによるカスタマイズも可能にしている。

参考文献

[1] Stability AI. (2024). Stable Diffusion 3.5 Large. Hugging Face Model Repository. https://huggingface.co/stabilityai/stable-diffusion-3.5-large

[2] Esser, P., et al. (2024). Scaling Rectified Flow Transformers for High-Resolution Image Synthesis. arXiv:2403.03206. https://arxiv.org/abs/2403.03206

[3] Podell, D., et al. (2023). SDXL: Improving Latent Diffusion Models for High-Resolution Image Synthesis. arXiv:2307.01952. https://arxiv.org/abs/2307.01952

[4] Hugging Face. (2024). Diffusers Documentation. https://huggingface.co/docs/diffusers

ソースコード


# プログラム名: Stable Diffusion 3.5 Large による Text-to-Image プログラム
# pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
# pip install diffusers huggingface_hub transformers accelerate pillow
import os
import sys
import subprocess
import torch
from diffusers import StableDiffusion3Pipeline, FlowMatchEulerDiscreteScheduler, StableDiffusionXLPipeline
from huggingface_hub import login, model_info
from huggingface_hub.utils import RepositoryNotFoundError, GatedRepoError

def print_dependency_guide():
   print("\n依存関係インストールガイド")
   print("GPU環境:")
   print("  1. PyTorch公式サイトからCUDA対応版を選択してインストール")
   print("     https://pytorch.org/get-started/locally/")
   print("  2. 任意: pip install xformers")
   print("CPU環境:")
   print("  pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu")
   print("共通:")
   print("  pip install 'diffusers>=0.30.0' huggingface_hub transformers accelerate pillow")

# モデル固有の設定を定数化
MODEL_CONFIGS = {
   "sd3.5-large": {
       "model_id": "stabilityai/stable-diffusion-3.5-large",
       "pipeline_class": StableDiffusion3Pipeline,
       "default_dtype": torch.bfloat16,
       "cpu_dtype": torch.float32,
       "num_inference_steps": 28,  # 公式推奨値
       "guidance_scale": 3.5,      # 公式推奨値
       "width": 1024,              # 1メガピクセル対応
       "height": 1024,             # 1メガピクセル対応
       "scheduler_class": FlowMatchEulerDiscreteScheduler,  # SD3.5に適したスケジューラー
       "enable_cpu_offload": True,
       "enable_vae_tiling": True,
       "supports_negative_prompt": True,  # ネガティブプロンプトをサポート
       "model_name": "SD3.5 Large",
       "model_url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-large",
       "setup_message": "SD3.5 Largeを使用する前に以下を確認してください:\n"
                        "1. https://huggingface.co/stabilityai/stable-diffusion-3.5-large にアクセス\n"
                        "2. 「Agree and access repository」ボタンをクリックしてライセンスに同意\n"
                        "3. アクセスが承認されるまで待つ\n"
                        "4. pip install -U diffusers を実行済みであること",
       "sample_prompt": "A capybara holding a sign that reads Hello World",  # 公式例
       "model_description": "最新・高品質・英語・1メガピクセル"
   },
   "sd3.5-large-turbo": {
       "model_id": "stabilityai/stable-diffusion-3.5-large-turbo",
       "pipeline_class": StableDiffusion3Pipeline,
       "default_dtype": torch.bfloat16,
       "cpu_dtype": torch.float32,
       "num_inference_steps": 4,
       "guidance_scale": 0.0,      # Turboモデルはguidance不要(公式推奨値)
       "width": 1024,              # 1メガピクセル対応
       "height": 1024,             # 1メガピクセル対応
       "scheduler_class": FlowMatchEulerDiscreteScheduler,
       "enable_cpu_offload": True,
       "enable_vae_tiling": True,
       "supports_negative_prompt": False,  # Turboモデルはネガティブプロンプト不使用
       "model_name": "SD3.5 Large Turbo",
       "model_url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-large-turbo",
       "setup_message": "SD3.5 Large Turboを使用する前に以下を確認してください:\n"
                        "1. https://huggingface.co/stabilityai/stable-diffusion-3.5-large-turbo にアクセス\n"
                        "2. 「Agree and access repository」ボタンをクリックしてライセンスに同意\n"
                        "3. アクセスが承認されるまで待つ\n"
                        "4. pip install -U diffusers を実行済みであること",
       "sample_prompt": "A capybara holding a sign that reads Hello Fast World",  # 公式例
       "model_description": "超高速・英語・4ステップ生成・1メガピクセル"
   },
   # SDXL 1.0 モデル構成
   "sdxl-1.0": {
       "model_id": "stabilityai/stable-diffusion-xl-base-1.0",
       "pipeline_class": StableDiffusionXLPipeline,
       "default_dtype": torch.float16,   # SDXLはGPUでfloat16推奨
       "cpu_dtype": torch.float32,
       "num_inference_steps": 50,
       "guidance_scale": 7.5,
       "width": 1024,                    # 表示用(SDXLでは指定なしでも既定1024x1024)
       "height": 1024,
       "scheduler_class": "default",     # SDXLはデフォルトスケジューラーを使用
       "enable_cpu_offload": True,
       "enable_vae_tiling": True,
       "supports_negative_prompt": True,  # ネガティブプロンプトをサポート
       "model_name": "SDXL 1.0",
       "model_url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0",
       "setup_message": "SDXL 1.0は通常、追加のライセンス同意や認証は不要です。\n"
                        "pip install -U diffusers を実行済みであることを確認してください。",
       "sample_prompt": "A capybara in watercolor style, soft pastel colors",
       "model_description": "従来SDXL・1024x1024・英語"
   }
}

def setup_token():
   print("\nトークンの取得方法:")
   print("1. https://huggingface.co/settings/tokens にアクセス.ログインしていない場合はログイン")
   print("2. 「Create new token」をクリック")
   print("3. Token nameを入力(例: my-app-token)")
   print("4. Token typeで「Write」を選択")
   print("5. 「Create token」をクリック")
   print("6. 生成されたトークンをコピー(「Copy」をクリック)")

   # トークン入力を求める
   token = input("\nトークンを入力してください: ")

   # Windowsの場合、setxコマンドで永続的に設定
   try:
       subprocess.run(['setx', 'HF_TOKEN', token], check=True)
       print("\n環境変数を設定しました。新しいコマンドプロンプトを開いて再実行してください。VSCode や Windsurf などの開発環境を用いている場合には、開発環境を終了し、再度起動してください")
   except subprocess.CalledProcessError:
       print("\n環境変数の設定に失敗しました。")

   sys.exit(0)

# トークンの存在確認
hf_token = os.environ.get("HF_TOKEN")
if not hf_token:
   print("HF_TOKEN環境変数が設定されていません。")
   setup_token()

# huggingface_hubでログイン(トークンを保存)
try:
   login(token=hf_token, add_to_git_credential=False)
except Exception:
   print("トークンが無効です。")
   setup_token()

# 依存関係インストールガイドの表示(任意)
show_guide = input("\n依存関係インストールガイドを表示しますか? [y/N]: ").strip().lower()
if show_guide == 'y':
   print_dependency_guide()

# モデル選択メニューを表示し、ユーザーの選択を返す
print("\n使用するモデルを選択してください:")
model_keys = list(MODEL_CONFIGS.keys())
for i, key in enumerate(model_keys, 1):
   config = MODEL_CONFIGS[key]
   print(f"{i}. {config['model_name']} ({config['model_description']})")
   print(f"   モデルID: {config['model_id']}")
   print(f"   URL: {config['model_url']}")

while True:
   try:
       choice = input("\n番号を入力してください (1-{}): ".format(len(model_keys)))
       choice_idx = int(choice) - 1
       if 0 <= choice_idx < len(model_keys):
           selected_model = model_keys[choice_idx]
           print(f"\n{MODEL_CONFIGS[selected_model]['model_name']}を選択しました。")
           break
       else:
           print("有効な番号を入力してください。")
   except ValueError:
       print("数字を入力してください。")

model_config = MODEL_CONFIGS[selected_model]

# モデルへのアクセス権限を確認
try:
   info = model_info(model_config["model_id"], token=hf_token)
   access_ok, error_msg = True, None
except GatedRepoError:
   access_ok, error_msg = False, "ライセンス同意が必要です"
except RepositoryNotFoundError:
   access_ok, error_msg = False, "モデルが見つかりません"
except Exception as e:
   access_ok, error_msg = False, f"アクセスエラー: {str(e)}"

if not access_ok:
   print(f"\nモデルへのアクセスに問題があります: {error_msg}")
   print(model_config["setup_message"])
   input("\n準備ができたらEnterキーを押してください...")
else:
   print(f"\n{model_config['model_name']}へのアクセスが確認できました。")

# デバイスの自動選択
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = model_config["default_dtype"] if device == "cuda" else model_config["cpu_dtype"]

if device == "cpu":
   print("GPUが利用できないため、CPUで実行します。処理に時間がかかる場合があります。")
# デバイス名とdtypeの明示出力
if device == "cuda":
   try:
       print(f"デバイス: GPU ({torch.cuda.get_device_name(0)})")
   except Exception:
       print("デバイス: GPU")
else:
   print("デバイス: CPU")
print(f"データ型: {dtype}")

try:
   # パイプラインの読み込み
   print("モデルの読み込み中...")
   from_pretrained_kwargs = {
       "torch_dtype": dtype,
       "token": hf_token,
       "use_safetensors": True
   }

   # SDXL + GPU時にvariant設定を適切に処理
   if selected_model.startswith("sdxl") and device == "cuda":
       if dtype == torch.float16:
           from_pretrained_kwargs["variant"] = "fp16"
       # bfloat16の場合は特別な処理は不要(デフォルトで対応)

   pipe = model_config["pipeline_class"].from_pretrained(
       model_config["model_id"],
       **from_pretrained_kwargs
   )
   pipe = pipe.to(device)

   # メモリ最適化
   if device == "cuda" and model_config.get("enable_cpu_offload", False):
       pipe.enable_model_cpu_offload()
   if model_config.get("enable_vae_tiling", False) and hasattr(pipe, 'vae'):
       pipe.vae.enable_tiling()
   # attention slicing と xFormers(可能な場合のみ)
   if hasattr(pipe, "enable_attention_slicing"):
       try:
           pipe.enable_attention_slicing()
       except Exception:
           pass
   if device == "cuda" and hasattr(pipe, "enable_xformers_memory_efficient_attention"):
       try:
           pipe.enable_xformers_memory_efficient_attention()
           print("XFormers memory optimization enabled")
       except Exception:
           print("XFormers not available, skipping memory optimization")

   # VAEの精度設定
   if hasattr(pipe, 'vae'):
       pipe.vae.to(dtype)

   # スケジューラーの設定
   if "scheduler_class" in model_config:
       if model_config["scheduler_class"] == "default":
           # SDXLはデフォルトのスケジューラーを使用
           print("デフォルトのスケジューラーを使用します")
       elif model_config["scheduler_class"] is not None:
           # カスタムスケジューラーを設定
           pipe.scheduler = model_config["scheduler_class"].from_config(pipe.scheduler.config)
           print(f"カスタムスケジューラー({model_config['scheduler_class'].__name__})を設定しました")

   print("モデルの読み込みが完了しました。")

   # プロンプト入力ガイド(メイン+スタイル結合)
   print("\n=== プロンプト入力ガイド ===")
   print("1. メインプロンプト: 対象や構図、詳細描写を具体的に記述する")
   print("   例: 'a serene lakeside at sunset, detailed reflections, mountains in background'")
   print("2. スタイルプロンプト: 画風や質感、雰囲気を補足する(任意)")
   print("   例: 'high quality, detailed, sharp, cinematic lighting'")
   print("Enterのみでサンプルプロンプトを使用する")

   main_prompt_in = input("\nメインプロンプト(未入力でサンプル使用): ").strip()
   style_prompt_in = input("スタイルプロンプト(任意、未入力可): ").strip()

   if main_prompt_in:
       combined_prompt = f"{main_prompt_in}, {style_prompt_in}" if style_prompt_in else main_prompt_in
   else:
       combined_prompt = model_config["sample_prompt"]

   # ネガティブプロンプトの判定ロジックを修正
   use_neg_prompt = model_config.get("supports_negative_prompt", True)

   if use_neg_prompt:
       default_negative_prompt = (
           "low quality, worst quality, blurry, out of frame, watermark, signature, jpeg artifacts, "
           "text, logo, extra fingers, extra limbs, extra digits, deformed, disfigured, poorly drawn hands, "
           "bad anatomy, cropped, duplicate"
       )
       print("\nネガティブプロンプトについて")
       print("未入力の場合、以下のデフォルト値が使用される")
       print(f"デフォルトのネガティブプロンプト: {default_negative_prompt}")
       neg_in = input("\nネガティブプロンプト(任意、上書き可): ").strip()
       if neg_in:
           negative_prompt_value = neg_in
           print(f"ネガティブプロンプトを使用: {negative_prompt_value}")
       else:
           negative_prompt_value = default_negative_prompt
           print("デフォルトのネガティブプロンプトを使用する")
           print(f"使用するデフォルトのネガティブプロンプト: {negative_prompt_value}")
   else:
       negative_prompt_value = None
       print("\nこのモデルはネガティブプロンプトをサポートしていません")

   # 画像生成パラメータの準備
   generation_params = {
       "prompt": combined_prompt,
       "num_inference_steps": model_config["num_inference_steps"],
       "guidance_scale": model_config["guidance_scale"],
   }

   # ネガティブプロンプトの追加
   if use_neg_prompt:
       generation_params["negative_prompt"] = negative_prompt_value

   # 解像度設定(SD3.5とSDXL両方に適用)
   if "width" in model_config and "height" in model_config:
       generation_params.update({
           "width": model_config["width"],
           "height": model_config["height"],
       })

   # 実行前の設定表示(確認用)
   print("生成設定:")
   if "width" in model_config and "height" in model_config:
       print(f"  解像度: {model_config['width']}x{model_config['height']}")
   print(f"  推論ステップ数: {model_config['num_inference_steps']}")
   print(f"  ガイダンススケール: {model_config['guidance_scale']}")
   print(f"  使用プロンプト: {combined_prompt}")
   if use_neg_prompt:
       print(f"  ネガティブプロンプト: {negative_prompt_value}")
   else:
       if model_config.get("supports_negative_prompt", True):
           print("  ネガティブプロンプト: 未使用(ユーザー選択)")
       else:
           print("  ネガティブプロンプト: 非対応(モデル仕様)")

   # 画像生成
   print("画像生成中...")
   with torch.no_grad():
       image = pipe(**generation_params).images[0]
   print("画像生成が完了しました。")

   # 画像を保存(モデル名を含むファイル名)
   output_filename = f"{selected_model}_output.png"
   image.save(output_filename)
   print(f"画像を {output_filename} として保存しました。")
   try:
       image.show()
   except Exception:
       pass

except Exception as e:
   print(f"画像生成に失敗しました: {e}")
   if any(s in str(e).lower() for s in ["kmp_duplicate_lib_ok", "libiomp5", "openmp"]):
       print("\nOpenMP関連のエラーが疑われる。暫定対処として以下を試すことがある。")
       print("  Windows: setx KMP_DUPLICATE_LIB_OK TRUE")
       print("  Linux/macOS: export KMP_DUPLICATE_LIB_OK=TRUE")
       print("注意: 恒久対策ではなく、可能であれば重複するOpenMPランタイムの解消を推奨する")
   response = input("\nトークンを再設定しますか? [Y/n]: ")
   if response.lower() != 'n':
       setup_token()