comdoleger commited on
Commit
0aa9d53
·
verified ·
1 Parent(s): f9d7131

Upload extensions_built_in/diffusion_models/wan22/wan22_5b_model.py with huggingface_hub

Browse files
extensions_built_in/diffusion_models/wan22/wan22_5b_model.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import partial
2
+ import torch
3
+ from toolkit.prompt_utils import PromptEmbeds
4
+ from PIL import Image
5
+ from diffusers import UniPCMultistepScheduler
6
+ import torch
7
+ from toolkit.config_modules import GenerateImageConfig, ModelConfig
8
+ from toolkit.samplers.custom_flowmatch_sampler import (
9
+ CustomFlowMatchEulerDiscreteScheduler,
10
+ )
11
+ from .wan22_pipeline import Wan22Pipeline
12
+
13
+ from toolkit.data_transfer_object.data_loader import DataLoaderBatchDTO
14
+ from torchvision.transforms import functional as TF
15
+
16
+ from toolkit.models.wan21.wan21 import Wan21, AggressiveWanUnloadPipeline
17
+ from toolkit.models.wan21.wan_utils import add_first_frame_conditioning_v22
18
+
19
+
20
+ # for generation only?
21
+ scheduler_configUniPC = {
22
+ "_class_name": "UniPCMultistepScheduler",
23
+ "_diffusers_version": "0.35.0.dev0",
24
+ "beta_end": 0.02,
25
+ "beta_schedule": "linear",
26
+ "beta_start": 0.0001,
27
+ "disable_corrector": [],
28
+ "dynamic_thresholding_ratio": 0.995,
29
+ "final_sigmas_type": "zero",
30
+ "flow_shift": 5.0,
31
+ "lower_order_final": True,
32
+ "num_train_timesteps": 1000,
33
+ "predict_x0": True,
34
+ "prediction_type": "flow_prediction",
35
+ "rescale_betas_zero_snr": False,
36
+ "sample_max_value": 1.0,
37
+ "solver_order": 2,
38
+ "solver_p": None,
39
+ "solver_type": "bh2",
40
+ "steps_offset": 0,
41
+ "thresholding": False,
42
+ "time_shift_type": "exponential",
43
+ "timestep_spacing": "linspace",
44
+ "trained_betas": None,
45
+ "use_beta_sigmas": False,
46
+ "use_dynamic_shifting": False,
47
+ "use_exponential_sigmas": False,
48
+ "use_flow_sigmas": True,
49
+ "use_karras_sigmas": False,
50
+ }
51
+
52
+ # for training. I think it is right
53
+ scheduler_config = {
54
+ "num_train_timesteps": 1000,
55
+ "shift": 5.0,
56
+ "use_dynamic_shifting": False,
57
+ }
58
+
59
+ # TODO: this is a temporary monkeypatch to fix the time text embedding to allow for batch sizes greater than 1. Remove this when the diffusers library is fixed.
60
+ def time_text_monkeypatch(
61
+ self,
62
+ timestep: torch.Tensor,
63
+ encoder_hidden_states,
64
+ encoder_hidden_states_image = None,
65
+ timestep_seq_len = None,
66
+ ):
67
+ timestep = self.timesteps_proj(timestep)
68
+ if timestep_seq_len is not None:
69
+ timestep = timestep.unflatten(0, (encoder_hidden_states.shape[0], timestep_seq_len))
70
+
71
+ time_embedder_dtype = next(iter(self.time_embedder.parameters())).dtype
72
+ if timestep.dtype != time_embedder_dtype and time_embedder_dtype != torch.int8:
73
+ timestep = timestep.to(time_embedder_dtype)
74
+ temb = self.time_embedder(timestep).type_as(encoder_hidden_states)
75
+ timestep_proj = self.time_proj(self.act_fn(temb))
76
+
77
+ encoder_hidden_states = self.text_embedder(encoder_hidden_states)
78
+ if encoder_hidden_states_image is not None:
79
+ encoder_hidden_states_image = self.image_embedder(encoder_hidden_states_image)
80
+
81
+ return temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image
82
+
83
+ class Wan225bModel(Wan21):
84
+ arch = "wan22_5b"
85
+ _wan_generation_scheduler_config = scheduler_configUniPC
86
+ _wan_expand_timesteps = True
87
+
88
+ def __init__(
89
+ self,
90
+ device,
91
+ model_config: ModelConfig,
92
+ dtype="bf16",
93
+ custom_pipeline=None,
94
+ noise_scheduler=None,
95
+ **kwargs,
96
+ ):
97
+ super().__init__(
98
+ device=device,
99
+ model_config=model_config,
100
+ dtype=dtype,
101
+ custom_pipeline=custom_pipeline,
102
+ noise_scheduler=noise_scheduler,
103
+ **kwargs,
104
+ )
105
+
106
+ self._wan_cache = None
107
+
108
+ def load_model(self):
109
+ super().load_model()
110
+
111
+ # patch the condition embedder
112
+ self.model.condition_embedder.forward = partial(time_text_monkeypatch, self.model.condition_embedder)
113
+
114
+ def get_bucket_divisibility(self):
115
+ # 16x compression and 2x2 patch size
116
+ return 32
117
+
118
+ def get_generation_pipeline(self):
119
+ scheduler = UniPCMultistepScheduler(**self._wan_generation_scheduler_config)
120
+ pipeline = Wan22Pipeline(
121
+ vae=self.vae,
122
+ transformer=self.model,
123
+ transformer_2=self.model,
124
+ text_encoder=self.text_encoder,
125
+ tokenizer=self.tokenizer,
126
+ scheduler=scheduler,
127
+ expand_timesteps=self._wan_expand_timesteps,
128
+ device=self.device_torch,
129
+ aggressive_offload=self.model_config.low_vram,
130
+ )
131
+
132
+ pipeline = pipeline.to(self.device_torch)
133
+
134
+ return pipeline
135
+
136
+ # static method to get the scheduler
137
+ @staticmethod
138
+ def get_train_scheduler():
139
+ scheduler = CustomFlowMatchEulerDiscreteScheduler(**scheduler_config)
140
+ return scheduler
141
+
142
+ def get_base_model_version(self):
143
+ return "wan_2.2_5b"
144
+
145
+ def generate_single_image(
146
+ self,
147
+ pipeline: AggressiveWanUnloadPipeline,
148
+ gen_config: GenerateImageConfig,
149
+ conditional_embeds: PromptEmbeds,
150
+ unconditional_embeds: PromptEmbeds,
151
+ generator: torch.Generator,
152
+ extra: dict,
153
+ ):
154
+ # reactivate progress bar since this is slooooow
155
+ pipeline.set_progress_bar_config(disable=False)
156
+
157
+ num_frames = (
158
+ (gen_config.num_frames - 1) // 4
159
+ ) * 4 + 1 # make sure it is divisible by 4 + 1
160
+ gen_config.num_frames = num_frames
161
+
162
+ height = gen_config.height
163
+ width = gen_config.width
164
+ noise_mask = None
165
+ if gen_config.ctrl_img is not None:
166
+ control_img = Image.open(gen_config.ctrl_img).convert("RGB")
167
+
168
+ d = self.get_bucket_divisibility()
169
+
170
+ # make sure they are divisible by d
171
+ height = height // d * d
172
+ width = width // d * d
173
+
174
+ # resize the control image
175
+ control_img = control_img.resize((width, height), Image.LANCZOS)
176
+
177
+ # 5. Prepare latent variables
178
+ num_channels_latents = self.transformer.config.in_channels
179
+ latents = pipeline.prepare_latents(
180
+ 1,
181
+ num_channels_latents,
182
+ height,
183
+ width,
184
+ gen_config.num_frames,
185
+ torch.float32,
186
+ self.device_torch,
187
+ generator,
188
+ None,
189
+ ).to(self.torch_dtype)
190
+
191
+ first_frame_n1p1 = (
192
+ TF.to_tensor(control_img)
193
+ .unsqueeze(0)
194
+ .to(self.device_torch, dtype=self.torch_dtype)
195
+ * 2.0
196
+ - 1.0
197
+ ) # normalize to [-1, 1]
198
+
199
+ gen_config.latents, noise_mask = add_first_frame_conditioning_v22(
200
+ latent_model_input=latents, first_frame=first_frame_n1p1, vae=self.vae
201
+ )
202
+
203
+ output = pipeline(
204
+ prompt_embeds=conditional_embeds.text_embeds.to(
205
+ self.device_torch, dtype=self.torch_dtype
206
+ ),
207
+ negative_prompt_embeds=unconditional_embeds.text_embeds.to(
208
+ self.device_torch, dtype=self.torch_dtype
209
+ ),
210
+ height=height,
211
+ width=width,
212
+ num_inference_steps=gen_config.num_inference_steps,
213
+ guidance_scale=gen_config.guidance_scale,
214
+ latents=gen_config.latents,
215
+ num_frames=gen_config.num_frames,
216
+ generator=generator,
217
+ return_dict=False,
218
+ output_type="pil",
219
+ noise_mask=noise_mask,
220
+ **extra,
221
+ )[0]
222
+
223
+ # shape = [1, frames, channels, height, width]
224
+ batch_item = output[0] # list of pil images
225
+ if gen_config.num_frames > 1:
226
+ return batch_item # return the frames.
227
+ else:
228
+ # get just the first image
229
+ img = batch_item[0]
230
+ return img
231
+
232
+ def get_noise_prediction(
233
+ self,
234
+ latent_model_input: torch.Tensor,
235
+ timestep: torch.Tensor, # 0 to 1000 scale
236
+ text_embeddings: PromptEmbeds,
237
+ batch: DataLoaderBatchDTO,
238
+ **kwargs,
239
+ ):
240
+ # videos come in (bs, num_frames, channels, height, width)
241
+ # images come in (bs, channels, height, width)
242
+
243
+ # for wan, only do i2v for video for now. Images do normal t2i
244
+ conditioned_latent = latent_model_input
245
+ noise_mask = None
246
+
247
+ if batch.dataset_config.do_i2v:
248
+ with torch.no_grad():
249
+ frames = batch.tensor
250
+ if len(frames.shape) == 4:
251
+ first_frames = frames
252
+ elif len(frames.shape) == 5:
253
+ first_frames = frames[:, 0]
254
+ # Add conditioning using the standalone function
255
+ conditioned_latent, noise_mask = add_first_frame_conditioning_v22(
256
+ latent_model_input=latent_model_input.to(
257
+ self.device_torch, self.torch_dtype
258
+ ),
259
+ first_frame=first_frames.to(self.device_torch, self.torch_dtype),
260
+ vae=self.vae,
261
+ )
262
+ else:
263
+ raise ValueError(f"Unknown frame shape {frames.shape}")
264
+
265
+ # make the noise mask
266
+ if noise_mask is None:
267
+ noise_mask = torch.ones(
268
+ conditioned_latent.shape,
269
+ dtype=conditioned_latent.dtype,
270
+ device=conditioned_latent.device,
271
+ )
272
+ # todo write this better
273
+ t_chunks = torch.chunk(timestep, timestep.shape[0])
274
+ out_t_chunks = []
275
+ for t in t_chunks:
276
+ # seq_len: num_latent_frames * latent_height//2 * latent_width//2
277
+ temp_ts = (noise_mask[0][0][:, ::2, ::2] * t).flatten()
278
+ # batch_size, seq_len
279
+ temp_ts = temp_ts.unsqueeze(0)
280
+ out_t_chunks.append(temp_ts)
281
+ timestep = torch.cat(out_t_chunks, dim=0)
282
+
283
+ noise_pred = self.model(
284
+ hidden_states=conditioned_latent,
285
+ timestep=timestep,
286
+ encoder_hidden_states=text_embeddings.text_embeds,
287
+ return_dict=False,
288
+ **kwargs,
289
+ )[0]
290
+ return noise_pred