Upload inference-only latent diffusion model
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -33
- README.md +63 -0
- checkpoints/ldm_model.pt +3 -0
- checkpoints/vae_model.pt +3 -0
- configs/ldm_config.yaml +102 -0
- configs/vae_config.yaml +78 -0
- generation_config.yaml +20 -0
- inference.py +353 -0
- outputs/test_ddim/0000_a_small_dog_sitting_on_a_red_couch.png +0 -0
- outputs/test_ddim/grid.png +0 -0
- requirements.txt +8 -0
- src/__init__.py +0 -0
- src/__pycache__/__init__.cpython-311.pyc +0 -0
- src/diffusion/__init__.py +0 -0
- src/diffusion/__pycache__/__init__.cpython-311.pyc +0 -0
- src/diffusion/__pycache__/gaussian_diffusion.cpython-311.pyc +0 -0
- src/diffusion/__pycache__/noise_schedule.cpython-311.pyc +0 -0
- src/diffusion/__pycache__/prediction.cpython-311.pyc +0 -0
- src/diffusion/gaussian_diffusion.py +437 -0
- src/diffusion/noise_schedule.py +302 -0
- src/diffusion/prediction.py +259 -0
- src/diffusion/samplers/__init__.py +9 -0
- src/diffusion/samplers/__pycache__/__init__.cpython-311.pyc +0 -0
- src/diffusion/samplers/__pycache__/ddim.cpython-311.pyc +0 -0
- src/diffusion/samplers/__pycache__/ddpm.cpython-311.pyc +0 -0
- src/diffusion/samplers/ddim.py +234 -0
- src/diffusion/samplers/ddpm.py +235 -0
- src/losses/__pycache__/diffusion_loss.cpython-311.pyc +0 -0
- src/losses/__pycache__/vae_loss.cpython-311.pyc +0 -0
- src/losses/diffusion_loss.py +252 -0
- src/losses/vae_loss.py +143 -0
- src/models/__init__.py +0 -0
- src/models/__pycache__/__init__.cpython-311.pyc +0 -0
- src/models/autoencoder/__pycache__/blocks.cpython-311.pyc +0 -0
- src/models/autoencoder/__pycache__/decoder.cpython-311.pyc +0 -0
- src/models/autoencoder/__pycache__/distributions.cpython-311.pyc +0 -0
- src/models/autoencoder/__pycache__/encoder.cpython-311.pyc +0 -0
- src/models/autoencoder/__pycache__/vae.cpython-311.pyc +0 -0
- src/models/autoencoder/blocks.py +352 -0
- src/models/autoencoder/decoder.py +162 -0
- src/models/autoencoder/distributions.py +113 -0
- src/models/autoencoder/encoder.py +150 -0
- src/models/autoencoder/vae.py +215 -0
- src/models/conditioning/__pycache__/clip_text.cpython-311.pyc +0 -0
- src/models/conditioning/clip_text.py +181 -0
- src/models/conditioning/null_conditioning.py +144 -0
- src/models/diffusion/__pycache__/attention.cpython-311.pyc +0 -0
- src/models/diffusion/__pycache__/blocks.cpython-311.pyc +0 -0
- src/models/diffusion/__pycache__/timestep.cpython-311.pyc +0 -0
- src/models/diffusion/__pycache__/unet.cpython-311.pyc +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,3 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 3 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
tags:
|
| 4 |
+
- text-to-image
|
| 5 |
+
- diffusion
|
| 6 |
+
- latent-diffusion
|
| 7 |
+
- pytorch
|
| 8 |
+
- coco
|
| 9 |
+
library_name: pytorch
|
| 10 |
+
pipeline_tag: text-to-image
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# anilegin/lightweight-diffusion-ldm
|
| 14 |
+
|
| 15 |
+
Custom lightweight latent diffusion text-to-image model.
|
| 16 |
+
|
| 17 |
+
This repository contains inference-only files:
|
| 18 |
+
|
| 19 |
+
- VAE config and stripped VAE weights
|
| 20 |
+
- LDM/UNet config and stripped LDM weights
|
| 21 |
+
- diffusion/sampler code needed for DDPM and DDIM
|
| 22 |
+
- a simple `inference.py` script
|
| 23 |
+
- generation defaults in `generation_config.yaml`
|
| 24 |
+
|
| 25 |
+
The checkpoints are stripped to contain model weights only; optimizer state, scheduler state, and training logs are not included.
|
| 26 |
+
|
| 27 |
+
## Install
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
git clone https://huggingface.co/anilegin/lightweight-diffusion-ldm
|
| 31 |
+
cd lightweight-diffusion-ldm
|
| 32 |
+
pip install -r requirements.txt
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
## Generate images
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
python inference.py \
|
| 39 |
+
--prompt "a small dog sitting on a red couch" \
|
| 40 |
+
--sampler ddim \
|
| 41 |
+
--num-steps 50 \
|
| 42 |
+
--guidance-scale 3.0 \
|
| 43 |
+
--precision bf16 \
|
| 44 |
+
--output-dir outputs/example
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
For offline/local-only CLIP loading, make sure `openai/clip-vit-large-patch14` is cached locally and add:
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
--local-files-only
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## Notes
|
| 54 |
+
|
| 55 |
+
This is a custom PyTorch implementation, not a native Diffusers pipeline. The included source code is required for inference.
|
| 56 |
+
|
| 57 |
+
## Training data
|
| 58 |
+
|
| 59 |
+
Trained/evaluated with COCO-style image-caption data. Add more precise dataset, metrics, and limitations here before making the repo public.
|
| 60 |
+
|
| 61 |
+
## Citation
|
| 62 |
+
|
| 63 |
+
If you use this model, please cite the project/repository.
|
checkpoints/ldm_model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:79a241aa2d3f67e89d8d9227e5022efc36d603ddf07c426c599b78304dd668b7
|
| 3 |
+
size 1798549690
|
checkpoints/vae_model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:179bfe367d29e1e726638b71a8a9dfc17651d9b368d4fade5029022c89df5b15
|
| 3 |
+
size 426519034
|
configs/ldm_config.yaml
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
project:
|
| 2 |
+
root: .
|
| 3 |
+
data:
|
| 4 |
+
coco_root: /leonardo_scratch/large/userexternal/aegin000/datasets/coco2017
|
| 5 |
+
train_latent_dir: outputs/latents/coco_train2017_vae8_scaled032_allcaptions
|
| 6 |
+
val_latent_dir: outputs/latents/coco_val2017_vae8_scaled032_allcaptions
|
| 7 |
+
train_caption_mode: random
|
| 8 |
+
val_caption_mode: first
|
| 9 |
+
load_latents_to_memory: true
|
| 10 |
+
outputs:
|
| 11 |
+
root: outputs
|
| 12 |
+
vae_dir: outputs/vae
|
| 13 |
+
ldm_dir: outputs/ldm
|
| 14 |
+
latent_dir: outputs/latents
|
| 15 |
+
sample_dir: outputs/samples
|
| 16 |
+
log_dir: logs
|
| 17 |
+
cache:
|
| 18 |
+
root: cache
|
| 19 |
+
model:
|
| 20 |
+
name: latent_diffusion_unet_strong
|
| 21 |
+
in_channels: 8
|
| 22 |
+
out_channels: 8
|
| 23 |
+
latent_size: 32
|
| 24 |
+
base_channels: 304
|
| 25 |
+
channel_multipliers:
|
| 26 |
+
- 1
|
| 27 |
+
- 2
|
| 28 |
+
- 4
|
| 29 |
+
num_res_blocks: 3
|
| 30 |
+
dropout: 0.0
|
| 31 |
+
attention_resolutions:
|
| 32 |
+
- 16
|
| 33 |
+
- 8
|
| 34 |
+
use_middle_attention: true
|
| 35 |
+
context_dim: 768
|
| 36 |
+
num_heads: 8
|
| 37 |
+
head_dim: 64
|
| 38 |
+
transformer_depth: 1
|
| 39 |
+
time_embedding_dim: null
|
| 40 |
+
experiment:
|
| 41 |
+
name: ldm_coco_256_vae8_strong_vpred_ft
|
| 42 |
+
text_encoder:
|
| 43 |
+
model_name: openai/clip-vit-large-patch14
|
| 44 |
+
max_length: 77
|
| 45 |
+
freeze: true
|
| 46 |
+
use_last_hidden_state: true
|
| 47 |
+
conditioning:
|
| 48 |
+
cond_drop_prob: 0.05
|
| 49 |
+
empty_text: ''
|
| 50 |
+
diffusion:
|
| 51 |
+
schedule_type: cosine
|
| 52 |
+
num_timesteps: 1000
|
| 53 |
+
prediction_type: v
|
| 54 |
+
loss_type: mse
|
| 55 |
+
beta_start: 0.0001
|
| 56 |
+
beta_end: 0.02
|
| 57 |
+
cosine_s: 0.008
|
| 58 |
+
max_beta: 0.999
|
| 59 |
+
snr_weighting: min_snr
|
| 60 |
+
snr_gamma: 5.0
|
| 61 |
+
normalize_snr_weights: false
|
| 62 |
+
train:
|
| 63 |
+
seed: 42
|
| 64 |
+
precision: bf16
|
| 65 |
+
batch_size: 2
|
| 66 |
+
gradient_accumulation_steps: 12
|
| 67 |
+
num_workers: 2
|
| 68 |
+
pin_memory: true
|
| 69 |
+
max_epochs: 100
|
| 70 |
+
lr: 0.0001
|
| 71 |
+
min_lr: 5.0e-05
|
| 72 |
+
scheduler: cosine
|
| 73 |
+
warmup_steps: 2000
|
| 74 |
+
weight_decay: 0.01
|
| 75 |
+
betas:
|
| 76 |
+
- 0.9
|
| 77 |
+
- 0.999
|
| 78 |
+
grad_clip: 1.0
|
| 79 |
+
log_every: 50
|
| 80 |
+
validate_every: 1
|
| 81 |
+
save_every: 1
|
| 82 |
+
output_dir_key: outputs.ldm_dir
|
| 83 |
+
resume_from: null
|
| 84 |
+
finetune_from: outputs/ldm/ldm_coco_256_vae8_strong_vpred_ft/checkpoints/last.pt
|
| 85 |
+
validation:
|
| 86 |
+
enabled: true
|
| 87 |
+
max_batches: 100
|
| 88 |
+
bucket_max_batches: 25
|
| 89 |
+
timestep_buckets:
|
| 90 |
+
- - 0
|
| 91 |
+
- 100
|
| 92 |
+
- - 100
|
| 93 |
+
- 300
|
| 94 |
+
- - 300
|
| 95 |
+
- 700
|
| 96 |
+
- - 700
|
| 97 |
+
- 1000
|
| 98 |
+
optimizer:
|
| 99 |
+
name: adamw
|
| 100 |
+
distributed:
|
| 101 |
+
enabled: true
|
| 102 |
+
backend: nccl
|
configs/vae_config.yaml
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
project:
|
| 2 |
+
root: .
|
| 3 |
+
data:
|
| 4 |
+
coco_root: /leonardo_scratch/large/userexternal/aegin000/datasets/coco2017
|
| 5 |
+
outputs:
|
| 6 |
+
root: outputs
|
| 7 |
+
vae_dir: outputs/vae
|
| 8 |
+
ldm_dir: outputs/ldm
|
| 9 |
+
latent_dir: outputs/latents
|
| 10 |
+
sample_dir: outputs/samples
|
| 11 |
+
log_dir: logs
|
| 12 |
+
cache:
|
| 13 |
+
root: cache
|
| 14 |
+
dataset:
|
| 15 |
+
name: coco_captions
|
| 16 |
+
root_key: data.coco_root
|
| 17 |
+
train_split: train2017
|
| 18 |
+
val_split: val2017
|
| 19 |
+
resolution: 256
|
| 20 |
+
num_workers: 8
|
| 21 |
+
pin_memory: true
|
| 22 |
+
model:
|
| 23 |
+
name: autoencoder_kl
|
| 24 |
+
in_channels: 3
|
| 25 |
+
out_channels: 3
|
| 26 |
+
latent_channels: 8
|
| 27 |
+
base_channels: 128
|
| 28 |
+
channel_multipliers:
|
| 29 |
+
- 1
|
| 30 |
+
- 2
|
| 31 |
+
- 4
|
| 32 |
+
- 4
|
| 33 |
+
num_res_blocks: 3
|
| 34 |
+
dropout: 0.0
|
| 35 |
+
use_attention: true
|
| 36 |
+
attention_heads: 4
|
| 37 |
+
scaling_factor: 1.0
|
| 38 |
+
attention_resolutions:
|
| 39 |
+
- 32
|
| 40 |
+
experiment:
|
| 41 |
+
name: vae_coco_256_small
|
| 42 |
+
train:
|
| 43 |
+
seed: 42
|
| 44 |
+
precision: bf16
|
| 45 |
+
batch_size: 16
|
| 46 |
+
gradient_accumulation_steps: 4
|
| 47 |
+
num_workers: 8
|
| 48 |
+
max_epochs: 100
|
| 49 |
+
lr: 0.0001
|
| 50 |
+
weight_decay: 0.0
|
| 51 |
+
betas:
|
| 52 |
+
- 0.9
|
| 53 |
+
- 0.999
|
| 54 |
+
grad_clip: 1.0
|
| 55 |
+
log_every: 100
|
| 56 |
+
validate_every: 1
|
| 57 |
+
save_every: 1
|
| 58 |
+
sample_every: 1
|
| 59 |
+
num_sample_images: 8
|
| 60 |
+
output_dir_key: outputs.vae_dir
|
| 61 |
+
initialize_from_scratch: true
|
| 62 |
+
resume_from: outputs/vae/vae_coco_256_small/checkpoints/last.pt
|
| 63 |
+
finetune_from: null
|
| 64 |
+
early_stopping:
|
| 65 |
+
enabled: true
|
| 66 |
+
patience: 15
|
| 67 |
+
min_delta: 0.0
|
| 68 |
+
monitor_metric: val_total_loss
|
| 69 |
+
loss:
|
| 70 |
+
recon_loss_type: l1
|
| 71 |
+
recon_weight: 1.0
|
| 72 |
+
use_lpips: true
|
| 73 |
+
lpips_net: vgg
|
| 74 |
+
perceptual_weight: 0.1
|
| 75 |
+
kl_weight: 1.0e-06
|
| 76 |
+
kl_warmup_steps: 10000
|
| 77 |
+
optimizer:
|
| 78 |
+
name: adamw
|
generation_config.yaml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ldm:
|
| 2 |
+
config: configs/ldm_config.yaml
|
| 3 |
+
checkpoint: checkpoints/ldm_model.pt
|
| 4 |
+
vae:
|
| 5 |
+
config: configs/vae_config.yaml
|
| 6 |
+
checkpoint: checkpoints/vae_model.pt
|
| 7 |
+
scaling_factor: 1.032
|
| 8 |
+
conditioning:
|
| 9 |
+
empty_text: ''
|
| 10 |
+
generation:
|
| 11 |
+
seed: 42
|
| 12 |
+
precision: fp16
|
| 13 |
+
resolution: 256
|
| 14 |
+
batch_size: 4
|
| 15 |
+
sampler:
|
| 16 |
+
type: ddim
|
| 17 |
+
num_steps: 50
|
| 18 |
+
eta: 0.0
|
| 19 |
+
guidance_scale: 3.0
|
| 20 |
+
clip_denoised: false
|
inference.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import math
|
| 5 |
+
import random
|
| 6 |
+
import sys
|
| 7 |
+
import warnings
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
import yaml
|
| 13 |
+
from PIL import Image
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
|
| 16 |
+
sys.path.append(str(Path(__file__).resolve().parent))
|
| 17 |
+
|
| 18 |
+
from src.diffusion.gaussian_diffusion import GaussianDiffusion
|
| 19 |
+
from src.diffusion.samplers import DDPMSampler, DDIMSampler
|
| 20 |
+
from src.models.autoencoder.vae import AutoencoderKL
|
| 21 |
+
from src.models.conditioning.clip_text import FrozenCLIPTextEncoder
|
| 22 |
+
from src.models.diffusion.unet import build_latent_diffusion_unet_from_config
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def load_yaml(path: str | Path) -> dict[str, Any]:
|
| 26 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 27 |
+
return yaml.safe_load(f)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def safe_torch_load(path: str | Path, map_location="cpu"):
|
| 31 |
+
try:
|
| 32 |
+
return torch.load(path, map_location=map_location, weights_only=True)
|
| 33 |
+
except TypeError:
|
| 34 |
+
return torch.load(path, map_location=map_location)
|
| 35 |
+
except Exception:
|
| 36 |
+
return torch.load(path, map_location=map_location)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def get_dtype(name: str) -> torch.dtype:
|
| 40 |
+
name = name.lower()
|
| 41 |
+
if name == "fp16":
|
| 42 |
+
return torch.float16
|
| 43 |
+
if name == "bf16":
|
| 44 |
+
return torch.bfloat16
|
| 45 |
+
if name == "fp32":
|
| 46 |
+
return torch.float32
|
| 47 |
+
raise ValueError(f"Unknown precision={name}")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def autocast_context(device: torch.device, dtype: torch.dtype):
|
| 51 |
+
enabled = device.type == "cuda" and dtype in (torch.float16, torch.bfloat16)
|
| 52 |
+
if device.type == "cuda":
|
| 53 |
+
return torch.autocast("cuda", dtype=dtype, enabled=enabled)
|
| 54 |
+
return torch.autocast("cpu", enabled=False)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def set_seed(seed: int):
|
| 58 |
+
random.seed(seed)
|
| 59 |
+
torch.manual_seed(seed)
|
| 60 |
+
if torch.cuda.is_available():
|
| 61 |
+
torch.cuda.manual_seed_all(seed)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def sanitize_filename(text: str, max_len: int = 80) -> str:
|
| 65 |
+
text = text.lower().strip()
|
| 66 |
+
keep = []
|
| 67 |
+
for ch in text:
|
| 68 |
+
if ch.isalnum():
|
| 69 |
+
keep.append(ch)
|
| 70 |
+
elif ch in {" ", "-", "_"}:
|
| 71 |
+
keep.append("_")
|
| 72 |
+
out = "".join(keep)
|
| 73 |
+
while "__" in out:
|
| 74 |
+
out = out.replace("__", "_")
|
| 75 |
+
out = out.strip("_") or "sample"
|
| 76 |
+
return out[:max_len]
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def save_image_tensor(image: torch.Tensor, path: str | Path):
|
| 80 |
+
image = image.detach().cpu().clamp(0.0, 1.0)
|
| 81 |
+
image = image.permute(1, 2, 0).float().numpy()
|
| 82 |
+
image = (image * 255).round().astype("uint8")
|
| 83 |
+
Image.fromarray(image).save(path)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def save_image_grid(images: list[Image.Image], path: str | Path, nrow: int | None = None, padding: int = 2):
|
| 87 |
+
if not images:
|
| 88 |
+
return
|
| 89 |
+
|
| 90 |
+
path = Path(path)
|
| 91 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 92 |
+
|
| 93 |
+
if nrow is None:
|
| 94 |
+
nrow = int(math.ceil(math.sqrt(len(images))))
|
| 95 |
+
ncol = int(math.ceil(len(images) / nrow))
|
| 96 |
+
|
| 97 |
+
widths, heights = zip(*(img.size for img in images))
|
| 98 |
+
cell_w, cell_h = max(widths), max(heights)
|
| 99 |
+
grid_w = nrow * cell_w + padding * (nrow - 1)
|
| 100 |
+
grid_h = ncol * cell_h + padding * (ncol - 1)
|
| 101 |
+
|
| 102 |
+
grid = Image.new("RGB", (grid_w, grid_h), color=(255, 255, 255))
|
| 103 |
+
for idx, img in enumerate(images):
|
| 104 |
+
row = idx // nrow
|
| 105 |
+
col = idx % nrow
|
| 106 |
+
x = col * (cell_w + padding)
|
| 107 |
+
y = row * (cell_h + padding)
|
| 108 |
+
grid.paste(img.convert("RGB"), (x, y))
|
| 109 |
+
grid.save(path)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def load_model_state(module: torch.nn.Module, checkpoint_path: str | Path):
|
| 113 |
+
checkpoint = safe_torch_load(checkpoint_path, map_location="cpu")
|
| 114 |
+
if isinstance(checkpoint, dict):
|
| 115 |
+
state_dict = checkpoint.get("model", checkpoint.get("state_dict", checkpoint))
|
| 116 |
+
else:
|
| 117 |
+
state_dict = checkpoint
|
| 118 |
+
module.load_state_dict(state_dict, strict=True)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def build_vae(vae_cfg: dict) -> AutoencoderKL:
|
| 122 |
+
model_cfg = dict(vae_cfg["model"])
|
| 123 |
+
model_cfg.pop("name", None)
|
| 124 |
+
return AutoencoderKL(**model_cfg)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def build_diffusion(ldm_cfg: dict) -> GaussianDiffusion:
|
| 128 |
+
d = ldm_cfg["diffusion"]
|
| 129 |
+
return GaussianDiffusion(
|
| 130 |
+
schedule_type=str(d.get("schedule_type", "cosine")),
|
| 131 |
+
num_timesteps=int(d.get("num_timesteps", 1000)),
|
| 132 |
+
prediction_type=str(d.get("prediction_type", "v")),
|
| 133 |
+
loss_type=str(d.get("loss_type", "mse")),
|
| 134 |
+
beta_start=float(d.get("beta_start", 1e-4)),
|
| 135 |
+
beta_end=float(d.get("beta_end", 2e-2)),
|
| 136 |
+
cosine_s=float(d.get("cosine_s", 0.008)),
|
| 137 |
+
max_beta=float(d.get("max_beta", 0.999)),
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def build_text_encoder(ldm_cfg: dict, device: torch.device, local_files_only: bool):
|
| 142 |
+
warnings.filterwarnings("ignore", message=".*clean_up_tokenization_spaces.*", category=FutureWarning)
|
| 143 |
+
|
| 144 |
+
text_cfg = dict(ldm_cfg.get("text_encoder", {}))
|
| 145 |
+
text_encoder = FrozenCLIPTextEncoder(
|
| 146 |
+
model_name=str(text_cfg.get("model_name", "openai/clip-vit-large-patch14")),
|
| 147 |
+
max_length=int(text_cfg.get("max_length", 77)),
|
| 148 |
+
freeze=True,
|
| 149 |
+
use_last_hidden_state=bool(text_cfg.get("use_last_hidden_state", True)),
|
| 150 |
+
local_files_only=local_files_only,
|
| 151 |
+
)
|
| 152 |
+
text_encoder.to(device=device)
|
| 153 |
+
text_encoder.eval()
|
| 154 |
+
return text_encoder
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
@torch.no_grad()
|
| 158 |
+
def encode_contexts(text_encoder, prompts: list[str], empty_text: str, device: torch.device, dtype: torch.dtype):
|
| 159 |
+
context_dtype = dtype if device.type == "cuda" else torch.float32
|
| 160 |
+
cond_context = text_encoder.encode(prompts, device=device).to(dtype=context_dtype)
|
| 161 |
+
uncond_context = text_encoder.encode([empty_text] * len(prompts), device=device).to(dtype=context_dtype)
|
| 162 |
+
return cond_context, uncond_context
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
@torch.no_grad()
|
| 166 |
+
def decode_latents(vae, latents: torch.Tensor, scaling_factor: float, dtype: torch.dtype):
|
| 167 |
+
try:
|
| 168 |
+
with autocast_context(latents.device, dtype):
|
| 169 |
+
images = vae.decode(latents.float(), unscale=True)
|
| 170 |
+
except TypeError:
|
| 171 |
+
z = latents.float() / scaling_factor
|
| 172 |
+
with autocast_context(latents.device, dtype):
|
| 173 |
+
images = vae.decode(z)
|
| 174 |
+
|
| 175 |
+
if hasattr(images, "sample"):
|
| 176 |
+
images = images.sample
|
| 177 |
+
|
| 178 |
+
return ((images + 1.0) / 2.0).clamp(0.0, 1.0)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def read_prompts(args) -> list[str]:
|
| 182 |
+
prompts: list[str] = []
|
| 183 |
+
|
| 184 |
+
if args.prompt is not None:
|
| 185 |
+
prompts.append(args.prompt)
|
| 186 |
+
|
| 187 |
+
if args.prompts_file is not None:
|
| 188 |
+
with open(args.prompts_file, "r", encoding="utf-8") as f:
|
| 189 |
+
prompts.extend([line.strip() for line in f if line.strip()])
|
| 190 |
+
|
| 191 |
+
if not prompts:
|
| 192 |
+
raise ValueError("Provide --prompt or --prompts-file.")
|
| 193 |
+
|
| 194 |
+
repeated = []
|
| 195 |
+
for prompt in prompts:
|
| 196 |
+
repeated.extend([prompt] * args.num_images_per_prompt)
|
| 197 |
+
return repeated
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
@torch.no_grad()
|
| 201 |
+
def main():
|
| 202 |
+
parser = argparse.ArgumentParser()
|
| 203 |
+
parser.add_argument("--config", type=str, default="generation_config.yaml")
|
| 204 |
+
parser.add_argument("--prompt", type=str, default=None)
|
| 205 |
+
parser.add_argument("--prompts-file", type=str, default=None)
|
| 206 |
+
parser.add_argument("--output-dir", type=str, default="outputs")
|
| 207 |
+
parser.add_argument("--sampler", type=str, default=None, choices=["ddim", "ddpm"])
|
| 208 |
+
parser.add_argument("--num-steps", type=int, default=None)
|
| 209 |
+
parser.add_argument("--guidance-scale", type=float, default=None)
|
| 210 |
+
parser.add_argument("--eta", type=float, default=None)
|
| 211 |
+
parser.add_argument("--precision", type=str, default=None, choices=["fp32", "bf16", "fp16"])
|
| 212 |
+
parser.add_argument("--seed", type=int, default=None)
|
| 213 |
+
parser.add_argument("--batch-size", type=int, default=None)
|
| 214 |
+
parser.add_argument("--num-images-per-prompt", type=int, default=1)
|
| 215 |
+
parser.add_argument("--local-files-only", action="store_true", help="Use only locally cached CLIP files.")
|
| 216 |
+
args = parser.parse_args()
|
| 217 |
+
|
| 218 |
+
repo_root = Path(__file__).resolve().parent
|
| 219 |
+
cfg = load_yaml(repo_root / args.config)
|
| 220 |
+
|
| 221 |
+
gen_cfg = cfg.get("generation", {})
|
| 222 |
+
sampler_cfg = cfg.get("sampler", {})
|
| 223 |
+
|
| 224 |
+
seed = int(args.seed if args.seed is not None else gen_cfg.get("seed", 42))
|
| 225 |
+
precision = str(args.precision if args.precision is not None else gen_cfg.get("precision", "bf16"))
|
| 226 |
+
batch_size = int(args.batch_size if args.batch_size is not None else gen_cfg.get("batch_size", 4))
|
| 227 |
+
sampler_name = str(args.sampler if args.sampler is not None else sampler_cfg.get("type", "ddim")).lower()
|
| 228 |
+
num_steps = int(args.num_steps if args.num_steps is not None else sampler_cfg.get("num_steps", 50))
|
| 229 |
+
guidance_scale = float(args.guidance_scale if args.guidance_scale is not None else sampler_cfg.get("guidance_scale", 3.0))
|
| 230 |
+
eta = float(args.eta if args.eta is not None else sampler_cfg.get("eta", 0.0))
|
| 231 |
+
clip_denoised = bool(sampler_cfg.get("clip_denoised", False))
|
| 232 |
+
|
| 233 |
+
set_seed(seed)
|
| 234 |
+
|
| 235 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 236 |
+
dtype = get_dtype(precision)
|
| 237 |
+
|
| 238 |
+
ldm_cfg_path = repo_root / cfg["ldm"]["config"]
|
| 239 |
+
ldm_ckpt_path = repo_root / cfg["ldm"]["checkpoint"]
|
| 240 |
+
vae_cfg_path = repo_root / cfg["vae"]["config"]
|
| 241 |
+
vae_ckpt_path = repo_root / cfg["vae"]["checkpoint"]
|
| 242 |
+
|
| 243 |
+
ldm_cfg = load_yaml(ldm_cfg_path)
|
| 244 |
+
vae_cfg = load_yaml(vae_cfg_path)
|
| 245 |
+
|
| 246 |
+
vae = build_vae(vae_cfg)
|
| 247 |
+
load_model_state(vae, vae_ckpt_path)
|
| 248 |
+
vae.to(device=device)
|
| 249 |
+
vae.eval()
|
| 250 |
+
|
| 251 |
+
unet = build_latent_diffusion_unet_from_config(ldm_cfg)
|
| 252 |
+
load_model_state(unet, ldm_ckpt_path)
|
| 253 |
+
unet.to(device=device)
|
| 254 |
+
unet.eval()
|
| 255 |
+
|
| 256 |
+
diffusion = build_diffusion(ldm_cfg).to(device)
|
| 257 |
+
text_encoder = build_text_encoder(ldm_cfg, device=device, local_files_only=args.local_files_only)
|
| 258 |
+
|
| 259 |
+
if sampler_name == "ddim":
|
| 260 |
+
sampler = DDIMSampler(diffusion)
|
| 261 |
+
elif sampler_name == "ddpm":
|
| 262 |
+
sampler = DDPMSampler(diffusion)
|
| 263 |
+
else:
|
| 264 |
+
raise ValueError(f"Unknown sampler: {sampler_name}")
|
| 265 |
+
|
| 266 |
+
latent_channels = int(ldm_cfg["model"].get("in_channels", 8))
|
| 267 |
+
image_size = int(gen_cfg.get("resolution", 256))
|
| 268 |
+
latent_size = int(ldm_cfg["model"].get("latent_size", image_size // 8))
|
| 269 |
+
scaling_factor = float(cfg["vae"].get("scaling_factor", getattr(vae, "scaling_factor", 1.0)))
|
| 270 |
+
empty_text = str(cfg.get("conditioning", {}).get("empty_text", ""))
|
| 271 |
+
|
| 272 |
+
prompts = read_prompts(args)
|
| 273 |
+
|
| 274 |
+
output_dir = Path(args.output_dir)
|
| 275 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 276 |
+
|
| 277 |
+
print("=============================================")
|
| 278 |
+
print("Custom latent diffusion inference")
|
| 279 |
+
print("Device:", device)
|
| 280 |
+
print("Precision:", precision)
|
| 281 |
+
print("Sampler:", sampler_name)
|
| 282 |
+
print("Steps:", num_steps if sampler_name == "ddim" else diffusion.num_timesteps)
|
| 283 |
+
print("Guidance scale:", guidance_scale)
|
| 284 |
+
print("Eta:", eta)
|
| 285 |
+
print("Seed:", seed)
|
| 286 |
+
print("Total images:", len(prompts))
|
| 287 |
+
print("Output dir:", output_dir)
|
| 288 |
+
print("=============================================")
|
| 289 |
+
|
| 290 |
+
saved_images: list[Image.Image] = []
|
| 291 |
+
|
| 292 |
+
for batch_start in tqdm(range(0, len(prompts), batch_size), desc="batches"):
|
| 293 |
+
batch_prompts = prompts[batch_start: batch_start + batch_size]
|
| 294 |
+
cond_context, uncond_context = encode_contexts(
|
| 295 |
+
text_encoder=text_encoder,
|
| 296 |
+
prompts=batch_prompts,
|
| 297 |
+
empty_text=empty_text,
|
| 298 |
+
device=device,
|
| 299 |
+
dtype=dtype,
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
shape = (len(batch_prompts), latent_channels, latent_size, latent_size)
|
| 303 |
+
|
| 304 |
+
with autocast_context(device, dtype):
|
| 305 |
+
if sampler_name == "ddim":
|
| 306 |
+
sample_out = sampler.sample(
|
| 307 |
+
model=unet,
|
| 308 |
+
shape=shape,
|
| 309 |
+
device=device,
|
| 310 |
+
context=cond_context,
|
| 311 |
+
attention_mask=None,
|
| 312 |
+
uncond_context=uncond_context,
|
| 313 |
+
uncond_attention_mask=None,
|
| 314 |
+
guidance_scale=guidance_scale,
|
| 315 |
+
num_steps=num_steps,
|
| 316 |
+
eta=eta,
|
| 317 |
+
clip_denoised=clip_denoised,
|
| 318 |
+
return_trajectory=False,
|
| 319 |
+
progress=True,
|
| 320 |
+
)
|
| 321 |
+
else:
|
| 322 |
+
sample_out = sampler.sample(
|
| 323 |
+
model=unet,
|
| 324 |
+
shape=shape,
|
| 325 |
+
device=device,
|
| 326 |
+
context=cond_context,
|
| 327 |
+
attention_mask=None,
|
| 328 |
+
uncond_context=uncond_context,
|
| 329 |
+
uncond_attention_mask=None,
|
| 330 |
+
guidance_scale=guidance_scale,
|
| 331 |
+
clip_denoised=clip_denoised,
|
| 332 |
+
return_trajectory=False,
|
| 333 |
+
progress=True,
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
latents = sample_out.latents if hasattr(sample_out, "latents") else sample_out
|
| 337 |
+
images = decode_latents(vae, latents, scaling_factor=scaling_factor, dtype=dtype)
|
| 338 |
+
|
| 339 |
+
for i, image in enumerate(images):
|
| 340 |
+
prompt = batch_prompts[i]
|
| 341 |
+
global_idx = batch_start + i
|
| 342 |
+
out_path = output_dir / f"{global_idx:04d}_{sanitize_filename(prompt)}.png"
|
| 343 |
+
save_image_tensor(image, out_path)
|
| 344 |
+
|
| 345 |
+
pil = Image.open(out_path).convert("RGB")
|
| 346 |
+
saved_images.append(pil)
|
| 347 |
+
|
| 348 |
+
save_image_grid(saved_images, output_dir / "grid.png")
|
| 349 |
+
print("Saved", len(saved_images), "images to", output_dir)
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
if __name__ == "__main__":
|
| 353 |
+
main()
|
outputs/test_ddim/0000_a_small_dog_sitting_on_a_red_couch.png
ADDED
|
outputs/test_ddim/grid.png
ADDED
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchvision
|
| 3 |
+
transformers
|
| 4 |
+
huggingface_hub
|
| 5 |
+
python-dotenv
|
| 6 |
+
PyYAML
|
| 7 |
+
Pillow
|
| 8 |
+
tqdm
|
src/__init__.py
ADDED
|
File without changes
|
src/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (206 Bytes). View file
|
|
|
src/diffusion/__init__.py
ADDED
|
File without changes
|
src/diffusion/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (216 Bytes). View file
|
|
|
src/diffusion/__pycache__/gaussian_diffusion.cpython-311.pyc
ADDED
|
Binary file (12.2 kB). View file
|
|
|
src/diffusion/__pycache__/noise_schedule.cpython-311.pyc
ADDED
|
Binary file (8.57 kB). View file
|
|
|
src/diffusion/__pycache__/prediction.cpython-311.pyc
ADDED
|
Binary file (5.54 kB). View file
|
|
|
src/diffusion/gaussian_diffusion.py
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
from src.losses.diffusion_loss import DiffusionLoss
|
| 9 |
+
from src.diffusion.noise_schedule import NoiseSchedule, create_noise_schedule, extract
|
| 10 |
+
from src.diffusion.prediction import (
|
| 11 |
+
get_training_target,
|
| 12 |
+
model_output_to_x0_and_eps,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class DiffusionTrainingOutput:
|
| 18 |
+
loss: torch.Tensor
|
| 19 |
+
simple_loss: torch.Tensor
|
| 20 |
+
model_output: torch.Tensor
|
| 21 |
+
target: torch.Tensor
|
| 22 |
+
z_t: torch.Tensor
|
| 23 |
+
noise: torch.Tensor
|
| 24 |
+
timesteps: torch.Tensor
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class GaussianDiffusion:
|
| 28 |
+
"""
|
| 29 |
+
Core latent diffusion utilities.
|
| 30 |
+
|
| 31 |
+
This handles:
|
| 32 |
+
|
| 33 |
+
- sampling timesteps
|
| 34 |
+
- adding noise q(z_t | z_0)
|
| 35 |
+
- creating v-prediction targets
|
| 36 |
+
- computing diffusion training loss
|
| 37 |
+
- computing DDPM posterior mean/variance for sampling
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
schedule: NoiseSchedule | None = None,
|
| 43 |
+
schedule_type: str = "cosine",
|
| 44 |
+
num_timesteps: int = 1000,
|
| 45 |
+
prediction_type: str = "v",
|
| 46 |
+
loss_type: str = "mse",
|
| 47 |
+
beta_start: float = 1e-4,
|
| 48 |
+
beta_end: float = 2e-2,
|
| 49 |
+
cosine_s: float = 0.008,
|
| 50 |
+
max_beta: float = 0.999,
|
| 51 |
+
snr_gamma: float | None = None,
|
| 52 |
+
snr_weighting: str = "none",
|
| 53 |
+
normalize_snr_weights: bool = False,
|
| 54 |
+
):
|
| 55 |
+
if schedule is None:
|
| 56 |
+
schedule = create_noise_schedule(
|
| 57 |
+
schedule_type=schedule_type,
|
| 58 |
+
num_timesteps=num_timesteps,
|
| 59 |
+
beta_start=beta_start,
|
| 60 |
+
beta_end=beta_end,
|
| 61 |
+
cosine_s=cosine_s,
|
| 62 |
+
max_beta=max_beta,
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
self.schedule = schedule
|
| 66 |
+
self.prediction_type = prediction_type.lower()
|
| 67 |
+
self.loss_type = loss_type.lower()
|
| 68 |
+
self.snr_gamma = snr_gamma
|
| 69 |
+
self.snr_weighting = snr_weighting.lower()
|
| 70 |
+
self.normalize_snr_weights = normalize_snr_weights
|
| 71 |
+
|
| 72 |
+
if self.prediction_type not in {"v", "v_prediction", "eps", "epsilon", "x0", "sample"}:
|
| 73 |
+
raise ValueError(
|
| 74 |
+
f"Unknown prediction_type={prediction_type}. "
|
| 75 |
+
"Use 'v', 'eps', or 'x0'."
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
if self.loss_type not in {"mse", "l1", "huber"}:
|
| 79 |
+
raise ValueError(
|
| 80 |
+
f"Unknown loss_type={loss_type}. "
|
| 81 |
+
"Use 'mse', 'l1', or 'huber'."
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
self.diffusion_loss = DiffusionLoss(
|
| 85 |
+
prediction_type=self.prediction_type,
|
| 86 |
+
loss_type=self.loss_type,
|
| 87 |
+
snr_gamma=self.snr_gamma,
|
| 88 |
+
snr_weighting=self.snr_weighting,
|
| 89 |
+
normalize_snr_weights=self.normalize_snr_weights,
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
@property
|
| 93 |
+
def num_timesteps(self) -> int:
|
| 94 |
+
return self.schedule.num_timesteps
|
| 95 |
+
|
| 96 |
+
def to(self, device: torch.device | str) -> "GaussianDiffusion":
|
| 97 |
+
self.schedule = self.schedule.to(device)
|
| 98 |
+
return self
|
| 99 |
+
|
| 100 |
+
def sample_timesteps(
|
| 101 |
+
self,
|
| 102 |
+
batch_size: int,
|
| 103 |
+
device: torch.device | str,
|
| 104 |
+
) -> torch.Tensor:
|
| 105 |
+
"""
|
| 106 |
+
Sample random diffusion timesteps.
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
t: [B], values in [0, num_timesteps - 1]
|
| 110 |
+
"""
|
| 111 |
+
return torch.randint(
|
| 112 |
+
low=0,
|
| 113 |
+
high=self.num_timesteps,
|
| 114 |
+
size=(batch_size,),
|
| 115 |
+
device=device,
|
| 116 |
+
dtype=torch.long,
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
def q_sample(
|
| 120 |
+
self,
|
| 121 |
+
z_0: torch.Tensor,
|
| 122 |
+
t: torch.Tensor,
|
| 123 |
+
noise: torch.Tensor | None = None,
|
| 124 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 125 |
+
"""
|
| 126 |
+
Forward diffusion process:
|
| 127 |
+
|
| 128 |
+
q(z_t | z_0)
|
| 129 |
+
|
| 130 |
+
Formula:
|
| 131 |
+
|
| 132 |
+
z_t = sqrt(alpha_bar_t) * z_0
|
| 133 |
+
+ sqrt(1 - alpha_bar_t) * eps
|
| 134 |
+
|
| 135 |
+
Args:
|
| 136 |
+
z_0:
|
| 137 |
+
Clean latent [B, C, H, W].
|
| 138 |
+
|
| 139 |
+
t:
|
| 140 |
+
Timesteps [B].
|
| 141 |
+
|
| 142 |
+
noise:
|
| 143 |
+
Optional epsilon noise. If None, sampled from N(0, I).
|
| 144 |
+
|
| 145 |
+
Returns:
|
| 146 |
+
z_t:
|
| 147 |
+
Noisy latent.
|
| 148 |
+
|
| 149 |
+
noise:
|
| 150 |
+
The epsilon noise used.
|
| 151 |
+
"""
|
| 152 |
+
if noise is None:
|
| 153 |
+
noise = torch.randn_like(z_0)
|
| 154 |
+
|
| 155 |
+
sqrt_alpha_bar = extract(
|
| 156 |
+
self.schedule.sqrt_alphas_cumprod,
|
| 157 |
+
t,
|
| 158 |
+
z_0.shape,
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
sqrt_one_minus_alpha_bar = extract(
|
| 162 |
+
self.schedule.sqrt_one_minus_alphas_cumprod,
|
| 163 |
+
t,
|
| 164 |
+
z_0.shape,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
z_t = sqrt_alpha_bar * z_0 + sqrt_one_minus_alpha_bar * noise
|
| 168 |
+
|
| 169 |
+
return z_t, noise
|
| 170 |
+
|
| 171 |
+
def training_target(
|
| 172 |
+
self,
|
| 173 |
+
z_0: torch.Tensor,
|
| 174 |
+
noise: torch.Tensor,
|
| 175 |
+
t: torch.Tensor,
|
| 176 |
+
) -> torch.Tensor:
|
| 177 |
+
"""
|
| 178 |
+
Get target for current prediction type
|
| 179 |
+
"""
|
| 180 |
+
return get_training_target(
|
| 181 |
+
z_0=z_0,
|
| 182 |
+
eps=noise,
|
| 183 |
+
t=t,
|
| 184 |
+
schedule=self.schedule,
|
| 185 |
+
prediction_type=self.prediction_type,
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
def p_losses(
|
| 189 |
+
self,
|
| 190 |
+
model,
|
| 191 |
+
z_0: torch.Tensor,
|
| 192 |
+
context: torch.Tensor | None = None,
|
| 193 |
+
t: torch.Tensor | None = None,
|
| 194 |
+
noise: torch.Tensor | None = None,
|
| 195 |
+
model_kwargs: dict | None = None,
|
| 196 |
+
) -> DiffusionTrainingOutput:
|
| 197 |
+
"""
|
| 198 |
+
Full diffusion training step using loss module.
|
| 199 |
+
"""
|
| 200 |
+
if model_kwargs is None:
|
| 201 |
+
model_kwargs = {}
|
| 202 |
+
|
| 203 |
+
batch_size = z_0.shape[0]
|
| 204 |
+
device = z_0.device
|
| 205 |
+
|
| 206 |
+
if t is None:
|
| 207 |
+
t = self.sample_timesteps(batch_size, device)
|
| 208 |
+
|
| 209 |
+
z_t, noise = self.q_sample(
|
| 210 |
+
z_0=z_0,
|
| 211 |
+
t=t,
|
| 212 |
+
noise=noise,
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
target = self.training_target(
|
| 216 |
+
z_0=z_0,
|
| 217 |
+
noise=noise,
|
| 218 |
+
t=t,
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
if context is None:
|
| 222 |
+
model_output = model(z_t, t, **model_kwargs)
|
| 223 |
+
else:
|
| 224 |
+
model_output = model(z_t, t, context=context, **model_kwargs)
|
| 225 |
+
|
| 226 |
+
alpha_t = extract(
|
| 227 |
+
self.schedule.sqrt_alphas_cumprod,
|
| 228 |
+
t,
|
| 229 |
+
z_0.shape,
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
sigma_t = extract(
|
| 233 |
+
self.schedule.sqrt_one_minus_alphas_cumprod,
|
| 234 |
+
t,
|
| 235 |
+
z_0.shape,
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
alpha_bar_t = self.schedule.alphas_cumprod.gather(
|
| 239 |
+
0,
|
| 240 |
+
t,
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
snr = alpha_bar_t / (1.0 - alpha_bar_t).clamp(min=1e-8)
|
| 244 |
+
|
| 245 |
+
loss_out = self.diffusion_loss(
|
| 246 |
+
model_output=model_output,
|
| 247 |
+
x0=z_0,
|
| 248 |
+
noise=noise,
|
| 249 |
+
alpha_t=alpha_t,
|
| 250 |
+
sigma_t=sigma_t,
|
| 251 |
+
snr=snr,
|
| 252 |
+
return_dict=True,
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
loss = loss_out["loss"]
|
| 256 |
+
raw_loss = loss_out["raw_loss"]
|
| 257 |
+
|
| 258 |
+
return DiffusionTrainingOutput(
|
| 259 |
+
loss=loss,
|
| 260 |
+
simple_loss=raw_loss.detach(),
|
| 261 |
+
model_output=model_output,
|
| 262 |
+
target=target,
|
| 263 |
+
z_t=z_t,
|
| 264 |
+
noise=noise,
|
| 265 |
+
timesteps=t,
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
def predict_x0_and_eps(
|
| 269 |
+
self,
|
| 270 |
+
model_output: torch.Tensor,
|
| 271 |
+
z_t: torch.Tensor,
|
| 272 |
+
t: torch.Tensor,
|
| 273 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 274 |
+
"""
|
| 275 |
+
Convert model output to:
|
| 276 |
+
|
| 277 |
+
z_0 prediction
|
| 278 |
+
epsilon prediction
|
| 279 |
+
"""
|
| 280 |
+
return model_output_to_x0_and_eps(
|
| 281 |
+
model_output=model_output,
|
| 282 |
+
z_t=z_t,
|
| 283 |
+
t=t,
|
| 284 |
+
schedule=self.schedule,
|
| 285 |
+
prediction_type=self.prediction_type,
|
| 286 |
+
)
|
| 287 |
+
|
| 288 |
+
def q_posterior(
|
| 289 |
+
self,
|
| 290 |
+
z_0: torch.Tensor,
|
| 291 |
+
z_t: torch.Tensor,
|
| 292 |
+
t: torch.Tensor,
|
| 293 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 294 |
+
"""
|
| 295 |
+
Compute posterior:
|
| 296 |
+
|
| 297 |
+
q(z_{t-1} | z_t, z_0)
|
| 298 |
+
|
| 299 |
+
Returns:
|
| 300 |
+
posterior_mean
|
| 301 |
+
posterior_variance
|
| 302 |
+
posterior_log_variance_clipped
|
| 303 |
+
"""
|
| 304 |
+
posterior_mean_coef1 = extract(
|
| 305 |
+
self.schedule.posterior_mean_coef1,
|
| 306 |
+
t,
|
| 307 |
+
z_t.shape,
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
posterior_mean_coef2 = extract(
|
| 311 |
+
self.schedule.posterior_mean_coef2,
|
| 312 |
+
t,
|
| 313 |
+
z_t.shape,
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
posterior_mean = (
|
| 317 |
+
posterior_mean_coef1 * z_0
|
| 318 |
+
+ posterior_mean_coef2 * z_t
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
posterior_variance = extract(
|
| 322 |
+
self.schedule.posterior_variance,
|
| 323 |
+
t,
|
| 324 |
+
z_t.shape,
|
| 325 |
+
)
|
| 326 |
+
|
| 327 |
+
posterior_log_variance_clipped = extract(
|
| 328 |
+
self.schedule.posterior_log_variance_clipped,
|
| 329 |
+
t,
|
| 330 |
+
z_t.shape,
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
return (
|
| 334 |
+
posterior_mean,
|
| 335 |
+
posterior_variance,
|
| 336 |
+
posterior_log_variance_clipped,
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
@torch.no_grad()
|
| 340 |
+
def p_mean_variance(
|
| 341 |
+
self,
|
| 342 |
+
model,
|
| 343 |
+
z_t: torch.Tensor,
|
| 344 |
+
t: torch.Tensor,
|
| 345 |
+
context: torch.Tensor | None = None,
|
| 346 |
+
clip_denoised: bool = False,
|
| 347 |
+
model_kwargs: dict | None = None,
|
| 348 |
+
) -> dict[str, torch.Tensor]:
|
| 349 |
+
"""
|
| 350 |
+
One reverse-process prediction.
|
| 351 |
+
|
| 352 |
+
Model predicts v/eps/x0.
|
| 353 |
+
We convert to predicted z_0
|
| 354 |
+
"""
|
| 355 |
+
if model_kwargs is None:
|
| 356 |
+
model_kwargs = {}
|
| 357 |
+
|
| 358 |
+
if context is None:
|
| 359 |
+
model_output = model(
|
| 360 |
+
z_t,
|
| 361 |
+
t,
|
| 362 |
+
**model_kwargs,
|
| 363 |
+
)
|
| 364 |
+
else:
|
| 365 |
+
model_output = model(
|
| 366 |
+
z_t,
|
| 367 |
+
t,
|
| 368 |
+
context=context,
|
| 369 |
+
**model_kwargs,
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
pred_z0, pred_eps = self.predict_x0_and_eps(
|
| 373 |
+
model_output=model_output,
|
| 374 |
+
z_t=z_t,
|
| 375 |
+
t=t,
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
if clip_denoised:
|
| 379 |
+
pred_z0 = pred_z0.clamp(-1.0, 1.0)
|
| 380 |
+
|
| 381 |
+
(
|
| 382 |
+
posterior_mean,
|
| 383 |
+
posterior_variance,
|
| 384 |
+
posterior_log_variance,
|
| 385 |
+
) = self.q_posterior(
|
| 386 |
+
z_0=pred_z0,
|
| 387 |
+
z_t=z_t,
|
| 388 |
+
t=t,
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
return {
|
| 392 |
+
"mean": posterior_mean,
|
| 393 |
+
"variance": posterior_variance,
|
| 394 |
+
"log_variance": posterior_log_variance,
|
| 395 |
+
"pred_z0": pred_z0,
|
| 396 |
+
"pred_eps": pred_eps,
|
| 397 |
+
"model_output": model_output,
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
@torch.no_grad()
|
| 401 |
+
def p_sample(
|
| 402 |
+
self,
|
| 403 |
+
model,
|
| 404 |
+
z_t: torch.Tensor,
|
| 405 |
+
t: torch.Tensor,
|
| 406 |
+
context: torch.Tensor | None = None,
|
| 407 |
+
clip_denoised: bool = False,
|
| 408 |
+
model_kwargs: dict | None = None,
|
| 409 |
+
) -> torch.Tensor:
|
| 410 |
+
"""
|
| 411 |
+
This is one reverse step
|
| 412 |
+
"""
|
| 413 |
+
out = self.p_mean_variance(
|
| 414 |
+
model=model,
|
| 415 |
+
z_t=z_t,
|
| 416 |
+
t=t,
|
| 417 |
+
context=context,
|
| 418 |
+
clip_denoised=clip_denoised,
|
| 419 |
+
model_kwargs=model_kwargs,
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
noise = torch.randn_like(z_t)
|
| 423 |
+
|
| 424 |
+
# No noise when t == 0.
|
| 425 |
+
nonzero_mask = (t != 0).float()
|
| 426 |
+
|
| 427 |
+
while len(nonzero_mask.shape) < len(z_t.shape):
|
| 428 |
+
nonzero_mask = nonzero_mask[..., None]
|
| 429 |
+
|
| 430 |
+
z_prev = (
|
| 431 |
+
out["mean"]
|
| 432 |
+
+ nonzero_mask
|
| 433 |
+
* torch.exp(0.5 * out["log_variance"])
|
| 434 |
+
* noise
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
return z_prev
|
src/diffusion/noise_schedule.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@dataclass
|
| 10 |
+
class NoiseSchedule:
|
| 11 |
+
"""
|
| 12 |
+
Precomputed DDPM noise schedule.
|
| 13 |
+
|
| 14 |
+
Main variables:
|
| 15 |
+
|
| 16 |
+
beta_t:
|
| 17 |
+
amount of noise added at timestep t
|
| 18 |
+
|
| 19 |
+
alpha_t:
|
| 20 |
+
1 - beta_t
|
| 21 |
+
|
| 22 |
+
alpha_bar_t:
|
| 23 |
+
cumulative product of alphas up to t
|
| 24 |
+
|
| 25 |
+
q(z_t | z_0):
|
| 26 |
+
z_t = sqrt(alpha_bar_t) * z_0
|
| 27 |
+
+ sqrt(1 - alpha_bar_t) * eps
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
betas: torch.Tensor
|
| 31 |
+
alphas: torch.Tensor
|
| 32 |
+
alphas_cumprod: torch.Tensor
|
| 33 |
+
alphas_cumprod_prev: torch.Tensor
|
| 34 |
+
|
| 35 |
+
sqrt_alphas_cumprod: torch.Tensor
|
| 36 |
+
sqrt_one_minus_alphas_cumprod: torch.Tensor
|
| 37 |
+
|
| 38 |
+
log_one_minus_alphas_cumprod: torch.Tensor
|
| 39 |
+
|
| 40 |
+
sqrt_recip_alphas_cumprod: torch.Tensor
|
| 41 |
+
sqrt_recipm1_alphas_cumprod: torch.Tensor
|
| 42 |
+
|
| 43 |
+
posterior_variance: torch.Tensor
|
| 44 |
+
posterior_log_variance_clipped: torch.Tensor
|
| 45 |
+
posterior_mean_coef1: torch.Tensor
|
| 46 |
+
posterior_mean_coef2: torch.Tensor
|
| 47 |
+
|
| 48 |
+
num_timesteps: int
|
| 49 |
+
schedule_type: str
|
| 50 |
+
|
| 51 |
+
def to(self, device: torch.device | str) -> "NoiseSchedule":
|
| 52 |
+
device = torch.device(device)
|
| 53 |
+
|
| 54 |
+
return NoiseSchedule(
|
| 55 |
+
betas=self.betas.to(device),
|
| 56 |
+
alphas=self.alphas.to(device),
|
| 57 |
+
alphas_cumprod=self.alphas_cumprod.to(device),
|
| 58 |
+
alphas_cumprod_prev=self.alphas_cumprod_prev.to(device),
|
| 59 |
+
sqrt_alphas_cumprod=self.sqrt_alphas_cumprod.to(device),
|
| 60 |
+
sqrt_one_minus_alphas_cumprod=self.sqrt_one_minus_alphas_cumprod.to(device),
|
| 61 |
+
log_one_minus_alphas_cumprod=self.log_one_minus_alphas_cumprod.to(device),
|
| 62 |
+
sqrt_recip_alphas_cumprod=self.sqrt_recip_alphas_cumprod.to(device),
|
| 63 |
+
sqrt_recipm1_alphas_cumprod=self.sqrt_recipm1_alphas_cumprod.to(device),
|
| 64 |
+
posterior_variance=self.posterior_variance.to(device),
|
| 65 |
+
posterior_log_variance_clipped=self.posterior_log_variance_clipped.to(device),
|
| 66 |
+
posterior_mean_coef1=self.posterior_mean_coef1.to(device),
|
| 67 |
+
posterior_mean_coef2=self.posterior_mean_coef2.to(device),
|
| 68 |
+
num_timesteps=self.num_timesteps,
|
| 69 |
+
schedule_type=self.schedule_type,
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def make_beta_schedule(
|
| 74 |
+
schedule_type: str = "cosine",
|
| 75 |
+
num_timesteps: int = 1000,
|
| 76 |
+
beta_start: float = 1e-4,
|
| 77 |
+
beta_end: float = 2e-2,
|
| 78 |
+
cosine_s: float = 0.008,
|
| 79 |
+
max_beta: float = 0.999,
|
| 80 |
+
) -> torch.Tensor:
|
| 81 |
+
"""
|
| 82 |
+
Create beta schedule.
|
| 83 |
+
|
| 84 |
+
Supported:
|
| 85 |
+
linear:
|
| 86 |
+
Standard DDPM linear beta schedule.
|
| 87 |
+
|
| 88 |
+
cosine:
|
| 89 |
+
Improved DDPM cosine schedule.
|
| 90 |
+
Usually better behaved and good default for v-prediction.
|
| 91 |
+
|
| 92 |
+
Returns:
|
| 93 |
+
betas: [num_timesteps], float32
|
| 94 |
+
"""
|
| 95 |
+
schedule_type = schedule_type.lower()
|
| 96 |
+
|
| 97 |
+
if schedule_type == "linear":
|
| 98 |
+
betas = torch.linspace(
|
| 99 |
+
beta_start,
|
| 100 |
+
beta_end,
|
| 101 |
+
num_timesteps,
|
| 102 |
+
dtype=torch.float64,
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
elif schedule_type == "cosine":
|
| 106 |
+
betas = cosine_beta_schedule(
|
| 107 |
+
num_timesteps=num_timesteps,
|
| 108 |
+
cosine_s=cosine_s,
|
| 109 |
+
max_beta=max_beta,
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
else:
|
| 113 |
+
raise ValueError(
|
| 114 |
+
f"Unknown schedule_type={schedule_type}. "
|
| 115 |
+
"Use 'linear' or 'cosine'."
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
return betas.float()
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def cosine_beta_schedule(
|
| 122 |
+
num_timesteps: int,
|
| 123 |
+
cosine_s: float = 0.008,
|
| 124 |
+
max_beta: float = 0.999,
|
| 125 |
+
) -> torch.Tensor:
|
| 126 |
+
"""
|
| 127 |
+
Cosine beta schedule
|
| 128 |
+
Instead of directly defining beta_t, we define alpha_bar(t)
|
| 129 |
+
using a cosine curve, then derive beta_t.
|
| 130 |
+
"""
|
| 131 |
+
steps = num_timesteps + 1
|
| 132 |
+
|
| 133 |
+
x = torch.linspace(
|
| 134 |
+
0,
|
| 135 |
+
num_timesteps,
|
| 136 |
+
steps,
|
| 137 |
+
dtype=torch.float64,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
alphas_cumprod = torch.cos(
|
| 141 |
+
((x / num_timesteps) + cosine_s)
|
| 142 |
+
/ (1.0 + cosine_s)
|
| 143 |
+
* math.pi
|
| 144 |
+
* 0.5
|
| 145 |
+
) ** 2
|
| 146 |
+
|
| 147 |
+
alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
|
| 148 |
+
|
| 149 |
+
betas = 1.0 - (
|
| 150 |
+
alphas_cumprod[1:] / alphas_cumprod[:-1]
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
betas = torch.clamp(
|
| 154 |
+
betas,
|
| 155 |
+
min=1e-8,
|
| 156 |
+
max=max_beta,
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
return betas
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def create_noise_schedule(
|
| 163 |
+
schedule_type: str = "cosine",
|
| 164 |
+
num_timesteps: int = 1000,
|
| 165 |
+
beta_start: float = 1e-4,
|
| 166 |
+
beta_end: float = 2e-2,
|
| 167 |
+
cosine_s: float = 0.008,
|
| 168 |
+
max_beta: float = 0.999,
|
| 169 |
+
) -> NoiseSchedule:
|
| 170 |
+
"""
|
| 171 |
+
all precomputed schedule tensors needed for DDPM training and sampling.
|
| 172 |
+
"""
|
| 173 |
+
betas = make_beta_schedule(
|
| 174 |
+
schedule_type=schedule_type,
|
| 175 |
+
num_timesteps=num_timesteps,
|
| 176 |
+
beta_start=beta_start,
|
| 177 |
+
beta_end=beta_end,
|
| 178 |
+
cosine_s=cosine_s,
|
| 179 |
+
max_beta=max_beta,
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
alphas = 1.0 - betas
|
| 183 |
+
|
| 184 |
+
alphas_cumprod = torch.cumprod(
|
| 185 |
+
alphas,
|
| 186 |
+
dim=0,
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
alphas_cumprod_prev = torch.cat(
|
| 190 |
+
[
|
| 191 |
+
torch.ones(1, dtype=alphas_cumprod.dtype),
|
| 192 |
+
alphas_cumprod[:-1],
|
| 193 |
+
],
|
| 194 |
+
dim=0,
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
sqrt_alphas_cumprod = torch.sqrt(alphas_cumprod)
|
| 198 |
+
|
| 199 |
+
sqrt_one_minus_alphas_cumprod = torch.sqrt(
|
| 200 |
+
1.0 - alphas_cumprod
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
log_one_minus_alphas_cumprod = torch.log(
|
| 204 |
+
torch.clamp(
|
| 205 |
+
1.0 - alphas_cumprod,
|
| 206 |
+
min=1e-20,
|
| 207 |
+
)
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
sqrt_recip_alphas_cumprod = torch.sqrt(
|
| 211 |
+
1.0 / alphas_cumprod
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
sqrt_recipm1_alphas_cumprod = torch.sqrt(
|
| 215 |
+
1.0 / alphas_cumprod - 1.0
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
# Posterior q(z_{t-1} | z_t, z_0)
|
| 219 |
+
posterior_variance = (
|
| 220 |
+
betas
|
| 221 |
+
* (1.0 - alphas_cumprod_prev)
|
| 222 |
+
/ (1.0 - alphas_cumprod)
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
posterior_log_variance_clipped = torch.log(
|
| 226 |
+
torch.clamp(
|
| 227 |
+
posterior_variance,
|
| 228 |
+
min=1e-20,
|
| 229 |
+
)
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
posterior_mean_coef1 = (
|
| 233 |
+
betas
|
| 234 |
+
* torch.sqrt(alphas_cumprod_prev)
|
| 235 |
+
/ (1.0 - alphas_cumprod)
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
posterior_mean_coef2 = (
|
| 239 |
+
(1.0 - alphas_cumprod_prev)
|
| 240 |
+
* torch.sqrt(alphas)
|
| 241 |
+
/ (1.0 - alphas_cumprod)
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
return NoiseSchedule(
|
| 245 |
+
betas=betas,
|
| 246 |
+
alphas=alphas,
|
| 247 |
+
alphas_cumprod=alphas_cumprod,
|
| 248 |
+
alphas_cumprod_prev=alphas_cumprod_prev,
|
| 249 |
+
sqrt_alphas_cumprod=sqrt_alphas_cumprod,
|
| 250 |
+
sqrt_one_minus_alphas_cumprod=sqrt_one_minus_alphas_cumprod,
|
| 251 |
+
log_one_minus_alphas_cumprod=log_one_minus_alphas_cumprod,
|
| 252 |
+
sqrt_recip_alphas_cumprod=sqrt_recip_alphas_cumprod,
|
| 253 |
+
sqrt_recipm1_alphas_cumprod=sqrt_recipm1_alphas_cumprod,
|
| 254 |
+
posterior_variance=posterior_variance,
|
| 255 |
+
posterior_log_variance_clipped=posterior_log_variance_clipped,
|
| 256 |
+
posterior_mean_coef1=posterior_mean_coef1,
|
| 257 |
+
posterior_mean_coef2=posterior_mean_coef2,
|
| 258 |
+
num_timesteps=num_timesteps,
|
| 259 |
+
schedule_type=schedule_type,
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def extract(
|
| 264 |
+
values: torch.Tensor,
|
| 265 |
+
timesteps: torch.Tensor,
|
| 266 |
+
broadcast_shape: tuple[int, ...],
|
| 267 |
+
) -> torch.Tensor:
|
| 268 |
+
"""
|
| 269 |
+
Extract values[t] and reshape for broadcasting.
|
| 270 |
+
|
| 271 |
+
Args:
|
| 272 |
+
values:
|
| 273 |
+
Schedule tensor with shape [T].
|
| 274 |
+
|
| 275 |
+
timesteps:
|
| 276 |
+
Long tensor with shape [B].
|
| 277 |
+
|
| 278 |
+
broadcast_shape:
|
| 279 |
+
Shape of target tensor, e.g. z_t.shape = [B, C, H, W].
|
| 280 |
+
|
| 281 |
+
Returns:
|
| 282 |
+
Tensor with shape [B, 1, 1, 1], broadcastable to broadcast_shape.
|
| 283 |
+
|
| 284 |
+
Example:
|
| 285 |
+
sqrt_alpha_bar_t = extract(
|
| 286 |
+
schedule.sqrt_alphas_cumprod,
|
| 287 |
+
t,
|
| 288 |
+
z_0.shape,
|
| 289 |
+
)
|
| 290 |
+
"""
|
| 291 |
+
if timesteps.dtype != torch.long:
|
| 292 |
+
timesteps = timesteps.long()
|
| 293 |
+
|
| 294 |
+
out = values.gather(
|
| 295 |
+
dim=0,
|
| 296 |
+
index=timesteps,
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
while len(out.shape) < len(broadcast_shape):
|
| 300 |
+
out = out[..., None]
|
| 301 |
+
|
| 302 |
+
return out
|
src/diffusion/prediction.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from src.diffusion.noise_schedule import NoiseSchedule, extract
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def predict_x0_from_eps(
|
| 9 |
+
z_t: torch.Tensor,
|
| 10 |
+
t: torch.Tensor,
|
| 11 |
+
eps: torch.Tensor,
|
| 12 |
+
schedule: NoiseSchedule,
|
| 13 |
+
) -> torch.Tensor:
|
| 14 |
+
"""
|
| 15 |
+
Recover clean latent z_0 from epsilon prediction
|
| 16 |
+
"""
|
| 17 |
+
sqrt_recip_alpha_bar = extract(
|
| 18 |
+
schedule.sqrt_recip_alphas_cumprod,
|
| 19 |
+
t,
|
| 20 |
+
z_t.shape,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
sqrt_recipm1_alpha_bar = extract(
|
| 24 |
+
schedule.sqrt_recipm1_alphas_cumprod,
|
| 25 |
+
t,
|
| 26 |
+
z_t.shape,
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
return sqrt_recip_alpha_bar * z_t - sqrt_recipm1_alpha_bar * eps
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def predict_eps_from_x0(
|
| 33 |
+
z_t: torch.Tensor,
|
| 34 |
+
t: torch.Tensor,
|
| 35 |
+
z_0: torch.Tensor,
|
| 36 |
+
schedule: NoiseSchedule,
|
| 37 |
+
) -> torch.Tensor:
|
| 38 |
+
"""
|
| 39 |
+
Recover epsilon from clean latent z_0 and noisy latent z_t.
|
| 40 |
+
|
| 41 |
+
eps = (z_t - sqrt(alpha_bar_t) * z_0)
|
| 42 |
+
/ sqrt(1 - alpha_bar_t)
|
| 43 |
+
"""
|
| 44 |
+
sqrt_alpha_bar = extract(
|
| 45 |
+
schedule.sqrt_alphas_cumprod,
|
| 46 |
+
t,
|
| 47 |
+
z_t.shape,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
sqrt_one_minus_alpha_bar = extract(
|
| 51 |
+
schedule.sqrt_one_minus_alphas_cumprod,
|
| 52 |
+
t,
|
| 53 |
+
z_t.shape,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
return (z_t - sqrt_alpha_bar * z_0) / sqrt_one_minus_alpha_bar
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def get_v_target(
|
| 60 |
+
z_0: torch.Tensor,
|
| 61 |
+
eps: torch.Tensor,
|
| 62 |
+
t: torch.Tensor,
|
| 63 |
+
schedule: NoiseSchedule,
|
| 64 |
+
) -> torch.Tensor:
|
| 65 |
+
"""
|
| 66 |
+
Compute v-prediction target.
|
| 67 |
+
|
| 68 |
+
v-prediction target:
|
| 69 |
+
|
| 70 |
+
v = sqrt(alpha_bar_t) * eps
|
| 71 |
+
- sqrt(1 - alpha_bar_t) * z_0
|
| 72 |
+
"""
|
| 73 |
+
sqrt_alpha_bar = extract(
|
| 74 |
+
schedule.sqrt_alphas_cumprod,
|
| 75 |
+
t,
|
| 76 |
+
z_0.shape,
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
sqrt_one_minus_alpha_bar = extract(
|
| 80 |
+
schedule.sqrt_one_minus_alphas_cumprod,
|
| 81 |
+
t,
|
| 82 |
+
z_0.shape,
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
v = sqrt_alpha_bar * eps - sqrt_one_minus_alpha_bar * z_0
|
| 86 |
+
|
| 87 |
+
return v
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def predict_x0_from_v(
|
| 91 |
+
z_t: torch.Tensor,
|
| 92 |
+
t: torch.Tensor,
|
| 93 |
+
v: torch.Tensor,
|
| 94 |
+
schedule: NoiseSchedule,
|
| 95 |
+
) -> torch.Tensor:
|
| 96 |
+
"""
|
| 97 |
+
Recover clean latent z_0 from v prediction
|
| 98 |
+
|
| 99 |
+
defs:
|
| 100 |
+
|
| 101 |
+
z_t = a * z_0 + b * eps
|
| 102 |
+
v = a * eps - b * z_0
|
| 103 |
+
|
| 104 |
+
a = sqrt(alpha_bar_t)
|
| 105 |
+
b = sqrt(1 - alpha_bar_t)
|
| 106 |
+
|
| 107 |
+
it gives
|
| 108 |
+
|
| 109 |
+
z_0 = a * z_t - b * v
|
| 110 |
+
"""
|
| 111 |
+
sqrt_alpha_bar = extract(
|
| 112 |
+
schedule.sqrt_alphas_cumprod,
|
| 113 |
+
t,
|
| 114 |
+
z_t.shape,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
sqrt_one_minus_alpha_bar = extract(
|
| 118 |
+
schedule.sqrt_one_minus_alphas_cumprod,
|
| 119 |
+
t,
|
| 120 |
+
z_t.shape,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
z_0 = sqrt_alpha_bar * z_t - sqrt_one_minus_alpha_bar * v
|
| 124 |
+
|
| 125 |
+
return z_0
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def predict_eps_from_v(
|
| 129 |
+
z_t: torch.Tensor,
|
| 130 |
+
t: torch.Tensor,
|
| 131 |
+
v: torch.Tensor,
|
| 132 |
+
schedule: NoiseSchedule,
|
| 133 |
+
) -> torch.Tensor:
|
| 134 |
+
"""
|
| 135 |
+
Recover epsilon from v prediction
|
| 136 |
+
|
| 137 |
+
defs:
|
| 138 |
+
|
| 139 |
+
z_t = a * z_0 + b * eps
|
| 140 |
+
v = a * eps - b * z_0
|
| 141 |
+
|
| 142 |
+
it gives
|
| 143 |
+
|
| 144 |
+
eps = b * z_t + a * v
|
| 145 |
+
"""
|
| 146 |
+
sqrt_alpha_bar = extract(
|
| 147 |
+
schedule.sqrt_alphas_cumprod,
|
| 148 |
+
t,
|
| 149 |
+
z_t.shape,
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
sqrt_one_minus_alpha_bar = extract(
|
| 153 |
+
schedule.sqrt_one_minus_alphas_cumprod,
|
| 154 |
+
t,
|
| 155 |
+
z_t.shape,
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
eps = sqrt_one_minus_alpha_bar * z_t + sqrt_alpha_bar * v
|
| 159 |
+
|
| 160 |
+
return eps
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def model_output_to_x0_and_eps(
|
| 164 |
+
model_output: torch.Tensor,
|
| 165 |
+
z_t: torch.Tensor,
|
| 166 |
+
t: torch.Tensor,
|
| 167 |
+
schedule: NoiseSchedule,
|
| 168 |
+
prediction_type: str = "v",
|
| 169 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 170 |
+
"""
|
| 171 |
+
Convert model output into both:
|
| 172 |
+
z_0 prediction
|
| 173 |
+
epsilon prediction
|
| 174 |
+
"""
|
| 175 |
+
prediction_type = prediction_type.lower()
|
| 176 |
+
|
| 177 |
+
if prediction_type in {"v", "v_prediction"}:
|
| 178 |
+
z_0 = predict_x0_from_v(
|
| 179 |
+
z_t=z_t,
|
| 180 |
+
t=t,
|
| 181 |
+
v=model_output,
|
| 182 |
+
schedule=schedule,
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
eps = predict_eps_from_v(
|
| 186 |
+
z_t=z_t,
|
| 187 |
+
t=t,
|
| 188 |
+
v=model_output,
|
| 189 |
+
schedule=schedule,
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
elif prediction_type in {"eps", "epsilon"}:
|
| 193 |
+
eps = model_output
|
| 194 |
+
|
| 195 |
+
z_0 = predict_x0_from_eps(
|
| 196 |
+
z_t=z_t,
|
| 197 |
+
t=t,
|
| 198 |
+
eps=eps,
|
| 199 |
+
schedule=schedule,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
elif prediction_type in {"x0", "sample"}:
|
| 203 |
+
z_0 = model_output
|
| 204 |
+
|
| 205 |
+
eps = predict_eps_from_x0(
|
| 206 |
+
z_t=z_t,
|
| 207 |
+
t=t,
|
| 208 |
+
z_0=z_0,
|
| 209 |
+
schedule=schedule,
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
else:
|
| 213 |
+
raise ValueError(
|
| 214 |
+
f"Unknown prediction_type={prediction_type}. "
|
| 215 |
+
"Use 'v', 'eps', or 'x0'."
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
return z_0, eps
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def get_training_target(
|
| 222 |
+
z_0: torch.Tensor,
|
| 223 |
+
eps: torch.Tensor,
|
| 224 |
+
t: torch.Tensor,
|
| 225 |
+
schedule: NoiseSchedule,
|
| 226 |
+
prediction_type: str = "v",
|
| 227 |
+
) -> torch.Tensor:
|
| 228 |
+
"""
|
| 229 |
+
Return the target the U-Net should learn
|
| 230 |
+
|
| 231 |
+
For our project, default is:
|
| 232 |
+
|
| 233 |
+
prediction_type = "v"
|
| 234 |
+
|
| 235 |
+
Then target is:
|
| 236 |
+
|
| 237 |
+
v = sqrt(alpha_bar_t) * eps
|
| 238 |
+
- sqrt(1 - alpha_bar_t) * z_0
|
| 239 |
+
"""
|
| 240 |
+
prediction_type = prediction_type.lower()
|
| 241 |
+
|
| 242 |
+
if prediction_type in {"v", "v_prediction"}:
|
| 243 |
+
return get_v_target(
|
| 244 |
+
z_0=z_0,
|
| 245 |
+
eps=eps,
|
| 246 |
+
t=t,
|
| 247 |
+
schedule=schedule,
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
if prediction_type in {"eps", "epsilon"}:
|
| 251 |
+
return eps
|
| 252 |
+
|
| 253 |
+
if prediction_type in {"x0", "sample"}:
|
| 254 |
+
return z_0
|
| 255 |
+
|
| 256 |
+
raise ValueError(
|
| 257 |
+
f"Unknown prediction_type={prediction_type}. "
|
| 258 |
+
"Use 'v', 'eps', or 'x0'."
|
| 259 |
+
)
|
src/diffusion/samplers/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.diffusion.samplers.ddpm import DDPMSampler, DDPMSamplerOutput
|
| 2 |
+
from src.diffusion.samplers.ddim import DDIMSampler, DDIMSamplerOutput
|
| 3 |
+
|
| 4 |
+
__all__ = [
|
| 5 |
+
"DDPMSampler",
|
| 6 |
+
"DDPMSamplerOutput",
|
| 7 |
+
"DDIMSampler",
|
| 8 |
+
"DDIMSamplerOutput",
|
| 9 |
+
]
|
src/diffusion/samplers/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (503 Bytes). View file
|
|
|
src/diffusion/samplers/__pycache__/ddim.cpython-311.pyc
ADDED
|
Binary file (7.42 kB). View file
|
|
|
src/diffusion/samplers/__pycache__/ddpm.cpython-311.pyc
ADDED
|
Binary file (6.81 kB). View file
|
|
|
src/diffusion/samplers/ddim.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
|
| 8 |
+
from src.diffusion.gaussian_diffusion import GaussianDiffusion
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class DDIMSamplerOutput:
|
| 13 |
+
latents: torch.Tensor
|
| 14 |
+
trajectory: list[torch.Tensor] | None = None
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DDIMSampler:
|
| 18 |
+
"""
|
| 19 |
+
DDIM sampler.
|
| 20 |
+
|
| 21 |
+
eta controls stochasticity:
|
| 22 |
+
|
| 23 |
+
eta = 0.0 -> deterministic DDIM
|
| 24 |
+
eta > 0.0 -> more stochastic
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
def __init__(
|
| 28 |
+
self,
|
| 29 |
+
diffusion: GaussianDiffusion,
|
| 30 |
+
):
|
| 31 |
+
self.diffusion = diffusion
|
| 32 |
+
|
| 33 |
+
def make_timesteps(
|
| 34 |
+
self,
|
| 35 |
+
num_steps: int,
|
| 36 |
+
device: torch.device | str,
|
| 37 |
+
) -> torch.Tensor:
|
| 38 |
+
"""
|
| 39 |
+
Select evenly spaced timesteps from the original diffusion schedule.
|
| 40 |
+
|
| 41 |
+
Example:
|
| 42 |
+
original T = 1000
|
| 43 |
+
num_steps = 50
|
| 44 |
+
|
| 45 |
+
returns 50 timesteps descending from high noise to low noise.
|
| 46 |
+
"""
|
| 47 |
+
if num_steps > self.diffusion.num_timesteps:
|
| 48 |
+
raise ValueError(
|
| 49 |
+
f"num_steps={num_steps} cannot be larger than "
|
| 50 |
+
f"num_timesteps={self.diffusion.num_timesteps}"
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
timesteps = torch.linspace(
|
| 54 |
+
0,
|
| 55 |
+
self.diffusion.num_timesteps - 1,
|
| 56 |
+
steps=num_steps,
|
| 57 |
+
device=device,
|
| 58 |
+
).long()
|
| 59 |
+
|
| 60 |
+
timesteps = torch.flip(timesteps, dims=[0])
|
| 61 |
+
|
| 62 |
+
return timesteps
|
| 63 |
+
|
| 64 |
+
@torch.no_grad()
|
| 65 |
+
def predict_model_output(
|
| 66 |
+
self,
|
| 67 |
+
model,
|
| 68 |
+
z_t: torch.Tensor,
|
| 69 |
+
t: torch.Tensor,
|
| 70 |
+
context: torch.Tensor | None = None,
|
| 71 |
+
attention_mask: torch.Tensor | None = None,
|
| 72 |
+
uncond_context: torch.Tensor | None = None,
|
| 73 |
+
uncond_attention_mask: torch.Tensor | None = None,
|
| 74 |
+
guidance_scale: float = 1.0,
|
| 75 |
+
) -> torch.Tensor:
|
| 76 |
+
"""
|
| 77 |
+
Predict v/eps/x0 with optional classifier-free guidance.
|
| 78 |
+
"""
|
| 79 |
+
if uncond_context is None or guidance_scale == 1.0:
|
| 80 |
+
if context is None:
|
| 81 |
+
return model(
|
| 82 |
+
z_t,
|
| 83 |
+
t,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
return model(
|
| 87 |
+
z_t,
|
| 88 |
+
t,
|
| 89 |
+
context=context,
|
| 90 |
+
attention_mask=attention_mask,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
cond_output = model(
|
| 94 |
+
z_t,
|
| 95 |
+
t,
|
| 96 |
+
context=context,
|
| 97 |
+
attention_mask=attention_mask,
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
uncond_output = model(
|
| 101 |
+
z_t,
|
| 102 |
+
t,
|
| 103 |
+
context=uncond_context,
|
| 104 |
+
attention_mask=uncond_attention_mask,
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
return uncond_output + guidance_scale * (cond_output - uncond_output)
|
| 108 |
+
|
| 109 |
+
@torch.no_grad()
|
| 110 |
+
def sample(
|
| 111 |
+
self,
|
| 112 |
+
model,
|
| 113 |
+
shape: tuple[int, int, int, int],
|
| 114 |
+
device: torch.device | str,
|
| 115 |
+
context: torch.Tensor | None = None,
|
| 116 |
+
attention_mask: torch.Tensor | None = None,
|
| 117 |
+
uncond_context: torch.Tensor | None = None,
|
| 118 |
+
uncond_attention_mask: torch.Tensor | None = None,
|
| 119 |
+
guidance_scale: float = 1.0,
|
| 120 |
+
num_steps: int = 50,
|
| 121 |
+
eta: float = 0.0,
|
| 122 |
+
clip_denoised: bool = False,
|
| 123 |
+
return_trajectory: bool = False,
|
| 124 |
+
progress: bool = True,
|
| 125 |
+
) -> DDIMSamplerOutput:
|
| 126 |
+
"""
|
| 127 |
+
DDIM sampling.
|
| 128 |
+
|
| 129 |
+
Returns:
|
| 130 |
+
clean latent estimate z_0 at the final step.
|
| 131 |
+
"""
|
| 132 |
+
device = torch.device(device)
|
| 133 |
+
model.eval()
|
| 134 |
+
|
| 135 |
+
z_t = torch.randn(
|
| 136 |
+
shape,
|
| 137 |
+
device=device,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
trajectory = [] if return_trajectory else None
|
| 141 |
+
|
| 142 |
+
ddim_timesteps = self.make_timesteps(
|
| 143 |
+
num_steps=num_steps,
|
| 144 |
+
device=device,
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
if progress:
|
| 148 |
+
iterator = tqdm(
|
| 149 |
+
range(len(ddim_timesteps)),
|
| 150 |
+
desc=f"DDIM sampling ({num_steps} steps)",
|
| 151 |
+
)
|
| 152 |
+
else:
|
| 153 |
+
iterator = range(len(ddim_timesteps))
|
| 154 |
+
|
| 155 |
+
for i in iterator:
|
| 156 |
+
step = ddim_timesteps[i]
|
| 157 |
+
|
| 158 |
+
t = torch.full(
|
| 159 |
+
(shape[0],),
|
| 160 |
+
int(step.item()),
|
| 161 |
+
device=device,
|
| 162 |
+
dtype=torch.long,
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
if i == len(ddim_timesteps) - 1:
|
| 166 |
+
prev_step = torch.tensor(
|
| 167 |
+
-1,
|
| 168 |
+
device=device,
|
| 169 |
+
dtype=torch.long,
|
| 170 |
+
)
|
| 171 |
+
else:
|
| 172 |
+
prev_step = ddim_timesteps[i + 1]
|
| 173 |
+
|
| 174 |
+
model_output = self.predict_model_output(
|
| 175 |
+
model=model,
|
| 176 |
+
z_t=z_t,
|
| 177 |
+
t=t,
|
| 178 |
+
context=context,
|
| 179 |
+
attention_mask=attention_mask,
|
| 180 |
+
uncond_context=uncond_context,
|
| 181 |
+
uncond_attention_mask=uncond_attention_mask,
|
| 182 |
+
guidance_scale=guidance_scale,
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
pred_z0, pred_eps = self.diffusion.predict_x0_and_eps(
|
| 186 |
+
model_output=model_output,
|
| 187 |
+
z_t=z_t,
|
| 188 |
+
t=t,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
if clip_denoised:
|
| 192 |
+
pred_z0 = pred_z0.clamp(-1.0, 1.0)
|
| 193 |
+
|
| 194 |
+
alpha_t = self.diffusion.schedule.alphas_cumprod[t]
|
| 195 |
+
alpha_t = alpha_t.view(shape[0], 1, 1, 1)
|
| 196 |
+
|
| 197 |
+
if prev_step.item() < 0:
|
| 198 |
+
alpha_prev = torch.ones_like(alpha_t)
|
| 199 |
+
else:
|
| 200 |
+
alpha_prev = self.diffusion.schedule.alphas_cumprod[
|
| 201 |
+
torch.full(
|
| 202 |
+
(shape[0],),
|
| 203 |
+
int(prev_step.item()),
|
| 204 |
+
device=device,
|
| 205 |
+
dtype=torch.long,
|
| 206 |
+
)
|
| 207 |
+
]
|
| 208 |
+
alpha_prev = alpha_prev.view(shape[0], 1, 1, 1)
|
| 209 |
+
|
| 210 |
+
sigma_t = eta * torch.sqrt(
|
| 211 |
+
(1.0 - alpha_prev)
|
| 212 |
+
/ (1.0 - alpha_t)
|
| 213 |
+
* (1.0 - alpha_t / alpha_prev)
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
# Direction pointing to z_t.
|
| 217 |
+
dir_xt = torch.sqrt(
|
| 218 |
+
torch.clamp(
|
| 219 |
+
1.0 - alpha_prev - sigma_t ** 2,
|
| 220 |
+
min=0.0,
|
| 221 |
+
)
|
| 222 |
+
) * pred_eps
|
| 223 |
+
|
| 224 |
+
noise = sigma_t * torch.randn_like(z_t)
|
| 225 |
+
|
| 226 |
+
z_t = torch.sqrt(alpha_prev) * pred_z0 + dir_xt + noise
|
| 227 |
+
|
| 228 |
+
if return_trajectory:
|
| 229 |
+
trajectory.append(z_t.detach().cpu())
|
| 230 |
+
|
| 231 |
+
return DDIMSamplerOutput(
|
| 232 |
+
latents=z_t,
|
| 233 |
+
trajectory=trajectory,
|
| 234 |
+
)
|
src/diffusion/samplers/ddpm.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
|
| 8 |
+
from src.diffusion.gaussian_diffusion import GaussianDiffusion
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class DDPMSamplerOutput:
|
| 13 |
+
latents: torch.Tensor
|
| 14 |
+
trajectory: list[torch.Tensor] | None = None
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DDPMSampler:
|
| 18 |
+
"""
|
| 19 |
+
DDPM sampler.
|
| 20 |
+
|
| 21 |
+
This sampler uses the learned reverse process:
|
| 22 |
+
|
| 23 |
+
z_T ~ N(0, I)
|
| 24 |
+
z_T -> z_{T-1} -> ... -> z_0
|
| 25 |
+
|
| 26 |
+
Supports classifier-free guidance if both conditional and unconditional
|
| 27 |
+
context are provided.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
diffusion: GaussianDiffusion,
|
| 33 |
+
):
|
| 34 |
+
self.diffusion = diffusion
|
| 35 |
+
|
| 36 |
+
@torch.no_grad()
|
| 37 |
+
def predict_model_output(
|
| 38 |
+
self,
|
| 39 |
+
model,
|
| 40 |
+
z_t: torch.Tensor,
|
| 41 |
+
t: torch.Tensor,
|
| 42 |
+
context: torch.Tensor | None = None,
|
| 43 |
+
attention_mask: torch.Tensor | None = None,
|
| 44 |
+
uncond_context: torch.Tensor | None = None,
|
| 45 |
+
uncond_attention_mask: torch.Tensor | None = None,
|
| 46 |
+
guidance_scale: float = 1.0,
|
| 47 |
+
) -> torch.Tensor:
|
| 48 |
+
"""
|
| 49 |
+
Predict model output with optional classifier-free guidance.
|
| 50 |
+
|
| 51 |
+
If guidance_scale == 1 or uncond_context is None:
|
| 52 |
+
normal conditional prediction.
|
| 53 |
+
|
| 54 |
+
If guidance_scale > 1:
|
| 55 |
+
output = uncond + scale * (cond - uncond)
|
| 56 |
+
"""
|
| 57 |
+
if uncond_context is None or guidance_scale == 1.0:
|
| 58 |
+
if context is None:
|
| 59 |
+
return model(
|
| 60 |
+
z_t,
|
| 61 |
+
t,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
return model(
|
| 65 |
+
z_t,
|
| 66 |
+
t,
|
| 67 |
+
context=context,
|
| 68 |
+
attention_mask=attention_mask,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# Conditional prediction
|
| 72 |
+
cond_output = model(
|
| 73 |
+
z_t,
|
| 74 |
+
t,
|
| 75 |
+
context=context,
|
| 76 |
+
attention_mask=attention_mask,
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
# Unconditional prediction
|
| 80 |
+
uncond_output = model(
|
| 81 |
+
z_t,
|
| 82 |
+
t,
|
| 83 |
+
context=uncond_context,
|
| 84 |
+
attention_mask=uncond_attention_mask,
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
return uncond_output + guidance_scale * (cond_output - uncond_output)
|
| 88 |
+
|
| 89 |
+
@torch.no_grad()
|
| 90 |
+
def p_mean_variance_with_cfg(
|
| 91 |
+
self,
|
| 92 |
+
model,
|
| 93 |
+
z_t: torch.Tensor,
|
| 94 |
+
t: torch.Tensor,
|
| 95 |
+
context: torch.Tensor | None = None,
|
| 96 |
+
attention_mask: torch.Tensor | None = None,
|
| 97 |
+
uncond_context: torch.Tensor | None = None,
|
| 98 |
+
uncond_attention_mask: torch.Tensor | None = None,
|
| 99 |
+
guidance_scale: float = 1.0,
|
| 100 |
+
clip_denoised: bool = False,
|
| 101 |
+
) -> dict[str, torch.Tensor]:
|
| 102 |
+
"""
|
| 103 |
+
Same as GaussianDiffusion.p_mean_variance, but supports CFG.
|
| 104 |
+
"""
|
| 105 |
+
model_output = self.predict_model_output(
|
| 106 |
+
model=model,
|
| 107 |
+
z_t=z_t,
|
| 108 |
+
t=t,
|
| 109 |
+
context=context,
|
| 110 |
+
attention_mask=attention_mask,
|
| 111 |
+
uncond_context=uncond_context,
|
| 112 |
+
uncond_attention_mask=uncond_attention_mask,
|
| 113 |
+
guidance_scale=guidance_scale,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
pred_z0, pred_eps = self.diffusion.predict_x0_and_eps(
|
| 117 |
+
model_output=model_output,
|
| 118 |
+
z_t=z_t,
|
| 119 |
+
t=t,
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
if clip_denoised:
|
| 123 |
+
pred_z0 = pred_z0.clamp(-1.0, 1.0)
|
| 124 |
+
|
| 125 |
+
(
|
| 126 |
+
posterior_mean,
|
| 127 |
+
posterior_variance,
|
| 128 |
+
posterior_log_variance,
|
| 129 |
+
) = self.diffusion.q_posterior(
|
| 130 |
+
z_0=pred_z0,
|
| 131 |
+
z_t=z_t,
|
| 132 |
+
t=t,
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
return {
|
| 136 |
+
"mean": posterior_mean,
|
| 137 |
+
"variance": posterior_variance,
|
| 138 |
+
"log_variance": posterior_log_variance,
|
| 139 |
+
"pred_z0": pred_z0,
|
| 140 |
+
"pred_eps": pred_eps,
|
| 141 |
+
"model_output": model_output,
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
@torch.no_grad()
|
| 145 |
+
def sample(
|
| 146 |
+
self,
|
| 147 |
+
model,
|
| 148 |
+
shape: tuple[int, int, int, int],
|
| 149 |
+
device: torch.device | str,
|
| 150 |
+
context: torch.Tensor | None = None,
|
| 151 |
+
attention_mask: torch.Tensor | None = None,
|
| 152 |
+
uncond_context: torch.Tensor | None = None,
|
| 153 |
+
uncond_attention_mask: torch.Tensor | None = None,
|
| 154 |
+
guidance_scale: float = 1.0,
|
| 155 |
+
clip_denoised: bool = False,
|
| 156 |
+
return_trajectory: bool = False,
|
| 157 |
+
progress: bool = True,
|
| 158 |
+
) -> DDPMSamplerOutput:
|
| 159 |
+
"""
|
| 160 |
+
Generate clean latents from pure noise.
|
| 161 |
+
|
| 162 |
+
Args:
|
| 163 |
+
shape:
|
| 164 |
+
Usually [B, 8, 32, 32] for your model.
|
| 165 |
+
|
| 166 |
+
context:
|
| 167 |
+
Conditional CLIP text context.
|
| 168 |
+
|
| 169 |
+
uncond_context:
|
| 170 |
+
Empty-prompt CLIP context for CFG.
|
| 171 |
+
|
| 172 |
+
guidance_scale:
|
| 173 |
+
CFG scale. Common values: 3.0 to 7.5.
|
| 174 |
+
"""
|
| 175 |
+
device = torch.device(device)
|
| 176 |
+
|
| 177 |
+
model.eval()
|
| 178 |
+
|
| 179 |
+
z_t = torch.randn(
|
| 180 |
+
shape,
|
| 181 |
+
device=device,
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
trajectory = [] if return_trajectory else None
|
| 185 |
+
|
| 186 |
+
timesteps = reversed(range(self.diffusion.num_timesteps))
|
| 187 |
+
|
| 188 |
+
if progress:
|
| 189 |
+
timesteps = tqdm(
|
| 190 |
+
timesteps,
|
| 191 |
+
total=self.diffusion.num_timesteps,
|
| 192 |
+
desc="DDPM sampling",
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
for step in timesteps:
|
| 196 |
+
t = torch.full(
|
| 197 |
+
(shape[0],),
|
| 198 |
+
step,
|
| 199 |
+
device=device,
|
| 200 |
+
dtype=torch.long,
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
out = self.p_mean_variance_with_cfg(
|
| 204 |
+
model=model,
|
| 205 |
+
z_t=z_t,
|
| 206 |
+
t=t,
|
| 207 |
+
context=context,
|
| 208 |
+
attention_mask=attention_mask,
|
| 209 |
+
uncond_context=uncond_context,
|
| 210 |
+
uncond_attention_mask=uncond_attention_mask,
|
| 211 |
+
guidance_scale=guidance_scale,
|
| 212 |
+
clip_denoised=clip_denoised,
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
noise = torch.randn_like(z_t)
|
| 216 |
+
|
| 217 |
+
nonzero_mask = (t != 0).float()
|
| 218 |
+
|
| 219 |
+
while len(nonzero_mask.shape) < len(z_t.shape):
|
| 220 |
+
nonzero_mask = nonzero_mask[..., None]
|
| 221 |
+
|
| 222 |
+
z_t = (
|
| 223 |
+
out["mean"]
|
| 224 |
+
+ nonzero_mask
|
| 225 |
+
* torch.exp(0.5 * out["log_variance"])
|
| 226 |
+
* noise
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
if return_trajectory:
|
| 230 |
+
trajectory.append(z_t.detach().cpu())
|
| 231 |
+
|
| 232 |
+
return DDPMSamplerOutput(
|
| 233 |
+
latents=z_t,
|
| 234 |
+
trajectory=trajectory,
|
| 235 |
+
)
|
src/losses/__pycache__/diffusion_loss.cpython-311.pyc
ADDED
|
Binary file (8.42 kB). View file
|
|
|
src/losses/__pycache__/vae_loss.cpython-311.pyc
ADDED
|
Binary file (5.78 kB). View file
|
|
|
src/losses/diffusion_loss.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def extract(a: torch.Tensor, t: torch.Tensor, x_shape: torch.Size):
|
| 9 |
+
"""
|
| 10 |
+
Extract coefficients at timestep t
|
| 11 |
+
a: [T]
|
| 12 |
+
t: [B]
|
| 13 |
+
returns: [B, 1, 1, 1]
|
| 14 |
+
"""
|
| 15 |
+
b = t.shape[0]
|
| 16 |
+
out = a.gather(-1, t)
|
| 17 |
+
return out.view(b, *((1,) * (len(x_shape) - 1)))
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class DiffusionLoss(nn.Module):
|
| 21 |
+
"""
|
| 22 |
+
Diffusion loss supporting:
|
| 23 |
+
- epsilon prediction
|
| 24 |
+
- v prediction
|
| 25 |
+
|
| 26 |
+
v-prediction:
|
| 27 |
+
v = alpha_t * epsilon - sigma_t * x0
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
prediction_type: str = "v", # "epsilon" or "v"
|
| 33 |
+
loss_type: str = "mse",
|
| 34 |
+
snr_gamma: float | None = None,
|
| 35 |
+
snr_weighting: str = "none",
|
| 36 |
+
normalize_snr_weights: bool = False,
|
| 37 |
+
eps: float = 1e-8,
|
| 38 |
+
):
|
| 39 |
+
super().__init__()
|
| 40 |
+
|
| 41 |
+
prediction_type = prediction_type.lower()
|
| 42 |
+
loss_type = loss_type.lower()
|
| 43 |
+
snr_weighting = snr_weighting.lower()
|
| 44 |
+
|
| 45 |
+
if prediction_type in {"eps", "epsilon"}:
|
| 46 |
+
prediction_type = "epsilon"
|
| 47 |
+
|
| 48 |
+
elif prediction_type in {"v", "v_prediction"}:
|
| 49 |
+
prediction_type = "v"
|
| 50 |
+
|
| 51 |
+
elif prediction_type in {"x0", "sample"}:
|
| 52 |
+
prediction_type = "x0"
|
| 53 |
+
|
| 54 |
+
else:
|
| 55 |
+
raise ValueError(
|
| 56 |
+
"prediction_type must be 'epsilon', 'v', or 'x0'"
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
if loss_type not in {"mse", "l1", "huber"}:
|
| 60 |
+
raise ValueError(
|
| 61 |
+
"loss_type must be 'mse', 'l1', or 'huber'"
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
if snr_weighting not in {"none", "min_snr"}:
|
| 65 |
+
raise ValueError(
|
| 66 |
+
"snr_weighting must be 'none' or 'min_snr'"
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
if snr_weighting == "min_snr" and snr_gamma is None:
|
| 70 |
+
raise ValueError(
|
| 71 |
+
"snr_gamma must be set when snr_weighting='min_snr'"
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
self.prediction_type = prediction_type
|
| 75 |
+
self.loss_type = loss_type
|
| 76 |
+
self.snr_gamma = snr_gamma
|
| 77 |
+
self.snr_weighting = snr_weighting
|
| 78 |
+
self.normalize_snr_weights = normalize_snr_weights
|
| 79 |
+
self.eps = eps
|
| 80 |
+
|
| 81 |
+
def v_target(self, x0, noise, alpha, sigma):
|
| 82 |
+
return alpha * noise - sigma * x0
|
| 83 |
+
|
| 84 |
+
def epsilon_target(self, x0, noise):
|
| 85 |
+
return noise
|
| 86 |
+
|
| 87 |
+
def x0_target(self, x0):
|
| 88 |
+
return x0
|
| 89 |
+
|
| 90 |
+
def get_target(
|
| 91 |
+
self,
|
| 92 |
+
x0: torch.Tensor,
|
| 93 |
+
noise: torch.Tensor,
|
| 94 |
+
alpha_t: torch.Tensor,
|
| 95 |
+
sigma_t: torch.Tensor,
|
| 96 |
+
) -> torch.Tensor:
|
| 97 |
+
if self.prediction_type == "epsilon":
|
| 98 |
+
return self.epsilon_target(x0, noise)
|
| 99 |
+
|
| 100 |
+
if self.prediction_type == "v":
|
| 101 |
+
return self.v_target(x0, noise, alpha_t, sigma_t)
|
| 102 |
+
|
| 103 |
+
if self.prediction_type == "x0":
|
| 104 |
+
return self.x0_target(x0)
|
| 105 |
+
|
| 106 |
+
raise RuntimeError("Invalid prediction type.")
|
| 107 |
+
|
| 108 |
+
def elementwise_loss(
|
| 109 |
+
self,
|
| 110 |
+
model_output: torch.Tensor,
|
| 111 |
+
target: torch.Tensor,
|
| 112 |
+
) -> torch.Tensor:
|
| 113 |
+
if self.loss_type == "mse":
|
| 114 |
+
return F.mse_loss(
|
| 115 |
+
model_output,
|
| 116 |
+
target,
|
| 117 |
+
reduction="none",
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
if self.loss_type == "l1":
|
| 121 |
+
return F.l1_loss(
|
| 122 |
+
model_output,
|
| 123 |
+
target,
|
| 124 |
+
reduction="none",
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
if self.loss_type == "huber":
|
| 128 |
+
return F.smooth_l1_loss(
|
| 129 |
+
model_output,
|
| 130 |
+
target,
|
| 131 |
+
reduction="none",
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
raise RuntimeError("Invalid loss type.")
|
| 135 |
+
|
| 136 |
+
def get_snr_weights(
|
| 137 |
+
self,
|
| 138 |
+
snr: torch.Tensor,
|
| 139 |
+
) -> torch.Tensor | None:
|
| 140 |
+
"""
|
| 141 |
+
Returns per-sample SNR weights.
|
| 142 |
+
|
| 143 |
+
snr:
|
| 144 |
+
[B]
|
| 145 |
+
|
| 146 |
+
For Min-SNR:
|
| 147 |
+
epsilon prediction:
|
| 148 |
+
weight = min(snr, gamma) / snr
|
| 149 |
+
|
| 150 |
+
v prediction:
|
| 151 |
+
weight = min(snr, gamma) / (snr + 1)
|
| 152 |
+
|
| 153 |
+
x0 prediction:
|
| 154 |
+
weight = min(snr, gamma)
|
| 155 |
+
"""
|
| 156 |
+
if self.snr_weighting == "none":
|
| 157 |
+
return None
|
| 158 |
+
|
| 159 |
+
if self.snr_weighting == "min_snr":
|
| 160 |
+
if self.snr_gamma is None:
|
| 161 |
+
raise RuntimeError("snr_gamma is required for min_snr weighting.")
|
| 162 |
+
|
| 163 |
+
snr = snr.float().clamp(min=self.eps)
|
| 164 |
+
|
| 165 |
+
gamma = torch.full_like(
|
| 166 |
+
snr,
|
| 167 |
+
fill_value=float(self.snr_gamma),
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
clipped_snr = torch.minimum(
|
| 171 |
+
snr,
|
| 172 |
+
gamma,
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
if self.prediction_type == "epsilon":
|
| 176 |
+
weights = clipped_snr / snr
|
| 177 |
+
|
| 178 |
+
elif self.prediction_type == "v":
|
| 179 |
+
weights = clipped_snr / (snr + 1.0)
|
| 180 |
+
|
| 181 |
+
elif self.prediction_type == "x0":
|
| 182 |
+
weights = clipped_snr
|
| 183 |
+
|
| 184 |
+
else:
|
| 185 |
+
raise RuntimeError("Invalid prediction type.")
|
| 186 |
+
|
| 187 |
+
if self.normalize_snr_weights:
|
| 188 |
+
weights = weights / weights.mean().clamp(min=self.eps)
|
| 189 |
+
|
| 190 |
+
return weights
|
| 191 |
+
|
| 192 |
+
raise RuntimeError("Invalid SNR weighting type.")
|
| 193 |
+
|
| 194 |
+
def forward(
|
| 195 |
+
self,
|
| 196 |
+
model_output: torch.Tensor,
|
| 197 |
+
x0: torch.Tensor,
|
| 198 |
+
noise: torch.Tensor,
|
| 199 |
+
alpha_t: torch.Tensor,
|
| 200 |
+
sigma_t: torch.Tensor,
|
| 201 |
+
snr: torch.Tensor | None = None,
|
| 202 |
+
return_dict: bool = False,
|
| 203 |
+
):
|
| 204 |
+
|
| 205 |
+
target = self.get_target(
|
| 206 |
+
x0=x0,
|
| 207 |
+
noise=noise,
|
| 208 |
+
alpha_t=alpha_t,
|
| 209 |
+
sigma_t=sigma_t,
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
loss = self.elementwise_loss(
|
| 213 |
+
model_output=model_output,
|
| 214 |
+
target=target,
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
# [B, C, H, W] -> [B]
|
| 218 |
+
per_sample_loss = loss.mean(
|
| 219 |
+
dim=tuple(range(1, loss.ndim)),
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
raw_loss = per_sample_loss.mean()
|
| 223 |
+
|
| 224 |
+
weights = None
|
| 225 |
+
|
| 226 |
+
if self.snr_weighting != "none":
|
| 227 |
+
if snr is None:
|
| 228 |
+
raise ValueError(
|
| 229 |
+
"snr must be passed when SNR weighting is enabled."
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
weights = self.get_snr_weights(snr)
|
| 233 |
+
|
| 234 |
+
if weights is not None:
|
| 235 |
+
per_sample_loss = per_sample_loss * weights.to(per_sample_loss.device)
|
| 236 |
+
|
| 237 |
+
weighted_loss = per_sample_loss.mean()
|
| 238 |
+
|
| 239 |
+
if return_dict:
|
| 240 |
+
out = {
|
| 241 |
+
"loss": weighted_loss,
|
| 242 |
+
"raw_loss": raw_loss.detach(),
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
if weights is not None:
|
| 246 |
+
out["snr_weight_mean"] = weights.mean().detach()
|
| 247 |
+
out["snr_weight_min"] = weights.min().detach()
|
| 248 |
+
out["snr_weight_max"] = weights.max().detach()
|
| 249 |
+
|
| 250 |
+
return out
|
| 251 |
+
|
| 252 |
+
return weighted_loss
|
src/losses/vae_loss.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from torch import nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@dataclass
|
| 11 |
+
class VAELossOutput:
|
| 12 |
+
total_loss: torch.Tensor
|
| 13 |
+
recon_loss: torch.Tensor
|
| 14 |
+
kl_loss: torch.Tensor
|
| 15 |
+
perceptual_loss: torch.Tensor
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class VAELoss(nn.Module):
|
| 19 |
+
"""
|
| 20 |
+
VAE loss:
|
| 21 |
+
total =
|
| 22 |
+
recon_weight * reconstruction_loss
|
| 23 |
+
+ kl_weight * KL
|
| 24 |
+
+ perceptual_weight * LPIPS
|
| 25 |
+
|
| 26 |
+
Inputs are expected to be in [-1, 1]
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
def __init__(
|
| 30 |
+
self,
|
| 31 |
+
recon_loss_type: str = "l1",
|
| 32 |
+
recon_weight: float = 1.0,
|
| 33 |
+
kl_weight: float = 1e-6,
|
| 34 |
+
perceptual_weight: float = 0.0,
|
| 35 |
+
use_lpips: bool = False,
|
| 36 |
+
lpips_net: str = "vgg",
|
| 37 |
+
):
|
| 38 |
+
super().__init__()
|
| 39 |
+
|
| 40 |
+
if recon_loss_type not in {"l1", "mse"}:
|
| 41 |
+
raise ValueError(
|
| 42 |
+
f"Unknown recon_loss_type={recon_loss_type}. "
|
| 43 |
+
"Use 'l1' or 'mse'."
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
self.recon_loss_type = recon_loss_type
|
| 47 |
+
self.recon_weight = recon_weight
|
| 48 |
+
self.kl_weight = kl_weight
|
| 49 |
+
self.perceptual_weight = perceptual_weight
|
| 50 |
+
self.use_lpips = use_lpips
|
| 51 |
+
|
| 52 |
+
self.lpips_model = None
|
| 53 |
+
|
| 54 |
+
if use_lpips:
|
| 55 |
+
try:
|
| 56 |
+
import lpips
|
| 57 |
+
except ImportError as exc:
|
| 58 |
+
raise ImportError(
|
| 59 |
+
"LPIPS is enabled but package 'lpips' is not installed. "
|
| 60 |
+
"Install it with: pip install lpips"
|
| 61 |
+
) from exc
|
| 62 |
+
|
| 63 |
+
self.lpips_model = lpips.LPIPS(net=lpips_net)
|
| 64 |
+
self.lpips_model.eval()
|
| 65 |
+
|
| 66 |
+
for p in self.lpips_model.parameters():
|
| 67 |
+
p.requires_grad = False
|
| 68 |
+
|
| 69 |
+
def reconstruction_loss(
|
| 70 |
+
self,
|
| 71 |
+
x_recon: torch.Tensor,
|
| 72 |
+
x: torch.Tensor,
|
| 73 |
+
) -> torch.Tensor:
|
| 74 |
+
if self.recon_loss_type == "l1":
|
| 75 |
+
return F.l1_loss(x_recon, x)
|
| 76 |
+
|
| 77 |
+
if self.recon_loss_type == "mse":
|
| 78 |
+
return F.mse_loss(x_recon, x)
|
| 79 |
+
|
| 80 |
+
raise RuntimeError("Invalid reconstruction loss type.")
|
| 81 |
+
|
| 82 |
+
def perceptual_loss(
|
| 83 |
+
self,
|
| 84 |
+
x_recon: torch.Tensor,
|
| 85 |
+
x: torch.Tensor,
|
| 86 |
+
) -> torch.Tensor:
|
| 87 |
+
if not self.use_lpips or self.lpips_model is None:
|
| 88 |
+
return torch.zeros((), device=x.device, dtype=x.dtype)
|
| 89 |
+
|
| 90 |
+
# LPIPS expects images in [-1, 1], which matches our transform.
|
| 91 |
+
with torch.cuda.amp.autocast(enabled=False):
|
| 92 |
+
loss = self.lpips_model(
|
| 93 |
+
x_recon.float(),
|
| 94 |
+
x.float(),
|
| 95 |
+
).mean()
|
| 96 |
+
|
| 97 |
+
return loss.to(dtype=x.dtype)
|
| 98 |
+
|
| 99 |
+
def forward(
|
| 100 |
+
self,
|
| 101 |
+
x_recon: torch.Tensor,
|
| 102 |
+
x: torch.Tensor,
|
| 103 |
+
posterior,
|
| 104 |
+
kl_weight: float | None = None,
|
| 105 |
+
) -> VAELossOutput:
|
| 106 |
+
"""
|
| 107 |
+
Args:
|
| 108 |
+
x_recon:
|
| 109 |
+
Reconstructed image [B, 3, H, W], in [-1, 1].
|
| 110 |
+
|
| 111 |
+
x:
|
| 112 |
+
Target image [B, 3, H, W], in [-1, 1].
|
| 113 |
+
|
| 114 |
+
posterior:
|
| 115 |
+
DiagonalGaussianDistribution from vae.encode(x).
|
| 116 |
+
|
| 117 |
+
kl_weight:
|
| 118 |
+
Optional current KL weight. Useful for KL warmup.
|
| 119 |
+
|
| 120 |
+
Returns:
|
| 121 |
+
VAELossOutput.
|
| 122 |
+
"""
|
| 123 |
+
current_kl_weight = self.kl_weight if kl_weight is None else kl_weight
|
| 124 |
+
|
| 125 |
+
recon = self.reconstruction_loss(x_recon, x)
|
| 126 |
+
|
| 127 |
+
# posterior.kl() returns [B], already summed over latent dimensions.
|
| 128 |
+
kl = posterior.kl().mean()
|
| 129 |
+
|
| 130 |
+
perceptual = self.perceptual_loss(x_recon, x)
|
| 131 |
+
|
| 132 |
+
total = (
|
| 133 |
+
self.recon_weight * recon
|
| 134 |
+
+ current_kl_weight * kl
|
| 135 |
+
+ self.perceptual_weight * perceptual
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
return VAELossOutput(
|
| 139 |
+
total_loss=total,
|
| 140 |
+
recon_loss=recon.detach(),
|
| 141 |
+
kl_loss=kl.detach(),
|
| 142 |
+
perceptual_loss=perceptual.detach(),
|
| 143 |
+
)
|
src/models/__init__.py
ADDED
|
File without changes
|
src/models/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (213 Bytes). View file
|
|
|
src/models/autoencoder/__pycache__/blocks.cpython-311.pyc
ADDED
|
Binary file (13.9 kB). View file
|
|
|
src/models/autoencoder/__pycache__/decoder.cpython-311.pyc
ADDED
|
Binary file (5.38 kB). View file
|
|
|
src/models/autoencoder/__pycache__/distributions.cpython-311.pyc
ADDED
|
Binary file (4.67 kB). View file
|
|
|
src/models/autoencoder/__pycache__/encoder.cpython-311.pyc
ADDED
|
Binary file (5.26 kB). View file
|
|
|
src/models/autoencoder/__pycache__/vae.cpython-311.pyc
ADDED
|
Binary file (6.87 kB). View file
|
|
|
src/models/autoencoder/blocks.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def init_conv_kaiming(module: nn.Module) -> None:
|
| 9 |
+
"""
|
| 10 |
+
Kaiming initialization for convolutional layers used with SiLU activations.
|
| 11 |
+
"""
|
| 12 |
+
if isinstance(module, nn.Conv2d):
|
| 13 |
+
nn.init.kaiming_normal_(
|
| 14 |
+
module.weight,
|
| 15 |
+
mode="fan_out",
|
| 16 |
+
nonlinearity="relu",
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
if module.bias is not None:
|
| 20 |
+
nn.init.zeros_(module.bias)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def init_linear_xavier(module: nn.Module) -> None:
|
| 24 |
+
"""
|
| 25 |
+
Xavier initialization for attention-style projection layers.
|
| 26 |
+
"""
|
| 27 |
+
if isinstance(module, nn.Conv2d):
|
| 28 |
+
nn.init.xavier_uniform_(module.weight)
|
| 29 |
+
|
| 30 |
+
if module.bias is not None:
|
| 31 |
+
nn.init.zeros_(module.bias)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def normalization(num_channels: int, num_groups: int = 32):
|
| 35 |
+
"""
|
| 36 |
+
GroupNorm used in VAE blocks
|
| 37 |
+
"""
|
| 38 |
+
num_groups = min(num_groups, num_channels)
|
| 39 |
+
|
| 40 |
+
while num_channels % num_groups != 0:
|
| 41 |
+
num_groups -= 1
|
| 42 |
+
|
| 43 |
+
return nn.GroupNorm(
|
| 44 |
+
num_groups=num_groups,
|
| 45 |
+
num_channels=num_channels,
|
| 46 |
+
eps=1e-6,
|
| 47 |
+
affine=True,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class ResBlock(nn.Module):
|
| 52 |
+
"""
|
| 53 |
+
Simple residual block:
|
| 54 |
+
|
| 55 |
+
x -> GroupNorm -> SiLU -> Conv
|
| 56 |
+
-> GroupNorm -> SiLU -> Conv
|
| 57 |
+
+ shortcut
|
| 58 |
+
|
| 59 |
+
Used both in encoder and decoder.
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
def __init__(
|
| 63 |
+
self,
|
| 64 |
+
in_channels: int,
|
| 65 |
+
out_channels: int | None = None,
|
| 66 |
+
dropout: float = 0.0,
|
| 67 |
+
):
|
| 68 |
+
super().__init__()
|
| 69 |
+
|
| 70 |
+
if out_channels is None:
|
| 71 |
+
out_channels = in_channels
|
| 72 |
+
|
| 73 |
+
self.in_channels = in_channels
|
| 74 |
+
self.out_channels = out_channels
|
| 75 |
+
|
| 76 |
+
self.norm1 = normalization(in_channels)
|
| 77 |
+
self.conv1 = nn.Conv2d(
|
| 78 |
+
in_channels,
|
| 79 |
+
out_channels,
|
| 80 |
+
kernel_size=3,
|
| 81 |
+
stride=1,
|
| 82 |
+
padding=1,
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
self.norm2 = normalization(out_channels)
|
| 86 |
+
self.dropout = nn.Dropout(dropout)
|
| 87 |
+
self.conv2 = nn.Conv2d(
|
| 88 |
+
out_channels,
|
| 89 |
+
out_channels,
|
| 90 |
+
kernel_size=3,
|
| 91 |
+
stride=1,
|
| 92 |
+
padding=1,
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
if in_channels != out_channels:
|
| 96 |
+
self.shortcut = nn.Conv2d(
|
| 97 |
+
in_channels,
|
| 98 |
+
out_channels,
|
| 99 |
+
kernel_size=1,
|
| 100 |
+
stride=1,
|
| 101 |
+
padding=0,
|
| 102 |
+
)
|
| 103 |
+
else:
|
| 104 |
+
self.shortcut = nn.Identity()
|
| 105 |
+
|
| 106 |
+
self.reset_parameters()
|
| 107 |
+
|
| 108 |
+
def reset_parameters(self) -> None:
|
| 109 |
+
init_conv_kaiming(self.conv1)
|
| 110 |
+
init_conv_kaiming(self.conv2)
|
| 111 |
+
|
| 112 |
+
if isinstance(self.shortcut, nn.Conv2d):
|
| 113 |
+
init_conv_kaiming(self.shortcut)
|
| 114 |
+
|
| 115 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 116 |
+
residual = self.shortcut(x)
|
| 117 |
+
|
| 118 |
+
h = self.norm1(x)
|
| 119 |
+
h = F.silu(h)
|
| 120 |
+
h = self.conv1(h)
|
| 121 |
+
|
| 122 |
+
h = self.norm2(h)
|
| 123 |
+
h = F.silu(h)
|
| 124 |
+
h = self.dropout(h)
|
| 125 |
+
h = self.conv2(h)
|
| 126 |
+
|
| 127 |
+
return h + residual
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
class Downsample(nn.Module):
|
| 131 |
+
"""
|
| 132 |
+
Downsample by factor 2 using strided convolution.
|
| 133 |
+
"""
|
| 134 |
+
|
| 135 |
+
def __init__(self, channels: int):
|
| 136 |
+
super().__init__()
|
| 137 |
+
|
| 138 |
+
self.conv = nn.Conv2d(
|
| 139 |
+
channels,
|
| 140 |
+
channels,
|
| 141 |
+
kernel_size=3,
|
| 142 |
+
stride=2,
|
| 143 |
+
padding=1,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
self.reset_parameters()
|
| 147 |
+
|
| 148 |
+
def reset_parameters(self) -> None:
|
| 149 |
+
init_conv_kaiming(self.conv)
|
| 150 |
+
|
| 151 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 152 |
+
return self.conv(x)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
class Upsample(nn.Module):
|
| 156 |
+
"""
|
| 157 |
+
Upsample by factor 2 using nearest-neighbor interpolation + convolution instead of ConvTranspose2d.
|
| 158 |
+
"""
|
| 159 |
+
|
| 160 |
+
def __init__(self, channels: int):
|
| 161 |
+
super().__init__()
|
| 162 |
+
|
| 163 |
+
self.conv = nn.Conv2d(
|
| 164 |
+
channels,
|
| 165 |
+
channels,
|
| 166 |
+
kernel_size=3,
|
| 167 |
+
stride=1,
|
| 168 |
+
padding=1,
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
self.reset_parameters()
|
| 172 |
+
|
| 173 |
+
def reset_parameters(self) -> None:
|
| 174 |
+
init_conv_kaiming(self.conv)
|
| 175 |
+
|
| 176 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 177 |
+
x = F.interpolate(x, scale_factor=2.0, mode="nearest")
|
| 178 |
+
x = self.conv(x)
|
| 179 |
+
return x
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
class SelfAttentionBlock(nn.Module):
|
| 183 |
+
"""
|
| 184 |
+
Spatial self-attention block for feature maps.
|
| 185 |
+
|
| 186 |
+
Input:
|
| 187 |
+
x: [B, C, H, W]
|
| 188 |
+
|
| 189 |
+
then get:
|
| 190 |
+
[B, C, H, W] -> [B, H*W, C]
|
| 191 |
+
"""
|
| 192 |
+
|
| 193 |
+
def __init__(
|
| 194 |
+
self,
|
| 195 |
+
channels: int,
|
| 196 |
+
num_heads: int = 1,
|
| 197 |
+
):
|
| 198 |
+
super().__init__()
|
| 199 |
+
|
| 200 |
+
if channels % num_heads != 0:
|
| 201 |
+
raise ValueError(
|
| 202 |
+
f"channels={channels} must be divisible by num_heads={num_heads}"
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
self.channels = channels
|
| 206 |
+
self.num_heads = num_heads
|
| 207 |
+
self.head_dim = channels // num_heads
|
| 208 |
+
|
| 209 |
+
self.norm = normalization(channels)
|
| 210 |
+
|
| 211 |
+
self.qkv = nn.Conv2d(
|
| 212 |
+
channels,
|
| 213 |
+
channels * 3,
|
| 214 |
+
kernel_size=1,
|
| 215 |
+
stride=1,
|
| 216 |
+
padding=0,
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
self.proj_out = nn.Conv2d(
|
| 220 |
+
channels,
|
| 221 |
+
channels,
|
| 222 |
+
kernel_size=1,
|
| 223 |
+
stride=1,
|
| 224 |
+
padding=0,
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
self.reset_parameters()
|
| 228 |
+
|
| 229 |
+
def reset_parameters(self) -> None:
|
| 230 |
+
init_linear_xavier(self.qkv)
|
| 231 |
+
init_linear_xavier(self.proj_out)
|
| 232 |
+
|
| 233 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 234 |
+
b, c, h, w = x.shape
|
| 235 |
+
residual = x
|
| 236 |
+
|
| 237 |
+
x = self.norm(x)
|
| 238 |
+
|
| 239 |
+
qkv = self.qkv(x)
|
| 240 |
+
q, k, v = torch.chunk(qkv, chunks=3, dim=1)
|
| 241 |
+
|
| 242 |
+
# [B, C, H, W] -> [B, num_heads, H*W, head_dim]
|
| 243 |
+
q = q.view(b, self.num_heads, self.head_dim, h * w)
|
| 244 |
+
k = k.view(b, self.num_heads, self.head_dim, h * w)
|
| 245 |
+
v = v.view(b, self.num_heads, self.head_dim, h * w)
|
| 246 |
+
|
| 247 |
+
q = q.permute(0, 1, 3, 2)
|
| 248 |
+
k = k.permute(0, 1, 3, 2)
|
| 249 |
+
v = v.permute(0, 1, 3, 2)
|
| 250 |
+
|
| 251 |
+
# Output: [B, num_heads, H*W, head_dim]
|
| 252 |
+
out = F.scaled_dot_product_attention(q, k, v)
|
| 253 |
+
|
| 254 |
+
# [B, num_heads, H*W, head_dim] -> [B, C, H, W]
|
| 255 |
+
out = out.permute(0, 1, 3, 2).contiguous()
|
| 256 |
+
out = out.view(b, c, h, w)
|
| 257 |
+
|
| 258 |
+
out = self.proj_out(out)
|
| 259 |
+
|
| 260 |
+
return residual + out
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
class AttnResBlock(nn.Module):
|
| 264 |
+
"""
|
| 265 |
+
Optional attention block:
|
| 266 |
+
|
| 267 |
+
ResBlock -> SelfAttentionBlock
|
| 268 |
+
"""
|
| 269 |
+
|
| 270 |
+
def __init__(
|
| 271 |
+
self,
|
| 272 |
+
in_channels: int,
|
| 273 |
+
out_channels: int | None = None,
|
| 274 |
+
dropout: float = 0.0,
|
| 275 |
+
use_attention: bool = False,
|
| 276 |
+
num_heads: int = 1,
|
| 277 |
+
):
|
| 278 |
+
super().__init__()
|
| 279 |
+
|
| 280 |
+
if out_channels is None:
|
| 281 |
+
out_channels = in_channels
|
| 282 |
+
|
| 283 |
+
self.resblock = ResBlock(
|
| 284 |
+
in_channels=in_channels,
|
| 285 |
+
out_channels=out_channels,
|
| 286 |
+
dropout=dropout,
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
if use_attention:
|
| 290 |
+
self.attn = SelfAttentionBlock(
|
| 291 |
+
channels=out_channels,
|
| 292 |
+
num_heads=num_heads,
|
| 293 |
+
)
|
| 294 |
+
else:
|
| 295 |
+
self.attn = nn.Identity()
|
| 296 |
+
|
| 297 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 298 |
+
x = self.resblock(x)
|
| 299 |
+
x = self.attn(x)
|
| 300 |
+
return x
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
class MidBlock(nn.Module):
|
| 304 |
+
"""
|
| 305 |
+
Bottleneck block:
|
| 306 |
+
|
| 307 |
+
ResBlock -> SelfAttentionBlock -> ResBlock
|
| 308 |
+
"""
|
| 309 |
+
|
| 310 |
+
def __init__(
|
| 311 |
+
self,
|
| 312 |
+
channels: int,
|
| 313 |
+
dropout: float = 0.0,
|
| 314 |
+
use_attention: bool = True,
|
| 315 |
+
num_heads: int = 1,
|
| 316 |
+
):
|
| 317 |
+
super().__init__()
|
| 318 |
+
|
| 319 |
+
self.block1 = ResBlock(
|
| 320 |
+
in_channels=channels,
|
| 321 |
+
out_channels=channels,
|
| 322 |
+
dropout=dropout,
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
if use_attention:
|
| 326 |
+
self.attn = SelfAttentionBlock(
|
| 327 |
+
channels=channels,
|
| 328 |
+
num_heads=num_heads,
|
| 329 |
+
)
|
| 330 |
+
else:
|
| 331 |
+
self.attn = nn.Identity()
|
| 332 |
+
|
| 333 |
+
self.block2 = ResBlock(
|
| 334 |
+
in_channels=channels,
|
| 335 |
+
out_channels=channels,
|
| 336 |
+
dropout=dropout,
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 340 |
+
x = self.block1(x)
|
| 341 |
+
x = self.attn(x)
|
| 342 |
+
x = self.block2(x)
|
| 343 |
+
return x
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def zero_module(module: nn.Module) -> nn.Module:
|
| 347 |
+
"""
|
| 348 |
+
Zero-initialize a module.
|
| 349 |
+
"""
|
| 350 |
+
for p in module.parameters():
|
| 351 |
+
nn.init.zeros_(p)
|
| 352 |
+
return module
|
src/models/autoencoder/decoder.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
from src.models.autoencoder.blocks import (
|
| 8 |
+
ResBlock,
|
| 9 |
+
Upsample,
|
| 10 |
+
MidBlock,
|
| 11 |
+
normalization,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class Decoder(nn.Module):
|
| 16 |
+
"""
|
| 17 |
+
VAE decoder.
|
| 18 |
+
|
| 19 |
+
Converts latent tensor back into image space.
|
| 20 |
+
Shape path:
|
| 21 |
+
|
| 22 |
+
[B, 4, 32, 32]
|
| 23 |
+
-> conv_in
|
| 24 |
+
[B, 512, 32, 32]
|
| 25 |
+
-> mid block
|
| 26 |
+
[B, 512, 32, 32]
|
| 27 |
+
-> upsample
|
| 28 |
+
[B, 512, 64, 64]
|
| 29 |
+
-> upsample
|
| 30 |
+
[B, 256, 128, 128]
|
| 31 |
+
-> upsample
|
| 32 |
+
[B, 128, 256, 256]
|
| 33 |
+
-> conv_out
|
| 34 |
+
[B, 3, 256, 256]
|
| 35 |
+
|
| 36 |
+
Output is in [-1, 1] because training images are normalized to [-1, 1].
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def __init__(
|
| 40 |
+
self,
|
| 41 |
+
out_channels: int = 3,
|
| 42 |
+
latent_channels: int = 4,
|
| 43 |
+
base_channels: int = 128,
|
| 44 |
+
channel_multipliers: list[int] | tuple[int, ...] = (1, 2, 4, 4),
|
| 45 |
+
num_res_blocks: int = 2,
|
| 46 |
+
dropout: float = 0.0,
|
| 47 |
+
use_attention: bool = True,
|
| 48 |
+
attention_heads: int = 1
|
| 49 |
+
):
|
| 50 |
+
super().__init__()
|
| 51 |
+
|
| 52 |
+
if len(channel_multipliers) < 2:
|
| 53 |
+
raise ValueError("channel_multipliers must contain at least 2 levels.")
|
| 54 |
+
|
| 55 |
+
self.out_channels = out_channels
|
| 56 |
+
self.latent_channels = latent_channels
|
| 57 |
+
self.base_channels = base_channels
|
| 58 |
+
self.channel_multipliers = list(channel_multipliers)
|
| 59 |
+
self.num_res_blocks = num_res_blocks
|
| 60 |
+
|
| 61 |
+
# Number of spatial upsampling operations
|
| 62 |
+
# Example:
|
| 63 |
+
# [1, 2, 4, 4] has 4 levels, so decoder upsamples 3 times:
|
| 64 |
+
# 32 -> 64 -> 128 -> 256
|
| 65 |
+
self.num_upsamples = len(self.channel_multipliers) - 1
|
| 66 |
+
|
| 67 |
+
# Start from the deepest encoder channel count
|
| 68 |
+
current_channels = base_channels * self.channel_multipliers[-1]
|
| 69 |
+
|
| 70 |
+
self.conv_in = nn.Conv2d(
|
| 71 |
+
latent_channels,
|
| 72 |
+
current_channels,
|
| 73 |
+
kernel_size=3,
|
| 74 |
+
stride=1,
|
| 75 |
+
padding=1,
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
# Bottleneck block at the lowest spatial resolution.
|
| 79 |
+
self.mid = MidBlock(
|
| 80 |
+
channels=current_channels,
|
| 81 |
+
dropout=dropout,
|
| 82 |
+
use_attention=use_attention,
|
| 83 |
+
num_heads=attention_heads,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
self.up_blocks = nn.ModuleList()
|
| 87 |
+
|
| 88 |
+
reversed_multipliers = list(reversed(self.channel_multipliers))
|
| 89 |
+
|
| 90 |
+
for level, multiplier in enumerate(reversed_multipliers):
|
| 91 |
+
out_stage_channels = base_channels * multiplier
|
| 92 |
+
|
| 93 |
+
resblocks = nn.ModuleList()
|
| 94 |
+
|
| 95 |
+
# one extra ResBlock per level
|
| 96 |
+
for _ in range(num_res_blocks + 1):
|
| 97 |
+
resblocks.append(
|
| 98 |
+
ResBlock(
|
| 99 |
+
in_channels=current_channels,
|
| 100 |
+
out_channels=out_stage_channels,
|
| 101 |
+
dropout=dropout,
|
| 102 |
+
)
|
| 103 |
+
)
|
| 104 |
+
current_channels = out_stage_channels
|
| 105 |
+
|
| 106 |
+
# Upsample after every stage except the full-resolution
|
| 107 |
+
if level < len(reversed_multipliers) - 1:
|
| 108 |
+
upsample = Upsample(
|
| 109 |
+
channels=current_channels
|
| 110 |
+
)
|
| 111 |
+
else:
|
| 112 |
+
upsample = nn.Identity()
|
| 113 |
+
|
| 114 |
+
self.up_blocks.append(
|
| 115 |
+
nn.ModuleDict(
|
| 116 |
+
{
|
| 117 |
+
"resblocks": resblocks,
|
| 118 |
+
"upsample": upsample,
|
| 119 |
+
}
|
| 120 |
+
)
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
self.norm_out = normalization(current_channels)
|
| 124 |
+
|
| 125 |
+
self.conv_out = nn.Conv2d(
|
| 126 |
+
current_channels,
|
| 127 |
+
out_channels,
|
| 128 |
+
kernel_size=3,
|
| 129 |
+
stride=1,
|
| 130 |
+
padding=1,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
def forward(self, z: torch.Tensor) -> torch.Tensor:
|
| 134 |
+
"""
|
| 135 |
+
Args:
|
| 136 |
+
z:
|
| 137 |
+
Latent tensor with shape [B, latent_channels, H/8, W/8].
|
| 138 |
+
For 256x256 images and downsample factor 8:
|
| 139 |
+
[B, latent_channels, 32, 32]
|
| 140 |
+
|
| 141 |
+
Returns:
|
| 142 |
+
x_recon:
|
| 143 |
+
Reconstructed image tensor with shape [B, 3, H, W].
|
| 144 |
+
Values are in [-1, 1].
|
| 145 |
+
"""
|
| 146 |
+
h = self.conv_in(z)
|
| 147 |
+
|
| 148 |
+
h = self.mid(h)
|
| 149 |
+
|
| 150 |
+
for stage in self.up_blocks:
|
| 151 |
+
for block in stage["resblocks"]:
|
| 152 |
+
h = block(h)
|
| 153 |
+
|
| 154 |
+
h = stage["upsample"](h)
|
| 155 |
+
|
| 156 |
+
h = self.norm_out(h)
|
| 157 |
+
h = F.silu(h)
|
| 158 |
+
h = self.conv_out(h)
|
| 159 |
+
|
| 160 |
+
x_recon = torch.tanh(h)
|
| 161 |
+
|
| 162 |
+
return x_recon
|
src/models/autoencoder/distributions.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class DiagonalGaussianDistribution:
|
| 7 |
+
"""
|
| 8 |
+
Diagonal Gaussian posterior used by the VAE.
|
| 9 |
+
|
| 10 |
+
The encoder predicts a tensor called `moments`:
|
| 11 |
+
|
| 12 |
+
moments: [B, 2 * latent_channels, H, W]
|
| 13 |
+
|
| 14 |
+
We split it into:
|
| 15 |
+
|
| 16 |
+
mean: [B, latent_channels, H, W]
|
| 17 |
+
logvar: [B, latent_channels, H, W]
|
| 18 |
+
|
| 19 |
+
Then sample:
|
| 20 |
+
|
| 21 |
+
z = mean + std * eps
|
| 22 |
+
|
| 23 |
+
where:
|
| 24 |
+
|
| 25 |
+
std = exp(0.5 * logvar)
|
| 26 |
+
eps ~ N(0, I)
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
def __init__(
|
| 30 |
+
self,
|
| 31 |
+
moments: torch.Tensor,
|
| 32 |
+
deterministic: bool = False,
|
| 33 |
+
logvar_min: float = -30.0,
|
| 34 |
+
logvar_max: float = 20.0,
|
| 35 |
+
):
|
| 36 |
+
self.moments = moments
|
| 37 |
+
self.deterministic = deterministic
|
| 38 |
+
|
| 39 |
+
self.mean, self.logvar = torch.chunk(moments, chunks=2, dim=1)
|
| 40 |
+
|
| 41 |
+
# Clamp log-variance for numerical stability
|
| 42 |
+
self.logvar = torch.clamp(self.logvar, min=logvar_min, max=logvar_max)
|
| 43 |
+
|
| 44 |
+
self.var = torch.exp(self.logvar)
|
| 45 |
+
self.std = torch.exp(0.5 * self.logvar)
|
| 46 |
+
|
| 47 |
+
if self.deterministic:
|
| 48 |
+
self.var = torch.zeros_like(self.mean)
|
| 49 |
+
self.std = torch.zeros_like(self.mean)
|
| 50 |
+
|
| 51 |
+
def sample(self) -> torch.Tensor:
|
| 52 |
+
"""
|
| 53 |
+
Reparameterized sampling:
|
| 54 |
+
|
| 55 |
+
z = mean + std * eps
|
| 56 |
+
"""
|
| 57 |
+
eps = torch.randn_like(self.mean)
|
| 58 |
+
return self.mean + self.std * eps
|
| 59 |
+
|
| 60 |
+
def mode(self) -> torch.Tensor:
|
| 61 |
+
"""
|
| 62 |
+
Most likely latent value
|
| 63 |
+
"""
|
| 64 |
+
return self.mean
|
| 65 |
+
|
| 66 |
+
def kl(self) -> torch.Tensor:
|
| 67 |
+
"""
|
| 68 |
+
KL divergence from posterior q(z|x) to standard normal N(0, I).
|
| 69 |
+
|
| 70 |
+
For diagonal Gaussian:
|
| 71 |
+
|
| 72 |
+
KL(q || N(0,I))
|
| 73 |
+
= 0.5 * (mean^2 + var - 1 - logvar)
|
| 74 |
+
|
| 75 |
+
Returns:
|
| 76 |
+
Per-sample KL with shape [B].
|
| 77 |
+
"""
|
| 78 |
+
if self.deterministic:
|
| 79 |
+
return torch.zeros(self.mean.shape[0], device=self.mean.device)
|
| 80 |
+
|
| 81 |
+
kl = 0.5 * (
|
| 82 |
+
torch.pow(self.mean, 2)
|
| 83 |
+
+ self.var
|
| 84 |
+
- 1.0
|
| 85 |
+
- self.logvar
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
# Sum over latent channels and spatial dimensions.
|
| 89 |
+
return torch.sum(kl, dim=[1, 2, 3])
|
| 90 |
+
|
| 91 |
+
def nll(self, sample: torch.Tensor) -> torch.Tensor:
|
| 92 |
+
"""
|
| 93 |
+
Negative log likelihood of `sample` under this posterior.
|
| 94 |
+
|
| 95 |
+
Mostly useful for debugging, not essential for our VAE training loop.
|
| 96 |
+
|
| 97 |
+
Returns:
|
| 98 |
+
Per-sample NLL with shape [B].
|
| 99 |
+
"""
|
| 100 |
+
if self.deterministic:
|
| 101 |
+
return torch.zeros(self.mean.shape[0], device=self.mean.device)
|
| 102 |
+
|
| 103 |
+
log_two_pi = torch.log(
|
| 104 |
+
torch.tensor(2.0 * torch.pi, device=sample.device, dtype=sample.dtype)
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
nll = 0.5 * (
|
| 108 |
+
log_two_pi
|
| 109 |
+
+ self.logvar
|
| 110 |
+
+ torch.pow(sample - self.mean, 2) / self.var
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
return torch.sum(nll, dim=[1, 2, 3])
|
src/models/autoencoder/encoder.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
from src.models.autoencoder.blocks import (
|
| 8 |
+
ResBlock,
|
| 9 |
+
Downsample,
|
| 10 |
+
MidBlock,
|
| 11 |
+
normalization,
|
| 12 |
+
SelfAttentionBlock,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
class Encoder(nn.Module):
|
| 16 |
+
"""
|
| 17 |
+
VAE encoder.
|
| 18 |
+
channel_multipliers=[1, 2, 4]: this controls the multiplier of number of feature maps
|
| 19 |
+
|
| 20 |
+
[B, 3, 256, 256]
|
| 21 |
+
-> [B, 128, 256, 256]
|
| 22 |
+
-> [B, 128, 128, 128]
|
| 23 |
+
-> [B, 256, 64, 64]
|
| 24 |
+
-> [B, 512, 32, 32]
|
| 25 |
+
-> [B, 2 * latent_channels, 32, 32]
|
| 26 |
+
|
| 27 |
+
Output channels are 2 * latent_channels because we predict:
|
| 28 |
+
mu
|
| 29 |
+
logvar
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
def __init__(
|
| 33 |
+
self,
|
| 34 |
+
in_channels: int = 3,
|
| 35 |
+
latent_channels: int = 8,
|
| 36 |
+
base_channels: int = 128,
|
| 37 |
+
channel_multipliers: list[int] | tuple[int, ...] = (1, 2, 4, 4),
|
| 38 |
+
num_res_blocks: int = 3,
|
| 39 |
+
dropout: float = 0.0,
|
| 40 |
+
use_attention: bool = True,
|
| 41 |
+
attention_heads: int = 4,
|
| 42 |
+
attention_resolutions: tuple[int, ...] = (32,),
|
| 43 |
+
):
|
| 44 |
+
super().__init__()
|
| 45 |
+
|
| 46 |
+
self.in_channels = in_channels
|
| 47 |
+
self.latent_channels = latent_channels
|
| 48 |
+
self.base_channels = base_channels
|
| 49 |
+
self.channel_multipliers = list(channel_multipliers)
|
| 50 |
+
self.num_res_blocks = num_res_blocks
|
| 51 |
+
self.attention_resolutions = set(attention_resolutions)
|
| 52 |
+
|
| 53 |
+
# Initial projection
|
| 54 |
+
self.conv_in = nn.Conv2d(
|
| 55 |
+
in_channels,
|
| 56 |
+
base_channels,
|
| 57 |
+
kernel_size=3,
|
| 58 |
+
stride=1,
|
| 59 |
+
padding=1,
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
# Downsampling
|
| 63 |
+
self.down_blocks = nn.ModuleList()
|
| 64 |
+
|
| 65 |
+
current_channels = base_channels
|
| 66 |
+
current_resolution = 256
|
| 67 |
+
|
| 68 |
+
for level, multiplier in enumerate(self.channel_multipliers):
|
| 69 |
+
out_channels = base_channels * multiplier
|
| 70 |
+
|
| 71 |
+
stage = nn.ModuleDict()
|
| 72 |
+
stage["resblocks"] = nn.ModuleList()
|
| 73 |
+
|
| 74 |
+
for _ in range(num_res_blocks):
|
| 75 |
+
stage["resblocks"].append(
|
| 76 |
+
ResBlock(
|
| 77 |
+
in_channels=current_channels,
|
| 78 |
+
out_channels=out_channels,
|
| 79 |
+
dropout=dropout,
|
| 80 |
+
)
|
| 81 |
+
)
|
| 82 |
+
current_channels = out_channels
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# This part also adds attention to 64x64 resolution along with bottleneck.
|
| 86 |
+
if use_attention and current_resolution in self.attention_resolutions:
|
| 87 |
+
stage["attention"] = SelfAttentionBlock(
|
| 88 |
+
channels=current_channels,
|
| 89 |
+
num_heads=attention_heads,
|
| 90 |
+
)
|
| 91 |
+
else:
|
| 92 |
+
stage["attention"] = nn.Identity()
|
| 93 |
+
|
| 94 |
+
# Downsample after each stage except the final one
|
| 95 |
+
if level != len(self.channel_multipliers) - 1:
|
| 96 |
+
stage["downsample"] = Downsample(current_channels)
|
| 97 |
+
next_resolution = current_resolution // 2
|
| 98 |
+
else:
|
| 99 |
+
stage["downsample"] = nn.Identity()
|
| 100 |
+
next_resolution = current_resolution
|
| 101 |
+
|
| 102 |
+
self.down_blocks.append(stage)
|
| 103 |
+
current_resolution = next_resolution
|
| 104 |
+
|
| 105 |
+
# Bottleneck
|
| 106 |
+
self.mid = MidBlock(
|
| 107 |
+
channels=current_channels,
|
| 108 |
+
dropout=dropout,
|
| 109 |
+
use_attention=use_attention,
|
| 110 |
+
num_heads=attention_heads,
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# Output projection to posterior parameters
|
| 114 |
+
self.norm_out = normalization(current_channels)
|
| 115 |
+
self.conv_out = nn.Conv2d(
|
| 116 |
+
current_channels,
|
| 117 |
+
2 * latent_channels,
|
| 118 |
+
kernel_size=3,
|
| 119 |
+
stride=1,
|
| 120 |
+
padding=1,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 124 |
+
"""
|
| 125 |
+
Args:
|
| 126 |
+
x:
|
| 127 |
+
Image tensor with shape [B, 3, H, W]
|
| 128 |
+
|
| 129 |
+
Returns:
|
| 130 |
+
moments:
|
| 131 |
+
Tensor with shape [B, 2 * latent_channels, H/8, W/8]
|
| 132 |
+
The first half is mu.
|
| 133 |
+
The second half is logvar.
|
| 134 |
+
"""
|
| 135 |
+
h = self.conv_in(x)
|
| 136 |
+
|
| 137 |
+
for stage in self.down_blocks:
|
| 138 |
+
for block in stage["resblocks"]:
|
| 139 |
+
h = block(h)
|
| 140 |
+
|
| 141 |
+
h = stage["attention"](h)
|
| 142 |
+
h = stage["downsample"](h)
|
| 143 |
+
|
| 144 |
+
h = self.mid(h)
|
| 145 |
+
|
| 146 |
+
h = self.norm_out(h)
|
| 147 |
+
h = F.silu(h)
|
| 148 |
+
moments = self.conv_out(h)
|
| 149 |
+
|
| 150 |
+
return moments
|
src/models/autoencoder/vae.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
|
| 6 |
+
from src.models.autoencoder.encoder import Encoder
|
| 7 |
+
from src.models.autoencoder.decoder import Decoder
|
| 8 |
+
from src.models.autoencoder.distributions import DiagonalGaussianDistribution
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class AutoencoderKL(nn.Module):
|
| 12 |
+
"""
|
| 13 |
+
VAE / AutoencoderKL wrapper
|
| 14 |
+
posterior = vae.encode(x)
|
| 15 |
+
z = posterior.sample()
|
| 16 |
+
x_recon = vae.decode(z)
|
| 17 |
+
|
| 18 |
+
Or directly:
|
| 19 |
+
|
| 20 |
+
x_recon, posterior, z = vae(x)
|
| 21 |
+
|
| 22 |
+
Input image range:
|
| 23 |
+
x in [-1, 1]
|
| 24 |
+
|
| 25 |
+
Output image range:
|
| 26 |
+
x_recon in [-1, 1]
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
def __init__(
|
| 30 |
+
self,
|
| 31 |
+
in_channels: int = 3,
|
| 32 |
+
out_channels: int = 3,
|
| 33 |
+
latent_channels: int = 8,
|
| 34 |
+
base_channels: int = 128,
|
| 35 |
+
channel_multipliers: list[int] | tuple[int, ...] = (1, 2, 4, 4),
|
| 36 |
+
num_res_blocks: int = 3,
|
| 37 |
+
dropout: float = 0.0,
|
| 38 |
+
use_attention: bool = True,
|
| 39 |
+
attention_heads: int = 4,
|
| 40 |
+
scaling_factor: float = 1.0,
|
| 41 |
+
attention_resolutions: tuple[int, ...] = (32,),
|
| 42 |
+
):
|
| 43 |
+
super().__init__()
|
| 44 |
+
|
| 45 |
+
self.in_channels = in_channels
|
| 46 |
+
self.out_channels = out_channels
|
| 47 |
+
self.latent_channels = latent_channels
|
| 48 |
+
self.base_channels = base_channels
|
| 49 |
+
self.channel_multipliers = list(channel_multipliers)
|
| 50 |
+
self.num_res_blocks = num_res_blocks
|
| 51 |
+
self.scaling_factor = scaling_factor
|
| 52 |
+
|
| 53 |
+
self.encoder = Encoder(
|
| 54 |
+
in_channels=in_channels,
|
| 55 |
+
latent_channels=latent_channels,
|
| 56 |
+
base_channels=base_channels,
|
| 57 |
+
channel_multipliers=channel_multipliers,
|
| 58 |
+
num_res_blocks=num_res_blocks,
|
| 59 |
+
dropout=dropout,
|
| 60 |
+
use_attention=use_attention,
|
| 61 |
+
attention_heads=attention_heads,
|
| 62 |
+
attention_resolutions= attention_resolutions
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
self.decoder = Decoder(
|
| 66 |
+
out_channels=out_channels,
|
| 67 |
+
latent_channels=latent_channels,
|
| 68 |
+
base_channels=base_channels,
|
| 69 |
+
channel_multipliers=channel_multipliers,
|
| 70 |
+
num_res_blocks=num_res_blocks,
|
| 71 |
+
dropout=dropout,
|
| 72 |
+
use_attention=use_attention,
|
| 73 |
+
attention_heads=attention_heads,
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
def encode(
|
| 77 |
+
self,
|
| 78 |
+
x: torch.Tensor,
|
| 79 |
+
deterministic: bool = False,
|
| 80 |
+
) -> DiagonalGaussianDistribution:
|
| 81 |
+
"""
|
| 82 |
+
Encode image into posterior distribution.
|
| 83 |
+
deterministic:
|
| 84 |
+
If True, posterior.sample() will return mean only.
|
| 85 |
+
Returns:
|
| 86 |
+
DiagonalGaussianDistribution.
|
| 87 |
+
"""
|
| 88 |
+
moments = self.encoder(x)
|
| 89 |
+
posterior = DiagonalGaussianDistribution(
|
| 90 |
+
moments=moments,
|
| 91 |
+
deterministic=deterministic,
|
| 92 |
+
)
|
| 93 |
+
return posterior
|
| 94 |
+
|
| 95 |
+
def decode(
|
| 96 |
+
self,
|
| 97 |
+
z: torch.Tensor,
|
| 98 |
+
unscale: bool = True,
|
| 99 |
+
) -> torch.Tensor:
|
| 100 |
+
"""
|
| 101 |
+
Decode latent into image
|
| 102 |
+
z:
|
| 103 |
+
Latent tensor [B, latent_channels, H/8, W/8].
|
| 104 |
+
|
| 105 |
+
unscale:
|
| 106 |
+
If True, divide by scaling_factor before decoding.
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
Reconstructed image in [-1, 1].
|
| 110 |
+
"""
|
| 111 |
+
if unscale:
|
| 112 |
+
z = z / self.scaling_factor
|
| 113 |
+
|
| 114 |
+
return self.decoder(z)
|
| 115 |
+
|
| 116 |
+
def forward(
|
| 117 |
+
self,
|
| 118 |
+
x: torch.Tensor,
|
| 119 |
+
sample_posterior: bool = True,
|
| 120 |
+
) -> tuple[torch.Tensor, DiagonalGaussianDistribution, torch.Tensor]:
|
| 121 |
+
"""
|
| 122 |
+
Full VAE forward pass.
|
| 123 |
+
|
| 124 |
+
Args:
|
| 125 |
+
x:
|
| 126 |
+
Image tensor [B, 3, H, W], normalized to [-1, 1].
|
| 127 |
+
|
| 128 |
+
sample_posterior:
|
| 129 |
+
If True:
|
| 130 |
+
z = posterior.sample()
|
| 131 |
+
If False:
|
| 132 |
+
z = posterior.mode()
|
| 133 |
+
|
| 134 |
+
Returns:
|
| 135 |
+
x_recon:
|
| 136 |
+
Reconstructed image [B, 3, H, W].
|
| 137 |
+
|
| 138 |
+
posterior:
|
| 139 |
+
DiagonalGaussianDistribution object.
|
| 140 |
+
|
| 141 |
+
z:
|
| 142 |
+
Latent tensor before scaling.
|
| 143 |
+
"""
|
| 144 |
+
posterior = self.encode(x)
|
| 145 |
+
|
| 146 |
+
if sample_posterior:
|
| 147 |
+
z = posterior.sample()
|
| 148 |
+
else:
|
| 149 |
+
z = posterior.mode()
|
| 150 |
+
|
| 151 |
+
x_recon = self.decode(z, unscale=False)
|
| 152 |
+
|
| 153 |
+
return x_recon, posterior, z
|
| 154 |
+
|
| 155 |
+
@torch.no_grad()
|
| 156 |
+
def reconstruct(
|
| 157 |
+
self,
|
| 158 |
+
x: torch.Tensor,
|
| 159 |
+
sample_posterior: bool = False,
|
| 160 |
+
) -> torch.Tensor:
|
| 161 |
+
"""
|
| 162 |
+
Convenience method for inference reconstruction.
|
| 163 |
+
|
| 164 |
+
By default, uses posterior mode for stable reconstructions.
|
| 165 |
+
"""
|
| 166 |
+
x_recon, _, _ = self.forward(
|
| 167 |
+
x,
|
| 168 |
+
sample_posterior=sample_posterior,
|
| 169 |
+
)
|
| 170 |
+
return x_recon
|
| 171 |
+
|
| 172 |
+
@torch.no_grad()
|
| 173 |
+
def encode_to_latent(
|
| 174 |
+
self,
|
| 175 |
+
x: torch.Tensor,
|
| 176 |
+
sample_posterior: bool = False,
|
| 177 |
+
scale: bool = True,
|
| 178 |
+
) -> torch.Tensor:
|
| 179 |
+
"""
|
| 180 |
+
Encode image into latent tensor for latent diffusion training.
|
| 181 |
+
|
| 182 |
+
Usually for latent caching, use:
|
| 183 |
+
|
| 184 |
+
sample_posterior=False
|
| 185 |
+
|
| 186 |
+
because the posterior mean is deterministic and stable.
|
| 187 |
+
|
| 188 |
+
If scale=True:
|
| 189 |
+
|
| 190 |
+
z_scaled = z * scaling_factor
|
| 191 |
+
|
| 192 |
+
Stable Diffusion-style LDMs often scale latents before diffusion.
|
| 193 |
+
"""
|
| 194 |
+
posterior = self.encode(x)
|
| 195 |
+
|
| 196 |
+
if sample_posterior:
|
| 197 |
+
z = posterior.sample()
|
| 198 |
+
else:
|
| 199 |
+
z = posterior.mode()
|
| 200 |
+
|
| 201 |
+
if scale:
|
| 202 |
+
z = z * self.scaling_factor
|
| 203 |
+
|
| 204 |
+
return z
|
| 205 |
+
|
| 206 |
+
@torch.no_grad()
|
| 207 |
+
def decode_from_latent(
|
| 208 |
+
self,
|
| 209 |
+
z: torch.Tensor,
|
| 210 |
+
unscale: bool = True,
|
| 211 |
+
) -> torch.Tensor:
|
| 212 |
+
"""
|
| 213 |
+
Decode latent tensor produced by diffusion model.
|
| 214 |
+
"""
|
| 215 |
+
return self.decode(z, unscale=unscale)
|
src/models/conditioning/__pycache__/clip_text.cpython-311.pyc
ADDED
|
Binary file (7.19 kB). View file
|
|
|
src/models/conditioning/clip_text.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from torch import nn
|
| 7 |
+
from transformers import CLIPTextModel, CLIPTokenizer
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@dataclass
|
| 11 |
+
class TextConditioningOutput:
|
| 12 |
+
"""
|
| 13 |
+
Output of the CLIP text encoder.
|
| 14 |
+
|
| 15 |
+
hidden_states:
|
| 16 |
+
Token-level CLIP embeddings.
|
| 17 |
+
Shape: [B, seq_len, hidden_dim]
|
| 18 |
+
|
| 19 |
+
attention_mask:
|
| 20 |
+
Token attention mask.
|
| 21 |
+
Shape: [B, seq_len]
|
| 22 |
+
|
| 23 |
+
pooled:
|
| 24 |
+
Optional pooled text embedding.
|
| 25 |
+
Shape: [B, hidden_dim]
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
hidden_states: torch.Tensor
|
| 29 |
+
attention_mask: torch.Tensor
|
| 30 |
+
pooled: torch.Tensor | None = None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class FrozenCLIPTextEncoder(nn.Module):
|
| 34 |
+
"""
|
| 35 |
+
Frozen CLIP text encoder for latent diffusion conditioning
|
| 36 |
+
model:
|
| 37 |
+
openai/clip-vit-large-patch14
|
| 38 |
+
|
| 39 |
+
This gives:
|
| 40 |
+
context_dim = 768
|
| 41 |
+
max_length = 77
|
| 42 |
+
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
def __init__(
|
| 46 |
+
self,
|
| 47 |
+
model_name: str = "openai/clip-vit-large-patch14",
|
| 48 |
+
max_length: int = 77,
|
| 49 |
+
freeze: bool = True,
|
| 50 |
+
use_last_hidden_state: bool = True,
|
| 51 |
+
cache_dir: str | None = None,
|
| 52 |
+
local_files_only: bool = False,
|
| 53 |
+
):
|
| 54 |
+
super().__init__()
|
| 55 |
+
|
| 56 |
+
self.model_name = model_name
|
| 57 |
+
self.max_length = max_length
|
| 58 |
+
self.freeze = freeze
|
| 59 |
+
self.use_last_hidden_state = use_last_hidden_state
|
| 60 |
+
self.cache_dir = cache_dir
|
| 61 |
+
self.local_files_only = local_files_only
|
| 62 |
+
|
| 63 |
+
self.tokenizer = CLIPTokenizer.from_pretrained(
|
| 64 |
+
model_name,
|
| 65 |
+
cache_dir=cache_dir,
|
| 66 |
+
local_files_only=local_files_only,
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
self.text_model = CLIPTextModel.from_pretrained(
|
| 70 |
+
model_name,
|
| 71 |
+
cache_dir=cache_dir,
|
| 72 |
+
local_files_only=local_files_only,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
if self.freeze:
|
| 76 |
+
self.text_model.eval()
|
| 77 |
+
for p in self.text_model.parameters():
|
| 78 |
+
p.requires_grad = False
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def context_dim(self) -> int:
|
| 82 |
+
return int(self.text_model.config.hidden_size)
|
| 83 |
+
|
| 84 |
+
@property
|
| 85 |
+
def vocab_size(self) -> int:
|
| 86 |
+
return int(self.tokenizer.vocab_size)
|
| 87 |
+
|
| 88 |
+
@property
|
| 89 |
+
def pad_token_id(self) -> int:
|
| 90 |
+
return int(self.tokenizer.pad_token_id)
|
| 91 |
+
|
| 92 |
+
def train(self, mode: bool = True):
|
| 93 |
+
"""
|
| 94 |
+
Keep CLIP frozen/eval even if parent model calls .train().
|
| 95 |
+
"""
|
| 96 |
+
super().train(mode)
|
| 97 |
+
|
| 98 |
+
if self.freeze:
|
| 99 |
+
self.text_model.eval()
|
| 100 |
+
|
| 101 |
+
return self
|
| 102 |
+
|
| 103 |
+
def tokenize(
|
| 104 |
+
self,
|
| 105 |
+
captions: list[str] | tuple[str, ...],
|
| 106 |
+
device: torch.device | str | None = None,
|
| 107 |
+
) -> dict[str, torch.Tensor]:
|
| 108 |
+
"""
|
| 109 |
+
Tokenize captions into CLIP input tensors.
|
| 110 |
+
"""
|
| 111 |
+
tokens = self.tokenizer(
|
| 112 |
+
list(captions),
|
| 113 |
+
padding="max_length",
|
| 114 |
+
truncation=True,
|
| 115 |
+
max_length=self.max_length,
|
| 116 |
+
return_tensors="pt",
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
if device is not None:
|
| 120 |
+
tokens = {
|
| 121 |
+
key: value.to(device)
|
| 122 |
+
for key, value in tokens.items()
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
return tokens
|
| 126 |
+
|
| 127 |
+
def forward(
|
| 128 |
+
self,
|
| 129 |
+
captions: list[str] | tuple[str, ...],
|
| 130 |
+
device: torch.device | str | None = None,
|
| 131 |
+
) -> TextConditioningOutput:
|
| 132 |
+
"""
|
| 133 |
+
Produces CLIP textual embeddings as diffusion condition.
|
| 134 |
+
"""
|
| 135 |
+
if device is None:
|
| 136 |
+
device = next(self.text_model.parameters()).device
|
| 137 |
+
|
| 138 |
+
tokens = self.tokenize(
|
| 139 |
+
captions=captions,
|
| 140 |
+
device=device,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
with torch.no_grad() if self.freeze else torch.enable_grad():
|
| 144 |
+
outputs = self.text_model(
|
| 145 |
+
input_ids=tokens["input_ids"],
|
| 146 |
+
attention_mask=tokens["attention_mask"],
|
| 147 |
+
output_hidden_states=not self.use_last_hidden_state,
|
| 148 |
+
return_dict=True,
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
if self.use_last_hidden_state:
|
| 152 |
+
hidden_states = outputs.last_hidden_state
|
| 153 |
+
else:
|
| 154 |
+
# Penultimate layer is sometimes used in diffusion models.
|
| 155 |
+
hidden_states = outputs.hidden_states[-2]
|
| 156 |
+
|
| 157 |
+
pooled = outputs.pooler_output
|
| 158 |
+
|
| 159 |
+
return TextConditioningOutput(
|
| 160 |
+
hidden_states=hidden_states,
|
| 161 |
+
attention_mask=tokens["attention_mask"],
|
| 162 |
+
pooled=pooled,
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
@torch.no_grad()
|
| 166 |
+
def encode(
|
| 167 |
+
self,
|
| 168 |
+
captions: list[str] | tuple[str, ...],
|
| 169 |
+
device: torch.device | str | None = None,
|
| 170 |
+
) -> torch.Tensor:
|
| 171 |
+
"""
|
| 172 |
+
Convenience function.
|
| 173 |
+
|
| 174 |
+
Returns only token-level context:
|
| 175 |
+
|
| 176 |
+
[B, seq_len, context_dim]
|
| 177 |
+
"""
|
| 178 |
+
return self.forward(
|
| 179 |
+
captions=captions,
|
| 180 |
+
device=device,
|
| 181 |
+
).hidden_states
|
src/models/conditioning/null_conditioning.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import random
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from torch import nn
|
| 7 |
+
|
| 8 |
+
from src.models.conditioning.clip_text import FrozenCLIPTextEncoder
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class ClassifierFreeGuidanceConditioner(nn.Module):
|
| 12 |
+
"""
|
| 13 |
+
During training, with probability `cond_drop_prob`, captions are replaced
|
| 14 |
+
with the empty string:
|
| 15 |
+
|
| 16 |
+
"a dog on grass" -> ""
|
| 17 |
+
|
| 18 |
+
Later at sampling time, CFG uses:
|
| 19 |
+
|
| 20 |
+
pred = pred_uncond + guidance_scale * (pred_cond - pred_uncond)
|
| 21 |
+
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(
|
| 25 |
+
self,
|
| 26 |
+
text_encoder: FrozenCLIPTextEncoder,
|
| 27 |
+
cond_drop_prob: float = 0.1,
|
| 28 |
+
empty_text: str = "",
|
| 29 |
+
):
|
| 30 |
+
super().__init__()
|
| 31 |
+
|
| 32 |
+
if cond_drop_prob < 0.0 or cond_drop_prob > 1.0:
|
| 33 |
+
raise ValueError("cond_drop_prob must be between 0 and 1.")
|
| 34 |
+
|
| 35 |
+
self.text_encoder = text_encoder
|
| 36 |
+
self.cond_drop_prob = cond_drop_prob
|
| 37 |
+
self.empty_text = empty_text
|
| 38 |
+
|
| 39 |
+
@property
|
| 40 |
+
def context_dim(self) -> int:
|
| 41 |
+
return self.text_encoder.context_dim
|
| 42 |
+
|
| 43 |
+
@property
|
| 44 |
+
def max_length(self) -> int:
|
| 45 |
+
return self.text_encoder.max_length
|
| 46 |
+
|
| 47 |
+
def apply_conditioning_dropout(
|
| 48 |
+
self,
|
| 49 |
+
captions: list[str] | tuple[str, ...],
|
| 50 |
+
force_drop_ids: torch.Tensor | None = None,
|
| 51 |
+
) -> tuple[list[str], torch.Tensor]:
|
| 52 |
+
"""
|
| 53 |
+
Replace some captions with empty text
|
| 54 |
+
"""
|
| 55 |
+
captions = list(captions)
|
| 56 |
+
batch_size = len(captions)
|
| 57 |
+
|
| 58 |
+
if force_drop_ids is not None:
|
| 59 |
+
drop_mask = force_drop_ids.bool().cpu()
|
| 60 |
+
else:
|
| 61 |
+
drop_mask = torch.zeros(batch_size, dtype=torch.bool)
|
| 62 |
+
|
| 63 |
+
for i in range(batch_size):
|
| 64 |
+
if random.random() < self.cond_drop_prob:
|
| 65 |
+
drop_mask[i] = True
|
| 66 |
+
|
| 67 |
+
dropped_captions = []
|
| 68 |
+
|
| 69 |
+
for caption, drop in zip(captions, drop_mask):
|
| 70 |
+
if bool(drop):
|
| 71 |
+
dropped_captions.append(self.empty_text)
|
| 72 |
+
else:
|
| 73 |
+
dropped_captions.append(caption)
|
| 74 |
+
|
| 75 |
+
return dropped_captions, drop_mask
|
| 76 |
+
|
| 77 |
+
def forward(
|
| 78 |
+
self,
|
| 79 |
+
captions: list[str] | tuple[str, ...],
|
| 80 |
+
device: torch.device | str | None = None,
|
| 81 |
+
apply_dropout: bool = True,
|
| 82 |
+
force_drop_ids: torch.Tensor | None = None,
|
| 83 |
+
):
|
| 84 |
+
"""
|
| 85 |
+
Encode captions with optional CFG dropout
|
| 86 |
+
"""
|
| 87 |
+
if apply_dropout:
|
| 88 |
+
captions, drop_mask = self.apply_conditioning_dropout(
|
| 89 |
+
captions=captions,
|
| 90 |
+
force_drop_ids=force_drop_ids,
|
| 91 |
+
)
|
| 92 |
+
else:
|
| 93 |
+
captions = list(captions)
|
| 94 |
+
drop_mask = torch.zeros(
|
| 95 |
+
len(captions),
|
| 96 |
+
dtype=torch.bool,
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
output = self.text_encoder(
|
| 100 |
+
captions=captions,
|
| 101 |
+
device=device,
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
if device is not None:
|
| 105 |
+
drop_mask = drop_mask.to(device)
|
| 106 |
+
|
| 107 |
+
return {
|
| 108 |
+
"context": output.hidden_states,
|
| 109 |
+
"attention_mask": output.attention_mask,
|
| 110 |
+
"pooled": output.pooled,
|
| 111 |
+
"drop_mask": drop_mask,
|
| 112 |
+
"captions": captions,
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
@torch.no_grad()
|
| 116 |
+
def encode_cond_uncond(
|
| 117 |
+
self,
|
| 118 |
+
captions: list[str] | tuple[str, ...],
|
| 119 |
+
device: torch.device | str | None = None,
|
| 120 |
+
) -> dict[str, torch.Tensor]:
|
| 121 |
+
"""
|
| 122 |
+
Encode both conditional and unconditional text.
|
| 123 |
+
|
| 124 |
+
Used during CFG sampling
|
| 125 |
+
"""
|
| 126 |
+
captions = list(captions)
|
| 127 |
+
batch_size = len(captions)
|
| 128 |
+
|
| 129 |
+
cond_output = self.text_encoder(
|
| 130 |
+
captions=captions,
|
| 131 |
+
device=device,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
uncond_output = self.text_encoder(
|
| 135 |
+
captions=[self.empty_text] * batch_size,
|
| 136 |
+
device=device,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
return {
|
| 140 |
+
"cond_context": cond_output.hidden_states,
|
| 141 |
+
"cond_attention_mask": cond_output.attention_mask,
|
| 142 |
+
"uncond_context": uncond_output.hidden_states,
|
| 143 |
+
"uncond_attention_mask": uncond_output.attention_mask,
|
| 144 |
+
}
|
src/models/diffusion/__pycache__/attention.cpython-311.pyc
ADDED
|
Binary file (11.5 kB). View file
|
|
|
src/models/diffusion/__pycache__/blocks.cpython-311.pyc
ADDED
|
Binary file (14.2 kB). View file
|
|
|
src/models/diffusion/__pycache__/timestep.cpython-311.pyc
ADDED
|
Binary file (2.93 kB). View file
|
|
|
src/models/diffusion/__pycache__/unet.cpython-311.pyc
ADDED
|
Binary file (10.5 kB). View file
|
|
|