-
Notifications
You must be signed in to change notification settings - Fork 7.1k
[lora training] fix aspect ratio bucketing in dreambooth scripts (+ caption dropout, --bucket_no_upscale) #14158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -260,3 +260,55 @@ def test_dreambooth_lora_with_metadata(self): | |
| self.assertTrue(loaded_lora_alpha == lora_alpha) | ||
| loaded_lora_rank = raw["transformer.r"] | ||
| self.assertTrue(loaded_lora_rank == rank) | ||
|
|
||
| def test_dreambooth_lora_flux2_aspect_ratio_buckets(self): | ||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| test_args = f""" | ||
| {self.script_path} | ||
| --pretrained_model_name_or_path {self.pretrained_model_name_or_path} | ||
| --instance_data_dir {self.instance_data_dir} | ||
| --instance_prompt {self.instance_prompt} | ||
| --aspect_ratio_buckets 64,64;64,128 | ||
| --bucket_no_upscale | ||
| --cache_latents | ||
| --train_batch_size 1 | ||
| --gradient_accumulation_steps 1 | ||
| --max_train_steps 2 | ||
| --learning_rate 5.0e-04 | ||
| --lr_scheduler constant | ||
| --lr_warmup_steps 0 | ||
| --max_sequence_length 8 | ||
| --text_encoder_out_layers 1 | ||
| --output_dir {tmpdir} | ||
| """.split() | ||
|
|
||
| run_command(self._launch_args + test_args) | ||
| self.assertTrue(os.path.isfile(os.path.join(tmpdir, "pytorch_lora_weights.safetensors"))) | ||
| lora_state_dict = safetensors.torch.load_file(os.path.join(tmpdir, "pytorch_lora_weights.safetensors")) | ||
| self.assertTrue(all("lora" in k for k in lora_state_dict.keys())) | ||
| self.assertTrue(all(key.startswith("transformer") for key in lora_state_dict.keys())) | ||
|
|
||
| def test_dreambooth_lora_flux2_caption_dropout(self): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for this. |
||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| test_args = f""" | ||
| {self.script_path} | ||
| --pretrained_model_name_or_path {self.pretrained_model_name_or_path} | ||
| --instance_data_dir {self.instance_data_dir} | ||
| --instance_prompt {self.instance_prompt} | ||
| --resolution 64 | ||
| --caption_dropout 1.0 | ||
| --train_batch_size 1 | ||
| --gradient_accumulation_steps 1 | ||
| --max_train_steps 2 | ||
| --learning_rate 5.0e-04 | ||
| --lr_scheduler constant | ||
| --lr_warmup_steps 0 | ||
| --max_sequence_length 8 | ||
| --text_encoder_out_layers 1 | ||
| --output_dir {tmpdir} | ||
| """.split() | ||
|
|
||
| run_command(self._launch_args + test_args) | ||
| self.assertTrue(os.path.isfile(os.path.join(tmpdir, "pytorch_lora_weights.safetensors"))) | ||
| lora_state_dict = safetensors.torch.load_file(os.path.join(tmpdir, "pytorch_lora_weights.safetensors")) | ||
| self.assertTrue(all("lora" in k for k in lora_state_dict.keys())) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -450,6 +450,11 @@ def parse_args(input_args=None): | |
| "Images will be resized and cropped to fit the nearest bucket. If provided, --resolution is ignored." | ||
| ), | ||
| ) | ||
| parser.add_argument( | ||
| "--bucket_no_upscale", | ||
| action="store_true", | ||
| help="If set, images smaller than their aspect-ratio bucket are padded instead of upscaled.", | ||
| ) | ||
|
Comment on lines
+453
to
+457
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this help at all? If so, I would maybe keep it simple and just upscale. Also, should we enable bucketing through an arg? |
||
| parser.add_argument( | ||
| "--center_crop", | ||
| default=False, | ||
|
|
@@ -890,15 +895,6 @@ def __init__( | |
| else: | ||
| self.class_data_root = None | ||
|
|
||
| self.image_transforms = transforms.Compose( | ||
| [ | ||
| transforms.Resize(size, interpolation=transforms.InterpolationMode.BILINEAR), | ||
| transforms.CenterCrop(size) if center_crop else transforms.RandomCrop(size), | ||
| transforms.ToTensor(), | ||
| transforms.Normalize([0.5], [0.5]), | ||
| ] | ||
| ) | ||
|
Comment on lines
-893
to
-900
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we're going with the above suggestion i.e. make aspect-ratio bucketing conditional on an arg, then I guess this should stay? |
||
|
|
||
| def __len__(self): | ||
| return self._length | ||
|
|
||
|
|
@@ -924,37 +920,35 @@ def __getitem__(self, index): | |
|
|
||
| if not class_image.mode == "RGB": | ||
| class_image = class_image.convert("RGB") | ||
| example["class_images"] = self.image_transforms(class_image) | ||
| # Match the class image to the paired instance image's bucket so they can be stacked into one batch. | ||
| example["class_images"] = self.train_transform( | ||
| class_image, size=self.buckets[bucket_idx], center_crop=self.center_crop | ||
| ) | ||
| example["class_prompt"] = self.class_prompt | ||
|
|
||
| return example | ||
|
|
||
| def train_transform(self, image, size=(224, 224), center_crop=False, random_flip=False): | ||
| # 1. Resize (deterministic) | ||
| resize = transforms.Resize(size, interpolation=transforms.InterpolationMode.BILINEAR) | ||
| image = resize(image) | ||
|
|
||
| # 2. Crop: either center or SAME random crop | ||
| def train_transform(self, image, size, center_crop=False, random_flip=False): | ||
| # Resize preserving aspect ratio so the image covers the bucket, then crop to the bucket size. | ||
| target_height, target_width = size | ||
| width, height = image.size | ||
| scale = max(target_height / height, target_width / width) | ||
| if args.bucket_no_upscale: | ||
| scale = min(scale, 1.0) | ||
| new_height, new_width = round(height * scale), round(width * scale) | ||
| image = TF.resize(image, [new_height, new_width], interpolation=transforms.InterpolationMode.BILINEAR) | ||
| # Pad to the bucket when no-upscale leaves the image smaller, so batched samples share a shape. | ||
| pad_w, pad_h = max(0, target_width - new_width), max(0, target_height - new_height) | ||
| if pad_w or pad_h: | ||
| image = TF.pad(image, [pad_w // 2, pad_h // 2, pad_w - pad_w // 2, pad_h - pad_h // 2]) | ||
| if center_crop: | ||
| crop = transforms.CenterCrop(size) | ||
| image = crop(image) | ||
| image = TF.center_crop(image, size) | ||
| else: | ||
| # get_params returns (i, j, h, w) | ||
| i, j, h, w = transforms.RandomCrop.get_params(image, output_size=size) | ||
| image = TF.crop(image, i, j, h, w) | ||
|
|
||
| # 3. Random horizontal flip with the SAME coin flip | ||
| if random_flip: | ||
| do_flip = random.random() < 0.5 | ||
| if do_flip: | ||
| image = TF.hflip(image) | ||
|
|
||
| # 4. ToTensor + Normalize (deterministic) | ||
| to_tensor = transforms.ToTensor() | ||
| normalize = transforms.Normalize([0.5], [0.5]) | ||
| image = normalize(to_tensor(image)) | ||
|
|
||
| return image | ||
| if random_flip and random.random() < 0.5: | ||
| image = TF.hflip(image) | ||
| return TF.normalize(TF.to_tensor(image), [0.5], [0.5]) | ||
|
|
||
|
|
||
| def collate_fn(examples, with_prior_preservation=False): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove this test for now. Let's see if this feature becomes more and more used and then add it back. So, we could just
skipit.