Files
sd-scripts/sdxl_train_network.py
Kohya S.andClaude Opus 4.8 b1f32e5820 Refactor for ai agents (#2372)
* refactor(phase0): 不要ファイルの削除と補助スクリプトの整理

- 非推奨スクリプトを削除: train_controlnet.py, sdxl_train_control_net_lllite_old.py,
  networks/merge_lora_old.py, train_textual_inversion_XTI.py
- 不要ディレクトリを削除: bitsandbytes_windows/, wuerstchen/
- XTI_hijack.py を gen_img_diffusers.py にインライン化して削除
- pytest 用 5–6 行スタブテストを削除(test_train.py 等 10 件)
- 開発補助スクリプトを tools/dev/ へ移動: manual_test_anima_*.py, visualize_masks.py

インペインティングテストランナー (run_*_inpainting_test.*) と密結合する
_verify_inpainting_checkpoint.py / generate_inpainting_test_data.py /
download_training_data.py は tests/ に維持。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase1a): library.subset を train_util.py から分離

BaseSubset / DreamBoothSubset / FineTuningSubset / ControlNetSubset の 4 クラスを
新規モジュール library/subset.py に移動。train_util.py 側は後方互換のため
re-export shim を残す。

- library/subset.py: 新規(typing 以外の依存なし)
- library/train_util.py: クラス定義 270 行を削除し re-export に置換
- library/config_util.py: subset クラスの import 元を library.subset に切替

動作仕様の変更なし。train_util.py 経由の既存 import は引き続き動作する。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase1b): library.caching を train_util.py から分離

VAE latent / text encoder output のキャッシュ関連 8 関数を新規モジュール
library/caching.py に移動。train_util.py 側は後方互換のため re-export。

PR-1(dataset 系)の 2 つ目の PR。BaseDataset がこれらの caching 関数を
内部で呼び出すため、後続の dataset.py 抽出(PR-1c)に先立って caching を
独立させ、循環依存を回避する。

- library/caching.py: 新規 8 関数
  - is_disk_cached_latents_is_expected
  - trim_and_resize_if_required
  - load_images_and_masks_for_caching
  - cache_batch_latents
  - cache_batch_text_encoder_outputs
  - cache_batch_text_encoder_outputs_sd3
  - save_text_encoder_outputs_to_disk
  - load_text_encoder_outputs_from_disk
- library/train_util.py: 上記 8 関数の定義を削除し re-export に置換
- library/strategy_base.py: caching.load_images_and_masks_for_caching を直接 import
- finetune/prepare_buckets_latents.py: caching.* を直接 import

train_util.py には BucketManager / load_image / HIGH_VRAM / get_hidden_states_sdxl
が残るため、caching は循環回避のため関数内で `_tu()` 経由で参照する
(PR-1c で dataset.py へ移動した後に整理予定)。

動作仕様の変更なし。両方向の import 順序で動作確認済み。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase1c): library.dataset を train_util.py から分離

データセット骨格となる以下の要素を新規モジュール library/dataset.py に移動。
train_util.py 側は後方互換のため re-export shim を残す。

PR-1(dataset 系)の 3 つ目の PR。約 1,800 行の最大級の抽出。

## 移動した要素(library/dataset.py、約 1,960 行)

定数:
- IMAGE_EXTENSIONS(pillow_avif / jxl の条件付き拡張含む)
- IMAGE_TRANSFORMS
- TEXT_ENCODER_OUTPUTS_CACHE_SUFFIX / _SD3

クラス:
- ImageInfo(画像メタデータ)
- BucketManager(アスペクト比 bucket)
- BucketBatchIndex(NamedTuple)
- AugHelper(color augmentation)
- BaseDataset(DreamBooth/FineTuning/ControlNet 共通の抽象データセット、約 1,260 行)
- DatasetGroup(torch.utils.data.ConcatDataset サブクラス)
- MinimalDataset(ユーザー定義データセット用)

関数:
- split_train_val
- debug_dataset
- glob_images / glob_images_pathlib
- load_arbitrary_dataset
- load_image

## 後方互換

DreamBoothDataset / FineTuningDataset / ControlNetDataset の 3 つは引き続き
train_util.py に残る(PR-1d で移動予定)。BaseDataset を継承する関係で、
train_util 内のサブクラスは re-export 経由で BaseDataset を参照する。

## 循環依存の整理

caching.py を更新:
- _ds() ヘルパーを追加し、library.dataset の BucketManager / load_image /
  IMAGE_TRANSFORMS を遅延参照
- _tu() は HIGH_VRAM / get_hidden_states_sdxl 用に維持
- caching.py 内の重複 IMAGE_TRANSFORMS 定義を削除(library.dataset を単一の
  ソースに)

## 内部参照の切替

- tests/test_validation.py: from library.dataset import split_train_val
- finetune/prepare_buckets_latents.py: dataset.BucketManager / dataset.ImageInfo を直接使用

学習スクリプト(train_*.py、fine_tune.py 等)の train_util.X 属性アクセスは
shim 経由で動作するため、後続 PR で順次切替予定。

## 動作確認

- ✅ library.dataset / caching / train_util / config_util / strategy_base 単独 import
- ✅ 両方向の import 順序
- ✅ 全主要学習スクリプト(fine_tune, train_db, train_network, sdxl/sd3/flux/lumina/anima 系)の py_compile

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* fix(phase1c): dataset.py の glob エイリアス除去

import glob as _glob としていたため、glob_images / glob_images_pathlib 内の
glob.glob(...) / glob.escape(...) 呼び出しが NameError になっていた。
元コードと同じ import glob に戻す。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* fix(phase1c): dataset.py に欠落していた import を追加

抽出時に top-level import が漏れていたため、BaseDataset のメソッドが
実行時 NameError になっていた:

- tqdm: from tqdm import tqdm
- re: import re
- Future / ThreadPoolExecutor: from concurrent.futures import ...

また、HIGH_VRAM の参照を _tu().HIGH_VRAM に修正(HIGH_VRAM は train_util に
mutable global として残存しており、本モジュールには無いため)。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase1d): DreamBooth/FineTuning/ControlNet データセットを分離

PR-1(dataset 系)の最終 PR。BaseDataset を継承する 3 つの具象データセット
クラスを、それぞれ独立したモジュールに移動。

## 新規モジュール

- library/dreambooth_dataset.py: DreamBoothDataset(約 379 行)
- library/finetuning_dataset.py: FineTuningDataset(約 280 行)
- library/controlnet_dataset.py: ControlNetDataset(約 279 行)

## 後方互換

train_util.py 側は約 760 行を削除し、3 クラスを re-export shim で維持:

    from library.dreambooth_dataset import DreamBoothDataset
    from library.finetuning_dataset import FineTuningDataset
    from library.controlnet_dataset import ControlNetDataset

## モジュール間依存

ControlNetDataset は内部で DreamBoothDataset / DreamBoothSubset を委譲先として
利用するため、library.controlnet_dataset → library.dreambooth_dataset の
依存がある。

## PR-1 シリーズ完了

これで train_util.py からのデータセット系分割(PR-1a〜1d)が完了。
- library/subset.py(PR-1a, #2333)
- library/caching.py(PR-1b, #2334)
- library/dataset.py(PR-1c, #2335)
- library/dreambooth_dataset.py / finetuning_dataset.py / controlnet_dataset.py
  (本 PR)

train_util.py は 6,888 行 → 3,774 行に縮小(約 45% 削減)。

## 動作確認

- ✅ 3 つの新規モジュール単独 import
- ✅ 全 PR-1 系列の identity(library.* と train_util re-export が同じクラス)
- ✅ 継承関係(DreamBoothDataset / FineTuningDataset / ControlNetDataset が
  BaseDataset のサブクラス)
- ✅ 全主要学習スクリプト(fine_tune, train_db, train_network, sdxl/sd3/flux/
  lumina/anima 系および各 control_net 系)の py_compile

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase2): 引数定義と設定ヘルパーを library/args.py に分離

train_util.py の add_*_arguments, verify_*, get_sanitized_config_or_none,
read_config_from_file, resume_from_local_or_hf_if_specified の計 12 関数を
library/args.py に移動。train_util.py は re-export shim として維持。
enable_high_vram は accelerator 系(PR-3 で accelerator_setup.py 行き)の
ため train_util.py に残し、verify_training_args からは関数内 import で参照。

train_util.py: 3,774 → 2,600 行 (1,174 行削減)
library/args.py: 1,221 行(新規)

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase3a): hidden states 関連を library/hidden_states.py に分離

train_util.py の get_hidden_states / pool_workaround / get_hidden_states_sdxl
を library/hidden_states.py に移動。train_util.py は re-export shim として維持。
caching.py は循環依存が無いため _tu() 経由ではなく直接 import に変更。

train_util.py: 2,600 → 2,449 行
library/hidden_states.py: 173 行(新規)

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase3b): model I/O・hash・metadata 系を library/model_io.py に分離

train_util.py から以下を library/model_io.py に移動:
- ハッシュ系: model_hash / calculate_sha256 / addnet_hash_legacy /
  addnet_hash_safetensors / precalculate_safetensors_hashes / get_git_revision_hash
- attention 切替: replace_unet_modules
- safetensors metadata: load_metadata_from_safetensors
- ss_* metadata: SS_METADATA_KEY_* 定数 + SS_METADATA_MINIMUM_KEYS +
  build_minimum_network_metadata
- SAI ModelSpec: get_sai_model_spec / get_sai_model_spec_dataclass
- model loader: _load_target_model / load_target_model

計 13 関数 + 7 定数。train_util.py は re-export shim として維持。
networks/lora_interrogator.py が train_util._load_target_model を参照しているため
shim にも _load_target_model を含める。

train_util.py: 2,449 → 2,103 行
library/model_io.py: 421 行(新規)

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase3c): accelerator setup を library/accelerator_setup.py に分離

train_util.py から以下を library/accelerator_setup.py に移動:
- HIGH_VRAM (mutable module-level flag)
- enable_high_vram
- prepare_dataset_args
- prepare_accelerator
- prepare_dtype
- patch_accelerator_for_fp16_training

train_util.py は関数の re-export shim を維持しつつ、HIGH_VRAM 属性アクセスは
module-level __getattr__ で accelerator_setup.HIGH_VRAM へ動的転送する。
これにより train_util.HIGH_VRAM 形式の参照(外部スクリプト互換性)でも
最新値が読み取れる。

リポジトリ内コードは shim に依存しない方針に従い、HIGH_VRAM の参照を
新パス直接参照に切替:
- library/args.py: enable_high_vram の参照
- library/caching.py: _tu().HIGH_VRAM → accelerator_setup.HIGH_VRAM
  (HIGH_VRAM のためだけに残っていた _tu() ヘルパーは削除)
- library/dataset.py: 同上 (_tu() ヘルパー削除)
- library/strategy_{sd,flux,sd3,lumina,anima,hunyuan_image}.py 6 ファイル:
  train_util.HIGH_VRAM → accelerator_setup.HIGH_VRAM

train_util.py: 2,103 → 1,969 行
library/accelerator_setup.py: 186 行(新規)

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase3d): optimizer / scheduler を library/optimizer.py に分離

train_util.py から以下を library/optimizer.py に移動:
- get_optimizer (AdamW / 8bit / Lion / DAdaptation / Prodigy / Adafactor /
  schedule-free / 任意の module.Class 形式に対応する統合ディスパッチャ)
- get_optimizer_train_eval_fn / is_schedulefree_optimizer / get_dummy_scheduler
- get_scheduler_fix (LR scheduler ファクトリ)
- append_lr_to_logs / append_lr_to_logs_with_names

get_my_scheduler は noise scheduler でサンプリング系のため対象外(PR-4 で
sampling.py 行きの予定)。

train_util.py は re-export shim を維持。tests/test_optimizer.py は明示 import
していたため、shim 非依存方針に従い from library.optimizer import に切替。

train_util.py: 1,969 → 1,379 行
library/optimizer.py: 637 行(新規)
累計 6,888 → 1,379 行(80% 削減、PR-3 完了)

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase4a): logging / tracker helpers を library/logging_util.py に分離

train_util.py から init_trackers と LossRecorder を library/logging_util.py
へ切り出し、旧位置は re-export shim に置換。

呼び出し側はすべて train_util.init_trackers / train_util.LossRecorder
の属性アクセスで利用しているため shim 経由で動作し、修正不要。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase4a): 当リポジトリ内の参照を library.logging_util へ切替

train_util.init_trackers / train_util.LossRecorder を logging_util.* で
直接参照する形に変更。15 ファイルで logging_util の import 追加と
属性アクセスの置換を実施。

train_util.py 経由の re-export shim は fork 互換のため引き続き維持。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase4b): loss / noise scheduling helpers を library/loss.py に分離

train_util.py から以下を library/loss.py に移動:
- get_timesteps (バッチ単位の diffusion timestep サンプリング)
- get_noise_noisy_latents_and_timesteps (noise offset / multires noise /
  IP noise 適用後のノイズ付き latents 生成)
- get_huber_threshold_if_needed (Huber/Smooth-L1 用の per-timestep
  threshold スケジュール: exponential / snr / constant)
- conditional_loss (l2 / l1 / huber / smooth_l1 のディスパッチャ)

train_util.py は re-export shim を維持。当リポジトリ内 14 ファイルの呼び出し
を loss_util.* (alias) に切替。loss というローカル変数名と衝突するため
import library.loss as loss_util の形で導入。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase4c): checkpoint 保存系を library/checkpoint_io.py に分離

train_util.py から以下を library/checkpoint_io.py に移動:
- ファイル名テンプレート定数 (EPOCH_FILE_NAME / STEP_STATE_NAME 等 10 個)
- default_if_none / get_epoch_ckpt_name / get_step_ckpt_name /
  get_last_ckpt_name (チェックポイント名ビルダ)
- get_remove_epoch_no / get_remove_step_no
  (--save_last_n_epochs / --save_last_n_steps の rotate 計算)
- save_sd_model_on_epoch_end_or_stepwise(_common) /
  save_sd_model_on_train_end(_common)
  (SD 1.x/2.x checkpoint 保存 + HF Hub upload + rotate)
- save_and_remove_state_on_epoch_end / save_and_remove_state_stepwise /
  save_state_on_train_end (accelerator state 保存 + rotate)

train_util.py は re-export shim を維持。当リポジトリ内 14 トップレベル
スクリプトと library/{anima,flux,leco,lumina,sd3,sdxl}_train_util.py 6 ファイル
の参照を checkpoint_io.* (alias) に切替。

train_util.py: 1,275 → 975 行 (250 行削減)。
library/checkpoint_io.py: 348 行 (新規)。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase4d): sample 画像生成系を library/sampling.py に分離

train_util.py から以下を library/sampling.py に移動 (498 行):
- noise scheduler 定数 (SCHEDULER_LINEAR_START / _END / _TIMESTEPS / SCHEDLER_SCHEDULE)
- get_my_scheduler (--sample_sampler 名 → diffusers Scheduler ファクトリ)
- line_to_prompt_dict / load_prompts (.txt / .toml / .json プロンプト解析)
- sample_images / sample_images_common / sample_image_inference
  (multi-GPU 分配・RNG 退避・inpainting 入力・W&B 画像 upload)

train_util.py は re-export shim を維持。当リポジトリ内 16 トップレベル
+ 5 library/*_train_util.py = 21 ファイルの参照を sampling.* (alias) に切替。

循環依存対策: sampling.py → sdxl_lpw_stable_diffusion → sdxl_train_util →
train_util → sampling のループになるため、SdxlStableDiffusionLongPromptWeightingPipeline
は sample_image_inference 内で遅延 import (isinstance チェックでのみ使用)。

train_util.py: 975 → 555 行 (420 行削減)。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(cleanup): train_util.py の残置 import / コメント遺物を整理

- 旧本体時代の unused top-level imports 80件を削除
- F811 重複定義 2件を解消 (logging / UNet2DConditionModel)
- replace_unet_modules / replace_vae_modules / load_latents_from_disk
  などのコメントアウト遺物を削除
- 各 shim block の noqa を F401, E402 へ更新
- clean_memory_on_device は strategy_lumina.py からの train_util.X
  参照が残るため shim として維持

train_util.py 555 → 295 行 (47% 削減)、ruff check clean、pytest 150 passed

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(cleanup): train_util.py の小ヘルパーと前処理クラスを再配置

- EPSILON / exists / default は参照ゼロのため完全削除(shim も置かない)
- ImageLoadingDataset を finetune/image_loading_dataset.py 新設に移動
  - 参照元 (finetune/prepare_buckets_latents.py / make_captions_by_git.py)
    の 2 箇所を新パス参照に書き換え
  - train_util shim は置かない(参照元が finetune/ 内に閉じているため)
- collator_class を library/dataset.py に移動し train_util から re-export
  - 学習スクリプト 15 ファイルの train_util.collator_class 参照は
    shim 経由でそのまま動作するため書き換え不要

train_util.py 295 → 222 行、pytest 150 passed

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(cleanup): 旧 cache_latents / cache_text_encoder_outputs 系メソッドを削除

学習スクリプト・tools/cache_*.py はすべて new_cache_latents /
new_cache_text_encoder_outputs に切り替わっており、以下の旧 API はどこからも呼ばれていない。

- BaseDataset.cache_latents
- BaseDataset.cache_text_encoder_outputs / _sd3 / _common
- DatasetGroup.cache_latents / cache_text_encoder_outputs / _sd3
- ControlNetDataset.cache_latents

合計 224 行を削除。pytest 全 150 件パス + 実機スモークテスト確認済。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(cleanup): caching.py の連鎖デッド関数と未使用 import を整理

PR #2348 で BaseDataset.cache_text_encoder_outputs_common 等が削除された結果、
それらからのみ呼ばれていた caching.py 内の以下が完全にデッドになったため削除。

- cache_batch_text_encoder_outputs
- cache_batch_text_encoder_outputs_sd3
- save_text_encoder_outputs_to_disk
- load_text_encoder_outputs_from_disk

これに伴う未使用 import の整理:
- library/caching.py: sd3_utils / get_hidden_states_sdxl の import 削除
- library/dataset.py: cache_batch_* / is_disk_cached_latents_is_expected の import 削除、
  古いコメント遺物 1 行削除
- library/{controlnet,dreambooth,finetuning}_dataset.py: caching からの旧 import ブロック
  全削除、TEXT_ENCODER_OUTPUTS_CACHE_SUFFIX* も未使用のため削除
- library/train_util.py: 削除済み関数の re-export 5 行削除(fork 側も new_* に
  移行する必要があるため shim 維持の意義なし)

合計 -121 / +4 行。pytest 全 150 件パス。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(cleanup): caching_mode 系のデッドコードを削除

strategy パターン導入以降、DatasetGroup.set_caching_mode は外部から呼ばれて
おらず、self.caching_mode は永遠に None。連鎖して以下が完全にデッドコード化
していたため削除。

- BaseDataset.set_caching_mode / DatasetGroup.set_caching_mode
- self.caching_mode 属性 (init=None)
- BaseDataset.__getitem__ 内の caching_mode 分岐
- BaseDataset.get_item_for_caching
- BaseDataset.get_input_ids
  - self.tokenizers / self.tokenizer_max_length 属性が BaseDataset で
    一度も代入されておらず、呼ばれた時点で AttributeError になる構造で、
    実行されていないことの裏付けにもなっていた

加えて、上記参照を含むコメントアウト遺物を削除し、MinimalDataset の docstring
内例の self.get_input_ids 参照を strategy ベース (self.tokenize_strategy.tokenize)
に更新。

合計 -154/+1 行。pytest 全 150 件パス。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(cleanup): SdxlNetworkTrainer.get_text_cond のデッドコードを削除

`SdxlNetworkTrainer.get_text_cond` はリポジトリ内のどこからも呼ばれていない
デッドコードとなっていた(SDXL の text encoder 出力取得は
`strategy_sdxl.SdxlTextEncodingStrategy` 経由に完全移行済)。これを削除する。

連鎖的にデッド化する `library.hidden_states.get_hidden_states_sdxl` は、
fork 側で `train_util.get_hidden_states_sdxl` を直接参照している可能性に
配慮し、関数本体・shim ともに残す。代わりに「もはや当リポジトリからは
呼ばれておらず、将来削除候補である」旨をモジュール docstring と関数直前の
コメントに明記した。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase2-1): NetworkTrainer.train() からメタデータ構築を _build_metadata() に分離

- 約 230 行のメタデータ構築ブロック(基本設定 / データセット / モデルハッシュ / minimum_metadata 抽出)を専用メソッドに切り出し
- train() 内のインライン処理を 17 行のメソッド呼び出しに置換
- サブクラスの update_metadata フックは現状の (metadata, args) シグネチャを維持
- 動作仕様は変更なし。dump 比較で session_id / training_started_at 以外の差分がないことを確認

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* fix: split_train_val をインポート

* refactor(phase2-2): step版/epoch版 validation の重複を _run_validation_loop() に統合

- 重複していた validation ブロック (step 版 67 行 + epoch 版 70 行) を共通メソッドに集約
- mode: Literal["step", "epoch"] 引数で tqdm desc / progress postfix / log キー名 / log 送信関数 (step_logging vs epoch_logging) を切替
- on_step_start と args.min/max_timestep 代入の順序を step 版に統一 (元の epoch 版は順序が逆だったが、ベース/全サブクラスの on_step_start は args.min_timestep を参照しないため実質的影響なし)
- epoch 版にあった avr_loss 中間変数は削除し loss_recorder.moving_average を直接利用 (step 版の書き方に揃える)
- 呼出側 (train() 内) は setup (optimizer_eval_fn / network.eval / switch_rng_state) と teardown (restore_rng_state / args 復元 / optimizer_train_fn / network.train / progress_bar.unpause) を保持

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase2-3): save_model/remove_model クロージャを method 化

train_network.py の train() 内にあった save_model / remove_model クロージャを
_save_model / _remove_model メソッドに昇格。設計方針(案 C):
- 不変な state: self._metadata / self._minimum_metadata に格上げ
- 呼び出し局所の値: args / accelerator / save_dtype / ckpt_name 等を kw-only 引数
  で受け取る(_run_validation_loop のシグネチャ流儀を踏襲)

これに伴い _build_metadata() は返り値を廃止し self._metadata / self._minimum_metadata
を直接設定する形に変更。train() 内に残存していた metadata["ss_epoch"] /
metadata["ss_training_finished_at"] のローカル参照も self._metadata[...] に置換。

検証: py_compile OK / AST 未解決名チェック clean / 全サブクラス
(sdxl/flux/sd3/lumina/anima/hunyuan_image) import OK / pytest 150 passed。
動作ロジックは同型変換のため不変。

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(phase4e): train_util shim 経由の内部参照を直 import に切替

リポジトリ内の train_util.* 属性アクセス約 500 箇所 (69 ファイル) を
canonical モジュール (dataset / caching / args / optimizer / model_io /
checkpoint_io / accelerator_setup / hidden_states / loss / sampling /
logging_util / device_utils 等) への直 import に変換。

- モジュール修飾は `import library.X as X`、ローカル変数と衝突する
  モジュールは loss_util / optimizer_util / args_util / dataset_util を使用
- クラス・定数は from-import (例: from library.dataset import DatasetGroup)
- config_util.py の相対 import (from .train_util import ...) も
  dataset / dreambooth_dataset / finetuning_dataset / controlnet_dataset へ分解
- strategy_anima / strategy_flux / strategy_hunyuan_image / strategy_sd3 に
  欠落していた accelerator_setup の import を追加 (HIGH_VRAM 参照の既存バグ修正)
- test_lumina_train_network.py の patch 対象を library.model_io へ更新
- leco_train_util.py の未使用 train_util import を削除

train_util.py の re-export shim は fork 互換のため引き続き維持。
内部からの参照は gen_img_diffusers.py の load_tokenizer (shim に存在しない
既存の壊れた参照) を除きゼロになった。

Co-Authored-By: Claude Fable 5 <[email protected]>

* doc: 01-overview.md をリファクタ後のモジュール構成に更新

train_util.py 分割後の 15 モジュールの所在マップと import 規約
(直 import / エイリアス / shim の位置付け) を AI 向け context に追記。

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix: code-review 指摘のクラッシュ 3 件と import 重複を修正

- finetune/prepare_buckets_latents.py, make_captions_by_git.py:
  `from finetune.image_loading_dataset import ...` は finetune/ が
  パッケージでないため script 実行時に ModuleNotFoundError になる。
  既存の make_captions.py と同じ sys.path.append + 同階層 import に変更
- prepare_buckets_latents.py: モジュールエイリアス dataset がローカル変数
  `dataset = ImageLoadingDataset(...)` に遮蔽され UnboundLocalError に
  なっていた問題を修正 (二重 import を削除し dataset_util に統一)
- prepare_buckets_latents.py: is_disk_cached_latents_is_expected に
  欠けていた第 4 引数 alpha_mask=False を追加 (dev 由来の既存バグ、
  --skip_existing 指定時に TypeError)
- train_network.py: phase4e 変換で二重挿入された import ブロック
  (lines 42-49) と from-import 内の sai_model_spec 重複を削除

検証: 両スクリプトの --help 実行 OK、compileall OK、pytest 150 passed

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix: code-review 指摘 5-7 (doc 残存・stale docstring・未使用 import) を解消

- docs/train_README-ja.md / zh.md: 廃止済み XTI:P+ (train_textual_inversion_XTI.py)
  への言及を削除し、廃止の注記を追加
- library/dataset.py: docstring の「specializations は train_util にあり
  PR-1d で移動予定」という stale な記述を、移動済みの 3 モジュール
  (dreambooth/finetuning/controlnet_dataset) を指す記述に更新
- gen_img.py / sdxl_gen_img.py / hunyuan_image_train_network.py:
  使用箇所ゼロの train_util import を削除 (実使用は sdxl_train_util のみ)。
  リポジトリ全体をスイープし他に未使用 train_util import が無いことを確認

検証: import スモーク OK、pytest 150 passed

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix: code-review 指摘 8 (dataset 系 3 モジュールの未使用 import) を一掃

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix: code-review 指摘 9 (model_io の diffusers/model_util/original_unet を遅延 import 化)

networks/ 配下の軽量 CLI が hashing/metadata ヘルパーのためだけに
model_io を import する際の起動時間を約 2.7 秒短縮。

Co-Authored-By: Claude Fable 5 <[email protected]>

* refactor: code-review 指摘 10 (caching<->dataset 双方向依存を utils への葉モジュール化で解消)

- IMAGE_TRANSFORMS / load_image / get_crop_ltrb / trim_and_resize_if_required
  を library/utils.py へ移動 (dataset.py / caching.py からは再 export で互換維持、
  BucketManager.get_crop_ltrb は委譲 staticmethod として存続)
- caching.py の _ds() 遅延アクセサを撤去し utils 直 import に
- strategy_base.py の関数内 `from library import caching` を top-level に昇格
- utils.py の未使用関数 swap_weight_devices / weighs_to_device を削除
  (custom_offloading_utils.py に使用中の別実装があり、こちらは孤立した古いコピー)

Co-Authored-By: Claude Fable 5 <[email protected]>

* docs: fine-tuning メタデータファイル仕様を新設し、既存 README から誘導

finetuning_dataset.py が読み込む契約 (caption/tags/image_size、json/jsonl
両形式、画像パス解決・キャッシュ関係) を docs/dataset_metadata.md に仕様化。
config_README-en/ja の metadata_file 説明からリンクし、train_README-ja/zh の
陳腐化した「メタデータファイルの作成」節を仕様への誘導に置き換え。

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
2026-06-11 21:30:09 +09:00

184 lines
7.5 KiB
Python

import argparse
from typing import List, Optional, Union
import torch
from accelerate import Accelerator
from library.device_utils import init_ipex, clean_memory_on_device
init_ipex()
from library import sdxl_model_util, sdxl_train_util, strategy_base, strategy_sd, strategy_sdxl
import library.args as args_util
import library.model_io as model_io
from library.dataset import DatasetGroup, MinimalDataset
import train_network
from library.utils import setup_logging
setup_logging()
import logging
logger = logging.getLogger(__name__)
class SdxlNetworkTrainer(train_network.NetworkTrainer):
def __init__(self):
super().__init__()
self.vae_scale_factor = sdxl_model_util.VAE_SCALE_FACTOR
self.is_sdxl = True
def assert_extra_args(
self,
args,
train_dataset_group: Union[DatasetGroup, MinimalDataset],
val_dataset_group: Optional[DatasetGroup],
):
sdxl_train_util.verify_sdxl_training_args(args)
if args.cache_text_encoder_outputs:
assert (
train_dataset_group.is_text_encoder_output_cacheable()
), "when caching Text Encoder output, either caption_dropout_rate, shuffle_caption, token_warmup_step or caption_tag_dropout_rate cannot be used / Text Encoderの出力をキャッシュするときはcaption_dropout_rate, shuffle_caption, token_warmup_step, caption_tag_dropout_rateは使えません"
assert (
args.network_train_unet_only or not args.cache_text_encoder_outputs
), "network for Text Encoder cannot be trained with caching Text Encoder outputs / Text Encoderの出力をキャッシュしながらText Encoderのネットワークを学習することはできません"
train_dataset_group.verify_bucket_reso_steps(32)
if val_dataset_group is not None:
val_dataset_group.verify_bucket_reso_steps(32)
def load_target_model(self, args, weight_dtype, accelerator):
(
load_stable_diffusion_format,
text_encoder1,
text_encoder2,
vae,
unet,
logit_scale,
ckpt_info,
) = sdxl_train_util.load_target_model(args, accelerator, sdxl_model_util.MODEL_VERSION_SDXL_BASE_V1_0, weight_dtype)
self.load_stable_diffusion_format = load_stable_diffusion_format
self.logit_scale = logit_scale
self.ckpt_info = ckpt_info
# モデルに xformers とか memory efficient attention を組み込む
model_io.replace_unet_modules(unet, args.mem_eff_attn, args.xformers, args.sdpa)
if torch.__version__ >= "2.0.0": # PyTorch 2.0.0 以上対応のxformersなら以下が使える
vae.set_use_memory_efficient_attention_xformers(args.xformers)
return sdxl_model_util.MODEL_VERSION_SDXL_BASE_V1_0, [text_encoder1, text_encoder2], vae, unet
def get_tokenize_strategy(self, args):
return strategy_sdxl.SdxlTokenizeStrategy(args.max_token_length, args.tokenizer_cache_dir)
def get_tokenizers(self, tokenize_strategy: strategy_sdxl.SdxlTokenizeStrategy):
return [tokenize_strategy.tokenizer1, tokenize_strategy.tokenizer2]
def get_latents_caching_strategy(self, args):
latents_caching_strategy = strategy_sd.SdSdxlLatentsCachingStrategy(
False, args.cache_latents_to_disk, args.vae_batch_size, args.skip_cache_check
)
return latents_caching_strategy
def get_text_encoding_strategy(self, args):
return strategy_sdxl.SdxlTextEncodingStrategy()
def get_models_for_text_encoding(self, args, accelerator, text_encoders):
return text_encoders + [accelerator.unwrap_model(text_encoders[-1])]
def get_text_encoder_outputs_caching_strategy(self, args):
if args.cache_text_encoder_outputs:
return strategy_sdxl.SdxlTextEncoderOutputsCachingStrategy(
args.cache_text_encoder_outputs_to_disk, None, args.skip_cache_check, is_weighted=args.weighted_captions
)
else:
return None
def cache_text_encoder_outputs_if_needed(
self, args, accelerator: Accelerator, unet, vae, text_encoders, dataset: DatasetGroup, weight_dtype
):
if args.cache_text_encoder_outputs:
if not args.lowram:
# メモリ消費を減らす
logger.info("move vae and unet to cpu to save memory")
org_vae_device = vae.device
org_unet_device = unet.device
vae.to("cpu")
unet.to("cpu")
clean_memory_on_device(accelerator.device)
# When TE is not be trained, it will not be prepared so we need to use explicit autocast
text_encoders[0].to(accelerator.device, dtype=weight_dtype)
text_encoders[1].to(accelerator.device, dtype=weight_dtype)
with accelerator.autocast():
dataset.new_cache_text_encoder_outputs(text_encoders + [accelerator.unwrap_model(text_encoders[-1])], accelerator)
accelerator.wait_for_everyone()
text_encoders[0].to("cpu", dtype=torch.float32) # Text Encoder doesn't work with fp16 on CPU
text_encoders[1].to("cpu", dtype=torch.float32)
clean_memory_on_device(accelerator.device)
if not args.lowram:
logger.info("move vae and unet back to original device")
vae.to(org_vae_device)
unet.to(org_unet_device)
else:
# Text Encoderから毎回出力を取得するので、GPUに乗せておく
text_encoders[0].to(accelerator.device, dtype=weight_dtype)
text_encoders[1].to(accelerator.device, dtype=weight_dtype)
def call_unet(
self,
args,
accelerator,
unet,
noisy_latents,
timesteps,
text_conds,
batch,
weight_dtype,
indices: Optional[List[int]] = None,
):
noisy_latents = noisy_latents.to(weight_dtype) # TODO check why noisy_latents is not weight_dtype
# get size embeddings
orig_size = batch["original_sizes_hw"]
crop_size = batch["crop_top_lefts"]
target_size = batch["target_sizes_hw"]
embs = sdxl_train_util.get_size_embeddings(orig_size, crop_size, target_size, accelerator.device).to(weight_dtype)
# concat embeddings
encoder_hidden_states1, encoder_hidden_states2, pool2 = text_conds
vector_embedding = torch.cat([pool2, embs], dim=1).to(weight_dtype)
text_embedding = torch.cat([encoder_hidden_states1, encoder_hidden_states2], dim=2).to(weight_dtype)
if indices is not None and len(indices) > 0:
noisy_latents = noisy_latents[indices]
timesteps = timesteps[indices]
text_embedding = text_embedding[indices]
vector_embedding = vector_embedding[indices]
noise_pred = unet(noisy_latents, timesteps, text_embedding, vector_embedding)
return noise_pred
def sample_images(self, accelerator, args, epoch, global_step, device, vae, tokenizer, text_encoder, unet):
sdxl_train_util.sample_images(accelerator, args, epoch, global_step, device, vae, tokenizer, text_encoder, unet)
def setup_parser() -> argparse.ArgumentParser:
parser = train_network.setup_parser()
sdxl_train_util.add_sdxl_training_arguments(parser)
return parser
if __name__ == "__main__":
parser = setup_parser()
args = parser.parse_args()
args_util.verify_command_line_training_args(args)
args = args_util.read_config_from_file(args, parser)
trainer = SdxlNetworkTrainer()
trainer.train(args)