Skip to content

Commit 7c12518

Browse files
Allow bucket reshuffling with DreamBooth caches (#13712)
* Allow bucket reshuffling with DreamBooth caches * Address DreamBooth cache review suggestions * Seed bucket batch sampler shuffling --------- Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
1 parent ed07118 commit 7c12518

2 files changed

Lines changed: 191 additions & 101 deletions

File tree

examples/dreambooth/train_dreambooth_lora_flux2.py

Lines changed: 96 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,7 @@ def __len__(self):
905905
def __getitem__(self, index):
906906
example = {}
907907
instance_image, bucket_idx = self.pixel_values[index % self.num_instance_images]
908+
example["index"] = index
908909
example["instance_images"] = instance_image
909910
example["bucket_idx"] = bucket_idx
910911
if self.custom_instance_prompts:
@@ -957,7 +958,10 @@ def train_transform(self, image, size=(224, 224), center_crop=False, random_flip
957958

958959

959960
def collate_fn(examples, with_prior_preservation=False):
961+
indices = [example["index"] for example in examples]
960962
pixel_values = [example["instance_images"] for example in examples]
963+
# Keep instance_prompts unchanged for prompt cache precompute; prompts may be extended with class prompts below.
964+
instance_prompts = [example["instance_prompt"] for example in examples]
961965
prompts = [example["instance_prompt"] for example in examples]
962966

963967
# Concat class and instance examples for prior preservation.
@@ -969,18 +973,17 @@ def collate_fn(examples, with_prior_preservation=False):
969973
pixel_values = torch.stack(pixel_values)
970974
pixel_values = pixel_values.to(memory_format=torch.contiguous_format).float()
971975

972-
batch = {"pixel_values": pixel_values, "prompts": prompts}
976+
batch = {
977+
"indices": indices,
978+
"pixel_values": pixel_values,
979+
"instance_prompts": instance_prompts,
980+
"prompts": prompts,
981+
}
973982
return batch
974983

975984

976985
class BucketBatchSampler(BatchSampler):
977-
def __init__(
978-
self,
979-
dataset: DreamBoothDataset,
980-
batch_size: int,
981-
drop_last: bool = False,
982-
shuffle_batches_each_epoch: bool = True,
983-
):
986+
def __init__(self, dataset: DreamBoothDataset, batch_size: int, drop_last: bool = False, seed: int = None):
984987
if not isinstance(batch_size, int) or batch_size <= 0:
985988
raise ValueError("batch_size should be a positive integer value, but got batch_size={}".format(batch_size))
986989
if not isinstance(drop_last, bool):
@@ -989,37 +992,33 @@ def __init__(
989992
self.dataset = dataset
990993
self.batch_size = batch_size
991994
self.drop_last = drop_last
992-
self.shuffle_batches_each_epoch = shuffle_batches_each_epoch
995+
self.generator = random.Random(seed) if seed is not None else random
993996

994997
# Group indices by bucket
995998
self.bucket_indices = [[] for _ in range(len(self.dataset.buckets))]
996999
for idx, (_, bucket_idx) in enumerate(self.dataset.pixel_values):
9971000
self.bucket_indices[bucket_idx].append(idx)
9981001

9991002
self.sampler_len = 0
1000-
self.batches = []
1003+
for indices_in_bucket in self.bucket_indices:
1004+
num_batches, remainder = divmod(len(indices_in_bucket), self.batch_size)
1005+
self.sampler_len += num_batches
1006+
if remainder > 0 and not self.drop_last:
1007+
self.sampler_len += 1
10011008

1002-
# Pre-generate batches for each bucket
1009+
def __iter__(self):
1010+
batches = []
10031011
for indices_in_bucket in self.bucket_indices:
1004-
# Shuffle indices within the bucket
1005-
random.shuffle(indices_in_bucket)
1006-
# Create batches
1007-
for i in range(0, len(indices_in_bucket), self.batch_size):
1008-
batch = indices_in_bucket[i : i + self.batch_size]
1012+
shuffled_indices = indices_in_bucket.copy()
1013+
self.generator.shuffle(shuffled_indices)
1014+
for i in range(0, len(shuffled_indices), self.batch_size):
1015+
batch = shuffled_indices[i : i + self.batch_size]
10091016
if len(batch) < self.batch_size and self.drop_last:
1010-
continue # Skip partial batch if drop_last is True
1011-
self.batches.append(batch)
1012-
self.sampler_len += 1 # Count the number of batches
1013-
1014-
if not self.shuffle_batches_each_epoch:
1015-
# Shuffle the precomputed batches once to mix buckets while keeping
1016-
# the order stable across epochs for step-indexed caches.
1017-
random.shuffle(self.batches)
1017+
continue
1018+
batches.append(batch)
10181019

1019-
def __iter__(self):
1020-
if self.shuffle_batches_each_epoch:
1021-
random.shuffle(self.batches)
1022-
for batch in self.batches:
1020+
self.generator.shuffle(batches)
1021+
for batch in batches:
10231022
yield batch
10241023

10251024
def __len__(self):
@@ -1480,13 +1479,8 @@ def load_model_hook(models, input_dir):
14801479
center_crop=args.center_crop,
14811480
buckets=buckets,
14821481
)
1483-
has_step_indexed_caches = precompute_latents = args.cache_latents or train_dataset.custom_instance_prompts
1484-
batch_sampler = BucketBatchSampler(
1485-
train_dataset,
1486-
batch_size=args.train_batch_size,
1487-
drop_last=True,
1488-
shuffle_batches_each_epoch=not has_step_indexed_caches,
1489-
)
1482+
precompute_latents = args.cache_latents or train_dataset.custom_instance_prompts
1483+
batch_sampler = BucketBatchSampler(train_dataset, batch_size=args.train_batch_size, drop_last=True, seed=args.seed)
14901484
train_dataloader = torch.utils.data.DataLoader(
14911485
train_dataset,
14921486
batch_sampler=batch_sampler,
@@ -1599,32 +1593,72 @@ def _encode_single(prompt: str):
15991593
if args.with_prior_preservation:
16001594
prompt_embeds = torch.cat([prompt_embeds, class_prompt_hidden_states], dim=0)
16011595
text_ids = torch.cat([text_ids, class_text_ids], dim=0)
1596+
static_prompt_embeds = prompt_embeds
1597+
static_text_ids = text_ids
16021598

16031599
# if cache_latents is set to True, we encode images to latents and store them.
16041600
# Similar to pre-encoding in the case of a single instance prompt, if custom prompts are provided
16051601
# we encode them in advance as well.
1602+
if args.cache_latents:
1603+
instance_latents_cache = [None] * train_dataset.num_instance_images
1604+
class_latents_cache = [None] * train_dataset.num_instance_images if args.with_prior_preservation else None
1605+
if train_dataset.custom_instance_prompts:
1606+
prompt_embeds_cache = [None] * train_dataset.num_instance_images
1607+
text_ids_cache = [None] * train_dataset.num_instance_images
16061608
if precompute_latents:
1607-
prompt_embeds_cache = []
1608-
text_ids_cache = []
1609-
latents_cache = []
1610-
for batch in tqdm(train_dataloader, desc="Caching latents"):
1609+
cache_batch_sampler = BucketBatchSampler(
1610+
train_dataset, batch_size=args.train_batch_size, drop_last=False, seed=args.seed
1611+
)
1612+
cache_dataloader = torch.utils.data.DataLoader(
1613+
train_dataset,
1614+
batch_sampler=cache_batch_sampler,
1615+
collate_fn=lambda examples: collate_fn(examples, args.with_prior_preservation),
1616+
num_workers=args.dataloader_num_workers,
1617+
)
1618+
for batch in tqdm(cache_dataloader, desc="Caching latents"):
16111619
with torch.no_grad():
1620+
sample_indices = batch["indices"]
16121621
if args.cache_latents:
16131622
with offload_models(vae, device=accelerator.device, offload=args.offload):
16141623
batch["pixel_values"] = batch["pixel_values"].to(
16151624
accelerator.device, non_blocking=True, dtype=vae.dtype
16161625
)
1617-
latents_cache.append(vae.encode(batch["pixel_values"]).latent_dist)
1626+
latents = vae.encode(batch["pixel_values"]).latent_dist.mode()
1627+
if args.with_prior_preservation:
1628+
instance_latents, class_latents = torch.chunk(latents, 2, dim=0)
1629+
else:
1630+
instance_latents = latents
1631+
for i, idx in enumerate(sample_indices):
1632+
instance_latents_cache[idx] = instance_latents[i : i + 1]
1633+
if args.with_prior_preservation:
1634+
class_latents_cache[idx] = class_latents[i : i + 1]
16181635
if train_dataset.custom_instance_prompts:
16191636
if args.remote_text_encoder:
1620-
prompt_embeds, text_ids = compute_remote_text_embeddings(batch["prompts"])
1637+
prompt_embeds, text_ids = compute_remote_text_embeddings(batch["instance_prompts"])
16211638
elif args.fsdp_text_encoder:
1622-
prompt_embeds, text_ids = compute_text_embeddings(batch["prompts"], text_encoding_pipeline)
1639+
prompt_embeds, text_ids = compute_text_embeddings(
1640+
batch["instance_prompts"], text_encoding_pipeline
1641+
)
16231642
else:
16241643
with offload_models(text_encoding_pipeline, device=accelerator.device, offload=args.offload):
1625-
prompt_embeds, text_ids = compute_text_embeddings(batch["prompts"], text_encoding_pipeline)
1626-
prompt_embeds_cache.append(prompt_embeds)
1627-
text_ids_cache.append(text_ids)
1644+
prompt_embeds, text_ids = compute_text_embeddings(
1645+
batch["instance_prompts"], text_encoding_pipeline
1646+
)
1647+
for i, idx in enumerate(sample_indices):
1648+
prompt_embeds_cache[idx] = prompt_embeds[i : i + 1]
1649+
text_ids_cache[idx] = text_ids[i : i + 1]
1650+
1651+
if args.cache_latents:
1652+
assert all(latents is not None for latents in instance_latents_cache), "Latent cache has unfilled entries."
1653+
if args.with_prior_preservation:
1654+
assert all(latents is not None for latents in class_latents_cache), (
1655+
"Class latent cache has unfilled entries."
1656+
)
1657+
if train_dataset.custom_instance_prompts:
1658+
assert all(embeds is not None for embeds in prompt_embeds_cache), (
1659+
"Prompt embedding cache has unfilled entries."
1660+
)
1661+
assert all(ids is not None for ids in text_ids_cache), "Text ID cache has unfilled entries."
16281662

16291663
# move back to cpu before deleting to ensure memory is freed see: https://github.com/huggingface/diffusers/issues/11376#issue-3008144624
16301664
if args.cache_latents:
@@ -1748,25 +1782,36 @@ def get_sigmas(timesteps, n_dim=4, dtype=torch.float32):
17481782
for epoch in range(first_epoch, args.num_train_epochs):
17491783
transformer.train()
17501784

1751-
for step, batch in enumerate(train_dataloader):
1785+
for batch in train_dataloader:
17521786
models_to_accumulate = [transformer]
1787+
sample_indices = batch["indices"]
17531788
prompts = batch["prompts"]
17541789

17551790
with accelerator.accumulate(models_to_accumulate):
17561791
if train_dataset.custom_instance_prompts:
1757-
prompt_embeds = prompt_embeds_cache[step]
1758-
text_ids = text_ids_cache[step]
1792+
prompt_embeds = torch.cat([prompt_embeds_cache[idx] for idx in sample_indices], dim=0)
1793+
text_ids = torch.cat([text_ids_cache[idx] for idx in sample_indices], dim=0)
1794+
if args.with_prior_preservation:
1795+
prompt_embeds = torch.cat(
1796+
[prompt_embeds, class_prompt_hidden_states.repeat(len(sample_indices), 1, 1)], dim=0
1797+
)
1798+
text_ids = torch.cat([text_ids, class_text_ids.repeat(len(sample_indices), 1, 1)], dim=0)
17591799
else:
17601800
# With prior preservation, prompt_embeds/text_ids already contain [instance, class] entries,
17611801
# while collate_fn orders batches as [inst1..instB, class1..classB]. Repeat each entry along
17621802
# dim 0 to preserve that grouping instead of interleaving [inst, class, inst, class, ...].
17631803
num_repeat_elements = len(prompts) // 2 if args.with_prior_preservation else len(prompts)
1764-
prompt_embeds = prompt_embeds.repeat_interleave(num_repeat_elements, dim=0)
1765-
text_ids = text_ids.repeat_interleave(num_repeat_elements, dim=0)
1804+
prompt_embeds = static_prompt_embeds.repeat_interleave(num_repeat_elements, dim=0)
1805+
text_ids = static_text_ids.repeat_interleave(num_repeat_elements, dim=0)
17661806

17671807
# Convert images to latent space
17681808
if args.cache_latents:
1769-
model_input = latents_cache[step].mode()
1809+
model_input = torch.cat([instance_latents_cache[idx] for idx in sample_indices], dim=0)
1810+
if args.with_prior_preservation:
1811+
model_input = torch.cat(
1812+
[model_input, torch.cat([class_latents_cache[idx] for idx in sample_indices], dim=0)],
1813+
dim=0,
1814+
)
17701815
else:
17711816
with offload_models(vae, device=accelerator.device, offload=args.offload):
17721817
pixel_values = batch["pixel_values"].to(device=accelerator.device, dtype=vae.dtype)

0 commit comments

Comments
 (0)