File size: 11,492 Bytes
29691f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
import torch
import torch.nn as nn
import numpy as np
from typing import Optional, Tuple, Union
import math


class BetaScheduler:
    """Beta调度器"""
    @staticmethod
    def linear(num_timesteps: int, beta_start: float = 0.0001, beta_end: float = 0.02) -> np.ndarray:
        """线性调度"""
        return np.linspace(beta_start, beta_end, num_timesteps, dtype=np.float32)
    
    @staticmethod
    def cosine(num_timesteps: int, s: float = 0.008) -> np.ndarray:
        """余弦调度"""
        steps = num_timesteps + 1
        x = np.linspace(0, num_timesteps, steps)
        alphas_cumprod = np.cos(((x / num_timesteps) + s) / (1 + s) * np.pi * 0.5) ** 2
        alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
        betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
        return np.clip(betas, 0, 0.999)
    
    @staticmethod
    def scaled_linear(num_timesteps: int) -> np.ndarray:
        """缩放线性调度(Stable Diffusion默认)"""
        beta_start = 0.00085
        beta_end = 0.012
        return np.linspace(beta_start**0.5, beta_end**0.5, num_timesteps) ** 2


class DiffusionProcess:
    """扩散过程管理"""
    def __init__(self, config: dict):
        self.config = config
        diff_config = config.get('diffusion', {})
        
        self.num_train_timesteps = diff_config.get('num_train_timesteps', 1000)
        self.num_inference_timesteps = diff_config.get('num_inference_timesteps', 50)
        self.beta_start = diff_config.get('beta_start', 0.00085)
        self.beta_end = diff_config.get('beta_end', 0.012)
        self.beta_schedule = diff_config.get('beta_schedule', 'scaled_linear')
        self.prediction_type = diff_config.get('prediction_type', 'epsilon')
        
        # 初始化调度参数
        self._init_schedule()
    
    def _init_schedule(self):
        """初始化扩散调度参数"""
        # 计算betas
        if self.beta_schedule == "linear":
            betas = BetaScheduler.linear(
                self.num_train_timesteps, 
                self.beta_start, 
                self.beta_end
            )
        elif self.beta_schedule == "cosine":
            betas = BetaScheduler.cosine(self.num_train_timesteps)
        elif self.beta_schedule == "scaled_linear":
            betas = BetaScheduler.scaled_linear(self.num_train_timesteps)
        else:
            raise ValueError(f"Unknown beta schedule: {self.beta_schedule}")
        
        self.betas = torch.from_numpy(betas).float()
        
        # 计算alphas
        self.alphas = 1.0 - self.betas
        self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
        self.alphas_cumprod_prev = F.pad(self.alphas_cumprod[:-1], (1, 0), value=1.0)
        
        # 计算扩散后验方差
        self.variance = self.betas * (1.0 - self.alphas_cumprod_prev) / (1.0 - self.alphas_cumprod)
        
        # 注册为buffer
        self.register_buffer = lambda name, tensor: setattr(self, name, tensor)
        self.register_buffer('betas', self.betas)
        self.register_buffer('alphas', self.alphas)
        self.register_buffer('alphas_cumprod', self.alphas_cumprod)
        self.register_buffer('alphas_cumprod_prev', self.alphas_cumprod_prev)
        self.register_buffer('variance', self.variance)
        
        # 计算采样系数
        self.register_buffer('sqrt_alphas_cumprod', torch.sqrt(self.alphas_cumprod))
        self.register_buffer('sqrt_one_minus_alphas_cumprod', torch.sqrt(1.0 - self.alphas_cumprod))
        self.register_buffer('log_one_minus_alphas_cumprod', torch.log(1.0 - self.alphas_cumprod))
        self.register_buffer('sqrt_recip_alphas_cumprod', torch.sqrt(1.0 / self.alphas_cumprod))
        self.register_buffer('sqrt_recipm1_alphas_cumprod', torch.sqrt(1.0 / self.alphas_cumprod - 1))
    
    def q_sample(self, x_start: torch.Tensor, t: torch.Tensor, noise: Optional[torch.Tensor] = None) -> torch.Tensor:
        """前向扩散过程:加噪"""
        if noise is None:
            noise = torch.randn_like(x_start)
        
        sqrt_alphas_cumprod_t = self.extract(self.sqrt_alphas_cumprod, t, x_start.shape)
        sqrt_one_minus_alphas_cumprod_t = self.extract(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape)
        
        return sqrt_alphas_cumprod_t * x_start + sqrt_one_minus_alphas_cumprod_t * noise
    
    def extract(self, a: torch.Tensor, t: torch.Tensor, x_shape: Tuple[int, ...]) -> torch.Tensor:
        """从张量a中提取索引t处的值"""
        batch_size = t.shape[0]
        out = a.gather(-1, t.cpu())
        return out.reshape(batch_size, *((1,) * (len(x_shape) - 1))).to(t.device)
    
    def get_loss_weight(self, snr: torch.Tensor, gamma: float = 5.0) -> torch.Tensor:
        """根据SNR计算损失权重"""
        if gamma is None:
            return torch.ones_like(snr)
        
        snr = torch.clamp(snr, min=1e-8)
        min_snr = torch.tensor(gamma, device=snr.device)
        weight = torch.minimum(snr, min_snr) / snr
        return weight
    
    def compute_snr(self, timesteps: torch.Tensor) -> torch.Tensor:
        """计算信噪比(SNR)"""
        alphas_cumprod = self.extract(self.alphas_cumprod, timesteps, timesteps.shape)
        snr = alphas_cumprod / (1 - alphas_cumprod)
        return snr


class DDIMScheduler:
    """DDIM采样器"""
    def __init__(self, diffusion: DiffusionProcess):
        self.diffusion = diffusion
        self.num_train_timesteps = diffusion.num_train_timesteps
        self.num_inference_timesteps = diffusion.num_inference_timesteps
        
        # 设置时间步
        self.set_timesteps(self.num_inference_timesteps)
    
    def set_timesteps(self, num_inference_timesteps: int):
        """设置推理时间步"""
        self.num_inference_timesteps = num_inference_timesteps
        
        # 选择时间步
        if self.num_train_timesteps == self.num_inference_timesteps:
            self.timesteps = torch.arange(0, self.num_train_timesteps).long()
        else:
            step_ratio = self.num_train_timesteps // self.num_inference_timesteps
            self.timesteps = torch.arange(0, self.num_train_timesteps, step_ratio).long()
        
        self.timesteps = self.timesteps.flip(0)  # 从T到0
    
    @torch.no_grad()
    def step(self, model_output: torch.Tensor, timestep: int, sample: torch.Tensor, eta: float = 0.0) -> torch.Tensor:
        """DDIM单步采样"""
        # 获取当前时间步的参数
        prev_timestep = timestep - self.num_train_timesteps // self.num_inference_timesteps
        
        # 提取alpha参数
        alpha_prod_t = self.diffusion.extract(self.diffusion.alphas_cumprod, timestep, sample.shape)
        alpha_prod_t_prev = self.diffusion.extract(
            self.diffusion.alphas_cumprod, 
            prev_timestep, 
            sample.shape
        ) if prev_timestep >= 0 else torch.ones_like(alpha_prod_t)
        
        # 根据预测类型处理模型输出
        if self.diffusion.prediction_type == "epsilon":
            pred_original_sample = (sample - (1 - alpha_prod_t) ** 0.5 * model_output) / alpha_prod_t ** 0.5
            pred_epsilon = model_output
        elif self.diffusion.prediction_type == "sample":
            pred_original_sample = model_output
            pred_epsilon = (sample - alpha_prod_t ** 0.5 * pred_original_sample) / (1 - alpha_prod_t) ** 0.5
        elif self.diffusion.prediction_type == "v_prediction":
            pred_original_sample = (alpha_prod_t ** 0.5) * sample - (1 - alpha_prod_t) ** 0.5 * model_output
            pred_epsilon = (alpha_prod_t ** 0.5) * model_output + (1 - alpha_prod_t) ** 0.5 * sample
        else:
            raise ValueError(f"Unsupported prediction type: {self.diffusion.prediction_type}")
        
        # 计算x_t-1的方差
        variance = (1 - alpha_prod_t_prev) / (1 - alpha_prod_t) * (1 - alpha_prod_t / alpha_prod_t_prev)
        std_dev_t = eta * variance ** 0.5
        
        # 计算x_t-1的均值
        pred_sample_direction = (1 - alpha_prod_t_prev - std_dev_t**2) ** 0.5 * pred_epsilon
        prev_sample = alpha_prod_t_prev ** 0.5 * pred_original_sample + pred_sample_direction
        
        # 添加噪声
        if eta > 0:
            noise = torch.randn_like(model_output)
            prev_sample = prev_sample + std_dev_t * noise
        
        return prev_sample


class DiffusionModel(nn.Module):
    """扩散模型封装"""
    def __init__(self, unet: nn.Module, diffusion: DiffusionProcess):
        super().__init__()
        self.unet = unet
        self.diffusion = diffusion
        self.scheduler = DDIMScheduler(diffusion)
        
    def forward(self, x: torch.Tensor, timesteps: torch.Tensor, context: torch.Tensor) -> torch.Tensor:
        """前向传播:预测噪声"""
        return self.unet(x, timesteps, context)
    
    def compute_loss(self, x_start: torch.Tensor, context: torch.Tensor, noise: Optional[torch.Tensor] = None) -> torch.Tensor:
        """计算扩散损失"""
        if noise is None:
            noise = torch.randn_like(x_start)
        
        # 随机采样时间步
        batch_size = x_start.shape[0]
        timesteps = torch.randint(
            0, self.diffusion.num_train_timesteps, 
            (batch_size,), device=x_start.device
        ).long()
        
        # 前向扩散
        x_noisy = self.diffusion.q_sample(x_start, timesteps, noise)
        
        # 预测噪声
        predicted_noise = self.unet(x_noisy, timesteps, context)
        
        # 计算损失
        loss = F.mse_loss(predicted_noise, noise)
        
        return loss
    
    @torch.no_grad()
    def generate(

        self, 

        context: torch.Tensor, 

        num_samples: int = 1, 

        height: int = 512, 

        width: int = 512,

        guidance_scale: float = 7.5

    ) -> torch.Tensor:
        """生成图像"""
        # 初始化噪声
        latents = torch.randn(
            (num_samples, self.unet.in_channels, height // 8, width // 8),
            device=next(self.unet.parameters()).device
        )
        
        # DDIM采样
        self.scheduler.set_timesteps(self.diffusion.num_inference_timesteps)
        
        for t in self.scheduler.timesteps:
            # 扩展latents以匹配批大小
            latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1.0 else latents
            latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
            
            # 预测噪声
            timesteps = torch.full((num_samples,), t, device=latents.device).long()
            if guidance_scale > 1.0:
                timesteps = torch.cat([timesteps] * 2)
            
            noise_pred = self.unet(latent_model_input, timesteps, context)
            
            # 应用分类器自由引导
            if guidance_scale > 1.0:
                noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
                noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
            
            # 计算上一个样本
            latents = self.scheduler.step(noise_pred, t, latents).prev_sample
        
        return latents