Diffusers documentation
哲学
哲学
🧨 Diffusers は、最先端の事前学習済み拡散モデルを、複数のモダリティにわたって提供しています。 その目的は、推論と学習の両方においてモジュール化されたツールボックスとして機能することです。
私たちは、長期的な信頼性を備えたライブラリを構築することを目標としており、そのためAPI設計を非常に重視しています。
要するに、Diffusers は PyTorch の自然な拡張として構築されています。そのため、ほとんどの設計上の判断は PyTorch の設計原則に基づいています。最も重要なポイントをいくつか見ていきましょう。
使いやすさを性能より優先
- Diffusersには多くのパフォーマンス向上機能が組み込まれていますが(メモリと速度を参照)、モデルは常に最高精度かつ最低限の最適化で読み込まれます。そのため、ユーザーが明示的に指定しない限り、デフォルトでは拡散パイプラインはCPU上でfloat32精度でインスタンス化されます。これにより、異なるプラットフォームやアクセラレーター間で利用可能であり、ライブラリを実行するために複雑なインストールは不要です。
- Diffusers は 軽量なパッケージであることを目指しており、必須の依存関係は非常に少ないですが、
accelerate、safetensors、onnxなど、パフォーマンスを向上させることができる多くの任意の依存関係があります。他のパッケージへの依存関係として、大きな懸念なく追加できるように、ライブラリをできるだけ軽量に保つよう努めています。 - Diffusersでは、凝縮されたマジックなコードよりも、シンプルで自己説明可能なコードを優先しています。そのため、ラムダ関数などの短縮記法や、高度なPyTorch演算子は、多くの場合望ましくありません。
シンプルでかんたん
PyTorchが述べているように、明示的であることは暗黙的であることより良い、そして単純であることは複雑であることより良いです。この設計思想は、ライブラリの複数の部分に反映されています。
- PyTorch の API に従い、
DiffusionPipeline.toのようなメソッドを通じて、ユーザーがデバイス管理を処理できるようにしています。 - 誤った入力を黙って修正するよりも、簡潔なエラーメッセージを送出することが推奨されます。Diffusersは、ライブラリをできるだけ使いやすくすることよりも、ユーザーに学習させることを目的としています。
- 複雑なモデルとスケジューラのロジックが内部で自動的に処理されるのではなく、明示的に公開されています。Scheduler/Samplerは拡散モデルから分離されており、相互の依存関係は最小限に抑えられています。これにより、ユーザーは展開されたデノイジングループを自分で記述する必要があります。ただし、この分離によりデバッグが容易になり、デノイジングプロセスの適応や拡散モデルおよびスケジューラの切り替えにおいて、ユーザーにより多くの制御が可能になります。
- 拡散パイプラインの個別に学習されたコンポーネント、例えばテキストエンコーダー、unet、変分オートエンコーダーは、それぞれ独自のモデルクラスを持ちます。これにより、ユーザーは異なるモデルコンポーネント間の相互作用を処理する必要があり、シリアライズ形式ではモデルコンポーネントが異なるファイルに分離されます。ただし、これによりデバッグとカスタマイズが容易になります。DreamBoothやTextual Inversionの学習は、Diffusersが拡散パイプラインの単一のコンポーネントを分離できるため、非常にシンプルです。
調整可能で、コントリビューターフレンドリーな抽象化
ライブラリの大部分において、Diffusers は Transformers ライブラリ の重要な設計原則を採用しています。それは、急ぎ足で抽象化を行うよりも、コードのコピー&ペーストを優先するというものです。この設計原則は非常に強い主張を含んでおり、DRY(Don’t repeat yourself) といった一般的な設計原則とは対照的です。 要するに、Transformers がモデリングファイルに対して行っているのと同様に、Diffusers もパイプラインとスケジューラに対して、極めて低い抽象化レベルと、非常に自己完結的なコードを維持することを好みます。 関数、長いコードブロック、さらにはクラスであっても、複数のファイル間でコピーされることがあります。一見すると、これはライブラリをメンテナンス不能にする、粗末で不適切な設計判断のように見えるかもしれません。しかし、この設計は Transformers において極めて成功を収めており、コミュニティ主導のオープンソース機械学習ライブラリにとって非常に理にかなっています。その理由は以下の通りです:
- 機械学習は、パラダイム、モデルアーキテクチャ、アルゴリズムが急速に変化する非常に動きの速い分野であり、そのため長期的に持続可能なコードの抽象化を定義することが非常に困難です。
- 機械学習の実務者は、アイデア出しや研究のために既存のコードを素早く調整できることを好むため、多くの抽象化を含むコードよりも、自己完結型のコードを好みます。
- オープンソースのライブラリはコミュニティの貢献に依存しているため、貢献しやすいライブラリを構築する必要があります。コードが抽象的であればあるほど、依存関係が増え、読み解くのが難しくなり、貢献しにくくなります。貢献者は、重要な機能が壊れてしまうことを恐れ、非常に抽象度の高いライブラリへの貢献を止めてしまう傾向があります。ライブラリへの貢献が他の基本的なコードを壊すリスクがなければ、潜在的な新規貢献者にとって参加しやすくなるだけでなく、複数の部分へのレビューや並行した貢献もしやすくなります。
Hugging Face では、この設計を単一ファイルポリシーと呼び、あるクラスのコードのほとんどは、単一の自己完結型ファイルに記述されるべきであることを意味します。この哲学について詳しく知るには、このブログ記事をご覧ください。
Diffusersでは、パイプライン、スケジューラ、モデルのすべてにおいて、この哲学を踏襲しています。この慣例より前に登場した一部の古いモデルはそのままの形で維持されていますが、すべての新しいモデルアーキテクチャは、自己完結型の個別ファイルに配置されています。詳細は、以下のModelsセクションを参照してください。
素晴らしいですね。これで、🧨 Diffusers がなぜこのように設計されているのか、その理由が概ねご理解いただけたはずです 🤗。 私たちは、これらの設計原則をライブラリ全体に一貫して適用するように努めています。ただし、この哲学に対する些細な例外や、残念ながら適切とは言えない設計判断が存在する箇所もあります。設計に関するフィードバックをいただければ、GitHub 上で直接お聞かせいただけたら嬉しいです ❤️。
設計思想の詳細
次に、設計思想の詳細な点について説明します。Diffusersでは、モデルとスケジューラを実行可能なワークフローとして構成する2つの方法が提供されています。1つは、1つのパイプラインクラスごとに1つのタスクを処理する単一構造の標準的なパイプラインです。もう1つは、ブロックベースで組み合わせ可能なModular Diffusersです。以下のセクションでは、パイプライン、モジュラーパイプライン、モデル、スケジューラについて順に説明します。
パイプライン
Pipelines(標準型およびモジュラー型)は推論専用です。使いやすさを重視して設計されているため、Simple over easy を 100% には従っていません。読みやすく、自己完結的で、調整が容易であり、models と schedulers の使い方の例として捉えるのが最も適切です。機能は完全ではありません。Diffusers を基盤として機能完全なユーザーインターフェースを構築するには、Modular Diffusers を使用してください。
以下の設計原則が遵守されています:
- Pipelines follow the single-file policy. All pipelines can be found in individual directories under src/diffusers/pipelines. One pipeline folder corresponds to one diffusion paper/project/release. Multiple pipeline files can be gathered in one pipeline folder, as it’s done for
src/diffusers/pipelines/stable-diffusion. If pipelines share similar functionality, one can make use of the # Copied from mechanism. - すべてのPipelinesはDiffusionPipelineを継承しています。
- すべてのパイプラインは、
model_index.jsonファイルにドキュメント化された異なるモデルとスケジューラのコンポーネントで構成されており、パイプラインの属性として同名でアクセスでき、DiffusionPipeline.components関数を用いてパイプライン間で共有できます。 - すべてのパイプラインは、
DiffusionPipeline.from_pretrained関数によって読み込める必要があります。 - すべてのパイプラインには、
__call__メソッド経由で実行するための方法が1つだけ、かつ1つだけ存在する必要があります。__call__の引数の命名は、すべてのパイプライン間で一貫性を持たせる必要があります。 - Pipelines は、解決しようとするタスクにちなんで命名されるべきです。
モジュール型 Diffusers
Modular Diffusers は、標準的なパイプラインの代替として、コンポーザブルなアプローチを提供します。再利用可能なパイプラインブロックからワークフローを構築し、それらを組み合わせ、交換、共有できます。標準的なパイプラインは、モデルとスケジューラーの使用方法を示す緩やかな参考例です。Modular Diffusers は、Diffusers の上に機能完全なユーザーインターフェースを構築するための推奨される方法であり、コミュニティが分散的な形で新しいパイプラインを構築・共有するための基盤です。
以下の設計原則が遵守されています:
- Modular pipelines は単一ファイルポリシーに従います。それぞれは src/diffusers/modular_pipelines/ 配下の独立したフォルダに配置され、そのフォルダ内でワークフローをステージごとに1ファイルずつに分割します:
encoders.py、before_denoise.py、denoise.py、decoders.py。さらに2つのファイルでパイプラインが完成します。modular_blocks_<model>.pyが各ステージを組み立て、modular_pipeline.pyがモデルごとの ModularPipeline サブクラスを定義します。Modular pipelines は相互にインポートしません。 - 各モジュラーパイプラインは、ModularPipelineBlocks の集合として定義されます。リーフブロックはステージファイル(
encoders.py、before_denoise.py、denoise.py、decoders.py)に配置され、modular_blocks_<model>.pyでは SequentialPipelineBlocks や AutoPipelineBlocks といったコンテナクラスを用いて、それらを完全なワークフローに組み立てます。これにより、DiffusionPipeline combinesで混在していた2つの概念が分離されます。ブロックは純粋な定義です。入力、出力、コンポーネントの依存関係を宣言しますが、重みを保持せず、単体では実行できません。.init_pipeline(repo_id)で作成される ModularPipeline は、実行可能な対応物です。ブロックをステートレスかつ重みを持たない状態に保つことで、ワークフロー間で自由に組み合わせ、交換、共有できるようになります。 - 新しいタスクをサポートするには、タスク固有のブロックを作成し、既存のブロックと組み合わせ、トップレベルのブロックアセンブリにある
_workflow_mapにワークフローを登録します。単一のModularPipelineは、テキストから画像への変換、画像から画像への変換、インペインティングなど、複数のワークフローをサポートできますが、DiffusionPipelineは1つのワークフローのみを実行します。
完全な設計と使用ガイドについては、Modular Diffusers ドキュメントを参照してください。
モデル
モデルは、PyTorchのModuleクラスの自然な拡張として設計された、設定可能なツールボックスです。単一ファイルポリシーに従う必要があります。この慣例より前に作成された一部の古いモデルは、そのまま維持されています。これらはレガシーの例外であり、新しいモデルで模範とするべきパターンではありません。例えば、元のUNet2DConditionModelクラスは、複数のUNetのバリエーションに使用されていました。
以下の設計原則が遵守されています:
- 各モデルアーキテクチャのタイプは、src/diffusers/models 配下の個別のフォルダに配置されています。例えば transformers/、autoencoders/、または unets/ などです。各モデルファミリーには、そのフォルダ内に専用のファイルが用意されており、transformer_flux.py や transformer_wan.py などが該当します。
- モデルは単一ファイルポリシーに従います。各モデルファイルは自己完結型であるべきですが、タイムステップ埋め込みや正規化レイヤーなど、すべてのモデルが同一に使用する標準モジュールの少数を除きます。それらは embeddings.py および normalization.py からインポートしてください。
- モデルは、PyTorchの
Moduleクラスと同様に、複雑さを明示的に公開し、明確なエラーメッセージを提供することを意図しています。 - すべてのモデルは
ModelMixinとConfigMixinを継承します。 - モデルは、大幅なコード変更を必要とせず、後方互換性を保ちながら、メモリや計算リソースに大きな改善をもたらす場合に、パフォーマンスの最適化を行うことができます。
- モデルは、デフォルトで最高精度かつ最低パフォーマンスの設定を持つべきです。
- 既存のモデルアーキテクチャに類似した新しいモデルアーキテクチャを統合するには、既存のファイルをコピーして起点とし、それに基づいて適応させます。同一のままのレイヤーには
# Copied fromアノテーションを付与し、make fix-copiesによってそれらが同期されるようにします。
スケジューラー
単一ファイルポリシーを厳密に遵守した個別クラスとして設計されており、読み込み可能な設定ファイルを持つ Scheduler は、推論時のノイズ除去プロセスの制御と、学習時のノイズスケジュールの定義を担当します。
以下の設計原則が遵守されています:
- すべてのスケジューラは、
src/diffusers/schedulersにあります。 - Scheduler は大きな utils ファイルからのインポートが許可されていません。非常に自己完結型に保たれる必要があります。
- 1つのスケジューラ用のPythonファイルは、1つのスケジューラアルゴリズム(論文などで定義されるもの)に対応します。
- スケジューラが類似した機能を持つ場合、
# Copied fromメカニズムを活用できます。 - すべての Scheduler は
SchedulerMixinとConfigMixinを継承しています。 ConfigMixin.from_configメソッドを使用することで、スケジューラーを簡単に交換できます。詳細はこちらを参照してください。- すべてのスケジューラには、
set_num_inference_stepsとstep関数が必要です。set_num_inference_steps(...)は、すべてのノイズ除去プロセスの前に、つまりstep(...)が呼び出される前に呼び出す必要があります。 - すべてのスケジューラは、
timesteps属性を通じて、ループで反復されるべきタイムステップを公開します。これは、モデルが呼び出されるタイムステップの配列です。 step(...)関数は、予測されたモデルの出力と「現在の」サンプル (x_t) を受け取り、「前の」、わずかにノイズ除去されたサンプル (x_t-1) を返します。- 拡散スケジューラーの複雑さから、
step関数はその複雑さをすべて公開しておらず、ある種の「ブラックボックス」になり得ます。 - ほぼすべての場合において、新しいスケジューラーは新しいスケジューリングファイルに実装されるべきです。