Skip to content

Commit 9580110

Browse files
authored
Merge pull request #159 from RachelElizaUK/feat_dpm2
feat: add new sample dpm2
2 parents 564adf1 + 61983e3 commit 9580110

3 files changed

Lines changed: 145 additions & 1 deletion

File tree

iddm/config/choices.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# Choice settings
1717
# Support option
1818
bool_choices = [True, False]
19-
sample_choices = ["ddpm", "ddim", "plms"]
19+
sample_choices = ["ddpm", "ddim", "plms", "dpm2"]
2020
network_choices = ["unet", "cspdarkunet", "unetv2", "unet-slim", "unet-cross-attn", "unet-flash-self-attn",]
2121
optim_choices = ["adam", "adamw", "sgd"]
2222
act_choices = ["gelu", "silu", "relu", "relu6", "lrelu"]

iddm/model/samples/dpm2.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python
2+
# -*- coding:utf-8 -*-
3+
"""
4+
@Date : 2025/9/2 16:18
5+
@Author : Elizabeth
6+
@Site : https://github.com/RachelElizaUK
7+
"""
8+
from typing import Optional, List, Union
9+
10+
import torch
11+
import logging
12+
import coloredlogs
13+
from torch import nn
14+
15+
from tqdm import tqdm
16+
17+
from iddm.model.samples.base import BaseDiffusion
18+
19+
logger = logging.getLogger(__name__)
20+
coloredlogs.install(level="INFO")
21+
22+
23+
class DPM2Diffusion(BaseDiffusion):
24+
"""
25+
DPM2 sampler class
26+
"""
27+
28+
def __init__(
29+
self,
30+
noise_steps: int = 1000,
31+
sample_steps: int = 50,
32+
beta_start: float = 1e-4,
33+
beta_end: float = 2e-2,
34+
img_size: Optional[List[int]] = None,
35+
device: Union[str, torch.device] = "cpu",
36+
schedule_name: str = "linear",
37+
latent: bool = False,
38+
latent_channel: int = 8,
39+
autoencoder: Optional[nn.Module] = None
40+
):
41+
"""
42+
The implement of DPM2
43+
:param noise_steps: Total noise steps
44+
:param sample_steps: Sampling steps (DPM2 typically uses fewer steps than full noise steps)
45+
:param beta_start: β start value
46+
:param beta_end: β end value
47+
:param img_size: Image size
48+
:param device: Device type
49+
:param schedule_name: Noise schedule name
50+
:param latent: Whether to use latent diffusion
51+
:param latent_channel: Latent channel size
52+
:param autoencoder: Autoencoder model for latent diffusion
53+
"""
54+
super().__init__(
55+
noise_steps=noise_steps,
56+
beta_start=beta_start,
57+
beta_end=beta_end,
58+
img_size=img_size,
59+
device=device,
60+
schedule_name=schedule_name,
61+
latent=latent,
62+
latent_channel=latent_channel,
63+
autoencoder=autoencoder
64+
)
65+
self.sample_steps = sample_steps
66+
self.eta = 0.0 # DPM2 uses deterministic path by default
67+
self._init_time_steps()
68+
69+
def _init_time_steps(self):
70+
"""
71+
Initialize time steps for DPM2 sampling
72+
Creates a sequence of time steps from T down to 0 with equal intervals
73+
"""
74+
step_ratio = self.noise_steps // self.sample_steps
75+
self.time_steps = torch.arange(0, self.noise_steps, step_ratio).long() + 1
76+
self.time_steps = reversed(torch.cat((torch.tensor([0], dtype=torch.long), self.time_steps)))
77+
self.time_steps = list(zip(self.time_steps[:-1], self.time_steps[1:]))
78+
79+
def sample(
80+
self,
81+
model: nn.Module,
82+
x: Optional[torch.Tensor] = None,
83+
n: int = 1,
84+
labels: Optional[torch.Tensor] = None,
85+
cfg_scale: Optional[float] = None
86+
) -> torch.Tensor:
87+
"""
88+
DPM2 sampling method
89+
:param model: Diffusion model
90+
:param x: Initial input tensor (optional)
91+
:param n: Number of samples to generate
92+
:param labels: Conditional labels (optional)
93+
:param cfg_scale: Classifier-free guidance scale (optional)
94+
:return: Generated images tensor
95+
"""
96+
# Get initial input image
97+
x, n = self._get_input_image(n=n, x=x)
98+
logger.info(msg=f"DPM2 Sampling {n} new images....")
99+
model.eval()
100+
101+
with torch.no_grad():
102+
for i, p_i in tqdm(self.time_steps, position=0, total=len(self.time_steps)):
103+
# Current and previous time steps
104+
t = (torch.ones(n) * i).long().to(self.device)
105+
p_t = (torch.ones(n) * p_i).long().to(self.device)
106+
107+
# Get alpha values for current and previous steps
108+
alpha_curr = self.alpha_hat[t][:, None, None, None]
109+
alpha_prev = self.alpha_hat[p_t][:, None, None, None]
110+
111+
# Step 1: Predict noise at current time step
112+
predicted_noise = self._get_predicted_noise(model, x, t, labels, cfg_scale)
113+
114+
# Step 2: Compute x0 from current x and predicted noise
115+
x0 = (x - torch.sqrt(1 - alpha_curr) * predicted_noise) / torch.sqrt(alpha_curr)
116+
x0 = torch.clamp(x0, -1.0, 1.0) # Stabilize x0 prediction
117+
118+
# Step 3: Midpoint prediction (DPM2 uses 2nd-order method)
119+
t_mid = (t + p_t) // 2
120+
alpha_mid = self.alpha_hat[t_mid][:, None, None, None]
121+
122+
# Compute midpoint x
123+
x_mid = torch.sqrt(alpha_mid) * x0 + torch.sqrt(1 - alpha_mid) * predicted_noise
124+
125+
# Predict noise at midpoint
126+
predicted_noise_mid = self._get_predicted_noise(model, x_mid, t_mid, labels, cfg_scale)
127+
128+
# Step 4: Update x using midpoint correction
129+
x = torch.sqrt(alpha_prev) * x0 + torch.sqrt(1 - alpha_prev) * predicted_noise_mid
130+
131+
# Add noise if using stochastic sampling (eta > 0)
132+
if self.eta > 0 and i > 1:
133+
sigma = self.eta * torch.sqrt(
134+
(1 - alpha_prev) / (1 - alpha_curr) * (1 - alpha_curr / alpha_prev)
135+
)
136+
x += sigma * torch.randn_like(x)
137+
138+
x = self.post_process(x=x)
139+
140+
model.train()
141+
return x

iddm/utils/initializer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from iddm.model.samples.ddim import DDIMDiffusion
2626
from iddm.model.samples.ddpm import DDPMDiffusion
2727
from iddm.model.samples.plms import PLMSDiffusion
28+
from iddm.model.samples.dpm2 import DPM2Diffusion
2829
from iddm.utils.check import check_path_is_exist, check_package_is_exist
2930
from iddm.utils.loss import MSEKLLoss
3031
from iddm.utils.lr_scheduler import set_cosine_lr
@@ -210,6 +211,8 @@ def sample_initializer(sample, image_size, device, schedule_name="linear", **kwa
210211
diffusion = DDIMDiffusion(img_size=image_size, device=device, schedule_name=schedule_name, **kwargs)
211212
elif sample == "plms":
212213
diffusion = PLMSDiffusion(img_size=image_size, device=device, schedule_name=schedule_name, **kwargs)
214+
elif sample == "dpm2":
215+
diffusion = DPM2Diffusion(img_size=image_size, device=device, schedule_name=schedule_name, **kwargs)
213216
else:
214217
diffusion = DDPMDiffusion(img_size=image_size, device=device, schedule_name=schedule_name, **kwargs)
215218
logger.warning(msg=f"[{device}]: Setting sample error, we has been automatically set to ddpm.")

0 commit comments

Comments
 (0)