mirror of
https://github.com/kohya-ss/sd-scripts.git
synced 2026-04-09 06:45:09 +00:00
Merge branch 'kohya-ss:main' into size-from-weights
This commit is contained in:
14
README-ja.md
14
README-ja.md
@@ -16,13 +16,13 @@ GUIやPowerShellスクリプトなど、より使いやすくする機能が[bma
|
|||||||
|
|
||||||
当リポジトリ内およびnote.comに記事がありますのでそちらをご覧ください(将来的にはすべてこちらへ移すかもしれません)。
|
当リポジトリ内およびnote.comに記事がありますのでそちらをご覧ください(将来的にはすべてこちらへ移すかもしれません)。
|
||||||
|
|
||||||
* [学習について、共通編](./train_README-ja.md) : データ整備やオプションなど
|
* [学習について、共通編](./docs/train_README-ja.md) : データ整備やオプションなど
|
||||||
* [データセット設定](./config_README-ja.md)
|
* [データセット設定](./docs/config_README-ja.md)
|
||||||
* [DreamBoothの学習について](./train_db_README-ja.md)
|
* [DreamBoothの学習について](./docs/train_db_README-ja.md)
|
||||||
* [fine-tuningのガイド](./fine_tune_README_ja.md):
|
* [fine-tuningのガイド](./docs/fine_tune_README_ja.md):
|
||||||
* [LoRAの学習について](./train_network_README-ja.md)
|
* [LoRAの学習について](./docs/train_network_README-ja.md)
|
||||||
* [Textual Inversionの学習について](./train_ti_README-ja.md)
|
* [Textual Inversionの学習について](./docs/train_ti_README-ja.md)
|
||||||
* note.com [画像生成スクリプト](https://note.com/kohya_ss/n/n2693183a798e)
|
* [画像生成スクリプト](./docs/gen_img_README-ja.md)
|
||||||
* note.com [モデル変換スクリプト](https://note.com/kohya_ss/n/n374f316fe4ad)
|
* note.com [モデル変換スクリプト](https://note.com/kohya_ss/n/n374f316fe4ad)
|
||||||
|
|
||||||
## Windowsでの動作に必要なプログラム
|
## Windowsでの動作に必要なプログラム
|
||||||
|
|||||||
66
README.md
66
README.md
@@ -16,7 +16,7 @@ This repository contains the scripts for:
|
|||||||
* Image generation
|
* Image generation
|
||||||
* Model conversion (supports 1.x and 2.x, Stable Diffision ckpt/safetensors and Diffusers)
|
* Model conversion (supports 1.x and 2.x, Stable Diffision ckpt/safetensors and Diffusers)
|
||||||
|
|
||||||
__Stable Diffusion web UI now seems to support LoRA trained by ``sd-scripts``.__ (SD 1.x based only) Thank you for great work!!!
|
__Stable Diffusion web UI now seems to support LoRA trained by ``sd-scripts``.__ Thank you for great work!!!
|
||||||
|
|
||||||
## About requirements.txt
|
## About requirements.txt
|
||||||
|
|
||||||
@@ -28,14 +28,14 @@ The scripts are tested with PyTorch 1.12.1 and 1.13.0, Diffusers 0.10.2.
|
|||||||
|
|
||||||
Most of the documents are written in Japanese.
|
Most of the documents are written in Japanese.
|
||||||
|
|
||||||
* [Training guide - common](./train_README-ja.md) : data preparation, options etc...
|
* [Training guide - common](./docs/train_README-ja.md) : data preparation, options etc...
|
||||||
* [Chinese version](./train_README-zh.md)
|
* [Chinese version](./docs/train_README-zh.md)
|
||||||
* [Dataset config](./config_README-ja.md)
|
* [Dataset config](./docs/config_README-ja.md)
|
||||||
* [DreamBooth training guide](./train_db_README-ja.md)
|
* [DreamBooth training guide](./docs/train_db_README-ja.md)
|
||||||
* [Step by Step fine-tuning guide](./fine_tune_README_ja.md):
|
* [Step by Step fine-tuning guide](./docs/fine_tune_README_ja.md):
|
||||||
* [training LoRA](./train_network_README-ja.md)
|
* [training LoRA](./docs/train_network_README-ja.md)
|
||||||
* [training Textual Inversion](./train_ti_README-ja.md)
|
* [training Textual Inversion](./docs/train_ti_README-ja.md)
|
||||||
* note.com [Image generation](https://note.com/kohya_ss/n/n2693183a798e)
|
* [Image generation](./docs/gen_img_README-ja.md)
|
||||||
* note.com [Model conversion](https://note.com/kohya_ss/n/n374f316fe4ad)
|
* note.com [Model conversion](https://note.com/kohya_ss/n/n374f316fe4ad)
|
||||||
|
|
||||||
## Windows Required Dependencies
|
## Windows Required Dependencies
|
||||||
@@ -138,29 +138,35 @@ The majority of scripts is licensed under ASL 2.0 (including codes from Diffuser
|
|||||||
|
|
||||||
## Change History
|
## Change History
|
||||||
|
|
||||||
### 3 May 2023, 2023/05/03
|
### 7 May 2023, 2023/05/07
|
||||||
|
|
||||||
- When saving v2 models in Diffusers format in training scripts and conversion scripts, it was found that the U-Net configuration is different from those of Hugging Face's stabilityai models (this repository is `"use_linear_projection": false`, stabilityai is `true`). Please note that the weight shapes are different, so please be careful when using the weight files directly. We apologize for the inconvenience.
|
- The documentation has been moved to the `docs` folder. If you have links, please change them.
|
||||||
- Since the U-Net model is created based on the configuration, it should not cause any problems in training or inference.
|
- Removed `gradio` from `requirements.txt`.
|
||||||
- Added `--unet_use_linear_projection` option to `convert_diffusers20_original_sd.py` script. If you specify this option, you can save a Diffusers format model with the same configuration as stabilityai's model from an SD format model (a single `*.safetensors` or `*.ckpt` file). Unfortunately, it is not possible to convert a Diffusers format model to the same format.
|
- DAdaptAdaGrad, DAdaptAdan, and DAdaptSGD are now supported by DAdaptation. [PR#455](https://github.com/kohya-ss/sd-scripts/pull/455) Thanks to sdbds!
|
||||||
|
- DAdaptation needs to be installed. Also, depending on the optimizer, DAdaptation may need to be updated. Please update with `pip install --upgrade dadaptation`.
|
||||||
|
- Added support for pre-calculation of LoRA weights in image generation scripts. Specify `--network_pre_calc`.
|
||||||
|
- The prompt option `--am` is available. Also, it is disabled when Regional LoRA is used.
|
||||||
|
- Added Adaptive noise scale to each training script. Specify a number with `--adaptive_noise_scale` to enable it.
|
||||||
|
- __Experimental option. It may be removed or changed in the future.__
|
||||||
|
- This is an original implementation that automatically adjusts the value of the noise offset according to the absolute value of the mean of each channel of the latents. It is expected that appropriate noise offsets will be set for bright and dark images, respectively.
|
||||||
|
- Specify it together with `--noise_offset`.
|
||||||
|
- The actual value of the noise offset is calculated as `noise_offset + abs(mean(latents, dim=(2,3))) * adaptive_noise_scale`. Since the latent is close to a normal distribution, it may be a good idea to specify a value of about 1/10 to the same as the noise offset.
|
||||||
|
- Negative values can also be specified, in which case the noise offset will be clipped to 0 or more.
|
||||||
|
- Other minor fixes.
|
||||||
|
|
||||||
- Lion8bit optimizer is supported. [PR #447](https://github.com/kohya-ss/sd-scripts/pull/447) Thanks to sdbds!
|
- ドキュメントを`docs`フォルダに移動しました。リンク等を張られている場合は変更をお願いいたします。
|
||||||
- Currently it is optional because you need to update `bitsandbytes` version. See "Optional: Use Lion8bit" in installation instructions to use it.
|
- `requirements.txt`から`gradio`を削除しました。
|
||||||
- Multi-GPU training with DDP is supported in each training script. [PR #448](https://github.com/kohya-ss/sd-scripts/pull/448) Thanks to Isotr0py!
|
- DAdaptationで新しくDAdaptAdaGrad、DAdaptAdan、DAdaptSGDがサポートされました。[PR#455](https://github.com/kohya-ss/sd-scripts/pull/455) sdbds氏に感謝します。
|
||||||
- Multi resolution noise (pyramid noise) is supported in each training script. [PR #471](https://github.com/kohya-ss/sd-scripts/pull/471) Thanks to pamparamm!
|
- dadaptationのインストールが必要です。またオプティマイザによってはdadaptationの更新が必要です。`pip install --upgrade dadaptation`で更新してください。
|
||||||
- See PR and this page [Multi-Resolution Noise for Diffusion Model Training](https://wandb.ai/johnowhitaker/multires_noise/reports/Multi-Resolution-Noise-for-Diffusion-Model-Training--VmlldzozNjYyOTU2) for details.
|
- 画像生成スクリプトでLoRAの重みの事前計算をサポートしました。`--network_pre_calc`を指定してください。
|
||||||
|
- プロンプトオプションの`--am`が利用できます。またRegional LoRA使用時には無効になります。
|
||||||
- 学習スクリプトや変換スクリプトでDiffusers形式でv2モデルを保存するとき、U-Netの設定がHugging Faceのstabilityaiのモデルと異なることがわかりました(当リポジトリでは `"use_linear_projection": false`、stabilityaiは`true`)。重みの形状が異なるため、直接重みファイルを利用する場合にはご注意ください。ご不便をお掛けし申し訳ありません。
|
- 各学習スクリプトにAdaptive noise scaleを追加しました。`--adaptive_noise_scale`で数値を指定すると有効になります。
|
||||||
- U-Netのモデルは設定に基づいて作成されるため、通常、学習や推論で問題になることはないと思われます。
|
- __実験的オプションです。将来的に削除、仕様変更される可能性があります。__
|
||||||
- `convert_diffusers20_original_sd.py`スクリプトに`--unet_use_linear_projection`オプションを追加しました。これを指定するとSD形式のモデル(単一の`*.safetensors`または`*.ckpt`ファイル)から、stabilityaiのモデルと同じ形状の重みファイルを持つDiffusers形式モデルが保存できます。なお、Diffusers形式のモデルを同形式に変換することはできません。
|
- Noise offsetの値を、latentsの各チャネルの平均値の絶対値に応じて自動調整するオプションです。独自の実装で、明るい画像、暗い画像に対してそれぞれ適切なnoise offsetが設定されることが期待されます。
|
||||||
|
- `--noise_offset` と同時に指定してください。
|
||||||
- Lion8bitオプティマイザがサポートされました。[PR #447](https://github.com/kohya-ss/sd-scripts/pull/447) sdbds氏に感謝します。
|
- 実際のNoise offsetの値は `noise_offset + abs(mean(latents, dim=(2,3))) * adaptive_noise_scale` で計算されます。 latentは正規分布に近いためnoise_offsetの1/10~同程度の値を指定するとよいかもしれません。
|
||||||
- `bitsandbytes`のバージョンを更新する必要があるため、現在はオプションです。使用するにはインストール手順の「[オプション:Lion8bitを使う](./README-ja.md#オプションlion8bitを使う)」を参照してください。
|
- 負の値も指定でき、その場合はnoise offsetは0以上にclipされます。
|
||||||
- 各学習スクリプトでDDPによるマルチGPU学習がサポートされました。[PR #448](https://github.com/kohya-ss/sd-scripts/pull/448) Isotr0py氏に感謝します。
|
- その他の細かい修正を行いました。
|
||||||
- Multi resolution noise (pyramid noise) が各学習スクリプトでサポートされました。[PR #471](https://github.com/kohya-ss/sd-scripts/pull/471) pamparamm氏に感謝します。
|
|
||||||
- 詳細はPRおよびこちらのページ [Multi-Resolution Noise for Diffusion Model Training](https://wandb.ai/johnowhitaker/multires_noise/reports/Multi-Resolution-Noise-for-Diffusion-Model-Training--VmlldzozNjYyOTU2) を参照してください。
|
|
||||||
- `--multires_noise_iterations` に数値を指定すると有効になります。`6`~`10`程度の値が良いようです。
|
|
||||||
- `--multires_noise_discount` に`0.1`~`0.3` 程度の値(LoRA学習等比較的データセットが小さい場合のPR作者の推奨)、ないしは`0.8`程度の値(元記事の推奨)を指定してください(デフォルトは `0.3`)。
|
|
||||||
|
|
||||||
Please read [Releases](https://github.com/kohya-ss/sd-scripts/releases) for recent updates.
|
Please read [Releases](https://github.com/kohya-ss/sd-scripts/releases) for recent updates.
|
||||||
最近の更新情報は [Release](https://github.com/kohya-ss/sd-scripts/releases) をご覧ください。
|
最近の更新情報は [Release](https://github.com/kohya-ss/sd-scripts/releases) をご覧ください。
|
||||||
|
|||||||
@@ -153,7 +153,9 @@ python gen_img_diffusers.py --ckpt <モデル名> --outdir <画像出力先>
|
|||||||
|
|
||||||
- `--network_mul`:使用する追加ネットワークの重みを何倍にするかを指定します。デフォルトは`1`です。`--network_mul 0.8`のように指定します。複数のLoRAを使用する場合は`--network_mul 0.4 0.5 0.7`のように指定します。引数の数は`--network_module`で指定した数と同じにしてください。
|
- `--network_mul`:使用する追加ネットワークの重みを何倍にするかを指定します。デフォルトは`1`です。`--network_mul 0.8`のように指定します。複数のLoRAを使用する場合は`--network_mul 0.4 0.5 0.7`のように指定します。引数の数は`--network_module`で指定した数と同じにしてください。
|
||||||
|
|
||||||
- `--network_merge`:使用する追加ネットワークの重みを`--network_mul`に指定した重みであらかじめマージします。プロンプトオプションの`--am`は使用できなくなりますが、LoRA未使用時と同じ程度まで生成が高速化されます。
|
- `--network_merge`:使用する追加ネットワークの重みを`--network_mul`に指定した重みであらかじめマージします。`--network_pre_calc` と同時に使用できません。プロンプトオプションの`--am`、およびRegional LoRAは使用できなくなりますが、LoRA未使用時と同じ程度まで生成が高速化されます。
|
||||||
|
|
||||||
|
- `--network_pre_calc`:使用する追加ネットワークの重みを生成ごとにあらかじめ計算します。プロンプトオプションの`--am`が使用できます。LoRA未使用時と同じ程度まで生成は高速化されますが、生成前に重みを計算する時間が必要で、またメモリ使用量も若干増加します。Regional LoRA使用時は無効になります 。
|
||||||
|
|
||||||
# 主なオプションの指定例
|
# 主なオプションの指定例
|
||||||
|
|
||||||
@@ -463,27 +463,6 @@ masterpiece, best quality, 1boy, in business suit, standing at street, looking b
|
|||||||
|
|
||||||
xformersオプションを指定するとxformersのCrossAttentionを用います。xformersをインストールしていない場合やエラーとなる場合(環境にもよりますが `mixed_precision="no"` の場合など)、代わりに `mem_eff_attn` オプションを指定すると省メモリ版CrossAttentionを使用します(xformersよりも速度は遅くなります)。
|
xformersオプションを指定するとxformersのCrossAttentionを用います。xformersをインストールしていない場合やエラーとなる場合(環境にもよりますが `mixed_precision="no"` の場合など)、代わりに `mem_eff_attn` オプションを指定すると省メモリ版CrossAttentionを使用します(xformersよりも速度は遅くなります)。
|
||||||
|
|
||||||
- `--save_precision`
|
|
||||||
|
|
||||||
保存時のデータ精度を指定します。save_precisionオプションにfloat、fp16、bf16のいずれかを指定すると、その形式でモデルを保存します(DreamBooth、fine tuningでDiffusers形式でモデルを保存する場合は無効です)。モデルのサイズを削減したい場合などにお使いください。
|
|
||||||
|
|
||||||
- `--save_every_n_epochs` / `--save_state` / `--resume`
|
|
||||||
save_every_n_epochsオプションに数値を指定すると、そのエポックごとに学習途中のモデルを保存します。
|
|
||||||
|
|
||||||
save_stateオプションを同時に指定すると、optimizer等の状態も含めた学習状態を合わせて保存します(保存したモデルからも学習再開できますが、それに比べると精度の向上、学習時間の短縮が期待できます)。保存先はフォルダになります。
|
|
||||||
|
|
||||||
学習状態は保存先フォルダに `<output_name>-??????-state`(??????はエポック数)という名前のフォルダで出力されます。長時間にわたる学習時にご利用ください。
|
|
||||||
|
|
||||||
保存された学習状態から学習を再開するにはresumeオプションを使います。学習状態のフォルダ(`output_dir` ではなくその中のstateのフォルダ)を指定してください。
|
|
||||||
|
|
||||||
なおAcceleratorの仕様により、エポック数、global stepは保存されておらず、resumeしたときにも1からになりますがご容赦ください。
|
|
||||||
|
|
||||||
- `--save_model_as` (DreamBooth, fine tuning のみ)
|
|
||||||
|
|
||||||
モデルの保存形式を`ckpt, safetensors, diffusers, diffusers_safetensors` から選べます。
|
|
||||||
|
|
||||||
`--save_model_as=safetensors` のように指定します。Stable Diffusion形式(ckptまたはsafetensors)を読み込み、Diffusers形式で保存する場合、不足する情報はHugging Faceからv1.5またはv2.1の情報を落としてきて補完します。
|
|
||||||
|
|
||||||
- `--clip_skip`
|
- `--clip_skip`
|
||||||
|
|
||||||
`2` を指定すると、Text Encoder (CLIP) の後ろから二番目の層の出力を用います。1またはオプション省略時は最後の層を用います。
|
`2` を指定すると、Text Encoder (CLIP) の後ろから二番目の層の出力を用います。1またはオプション省略時は最後の層を用います。
|
||||||
@@ -502,6 +481,12 @@ masterpiece, best quality, 1boy, in business suit, standing at street, looking b
|
|||||||
|
|
||||||
clip_skipと同様に、モデルの学習状態と異なる長さで学習するには、ある程度の教師データ枚数、長めの学習時間が必要になると思われます。
|
clip_skipと同様に、モデルの学習状態と異なる長さで学習するには、ある程度の教師データ枚数、長めの学習時間が必要になると思われます。
|
||||||
|
|
||||||
|
- `--weighted_captions`
|
||||||
|
|
||||||
|
指定するとAutomatic1111氏のWeb UIと同様の重み付きキャプションが有効になります。「Textual Inversion と XTI」以外の学習に使用できます。キャプションだけでなく DreamBooth 手法の token string でも有効です。
|
||||||
|
|
||||||
|
重みづけキャプションの記法はWeb UIとほぼ同じで、(abc)や[abc]、(abc:1.23)などが使用できます。入れ子も可能です。括弧内にカンマを含めるとプロンプトのshuffle/dropoutで括弧の対応付けがおかしくなるため、括弧内にはカンマを含めないでください。
|
||||||
|
|
||||||
- `--persistent_data_loader_workers`
|
- `--persistent_data_loader_workers`
|
||||||
|
|
||||||
Windows環境で指定するとエポック間の待ち時間が大幅に短縮されます。
|
Windows環境で指定するとエポック間の待ち時間が大幅に短縮されます。
|
||||||
@@ -527,12 +512,28 @@ masterpiece, best quality, 1boy, in business suit, standing at street, looking b
|
|||||||
|
|
||||||
その後ブラウザを開き、http://localhost:6006/ へアクセスすると表示されます。
|
その後ブラウザを開き、http://localhost:6006/ へアクセスすると表示されます。
|
||||||
|
|
||||||
|
- `--log_with` / `--log_tracker_name`
|
||||||
|
|
||||||
|
学習ログの保存に関するオプションです。`tensorboard` だけでなく `wandb`への保存が可能です。詳細は [PR#428](https://github.com/kohya-ss/sd-scripts/pull/428)をご覧ください。
|
||||||
|
|
||||||
- `--noise_offset`
|
- `--noise_offset`
|
||||||
|
|
||||||
こちらの記事の実装になります: https://www.crosslabs.org//blog/diffusion-with-offset-noise
|
こちらの記事の実装になります: https://www.crosslabs.org//blog/diffusion-with-offset-noise
|
||||||
|
|
||||||
全体的に暗い、明るい画像の生成結果が良くなる可能性があるようです。LoRA学習でも有効なようです。`0.1` 程度の値を指定するとよいようです。
|
全体的に暗い、明るい画像の生成結果が良くなる可能性があるようです。LoRA学習でも有効なようです。`0.1` 程度の値を指定するとよいようです。
|
||||||
|
|
||||||
|
- `--adaptive_noise_scale` (実験的オプション)
|
||||||
|
|
||||||
|
Noise offsetの値を、latentsの各チャネルの平均値の絶対値に応じて自動調整するオプションです。`--noise_offset` と同時に指定することで有効になります。Noise offsetの値は `noise_offset + abs(mean(latents, dim=(2,3))) * adaptive_noise_scale` で計算されます。latentは正規分布に近いためnoise_offsetの1/10~同程度の値を指定するとよいかもしれません。
|
||||||
|
|
||||||
|
負の値も指定でき、その場合はnoise offsetは0以上にclipされます。
|
||||||
|
|
||||||
|
- `--multires_noise_iterations` / `--multires_noise_discount`
|
||||||
|
|
||||||
|
Multi resolution noise (pyramid noise)の設定です。詳細は [PR#471](https://github.com/kohya-ss/sd-scripts/pull/471) およびこちらのページ [Multi-Resolution Noise for Diffusion Model Training](https://wandb.ai/johnowhitaker/multires_noise/reports/Multi-Resolution-Noise-for-Diffusion-Model-Training--VmlldzozNjYyOTU2) を参照してください。
|
||||||
|
|
||||||
|
`--multires_noise_iterations` に数値を指定すると有効になります。6~10程度の値が良いようです。`--multires_noise_discount` に0.1~0.3 程度の値(LoRA学習等比較的データセットが小さい場合のPR作者の推奨)、ないしは0.8程度の値(元記事の推奨)を指定してください(デフォルトは 0.3)。
|
||||||
|
|
||||||
- `--debug_dataset`
|
- `--debug_dataset`
|
||||||
|
|
||||||
このオプションを付けることで学習を行う前に事前にどのような画像データ、キャプションで学習されるかを確認できます。Escキーを押すと終了してコマンドラインに戻ります。`S`キーで次のステップ(バッチ)、`E`キーで次のエポックに進みます。
|
このオプションを付けることで学習を行う前に事前にどのような画像データ、キャプションで学習されるかを確認できます。Escキーを押すと終了してコマンドラインに戻ります。`S`キーで次のステップ(バッチ)、`E`キーで次のエポックに進みます。
|
||||||
@@ -545,14 +546,62 @@ masterpiece, best quality, 1boy, in business suit, standing at street, looking b
|
|||||||
|
|
||||||
DreamBoothおよびfine tuningでは、保存されるモデルはこのVAEを組み込んだものになります。
|
DreamBoothおよびfine tuningでは、保存されるモデルはこのVAEを組み込んだものになります。
|
||||||
|
|
||||||
- `--cache_latents`
|
- `--cache_latents` / `--cache_latents_to_disk`
|
||||||
|
|
||||||
使用VRAMを減らすためVAEの出力をメインメモリにキャッシュします。`flip_aug` 以外のaugmentationは使えなくなります。また全体の学習速度が若干速くなります。
|
使用VRAMを減らすためVAEの出力をメインメモリにキャッシュします。`flip_aug` 以外のaugmentationは使えなくなります。また全体の学習速度が若干速くなります。
|
||||||
|
|
||||||
|
cache_latents_to_diskを指定するとキャッシュをディスクに保存します。スクリプトを終了し、再度起動した場合もキャッシュが有効になります。
|
||||||
|
|
||||||
- `--min_snr_gamma`
|
- `--min_snr_gamma`
|
||||||
|
|
||||||
Min-SNR Weighting strategyを指定します。詳細は[こちら](https://github.com/kohya-ss/sd-scripts/pull/308)を参照してください。論文では`5`が推奨されています。
|
Min-SNR Weighting strategyを指定します。詳細は[こちら](https://github.com/kohya-ss/sd-scripts/pull/308)を参照してください。論文では`5`が推奨されています。
|
||||||
|
|
||||||
|
## モデルの保存に関する設定
|
||||||
|
|
||||||
|
- `--save_precision`
|
||||||
|
|
||||||
|
保存時のデータ精度を指定します。save_precisionオプションにfloat、fp16、bf16のいずれかを指定すると、その形式でモデルを保存します(DreamBooth、fine tuningでDiffusers形式でモデルを保存する場合は無効です)。モデルのサイズを削減したい場合などにお使いください。
|
||||||
|
|
||||||
|
- `--save_every_n_epochs` / `--save_state` / `--resume`
|
||||||
|
|
||||||
|
save_every_n_epochsオプションに数値を指定すると、そのエポックごとに学習途中のモデルを保存します。
|
||||||
|
|
||||||
|
save_stateオプションを同時に指定すると、optimizer等の状態も含めた学習状態を合わせて保存します(保存したモデルからも学習再開できますが、それに比べると精度の向上、学習時間の短縮が期待できます)。保存先はフォルダになります。
|
||||||
|
|
||||||
|
学習状態は保存先フォルダに `<output_name>-??????-state`(??????はエポック数)という名前のフォルダで出力されます。長時間にわたる学習時にご利用ください。
|
||||||
|
|
||||||
|
保存された学習状態から学習を再開するにはresumeオプションを使います。学習状態のフォルダ(`output_dir` ではなくその中のstateのフォルダ)を指定してください。
|
||||||
|
|
||||||
|
なおAcceleratorの仕様により、エポック数、global stepは保存されておらず、resumeしたときにも1からになりますがご容赦ください。
|
||||||
|
|
||||||
|
- `--save_every_n_steps`
|
||||||
|
|
||||||
|
save_every_n_stepsオプションに数値を指定すると、そのステップごとに学習途中のモデルを保存します。save_every_n_epochsと同時に指定できます。
|
||||||
|
|
||||||
|
- `--save_model_as` (DreamBooth, fine tuning のみ)
|
||||||
|
|
||||||
|
モデルの保存形式を`ckpt, safetensors, diffusers, diffusers_safetensors` から選べます。
|
||||||
|
|
||||||
|
`--save_model_as=safetensors` のように指定します。Stable Diffusion形式(ckptまたはsafetensors)を読み込み、Diffusers形式で保存する場合、不足する情報はHugging Faceからv1.5またはv2.1の情報を落としてきて補完します。
|
||||||
|
|
||||||
|
- `--huggingface_repo_id` 等
|
||||||
|
|
||||||
|
huggingface_repo_idが指定されているとモデル保存時に同時にHuggingFaceにアップロードします。アクセストークンの取り扱いに注意してください(HuggingFaceのドキュメントを参照してください)。
|
||||||
|
|
||||||
|
他の引数をたとえば以下のように指定してください。
|
||||||
|
|
||||||
|
- `--huggingface_repo_id "your-hf-name/your-model" --huggingface_path_in_repo "path" --huggingface_repo_type model --huggingface_repo_visibility private --huggingface_token hf_YourAccessTokenHere`
|
||||||
|
|
||||||
|
huggingface_repo_visibilityに`public`を指定するとリポジトリが公開されます。省略時または`private`(などpublic以外)を指定すると非公開になります。
|
||||||
|
|
||||||
|
`--save_state`オプション指定時に`--save_state_to_huggingface`を指定するとstateもアップロードします。
|
||||||
|
|
||||||
|
`--resume`オプション指定時に`--resume_from_huggingface`を指定するとHuggingFaceからstateをダウンロードして再開します。その時の --resumeオプションは `--resume {repo_id}/{path_in_repo}:{revision}:{repo_type}`になります。
|
||||||
|
|
||||||
|
例: `--resume_from_huggingface --resume your-hf-name/your-model/path/test-000002-state:main:model`
|
||||||
|
|
||||||
|
`--async_upload`オプションを指定するとアップロードを非同期で行います。
|
||||||
|
|
||||||
## オプティマイザ関係
|
## オプティマイザ関係
|
||||||
|
|
||||||
- `--optimizer_type`
|
- `--optimizer_type`
|
||||||
@@ -566,7 +615,10 @@ masterpiece, best quality, 1boy, in business suit, standing at street, looking b
|
|||||||
- Lion8bit : 引数は同上
|
- Lion8bit : 引数は同上
|
||||||
- SGDNesterov : [torch.optim.SGD](https://pytorch.org/docs/stable/generated/torch.optim.SGD.html), nesterov=True
|
- SGDNesterov : [torch.optim.SGD](https://pytorch.org/docs/stable/generated/torch.optim.SGD.html), nesterov=True
|
||||||
- SGDNesterov8bit : 引数は同上
|
- SGDNesterov8bit : 引数は同上
|
||||||
- DAdaptation : https://github.com/facebookresearch/dadaptation
|
- DAdaptation(DAdaptAdam) : https://github.com/facebookresearch/dadaptation
|
||||||
|
- DAdaptAdaGrad : 引数は同上
|
||||||
|
- DAdaptAdan : 引数は同上
|
||||||
|
- DAdaptSGD : 引数は同上
|
||||||
- AdaFactor : [Transformers AdaFactor](https://huggingface.co/docs/transformers/main_classes/optimizer_schedules)
|
- AdaFactor : [Transformers AdaFactor](https://huggingface.co/docs/transformers/main_classes/optimizer_schedules)
|
||||||
- 任意のオプティマイザ
|
- 任意のオプティマイザ
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ from library.config_util import (
|
|||||||
BlueprintGenerator,
|
BlueprintGenerator,
|
||||||
)
|
)
|
||||||
import library.custom_train_functions as custom_train_functions
|
import library.custom_train_functions as custom_train_functions
|
||||||
from library.custom_train_functions import apply_snr_weight, get_weighted_text_embeddings, pyramid_noise_like
|
from library.custom_train_functions import apply_snr_weight, get_weighted_text_embeddings, pyramid_noise_like, apply_noise_offset
|
||||||
|
|
||||||
|
|
||||||
def train(args):
|
def train(args):
|
||||||
@@ -305,8 +305,7 @@ def train(args):
|
|||||||
# Sample noise that we'll add to the latents
|
# Sample noise that we'll add to the latents
|
||||||
noise = torch.randn_like(latents, device=latents.device)
|
noise = torch.randn_like(latents, device=latents.device)
|
||||||
if args.noise_offset:
|
if args.noise_offset:
|
||||||
# https://www.crosslabs.org//blog/diffusion-with-offset-noise
|
noise = apply_noise_offset(latents, noise, args.noise_offset, args.adaptive_noise_scale)
|
||||||
noise += args.noise_offset * torch.randn((latents.shape[0], latents.shape[1], 1, 1), device=latents.device)
|
|
||||||
elif args.multires_noise_iterations:
|
elif args.multires_noise_iterations:
|
||||||
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
||||||
|
|
||||||
@@ -381,7 +380,7 @@ def train(args):
|
|||||||
current_loss = loss.detach().item() # 平均なのでbatch sizeは関係ないはず
|
current_loss = loss.detach().item() # 平均なのでbatch sizeは関係ないはず
|
||||||
if args.logging_dir is not None:
|
if args.logging_dir is not None:
|
||||||
logs = {"loss": current_loss, "lr": float(lr_scheduler.get_last_lr()[0])}
|
logs = {"loss": current_loss, "lr": float(lr_scheduler.get_last_lr()[0])}
|
||||||
if args.optimizer_type.lower() == "DAdaptation".lower(): # tracking d*lr value
|
if args.optimizer_type.lower().startswith("DAdapt".lower()): # tracking d*lr value
|
||||||
logs["lr/d*lr"] = (
|
logs["lr/d*lr"] = (
|
||||||
lr_scheduler.optimizers[0].param_groups[0]["d"] * lr_scheduler.optimizers[0].param_groups[0]["lr"]
|
lr_scheduler.optimizers[0].param_groups[0]["d"] * lr_scheduler.optimizers[0].param_groups[0]["lr"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2262,6 +2262,8 @@ def main(args):
|
|||||||
if args.network_module:
|
if args.network_module:
|
||||||
networks = []
|
networks = []
|
||||||
network_default_muls = []
|
network_default_muls = []
|
||||||
|
network_pre_calc=args.network_pre_calc
|
||||||
|
|
||||||
for i, network_module in enumerate(args.network_module):
|
for i, network_module in enumerate(args.network_module):
|
||||||
print("import network module:", network_module)
|
print("import network module:", network_module)
|
||||||
imported_module = importlib.import_module(network_module)
|
imported_module = importlib.import_module(network_module)
|
||||||
@@ -2298,11 +2300,11 @@ def main(args):
|
|||||||
if network is None:
|
if network is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
mergiable = hasattr(network, "merge_to")
|
mergeable = network.is_mergeable()
|
||||||
if args.network_merge and not mergiable:
|
if args.network_merge and not mergeable:
|
||||||
print("network is not mergiable. ignore merge option.")
|
print("network is not mergiable. ignore merge option.")
|
||||||
|
|
||||||
if not args.network_merge or not mergiable:
|
if not args.network_merge or not mergeable:
|
||||||
network.apply_to(text_encoder, unet)
|
network.apply_to(text_encoder, unet)
|
||||||
info = network.load_state_dict(weights_sd, False) # network.load_weightsを使うようにするとよい
|
info = network.load_state_dict(weights_sd, False) # network.load_weightsを使うようにするとよい
|
||||||
print(f"weights are loaded: {info}")
|
print(f"weights are loaded: {info}")
|
||||||
@@ -2311,6 +2313,10 @@ def main(args):
|
|||||||
network.to(memory_format=torch.channels_last)
|
network.to(memory_format=torch.channels_last)
|
||||||
network.to(dtype).to(device)
|
network.to(dtype).to(device)
|
||||||
|
|
||||||
|
if network_pre_calc:
|
||||||
|
print("backup original weights")
|
||||||
|
network.backup_weights()
|
||||||
|
|
||||||
networks.append(network)
|
networks.append(network)
|
||||||
else:
|
else:
|
||||||
network.merge_to(text_encoder, unet, weights_sd, dtype, device)
|
network.merge_to(text_encoder, unet, weights_sd, dtype, device)
|
||||||
@@ -2815,12 +2821,20 @@ def main(args):
|
|||||||
|
|
||||||
# generate
|
# generate
|
||||||
if networks:
|
if networks:
|
||||||
|
# 追加ネットワークの処理
|
||||||
shared = {}
|
shared = {}
|
||||||
for n, m in zip(networks, network_muls if network_muls else network_default_muls):
|
for n, m in zip(networks, network_muls if network_muls else network_default_muls):
|
||||||
n.set_multiplier(m)
|
n.set_multiplier(m)
|
||||||
if regional_network:
|
if regional_network:
|
||||||
n.set_current_generation(batch_size, num_sub_prompts, width, height, shared)
|
n.set_current_generation(batch_size, num_sub_prompts, width, height, shared)
|
||||||
|
|
||||||
|
if not regional_network and network_pre_calc:
|
||||||
|
for n in networks:
|
||||||
|
n.restore_weights()
|
||||||
|
for n in networks:
|
||||||
|
n.pre_calculation()
|
||||||
|
print("pre-calculation... done")
|
||||||
|
|
||||||
images = pipe(
|
images = pipe(
|
||||||
prompts,
|
prompts,
|
||||||
negative_prompts,
|
negative_prompts,
|
||||||
@@ -3204,6 +3218,7 @@ def setup_parser() -> argparse.ArgumentParser:
|
|||||||
)
|
)
|
||||||
parser.add_argument("--network_show_meta", action="store_true", help="show metadata of network model / ネットワークモデルのメタデータを表示する")
|
parser.add_argument("--network_show_meta", action="store_true", help="show metadata of network model / ネットワークモデルのメタデータを表示する")
|
||||||
parser.add_argument("--network_merge", action="store_true", help="merge network weights to original model / ネットワークの重みをマージする")
|
parser.add_argument("--network_merge", action="store_true", help="merge network weights to original model / ネットワークの重みをマージする")
|
||||||
|
parser.add_argument("--network_pre_calc", action="store_true", help="pre-calculate network for generation / ネットワークのあらかじめ計算して生成する")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--textual_inversion_embeddings",
|
"--textual_inversion_embeddings",
|
||||||
type=str,
|
type=str,
|
||||||
|
|||||||
@@ -348,10 +348,28 @@ def get_weighted_text_embeddings(
|
|||||||
# https://wandb.ai/johnowhitaker/multires_noise/reports/Multi-Resolution-Noise-for-Diffusion-Model-Training--VmlldzozNjYyOTU2
|
# https://wandb.ai/johnowhitaker/multires_noise/reports/Multi-Resolution-Noise-for-Diffusion-Model-Training--VmlldzozNjYyOTU2
|
||||||
def pyramid_noise_like(noise, device, iterations=6, discount=0.3):
|
def pyramid_noise_like(noise, device, iterations=6, discount=0.3):
|
||||||
b, c, w, h = noise.shape
|
b, c, w, h = noise.shape
|
||||||
u = torch.nn.Upsample(size=(w, h), mode='bilinear').to(device)
|
u = torch.nn.Upsample(size=(w, h), mode="bilinear").to(device)
|
||||||
for i in range(iterations):
|
for i in range(iterations):
|
||||||
r = random.random()*2+2 # Rather than always going 2x,
|
r = random.random() * 2 + 2 # Rather than always going 2x,
|
||||||
w, h = max(1, int(w/(r**i))), max(1, int(h/(r**i)))
|
w, h = max(1, int(w / (r**i))), max(1, int(h / (r**i)))
|
||||||
noise += u(torch.randn(b, c, w, h).to(device)) * discount**i
|
noise += u(torch.randn(b, c, w, h).to(device)) * discount**i
|
||||||
if w==1 or h==1: break # Lowest resolution is 1x1
|
if w == 1 or h == 1:
|
||||||
return noise/noise.std() # Scaled back to roughly unit variance
|
break # Lowest resolution is 1x1
|
||||||
|
return noise / noise.std() # Scaled back to roughly unit variance
|
||||||
|
|
||||||
|
|
||||||
|
# https://www.crosslabs.org//blog/diffusion-with-offset-noise
|
||||||
|
def apply_noise_offset(latents, noise, noise_offset, adaptive_noise_scale):
|
||||||
|
if noise_offset is None:
|
||||||
|
return noise
|
||||||
|
if adaptive_noise_scale is not None:
|
||||||
|
# latent shape: (batch_size, channels, height, width)
|
||||||
|
# abs mean value for each channel
|
||||||
|
latent_mean = torch.abs(latents.mean(dim=(2, 3), keepdim=True))
|
||||||
|
|
||||||
|
# multiply adaptive noise scale to the mean value and add it to the noise offset
|
||||||
|
noise_offset = noise_offset + adaptive_noise_scale * latent_mean
|
||||||
|
noise_offset = torch.clamp(noise_offset, 0.0, None) # in case of adaptive noise scale is negative
|
||||||
|
|
||||||
|
noise = noise + noise_offset * torch.randn((latents.shape[0], latents.shape[1], 1, 1), device=latents.device)
|
||||||
|
return noise
|
||||||
|
|||||||
@@ -1885,7 +1885,7 @@ def add_optimizer_arguments(parser: argparse.ArgumentParser):
|
|||||||
"--optimizer_type",
|
"--optimizer_type",
|
||||||
type=str,
|
type=str,
|
||||||
default="",
|
default="",
|
||||||
help="Optimizer to use / オプティマイザの種類: AdamW (default), AdamW8bit, Lion, Lion8bit,SGDNesterov, SGDNesterov8bit, DAdaptation, AdaFactor",
|
help="Optimizer to use / オプティマイザの種類: AdamW (default), AdamW8bit, Lion8bit, Lion, SGDNesterov, SGDNesterov8bit, DAdaptation(DAdaptAdam), DAdaptAdaGrad, DAdaptAdan, DAdaptSGD, AdaFactor",
|
||||||
)
|
)
|
||||||
|
|
||||||
# backward compatibility
|
# backward compatibility
|
||||||
@@ -2133,6 +2133,12 @@ def add_training_arguments(parser: argparse.ArgumentParser, support_dreambooth:
|
|||||||
default=0.3,
|
default=0.3,
|
||||||
help="set discount value for multires noise (has no effect without --multires_noise_iterations) / Multires noiseのdiscount値を設定する(--multires_noise_iterations指定時のみ有効)",
|
help="set discount value for multires noise (has no effect without --multires_noise_iterations) / Multires noiseのdiscount値を設定する(--multires_noise_iterations指定時のみ有効)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--adaptive_noise_scale",
|
||||||
|
type=float,
|
||||||
|
default=None,
|
||||||
|
help="add `latent mean absolute value * this value` to noise_offset (disabled if None, default) / latentの平均値の絶対値 * この値をnoise_offsetに加算する(Noneの場合は無効、デフォルト)",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--lowram",
|
"--lowram",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -2210,6 +2216,11 @@ def verify_training_args(args: argparse.Namespace):
|
|||||||
"noise_offset and multires_noise_iterations cannot be enabled at the same time / noise_offsetとmultires_noise_iterationsを同時に有効にすることはできません"
|
"noise_offset and multires_noise_iterations cannot be enabled at the same time / noise_offsetとmultires_noise_iterationsを同時に有効にすることはできません"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if args.adaptive_noise_scale is not None and args.noise_offset is None:
|
||||||
|
raise ValueError(
|
||||||
|
"adaptive_noise_scale requires noise_offset / adaptive_noise_scaleを使用するにはnoise_offsetが必要です"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_dataset_arguments(
|
def add_dataset_arguments(
|
||||||
parser: argparse.ArgumentParser, support_dreambooth: bool, support_caption: bool, support_caption_dropout: bool
|
parser: argparse.ArgumentParser, support_dreambooth: bool, support_caption: bool, support_caption_dropout: bool
|
||||||
@@ -2467,7 +2478,7 @@ def resume_from_local_or_hf_if_specified(accelerator, args):
|
|||||||
|
|
||||||
|
|
||||||
def get_optimizer(args, trainable_params):
|
def get_optimizer(args, trainable_params):
|
||||||
# "Optimizer to use: AdamW, AdamW8bit, Lion, Lion8bit, SGDNesterov, SGDNesterov8bit, DAdaptation, Adafactor"
|
# "Optimizer to use: AdamW, AdamW8bit, Lion, SGDNesterov, SGDNesterov8bit, Lion8bit, DAdaptation, DAdaptation(DAdaptAdam), DAdaptAdaGrad, DAdaptAdan, DAdaptSGD, Adafactor"
|
||||||
|
|
||||||
optimizer_type = args.optimizer_type
|
optimizer_type = args.optimizer_type
|
||||||
if args.use_8bit_adam:
|
if args.use_8bit_adam:
|
||||||
@@ -2570,13 +2581,15 @@ def get_optimizer(args, trainable_params):
|
|||||||
optimizer_class = torch.optim.SGD
|
optimizer_class = torch.optim.SGD
|
||||||
optimizer = optimizer_class(trainable_params, lr=lr, nesterov=True, **optimizer_kwargs)
|
optimizer = optimizer_class(trainable_params, lr=lr, nesterov=True, **optimizer_kwargs)
|
||||||
|
|
||||||
elif optimizer_type == "DAdaptation".lower():
|
elif optimizer_type.startswith("DAdapt".lower()):
|
||||||
|
# DAdaptation family
|
||||||
|
# check dadaptation is installed
|
||||||
try:
|
try:
|
||||||
import dadaptation
|
import dadaptation
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError("No dadaptation / dadaptation がインストールされていないようです")
|
raise ImportError("No dadaptation / dadaptation がインストールされていないようです")
|
||||||
print(f"use D-Adaptation Adam optimizer | {optimizer_kwargs}")
|
|
||||||
|
|
||||||
|
# check lr and lr_count, and print warning
|
||||||
actual_lr = lr
|
actual_lr = lr
|
||||||
lr_count = 1
|
lr_count = 1
|
||||||
if type(trainable_params) == list and type(trainable_params[0]) == dict:
|
if type(trainable_params) == list and type(trainable_params[0]) == dict:
|
||||||
@@ -2596,7 +2609,22 @@ def get_optimizer(args, trainable_params):
|
|||||||
f"when multiple learning rates are specified with dadaptation (e.g. for Text Encoder and U-Net), only the first one will take effect / D-Adaptationで複数の学習率を指定した場合(Text EncoderとU-Netなど)、最初の学習率のみが有効になります: lr={actual_lr}"
|
f"when multiple learning rates are specified with dadaptation (e.g. for Text Encoder and U-Net), only the first one will take effect / D-Adaptationで複数の学習率を指定した場合(Text EncoderとU-Netなど)、最初の学習率のみが有効になります: lr={actual_lr}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# set optimizer
|
||||||
|
if optimizer_type == "DAdaptation".lower() or optimizer_type == "DAdaptAdam".lower():
|
||||||
optimizer_class = dadaptation.DAdaptAdam
|
optimizer_class = dadaptation.DAdaptAdam
|
||||||
|
print(f"use D-Adaptation Adam optimizer | {optimizer_kwargs}")
|
||||||
|
elif optimizer_type == "DAdaptAdaGrad".lower():
|
||||||
|
optimizer_class = dadaptation.DAdaptAdaGrad
|
||||||
|
print(f"use D-Adaptation AdaGrad optimizer | {optimizer_kwargs}")
|
||||||
|
elif optimizer_type == "DAdaptAdan".lower():
|
||||||
|
optimizer_class = dadaptation.DAdaptAdan
|
||||||
|
print(f"use D-Adaptation Adan optimizer | {optimizer_kwargs}")
|
||||||
|
elif optimizer_type == "DAdaptSGD".lower():
|
||||||
|
optimizer_class = dadaptation.DAdaptSGD
|
||||||
|
print(f"use D-Adaptation SGD optimizer | {optimizer_kwargs}")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown optimizer type: {optimizer_type}")
|
||||||
|
|
||||||
optimizer = optimizer_class(trainable_params, lr=lr, **optimizer_kwargs)
|
optimizer = optimizer_class(trainable_params, lr=lr, **optimizer_kwargs)
|
||||||
|
|
||||||
elif optimizer_type == "Adafactor".lower():
|
elif optimizer_type == "Adafactor".lower():
|
||||||
@@ -3327,7 +3355,7 @@ def sample_images(
|
|||||||
os.makedirs(save_dir, exist_ok=True)
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
|
|
||||||
rng_state = torch.get_rng_state()
|
rng_state = torch.get_rng_state()
|
||||||
cuda_rng_state = torch.cuda.get_rng_state()
|
cuda_rng_state = torch.cuda.get_rng_state() if torch.cuda.is_available() else None
|
||||||
|
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
with accelerator.autocast():
|
with accelerator.autocast():
|
||||||
@@ -3434,6 +3462,7 @@ def sample_images(
|
|||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
|
|
||||||
torch.set_rng_state(rng_state)
|
torch.set_rng_state(rng_state)
|
||||||
|
if cuda_rng_state is not None:
|
||||||
torch.cuda.set_rng_state(cuda_rng_state)
|
torch.cuda.set_rng_state(cuda_rng_state)
|
||||||
vae.to(org_vae_device)
|
vae.to(org_vae_device)
|
||||||
|
|
||||||
|
|||||||
133
networks/lora.py
133
networks/lora.py
@@ -66,6 +66,39 @@ class LoRAModule(torch.nn.Module):
|
|||||||
self.org_module.forward = self.forward
|
self.org_module.forward = self.forward
|
||||||
del self.org_module
|
del self.org_module
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return self.org_forward(x) + self.lora_up(self.lora_down(x)) * self.multiplier * self.scale
|
||||||
|
|
||||||
|
|
||||||
|
class LoRAInfModule(LoRAModule):
|
||||||
|
def __init__(self, lora_name, org_module: torch.nn.Module, multiplier=1.0, lora_dim=4, alpha=1):
|
||||||
|
super().__init__(lora_name, org_module, multiplier, lora_dim, alpha)
|
||||||
|
|
||||||
|
self.org_module_ref = [org_module] # 後から参照できるように
|
||||||
|
self.enabled = True
|
||||||
|
|
||||||
|
# check regional or not by lora_name
|
||||||
|
self.text_encoder = False
|
||||||
|
if lora_name.startswith("lora_te_"):
|
||||||
|
self.regional = False
|
||||||
|
self.use_sub_prompt = True
|
||||||
|
self.text_encoder = True
|
||||||
|
elif "attn2_to_k" in lora_name or "attn2_to_v" in lora_name:
|
||||||
|
self.regional = False
|
||||||
|
self.use_sub_prompt = True
|
||||||
|
elif "time_emb" in lora_name:
|
||||||
|
self.regional = False
|
||||||
|
self.use_sub_prompt = False
|
||||||
|
else:
|
||||||
|
self.regional = True
|
||||||
|
self.use_sub_prompt = False
|
||||||
|
|
||||||
|
self.network: LoRANetwork = None
|
||||||
|
|
||||||
|
def set_network(self, network):
|
||||||
|
self.network = network
|
||||||
|
|
||||||
|
# freezeしてマージする
|
||||||
def merge_to(self, sd, dtype, device):
|
def merge_to(self, sd, dtype, device):
|
||||||
# get up/down weight
|
# get up/down weight
|
||||||
up_weight = sd["lora_up.weight"].to(torch.float).to(device)
|
up_weight = sd["lora_up.weight"].to(torch.float).to(device)
|
||||||
@@ -97,44 +130,45 @@ class LoRAModule(torch.nn.Module):
|
|||||||
org_sd["weight"] = weight.to(dtype)
|
org_sd["weight"] = weight.to(dtype)
|
||||||
self.org_module.load_state_dict(org_sd)
|
self.org_module.load_state_dict(org_sd)
|
||||||
|
|
||||||
|
# 復元できるマージのため、このモジュールのweightを返す
|
||||||
|
def get_weight(self, multiplier=None):
|
||||||
|
if multiplier is None:
|
||||||
|
multiplier = self.multiplier
|
||||||
|
|
||||||
|
# get up/down weight from module
|
||||||
|
up_weight = self.lora_up.weight.to(torch.float)
|
||||||
|
down_weight = self.lora_down.weight.to(torch.float)
|
||||||
|
|
||||||
|
# pre-calculated weight
|
||||||
|
if len(down_weight.size()) == 2:
|
||||||
|
# linear
|
||||||
|
weight = self.multiplier * (up_weight @ down_weight) * self.scale
|
||||||
|
elif down_weight.size()[2:4] == (1, 1):
|
||||||
|
# conv2d 1x1
|
||||||
|
weight = (
|
||||||
|
self.multiplier
|
||||||
|
* (up_weight.squeeze(3).squeeze(2) @ down_weight.squeeze(3).squeeze(2)).unsqueeze(2).unsqueeze(3)
|
||||||
|
* self.scale
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# conv2d 3x3
|
||||||
|
conved = torch.nn.functional.conv2d(down_weight.permute(1, 0, 2, 3), up_weight).permute(1, 0, 2, 3)
|
||||||
|
weight = self.multiplier * conved * self.scale
|
||||||
|
|
||||||
|
return weight
|
||||||
|
|
||||||
def set_region(self, region):
|
def set_region(self, region):
|
||||||
self.region = region
|
self.region = region
|
||||||
self.region_mask = None
|
self.region_mask = None
|
||||||
|
|
||||||
def forward(self, x):
|
|
||||||
return self.org_forward(x) + self.lora_up(self.lora_down(x)) * self.multiplier * self.scale
|
|
||||||
|
|
||||||
|
|
||||||
class LoRAInfModule(LoRAModule):
|
|
||||||
def __init__(self, lora_name, org_module: torch.nn.Module, multiplier=1.0, lora_dim=4, alpha=1):
|
|
||||||
super().__init__(lora_name, org_module, multiplier, lora_dim, alpha)
|
|
||||||
|
|
||||||
# check regional or not by lora_name
|
|
||||||
self.text_encoder = False
|
|
||||||
if lora_name.startswith("lora_te_"):
|
|
||||||
self.regional = False
|
|
||||||
self.use_sub_prompt = True
|
|
||||||
self.text_encoder = True
|
|
||||||
elif "attn2_to_k" in lora_name or "attn2_to_v" in lora_name:
|
|
||||||
self.regional = False
|
|
||||||
self.use_sub_prompt = True
|
|
||||||
elif "time_emb" in lora_name:
|
|
||||||
self.regional = False
|
|
||||||
self.use_sub_prompt = False
|
|
||||||
else:
|
|
||||||
self.regional = True
|
|
||||||
self.use_sub_prompt = False
|
|
||||||
|
|
||||||
self.network: LoRANetwork = None
|
|
||||||
|
|
||||||
def set_network(self, network):
|
|
||||||
self.network = network
|
|
||||||
|
|
||||||
def default_forward(self, x):
|
def default_forward(self, x):
|
||||||
# print("default_forward", self.lora_name, x.size())
|
# print("default_forward", self.lora_name, x.size())
|
||||||
return self.org_forward(x) + self.lora_up(self.lora_down(x)) * self.multiplier * self.scale
|
return self.org_forward(x) + self.lora_up(self.lora_down(x)) * self.multiplier * self.scale
|
||||||
|
|
||||||
def forward(self, x):
|
def forward(self, x):
|
||||||
|
if not self.enabled:
|
||||||
|
return self.org_forward(x)
|
||||||
|
|
||||||
if self.network is None or self.network.sub_prompt_index is None:
|
if self.network is None or self.network.sub_prompt_index is None:
|
||||||
return self.default_forward(x)
|
return self.default_forward(x)
|
||||||
if not self.regional and not self.use_sub_prompt:
|
if not self.regional and not self.use_sub_prompt:
|
||||||
@@ -769,6 +803,10 @@ class LoRANetwork(torch.nn.Module):
|
|||||||
lora.apply_to()
|
lora.apply_to()
|
||||||
self.add_module(lora.lora_name, lora)
|
self.add_module(lora.lora_name, lora)
|
||||||
|
|
||||||
|
# マージできるかどうかを返す
|
||||||
|
def is_mergeable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
# TODO refactor to common function with apply_to
|
# TODO refactor to common function with apply_to
|
||||||
def merge_to(self, text_encoder, unet, weights_sd, dtype, device):
|
def merge_to(self, text_encoder, unet, weights_sd, dtype, device):
|
||||||
apply_text_encoder = apply_unet = False
|
apply_text_encoder = apply_unet = False
|
||||||
@@ -955,3 +993,40 @@ class LoRANetwork(torch.nn.Module):
|
|||||||
w = (w + 1) // 2
|
w = (w + 1) // 2
|
||||||
|
|
||||||
self.mask_dic = mask_dic
|
self.mask_dic = mask_dic
|
||||||
|
|
||||||
|
def backup_weights(self):
|
||||||
|
# 重みのバックアップを行う
|
||||||
|
loras: List[LoRAInfModule] = self.text_encoder_loras + self.unet_loras
|
||||||
|
for lora in loras:
|
||||||
|
org_module = lora.org_module_ref[0]
|
||||||
|
if not hasattr(org_module, "_lora_org_weight"):
|
||||||
|
sd = org_module.state_dict()
|
||||||
|
org_module._lora_org_weight = sd["weight"].detach().clone()
|
||||||
|
org_module._lora_restored = True
|
||||||
|
|
||||||
|
def restore_weights(self):
|
||||||
|
# 重みのリストアを行う
|
||||||
|
loras: List[LoRAInfModule] = self.text_encoder_loras + self.unet_loras
|
||||||
|
for lora in loras:
|
||||||
|
org_module = lora.org_module_ref[0]
|
||||||
|
if not org_module._lora_restored:
|
||||||
|
sd = org_module.state_dict()
|
||||||
|
sd["weight"] = org_module._lora_org_weight
|
||||||
|
org_module.load_state_dict(sd)
|
||||||
|
org_module._lora_restored = True
|
||||||
|
|
||||||
|
def pre_calculation(self):
|
||||||
|
# 事前計算を行う
|
||||||
|
loras: List[LoRAInfModule] = self.text_encoder_loras + self.unet_loras
|
||||||
|
for lora in loras:
|
||||||
|
org_module = lora.org_module_ref[0]
|
||||||
|
sd = org_module.state_dict()
|
||||||
|
|
||||||
|
org_weight = sd["weight"]
|
||||||
|
lora_weight = lora.get_weight().to(org_weight.device, dtype=org_weight.dtype)
|
||||||
|
sd["weight"] = org_weight + lora_weight
|
||||||
|
assert sd["weight"].shape == org_weight.shape
|
||||||
|
org_module.load_state_dict(sd)
|
||||||
|
|
||||||
|
org_module._lora_restored = False
|
||||||
|
lora.enabled = False
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pytorch-lightning==1.9.0
|
|||||||
bitsandbytes==0.35.0
|
bitsandbytes==0.35.0
|
||||||
tensorboard==2.10.1
|
tensorboard==2.10.1
|
||||||
safetensors==0.2.6
|
safetensors==0.2.6
|
||||||
gradio==3.16.2
|
# gradio==3.16.2
|
||||||
altair==4.2.2
|
altair==4.2.2
|
||||||
easygui==0.98.3
|
easygui==0.98.3
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from library.config_util import (
|
|||||||
BlueprintGenerator,
|
BlueprintGenerator,
|
||||||
)
|
)
|
||||||
import library.custom_train_functions as custom_train_functions
|
import library.custom_train_functions as custom_train_functions
|
||||||
from library.custom_train_functions import apply_snr_weight, get_weighted_text_embeddings, pyramid_noise_like
|
from library.custom_train_functions import apply_snr_weight, get_weighted_text_embeddings, pyramid_noise_like, apply_noise_offset
|
||||||
|
|
||||||
|
|
||||||
def train(args):
|
def train(args):
|
||||||
@@ -271,8 +271,7 @@ def train(args):
|
|||||||
# Sample noise that we'll add to the latents
|
# Sample noise that we'll add to the latents
|
||||||
noise = torch.randn_like(latents, device=latents.device)
|
noise = torch.randn_like(latents, device=latents.device)
|
||||||
if args.noise_offset:
|
if args.noise_offset:
|
||||||
# https://www.crosslabs.org//blog/diffusion-with-offset-noise
|
noise = apply_noise_offset(latents, noise, args.noise_offset, args.adaptive_noise_scale)
|
||||||
noise += args.noise_offset * torch.randn((latents.shape[0], latents.shape[1], 1, 1), device=latents.device)
|
|
||||||
elif args.multires_noise_iterations:
|
elif args.multires_noise_iterations:
|
||||||
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
||||||
|
|
||||||
@@ -367,7 +366,7 @@ def train(args):
|
|||||||
current_loss = loss.detach().item()
|
current_loss = loss.detach().item()
|
||||||
if args.logging_dir is not None:
|
if args.logging_dir is not None:
|
||||||
logs = {"loss": current_loss, "lr": float(lr_scheduler.get_last_lr()[0])}
|
logs = {"loss": current_loss, "lr": float(lr_scheduler.get_last_lr()[0])}
|
||||||
if args.optimizer_type.lower() == "DAdaptation".lower(): # tracking d*lr value
|
if args.optimizer_type.lower().startswith("DAdapt".lower()): # tracking d*lr value
|
||||||
logs["lr/d*lr"] = (
|
logs["lr/d*lr"] = (
|
||||||
lr_scheduler.optimizers[0].param_groups[0]["d"] * lr_scheduler.optimizers[0].param_groups[0]["lr"]
|
lr_scheduler.optimizers[0].param_groups[0]["d"] * lr_scheduler.optimizers[0].param_groups[0]["lr"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from library.config_util import (
|
|||||||
)
|
)
|
||||||
import library.huggingface_util as huggingface_util
|
import library.huggingface_util as huggingface_util
|
||||||
import library.custom_train_functions as custom_train_functions
|
import library.custom_train_functions as custom_train_functions
|
||||||
from library.custom_train_functions import apply_snr_weight, get_weighted_text_embeddings, pyramid_noise_like
|
from library.custom_train_functions import apply_snr_weight, get_weighted_text_embeddings, pyramid_noise_like, apply_noise_offset
|
||||||
|
|
||||||
|
|
||||||
# TODO 他のスクリプトと共通化する
|
# TODO 他のスクリプトと共通化する
|
||||||
@@ -43,7 +43,7 @@ def generate_step_logs(args: argparse.Namespace, current_loss, avr_loss, lr_sche
|
|||||||
logs["lr/textencoder"] = float(lrs[0])
|
logs["lr/textencoder"] = float(lrs[0])
|
||||||
logs["lr/unet"] = float(lrs[-1]) # may be same to textencoder
|
logs["lr/unet"] = float(lrs[-1]) # may be same to textencoder
|
||||||
|
|
||||||
if args.optimizer_type.lower() == "DAdaptation".lower(): # tracking d*lr value of unet.
|
if args.optimizer_type.lower().startswith("DAdapt".lower()): # tracking d*lr value of unet.
|
||||||
logs["lr/d*lr"] = lr_scheduler.optimizers[-1].param_groups[0]["d"] * lr_scheduler.optimizers[-1].param_groups[0]["lr"]
|
logs["lr/d*lr"] = lr_scheduler.optimizers[-1].param_groups[0]["d"] * lr_scheduler.optimizers[-1].param_groups[0]["lr"]
|
||||||
else:
|
else:
|
||||||
idx = 0
|
idx = 0
|
||||||
@@ -53,7 +53,7 @@ def generate_step_logs(args: argparse.Namespace, current_loss, avr_loss, lr_sche
|
|||||||
|
|
||||||
for i in range(idx, len(lrs)):
|
for i in range(idx, len(lrs)):
|
||||||
logs[f"lr/group{i}"] = float(lrs[i])
|
logs[f"lr/group{i}"] = float(lrs[i])
|
||||||
if args.optimizer_type.lower() == "DAdaptation".lower():
|
if args.optimizer_type.lower().startswith("DAdapt".lower()):
|
||||||
logs[f"lr/d*lr/group{i}"] = (
|
logs[f"lr/d*lr/group{i}"] = (
|
||||||
lr_scheduler.optimizers[-1].param_groups[i]["d"] * lr_scheduler.optimizers[-1].param_groups[i]["lr"]
|
lr_scheduler.optimizers[-1].param_groups[i]["d"] * lr_scheduler.optimizers[-1].param_groups[i]["lr"]
|
||||||
)
|
)
|
||||||
@@ -610,11 +610,11 @@ def train(args):
|
|||||||
else:
|
else:
|
||||||
input_ids = batch["input_ids"].to(accelerator.device)
|
input_ids = batch["input_ids"].to(accelerator.device)
|
||||||
encoder_hidden_states = train_util.get_hidden_states(args, input_ids, tokenizer, text_encoder, weight_dtype)
|
encoder_hidden_states = train_util.get_hidden_states(args, input_ids, tokenizer, text_encoder, weight_dtype)
|
||||||
|
|
||||||
# Sample noise that we'll add to the latents
|
# Sample noise that we'll add to the latents
|
||||||
noise = torch.randn_like(latents, device=latents.device)
|
noise = torch.randn_like(latents, device=latents.device)
|
||||||
if args.noise_offset:
|
if args.noise_offset:
|
||||||
# https://www.crosslabs.org//blog/diffusion-with-offset-noise
|
noise = apply_noise_offset(latents, noise, args.noise_offset, args.adaptive_noise_scale)
|
||||||
noise += args.noise_offset * torch.randn((latents.shape[0], latents.shape[1], 1, 1), device=latents.device)
|
|
||||||
elif args.multires_noise_iterations:
|
elif args.multires_noise_iterations:
|
||||||
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from library.config_util import (
|
|||||||
BlueprintGenerator,
|
BlueprintGenerator,
|
||||||
)
|
)
|
||||||
import library.custom_train_functions as custom_train_functions
|
import library.custom_train_functions as custom_train_functions
|
||||||
from library.custom_train_functions import apply_snr_weight, pyramid_noise_like
|
from library.custom_train_functions import apply_snr_weight, pyramid_noise_like, apply_noise_offset
|
||||||
|
|
||||||
imagenet_templates_small = [
|
imagenet_templates_small = [
|
||||||
"a photo of a {}",
|
"a photo of a {}",
|
||||||
@@ -387,8 +387,7 @@ def train(args):
|
|||||||
# Sample noise that we'll add to the latents
|
# Sample noise that we'll add to the latents
|
||||||
noise = torch.randn_like(latents, device=latents.device)
|
noise = torch.randn_like(latents, device=latents.device)
|
||||||
if args.noise_offset:
|
if args.noise_offset:
|
||||||
# https://www.crosslabs.org//blog/diffusion-with-offset-noise
|
noise = apply_noise_offset(latents, noise, args.noise_offset, args.adaptive_noise_scale)
|
||||||
noise += args.noise_offset * torch.randn((latents.shape[0], latents.shape[1], 1, 1), device=latents.device)
|
|
||||||
elif args.multires_noise_iterations:
|
elif args.multires_noise_iterations:
|
||||||
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
||||||
|
|
||||||
@@ -465,7 +464,7 @@ def train(args):
|
|||||||
current_loss = loss.detach().item()
|
current_loss = loss.detach().item()
|
||||||
if args.logging_dir is not None:
|
if args.logging_dir is not None:
|
||||||
logs = {"loss": current_loss, "lr": float(lr_scheduler.get_last_lr()[0])}
|
logs = {"loss": current_loss, "lr": float(lr_scheduler.get_last_lr()[0])}
|
||||||
if args.optimizer_type.lower() == "DAdaptation".lower(): # tracking d*lr value
|
if args.optimizer_type.lower().startswith("DAdapt".lower()): # tracking d*lr value
|
||||||
logs["lr/d*lr"] = (
|
logs["lr/d*lr"] = (
|
||||||
lr_scheduler.optimizers[0].param_groups[0]["d"] * lr_scheduler.optimizers[0].param_groups[0]["lr"]
|
lr_scheduler.optimizers[0].param_groups[0]["d"] * lr_scheduler.optimizers[0].param_groups[0]["lr"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from library.config_util import (
|
|||||||
BlueprintGenerator,
|
BlueprintGenerator,
|
||||||
)
|
)
|
||||||
import library.custom_train_functions as custom_train_functions
|
import library.custom_train_functions as custom_train_functions
|
||||||
from library.custom_train_functions import apply_snr_weight, pyramid_noise_like
|
from library.custom_train_functions import apply_snr_weight, pyramid_noise_like, apply_noise_offset
|
||||||
from XTI_hijack import unet_forward_XTI, downblock_forward_XTI, upblock_forward_XTI
|
from XTI_hijack import unet_forward_XTI, downblock_forward_XTI, upblock_forward_XTI
|
||||||
|
|
||||||
imagenet_templates_small = [
|
imagenet_templates_small = [
|
||||||
@@ -426,8 +426,7 @@ def train(args):
|
|||||||
# Sample noise that we'll add to the latents
|
# Sample noise that we'll add to the latents
|
||||||
noise = torch.randn_like(latents, device=latents.device)
|
noise = torch.randn_like(latents, device=latents.device)
|
||||||
if args.noise_offset:
|
if args.noise_offset:
|
||||||
# https://www.crosslabs.org//blog/diffusion-with-offset-noise
|
noise = apply_noise_offset(latents, noise, args.noise_offset, args.adaptive_noise_scale)
|
||||||
noise += args.noise_offset * torch.randn((latents.shape[0], latents.shape[1], 1, 1), device=latents.device)
|
|
||||||
elif args.multires_noise_iterations:
|
elif args.multires_noise_iterations:
|
||||||
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
noise = pyramid_noise_like(noise, latents.device, args.multires_noise_iterations, args.multires_noise_discount)
|
||||||
|
|
||||||
@@ -504,7 +503,7 @@ def train(args):
|
|||||||
current_loss = loss.detach().item()
|
current_loss = loss.detach().item()
|
||||||
if args.logging_dir is not None:
|
if args.logging_dir is not None:
|
||||||
logs = {"loss": current_loss, "lr": float(lr_scheduler.get_last_lr()[0])}
|
logs = {"loss": current_loss, "lr": float(lr_scheduler.get_last_lr()[0])}
|
||||||
if args.optimizer_type.lower() == "DAdaptation".lower(): # tracking d*lr value
|
if args.optimizer_type.lower().startswith("DAdapt".lower()): # tracking d*lr value
|
||||||
logs["lr/d*lr"] = (
|
logs["lr/d*lr"] = (
|
||||||
lr_scheduler.optimizers[0].param_groups[0]["d"] * lr_scheduler.optimizers[0].param_groups[0]["lr"]
|
lr_scheduler.optimizers[0].param_groups[0]["d"] * lr_scheduler.optimizers[0].param_groups[0]["lr"]
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user