anilegin commited on
Commit
a04730e
·
verified ·
1 Parent(s): 4e04d74

Upload inference-only latent diffusion model

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -33
  2. README.md +63 -0
  3. checkpoints/ldm_model.pt +3 -0
  4. checkpoints/vae_model.pt +3 -0
  5. configs/ldm_config.yaml +102 -0
  6. configs/vae_config.yaml +78 -0
  7. generation_config.yaml +20 -0
  8. inference.py +353 -0
  9. outputs/test_ddim/0000_a_small_dog_sitting_on_a_red_couch.png +0 -0
  10. outputs/test_ddim/grid.png +0 -0
  11. requirements.txt +8 -0
  12. src/__init__.py +0 -0
  13. src/__pycache__/__init__.cpython-311.pyc +0 -0
  14. src/diffusion/__init__.py +0 -0
  15. src/diffusion/__pycache__/__init__.cpython-311.pyc +0 -0
  16. src/diffusion/__pycache__/gaussian_diffusion.cpython-311.pyc +0 -0
  17. src/diffusion/__pycache__/noise_schedule.cpython-311.pyc +0 -0
  18. src/diffusion/__pycache__/prediction.cpython-311.pyc +0 -0
  19. src/diffusion/gaussian_diffusion.py +437 -0
  20. src/diffusion/noise_schedule.py +302 -0
  21. src/diffusion/prediction.py +259 -0
  22. src/diffusion/samplers/__init__.py +9 -0
  23. src/diffusion/samplers/__pycache__/__init__.cpython-311.pyc +0 -0
  24. src/diffusion/samplers/__pycache__/ddim.cpython-311.pyc +0 -0
  25. src/diffusion/samplers/__pycache__/ddpm.cpython-311.pyc +0 -0
  26. src/diffusion/samplers/ddim.py +234 -0
  27. src/diffusion/samplers/ddpm.py +235 -0
  28. src/losses/__pycache__/diffusion_loss.cpython-311.pyc +0 -0
  29. src/losses/__pycache__/vae_loss.cpython-311.pyc +0 -0
  30. src/losses/diffusion_loss.py +252 -0
  31. src/losses/vae_loss.py +143 -0
  32. src/models/__init__.py +0 -0
  33. src/models/__pycache__/__init__.cpython-311.pyc +0 -0
  34. src/models/autoencoder/__pycache__/blocks.cpython-311.pyc +0 -0
  35. src/models/autoencoder/__pycache__/decoder.cpython-311.pyc +0 -0
  36. src/models/autoencoder/__pycache__/distributions.cpython-311.pyc +0 -0
  37. src/models/autoencoder/__pycache__/encoder.cpython-311.pyc +0 -0
  38. src/models/autoencoder/__pycache__/vae.cpython-311.pyc +0 -0
  39. src/models/autoencoder/blocks.py +352 -0
  40. src/models/autoencoder/decoder.py +162 -0
  41. src/models/autoencoder/distributions.py +113 -0
  42. src/models/autoencoder/encoder.py +150 -0
  43. src/models/autoencoder/vae.py +215 -0
  44. src/models/conditioning/__pycache__/clip_text.cpython-311.pyc +0 -0
  45. src/models/conditioning/clip_text.py +181 -0
  46. src/models/conditioning/null_conditioning.py +144 -0
  47. src/models/diffusion/__pycache__/attention.cpython-311.pyc +0 -0
  48. src/models/diffusion/__pycache__/blocks.cpython-311.pyc +0 -0
  49. src/models/diffusion/__pycache__/timestep.cpython-311.pyc +0 -0
  50. 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
- *.pth filter=lfs diff=lfs merge=lfs -text
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