mirror of
https://github.com/lucidrains/DALLE2-pytorch.git
synced 2026-02-14 14:34:30 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fff22834e | ||
|
|
a9421f49ec |
73
README.md
73
README.md
@@ -708,7 +708,77 @@ images = decoder.sample(mock_image_embed) # (1, 3, 1024, 1024)
|
|||||||
|
|
||||||
## Training wrapper (wip)
|
## Training wrapper (wip)
|
||||||
|
|
||||||
Offer training wrappers
|
### Decoder Training
|
||||||
|
|
||||||
|
Training the `Decoder` may be confusing, as one needs to keep track of an optimizer for each of the `Unet`(s) separately. Each `Unet` will also need its own corresponding exponential moving average. The `DecoderTrainer` hopes to make this simple, as shown below
|
||||||
|
|
||||||
|
```python
|
||||||
|
import torch
|
||||||
|
from dalle2_pytorch import DALLE2, Unet, Decoder, CLIP, DecoderTrainer
|
||||||
|
|
||||||
|
clip = CLIP(
|
||||||
|
dim_text = 512,
|
||||||
|
dim_image = 512,
|
||||||
|
dim_latent = 512,
|
||||||
|
num_text_tokens = 49408,
|
||||||
|
text_enc_depth = 6,
|
||||||
|
text_seq_len = 256,
|
||||||
|
text_heads = 8,
|
||||||
|
visual_enc_depth = 6,
|
||||||
|
visual_image_size = 256,
|
||||||
|
visual_patch_size = 32,
|
||||||
|
visual_heads = 8
|
||||||
|
).cuda()
|
||||||
|
|
||||||
|
# mock data
|
||||||
|
|
||||||
|
text = torch.randint(0, 49408, (4, 256)).cuda()
|
||||||
|
images = torch.randn(4, 3, 256, 256).cuda()
|
||||||
|
|
||||||
|
# decoder (with unet)
|
||||||
|
|
||||||
|
unet1 = Unet(
|
||||||
|
dim = 128,
|
||||||
|
image_embed_dim = 512,
|
||||||
|
text_embed_dim = 512,
|
||||||
|
cond_dim = 128,
|
||||||
|
channels = 3,
|
||||||
|
dim_mults=(1, 2, 4, 8)
|
||||||
|
).cuda()
|
||||||
|
|
||||||
|
unet2 = Unet(
|
||||||
|
dim = 16,
|
||||||
|
image_embed_dim = 512,
|
||||||
|
text_embed_dim = 512,
|
||||||
|
cond_dim = 128,
|
||||||
|
channels = 3,
|
||||||
|
dim_mults = (1, 2, 4, 8, 16),
|
||||||
|
cond_on_text_encodings = True
|
||||||
|
).cuda()
|
||||||
|
|
||||||
|
decoder = Decoder(
|
||||||
|
unet = (unet1, unet2),
|
||||||
|
image_sizes = (128, 256),
|
||||||
|
clip = clip,
|
||||||
|
timesteps = 1,
|
||||||
|
condition_on_text_encodings = True
|
||||||
|
).cuda()
|
||||||
|
|
||||||
|
decoder_trainer = DecoderTrainer(
|
||||||
|
decoder,
|
||||||
|
lr = 3e-4,
|
||||||
|
wd = 1e-2,
|
||||||
|
ema_beta = 0.99,
|
||||||
|
ema_update_after_step = 1000,
|
||||||
|
ema_update_every = 10,
|
||||||
|
)
|
||||||
|
|
||||||
|
for unet_number in (1, 2):
|
||||||
|
loss = decoder_trainer(images, text = text, unet_number = unet_number) # use the decoder_trainer forward
|
||||||
|
loss.backward()
|
||||||
|
|
||||||
|
decoder_trainer.update(unet_number) # update the specific unet as well as its exponential moving average
|
||||||
|
```
|
||||||
|
|
||||||
## CLI (wip)
|
## CLI (wip)
|
||||||
|
|
||||||
@@ -741,6 +811,7 @@ Once built, images will be saved to the same directory the command is invoked
|
|||||||
- [x] use inheritance just this once for sharing logic between decoder and prior network ddpms
|
- [x] use inheritance just this once for sharing logic between decoder and prior network ddpms
|
||||||
- [x] bring in vit-vqgan https://arxiv.org/abs/2110.04627 for the latent diffusion
|
- [x] bring in vit-vqgan https://arxiv.org/abs/2110.04627 for the latent diffusion
|
||||||
- [x] abstract interface for CLIP adapter class, so other CLIPs can be brought in
|
- [x] abstract interface for CLIP adapter class, so other CLIPs can be brought in
|
||||||
|
- [ ] take care of mixed precision as well as gradient accumulation within decoder trainer
|
||||||
- [ ] become an expert with unets, cleanup unet code, make it fully configurable, port all learnings over to https://github.com/lucidrains/x-unet
|
- [ ] become an expert with unets, cleanup unet code, make it fully configurable, port all learnings over to https://github.com/lucidrains/x-unet
|
||||||
- [ ] copy the cascading ddpm code to a separate repo (perhaps https://github.com/lucidrains/denoising-diffusion-pytorch) as the main contribution of dalle2 really is just the prior network
|
- [ ] copy the cascading ddpm code to a separate repo (perhaps https://github.com/lucidrains/denoising-diffusion-pytorch) as the main contribution of dalle2 really is just the prior network
|
||||||
- [ ] transcribe code to Jax, which lowers the activation energy for distributed training, given access to TPUs
|
- [ ] transcribe code to Jax, which lowers the activation energy for distributed training, given access to TPUs
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from dalle2_pytorch.dalle2_pytorch import DALLE2, DiffusionPriorNetwork, DiffusionPrior, Unet, Decoder
|
from dalle2_pytorch.dalle2_pytorch import DALLE2, DiffusionPriorNetwork, DiffusionPrior, Unet, Decoder
|
||||||
from dalle2_pytorch.dalle2_pytorch import OpenAIClipAdapter
|
from dalle2_pytorch.dalle2_pytorch import OpenAIClipAdapter
|
||||||
|
from dalle2_pytorch.train import DecoderTrainer
|
||||||
|
|
||||||
from dalle2_pytorch.vqgan_vae import VQGanVAE
|
from dalle2_pytorch.vqgan_vae import VQGanVAE
|
||||||
from x_clip import CLIP
|
from x_clip import CLIP
|
||||||
|
|||||||
@@ -1097,7 +1097,12 @@ class Unet(nn.Module):
|
|||||||
Rearrange('b (n d) -> b n d', n = num_image_tokens)
|
Rearrange('b (n d) -> b n d', n = num_image_tokens)
|
||||||
) if image_embed_dim != cond_dim else nn.Identity()
|
) if image_embed_dim != cond_dim else nn.Identity()
|
||||||
|
|
||||||
self.text_to_cond = nn.LazyLinear(cond_dim) if not exists(text_embed_dim) else nn.Linear(text_embed_dim, cond_dim)
|
# text encoding conditioning (optional)
|
||||||
|
|
||||||
|
self.text_to_cond = None
|
||||||
|
|
||||||
|
if cond_on_text_encodings:
|
||||||
|
self.text_to_cond = nn.LazyLinear(cond_dim) if not exists(text_embed_dim) else nn.Linear(text_embed_dim, cond_dim)
|
||||||
|
|
||||||
# finer control over whether to condition on image embeddings and text encodings
|
# finer control over whether to condition on image embeddings and text encodings
|
||||||
# so one can have the latter unets in the cascading DDPMs only focus on super-resoluting
|
# so one can have the latter unets in the cascading DDPMs only focus on super-resoluting
|
||||||
|
|||||||
@@ -1,7 +1,43 @@
|
|||||||
import copy
|
import copy
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from torch import nn
|
from torch import nn
|
||||||
|
|
||||||
|
from dalle2_pytorch.dalle2_pytorch import Decoder
|
||||||
|
from dalle2_pytorch.optimizer import get_optimizer
|
||||||
|
|
||||||
|
# helper functions
|
||||||
|
|
||||||
|
def exists(val):
|
||||||
|
return val is not None
|
||||||
|
|
||||||
|
def cast_tuple(val, length = 1):
|
||||||
|
return val if isinstance(val, tuple) else ((val,) * length)
|
||||||
|
|
||||||
|
def pick_and_pop(keys, d):
|
||||||
|
values = list(map(lambda key: d.pop(key), keys))
|
||||||
|
return dict(zip(keys, values))
|
||||||
|
|
||||||
|
def group_dict_by_key(cond, d):
|
||||||
|
return_val = [dict(),dict()]
|
||||||
|
for key in d.keys():
|
||||||
|
match = bool(cond(key))
|
||||||
|
ind = int(not match)
|
||||||
|
return_val[ind][key] = d[key]
|
||||||
|
return (*return_val,)
|
||||||
|
|
||||||
|
def string_begins_with(prefix, str):
|
||||||
|
return str.startswith(prefix)
|
||||||
|
|
||||||
|
def group_by_key_prefix(prefix, d):
|
||||||
|
return group_dict_by_key(partial(string_begins_with, prefix), d)
|
||||||
|
|
||||||
|
def groupby_prefix_and_trim(prefix, d):
|
||||||
|
kwargs_with_prefix, kwargs = group_dict_by_key(partial(string_begins_with, prefix), d)
|
||||||
|
kwargs_without_prefix = dict(map(lambda x: (x[0][len(prefix):], x[1]), tuple(kwargs_with_prefix.items())))
|
||||||
|
return kwargs_without_prefix, kwargs
|
||||||
|
|
||||||
# exponential moving average wrapper
|
# exponential moving average wrapper
|
||||||
|
|
||||||
class EMA(nn.Module):
|
class EMA(nn.Module):
|
||||||
@@ -9,16 +45,16 @@ class EMA(nn.Module):
|
|||||||
self,
|
self,
|
||||||
model,
|
model,
|
||||||
beta = 0.99,
|
beta = 0.99,
|
||||||
ema_update_after_step = 1000,
|
update_after_step = 1000,
|
||||||
ema_update_every = 10,
|
update_every = 10,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.beta = beta
|
self.beta = beta
|
||||||
self.online_model = model
|
self.online_model = model
|
||||||
self.ema_model = copy.deepcopy(model)
|
self.ema_model = copy.deepcopy(model)
|
||||||
|
|
||||||
self.ema_update_after_step = ema_update_after_step # only start EMA after this step number, starting at 0
|
self.update_after_step = update_after_step # only start EMA after this step number, starting at 0
|
||||||
self.ema_update_every = ema_update_every
|
self.update_every = update_every
|
||||||
|
|
||||||
self.register_buffer('initted', torch.Tensor([False]))
|
self.register_buffer('initted', torch.Tensor([False]))
|
||||||
self.register_buffer('step', torch.tensor([0.]))
|
self.register_buffer('step', torch.tensor([0.]))
|
||||||
@@ -26,7 +62,7 @@ class EMA(nn.Module):
|
|||||||
def update(self):
|
def update(self):
|
||||||
self.step += 1
|
self.step += 1
|
||||||
|
|
||||||
if self.step <= self.ema_update_after_step or (self.step % self.ema_update_every) != 0:
|
if self.step <= self.update_after_step or (self.step % self.update_every) != 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.initted:
|
if not self.initted:
|
||||||
@@ -51,3 +87,71 @@ class EMA(nn.Module):
|
|||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
return self.ema_model(*args, **kwargs)
|
return self.ema_model(*args, **kwargs)
|
||||||
|
|
||||||
|
# trainers
|
||||||
|
|
||||||
|
class DecoderTrainer(nn.Module):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
decoder,
|
||||||
|
use_ema = True,
|
||||||
|
lr = 3e-4,
|
||||||
|
wd = 1e-2,
|
||||||
|
max_grad_norm = None,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
assert isinstance(decoder, Decoder)
|
||||||
|
ema_kwargs, kwargs = groupby_prefix_and_trim('ema_', kwargs)
|
||||||
|
|
||||||
|
self.decoder = decoder
|
||||||
|
self.num_unets = len(self.decoder.unets)
|
||||||
|
|
||||||
|
self.use_ema = use_ema
|
||||||
|
|
||||||
|
if use_ema:
|
||||||
|
has_lazy_linear = any([type(module) == nn.LazyLinear for module in decoder.modules()])
|
||||||
|
assert not has_lazy_linear, 'you must set the text_embed_dim on your u-nets if you plan on doing automatic exponential moving average'
|
||||||
|
|
||||||
|
self.ema_unets = nn.ModuleList([])
|
||||||
|
|
||||||
|
# be able to finely customize learning rate, weight decay
|
||||||
|
# per unet
|
||||||
|
|
||||||
|
lr, wd = map(partial(cast_tuple, length = self.num_unets), (lr, wd))
|
||||||
|
|
||||||
|
for ind, (unet, unet_lr, unet_wd) in enumerate(zip(self.decoder.unets, lr, wd)):
|
||||||
|
optimizer = get_optimizer(
|
||||||
|
unet.parameters(),
|
||||||
|
lr = unet_lr,
|
||||||
|
wd = unet_wd,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
setattr(self, f'optim{ind}', optimizer) # cannot use pytorch ModuleList for some reason with optimizers
|
||||||
|
|
||||||
|
if self.use_ema:
|
||||||
|
self.ema_unets.append(EMA(unet, **ema_kwargs))
|
||||||
|
|
||||||
|
# gradient clipping if needed
|
||||||
|
|
||||||
|
self.max_grad_norm = max_grad_norm
|
||||||
|
|
||||||
|
def update(self, unet_number):
|
||||||
|
assert 1 <= unet_number <= self.num_unets
|
||||||
|
index = unet_number - 1
|
||||||
|
unet = self.decoder.unets[index]
|
||||||
|
|
||||||
|
if exists(self.max_grad_norm):
|
||||||
|
nn.utils.clip_grad_norm_(unet.parameters(), self.max_grad_norm)
|
||||||
|
|
||||||
|
optimizer = getattr(self, f'optim{index}')
|
||||||
|
optimizer.step()
|
||||||
|
optimizer.zero_grad()
|
||||||
|
|
||||||
|
if self.use_ema:
|
||||||
|
ema_unet = self.ema_unets[index]
|
||||||
|
ema_unet.update()
|
||||||
|
|
||||||
|
def forward(self, x, *, unet_number, **kwargs):
|
||||||
|
return self.decoder(x, unet_number = unet_number, **kwargs)
|
||||||
|
|||||||
Reference in New Issue
Block a user