| |
|
|
| import argparse |
| import math |
| from multiprocessing import Value |
| import os |
|
|
| from accelerate.utils import set_seed |
| import torch |
| from tqdm import tqdm |
|
|
| from library import config_util |
| from library import train_util |
| from library import sdxl_train_util |
| from library.config_util import ( |
| ConfigSanitizer, |
| BlueprintGenerator, |
| ) |
| from library.utils import setup_logging, add_logging_arguments |
| setup_logging() |
| import logging |
| logger = logging.getLogger(__name__) |
|
|
| def cache_to_disk(args: argparse.Namespace) -> None: |
| setup_logging(args, reset=True) |
| train_util.prepare_dataset_args(args, True) |
|
|
| |
| assert ( |
| args.cache_text_encoder_outputs_to_disk |
| ), "cache_text_encoder_outputs_to_disk must be True / cache_text_encoder_outputs_to_diskはTrueである必要があります" |
|
|
| |
| assert ( |
| args.sdxl |
| ), "cache_text_encoder_outputs_to_disk is only available for SDXL / cache_text_encoder_outputs_to_diskはSDXLのみ利用可能です" |
|
|
| use_dreambooth_method = args.in_json is None |
|
|
| if args.seed is not None: |
| set_seed(args.seed) |
|
|
| |
| if args.sdxl: |
| tokenizer1, tokenizer2 = sdxl_train_util.load_tokenizers(args) |
| tokenizers = [tokenizer1, tokenizer2] |
| else: |
| tokenizer = train_util.load_tokenizer(args) |
| tokenizers = [tokenizer] |
|
|
| |
| if args.dataset_class is None: |
| blueprint_generator = BlueprintGenerator(ConfigSanitizer(True, True, False, True)) |
| if args.dataset_config is not None: |
| logger.info(f"Load dataset config from {args.dataset_config}") |
| user_config = config_util.load_user_config(args.dataset_config) |
| ignored = ["train_data_dir", "in_json"] |
| if any(getattr(args, attr) is not None for attr in ignored): |
| logger.warning( |
| "ignore following options because config file is found: {0} / 設定ファイルが利用されるため以下のオプションは無視されます: {0}".format( |
| ", ".join(ignored) |
| ) |
| ) |
| else: |
| if use_dreambooth_method: |
| logger.info("Using DreamBooth method.") |
| user_config = { |
| "datasets": [ |
| { |
| "subsets": config_util.generate_dreambooth_subsets_config_by_subdirs( |
| args.train_data_dir, args.reg_data_dir |
| ) |
| } |
| ] |
| } |
| else: |
| logger.info("Training with captions.") |
| user_config = { |
| "datasets": [ |
| { |
| "subsets": [ |
| { |
| "image_dir": args.train_data_dir, |
| "metadata_file": args.in_json, |
| } |
| ] |
| } |
| ] |
| } |
|
|
| blueprint = blueprint_generator.generate(user_config, args, tokenizer=tokenizers) |
| train_dataset_group = config_util.generate_dataset_group_by_blueprint(blueprint.dataset_group) |
| else: |
| train_dataset_group = train_util.load_arbitrary_dataset(args, tokenizers) |
|
|
| current_epoch = Value("i", 0) |
| current_step = Value("i", 0) |
| ds_for_collator = train_dataset_group if args.max_data_loader_n_workers == 0 else None |
| collator = train_util.collator_class(current_epoch, current_step, ds_for_collator) |
|
|
| |
| logger.info("prepare accelerator") |
| args.deepspeed = False |
| accelerator = train_util.prepare_accelerator(args) |
|
|
| |
| weight_dtype, _ = train_util.prepare_dtype(args) |
|
|
| |
| logger.info("load model") |
| if args.sdxl: |
| (_, text_encoder1, text_encoder2, _, _, _, _) = sdxl_train_util.load_target_model(args, accelerator, "sdxl", weight_dtype) |
| text_encoders = [text_encoder1, text_encoder2] |
| else: |
| text_encoder1, _, _, _ = train_util.load_target_model(args, weight_dtype, accelerator) |
| text_encoders = [text_encoder1] |
|
|
| for text_encoder in text_encoders: |
| text_encoder.to(accelerator.device, dtype=weight_dtype) |
| text_encoder.requires_grad_(False) |
| text_encoder.eval() |
|
|
| |
| train_dataset_group.set_caching_mode("text") |
|
|
| |
| n_workers = min(args.max_data_loader_n_workers, os.cpu_count()) |
|
|
| train_dataloader = torch.utils.data.DataLoader( |
| train_dataset_group, |
| batch_size=1, |
| shuffle=True, |
| collate_fn=collator, |
| num_workers=n_workers, |
| persistent_workers=args.persistent_data_loader_workers, |
| ) |
|
|
| |
| train_dataloader = accelerator.prepare(train_dataloader) |
|
|
| |
| for batch in tqdm(train_dataloader): |
| absolute_paths = batch["absolute_paths"] |
| input_ids1_list = batch["input_ids1_list"] |
| input_ids2_list = batch["input_ids2_list"] |
|
|
| image_infos = [] |
| for absolute_path, input_ids1, input_ids2 in zip(absolute_paths, input_ids1_list, input_ids2_list): |
| image_info = train_util.ImageInfo(absolute_path, 1, "dummy", False, absolute_path) |
| image_info.text_encoder_outputs_npz = os.path.splitext(absolute_path)[0] + train_util.TEXT_ENCODER_OUTPUTS_CACHE_SUFFIX |
| image_info |
|
|
| if args.skip_existing: |
| if os.path.exists(image_info.text_encoder_outputs_npz): |
| logger.warning(f"Skipping {image_info.text_encoder_outputs_npz} because it already exists.") |
| continue |
| |
| image_info.input_ids1 = input_ids1 |
| image_info.input_ids2 = input_ids2 |
| image_infos.append(image_info) |
|
|
| if len(image_infos) > 0: |
| b_input_ids1 = torch.stack([image_info.input_ids1 for image_info in image_infos]) |
| b_input_ids2 = torch.stack([image_info.input_ids2 for image_info in image_infos]) |
| train_util.cache_batch_text_encoder_outputs( |
| image_infos, tokenizers, text_encoders, args.max_token_length, True, b_input_ids1, b_input_ids2, weight_dtype |
| ) |
|
|
| accelerator.wait_for_everyone() |
| accelerator.print(f"Finished caching latents for {len(train_dataset_group)} batches.") |
|
|
|
|
| def setup_parser() -> argparse.ArgumentParser: |
| parser = argparse.ArgumentParser() |
|
|
| add_logging_arguments(parser) |
| train_util.add_sd_models_arguments(parser) |
| train_util.add_training_arguments(parser, True) |
| train_util.add_dataset_arguments(parser, True, True, True) |
| config_util.add_config_arguments(parser) |
| sdxl_train_util.add_sdxl_training_arguments(parser) |
| parser.add_argument("--sdxl", action="store_true", help="Use SDXL model / SDXLモデルを使用する") |
| parser.add_argument( |
| "--skip_existing", |
| action="store_true", |
| help="skip images if npz already exists (both normal and flipped exists if flip_aug is enabled) / npzが既に存在する画像をスキップする(flip_aug有効時は通常、反転の両方が存在する画像をスキップ)", |
| ) |
| return parser |
|
|
|
|
| if __name__ == "__main__": |
| parser = setup_parser() |
|
|
| args = parser.parse_args() |
| args = train_util.read_config_from_file(args, parser) |
|
|
| cache_to_disk(args) |
|
|