ayushman-dashtoon commited on
Commit
3053bc1
·
verified ·
1 Parent(s): cd08e83

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +2 -298
README.md CHANGED
@@ -30,301 +30,5 @@ The tranining code can be found [here](https://github.com/dashtoon/hunyuan-video
30
  HunyuanVideo Keyframe Control Lora can be used directly from Diffusers. Install the latest version of Diffusers.
31
 
32
 
33
- ```python
34
- from typing import List, Optional, Tuple, Union
35
-
36
- import cv2
37
- import numpy as np
38
- import safetensors.torch
39
- import torch
40
- import torchvision.transforms.v2 as transforms
41
- from diffusers import FlowMatchEulerDiscreteScheduler, HunyuanVideoPipeline
42
- from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
43
- from diffusers.loaders import HunyuanVideoLoraLoaderMixin
44
- from diffusers.models import AutoencoderKLHunyuanVideo, HunyuanVideoTransformer3DModel
45
- from diffusers.models.attention import Attention
46
- from diffusers.models.embeddings import apply_rotary_emb
47
- from diffusers.models.transformers.transformer_hunyuan_video import HunyuanVideoPatchEmbed, HunyuanVideoTransformer3DModel
48
- from diffusers.pipelines.hunyuan_video.pipeline_hunyuan_video import DEFAULT_PROMPT_TEMPLATE, retrieve_timesteps
49
- from diffusers.pipelines.hunyuan_video.pipeline_output import HunyuanVideoPipelineOutput
50
- from diffusers.pipelines.pipeline_utils import DiffusionPipeline
51
- from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
52
- from diffusers.utils import export_to_video, is_torch_xla_available, load_image, logging, replace_example_docstring
53
- from diffusers.utils.state_dict_utils import convert_state_dict_to_diffusers, convert_unet_state_dict_to_peft
54
- from diffusers.utils.torch_utils import randn_tensor
55
- from diffusers.video_processor import VideoProcessor
56
- from peft import LoraConfig, get_peft_model_state_dict, set_peft_model_state_dict
57
- from PIL import Image
58
-
59
- video_transforms = transforms.Compose(
60
- [
61
- transforms.Lambda(lambda x: x / 255.0),
62
- transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
63
- ]
64
- )
65
-
66
-
67
- def resize_image_to_bucket(image: Union[Image.Image, np.ndarray], bucket_reso: tuple[int, int]) -> np.ndarray:
68
- """
69
- Resize the image to the bucket resolution.
70
- """
71
- is_pil_image = isinstance(image, Image.Image)
72
- if is_pil_image:
73
- image_width, image_height = image.size
74
- else:
75
- image_height, image_width = image.shape[:2]
76
-
77
- if bucket_reso == (image_width, image_height):
78
- return np.array(image) if is_pil_image else image
79
-
80
- bucket_width, bucket_height = bucket_reso
81
-
82
- scale_width = bucket_width / image_width
83
- scale_height = bucket_height / image_height
84
- scale = max(scale_width, scale_height)
85
- image_width = int(image_width * scale + 0.5)
86
- image_height = int(image_height * scale + 0.5)
87
-
88
- if scale > 1:
89
- image = Image.fromarray(image) if not is_pil_image else image
90
- image = image.resize((image_width, image_height), Image.LANCZOS)
91
- image = np.array(image)
92
- else:
93
- image = np.array(image) if is_pil_image else image
94
- image = cv2.resize(image, (image_width, image_height), interpolation=cv2.INTER_AREA)
95
-
96
- # crop the image to the bucket resolution
97
- crop_left = (image_width - bucket_width) // 2
98
- crop_top = (image_height - bucket_height) // 2
99
- image = image[crop_top : crop_top + bucket_height, crop_left : crop_left + bucket_width]
100
-
101
- return image
102
-
103
-
104
- model_id = "hunyuanvideo-community/HunyuanVideo"
105
- transformer = HunyuanVideoTransformer3DModel.from_pretrained(model_id, subfolder="transformer", torch_dtype=torch.bfloat16)
106
- pipe = HunyuanVideoPipeline.from_pretrained(model_id, transformer=transformer, torch_dtype=torch.bfloat16)
107
-
108
- pipe.to("cuda")
109
- pipe.vae.enable_tiling()
110
- pipe.vae.enable_slicing()
111
-
112
- with torch.no_grad(): # enable image inputs
113
- initial_input_channels = pipe.transformer.config.in_channels
114
- new_img_in = HunyuanVideoPatchEmbed(
115
- patch_size=(pipe.transformer.config.patch_size_t, pipe.transformer.config.patch_size, pipe.transformer.config.patch_size),
116
- in_chans=pipe.transformer.config.in_channels * 2,
117
- embed_dim=pipe.transformer.config.num_attention_heads * pipe.transformer.config.attention_head_dim,
118
- )
119
- new_img_in = new_img_in.to(pipe.device, dtype=pipe.dtype)
120
- new_img_in.proj.weight.zero_()
121
- new_img_in.proj.weight[:, :initial_input_channels].copy_(pipe.transformer.x_embedder.proj.weight)
122
-
123
- if pipe.transformer.x_embedder.proj.bias is not None:
124
- new_img_in.proj.bias.copy_(pipe.transformer.x_embedder.proj.bias)
125
-
126
- pipe.transformer.x_embedder = new_img_in
127
-
128
- LORA_PATH = "<PATH_TO_CONTROL_LORA_SAFETENSORS>"
129
- lora_state_dict = pipe.lora_state_dict(LORA_PATH)
130
- transformer_lora_state_dict = {f'{k.replace("transformer.", "")}': v for k, v in lora_state_dict.items() if k.startswith("transformer.") and "lora" in k}
131
- pipe.load_lora_into_transformer(transformer_lora_state_dict, transformer=pipe.transformer, adapter_name="i2v", _pipeline=pipe)
132
- pipe.set_adapters(["i2v"], adapter_weights=[1.0])
133
- pipe.fuse_lora(components=["transformer"], lora_scale=1.0, adapter_names=["i2v"])
134
- pipe.unload_lora_weights()
135
-
136
- n_frames, height, width = 77, 1280, 720
137
- prompt = "a woman"
138
- cond_frame1 = load_image("https://content.dashtoon.ai/stability-images/e524013d-55d4-483a-b80a-dfc51d639158.png")
139
- cond_frame1 = resize_image_to_bucket(cond_frame1, bucket_reso=(width, height))
140
-
141
- cond_frame2 = load_image("https://content.dashtoon.ai/stability-images/0b29c296-0a90-4b92-96b9-1ed0ae21e480.png")
142
- cond_frame2 = resize_image_to_bucket(cond_frame2, bucket_reso=(width, height))
143
-
144
- cond_video = np.zeros(shape=(n_frames, height, width, 3))
145
- cond_video[0], cond_video[-1] = np.array(cond_frame1), np.array(cond_frame2)
146
-
147
- cond_video = torch.from_numpy(cond_video.copy()).permute(0, 3, 1, 2)
148
- cond_video = torch.stack([video_transforms(x) for x in cond_video], dim=0).unsqueeze(0)
149
-
150
- with torch.no_grad():
151
- image_or_video = cond_video.to(device="cuda", dtype=pipe.dtype)
152
- image_or_video = image_or_video.permute(0, 2, 1, 3, 4).contiguous() # [B, F, C, H, W] -> [B, C, F, H, W]
153
- cond_latents = pipe.vae.encode(image_or_video).latent_dist.sample()
154
- cond_latents = cond_latents * pipe.vae.config.scaling_factor
155
- cond_latents = cond_latents.to(dtype=pipe.dtype)
156
-
157
-
158
- @torch.no_grad()
159
- def call_pipe(
160
- pipe,
161
- prompt: Union[str, List[str]] = None,
162
- prompt_2: Union[str, List[str]] = None,
163
- height: int = 720,
164
- width: int = 1280,
165
- num_frames: int = 129,
166
- num_inference_steps: int = 50,
167
- sigmas: List[float] = None,
168
- guidance_scale: float = 6.0,
169
- num_videos_per_prompt: Optional[int] = 1,
170
- generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
171
- latents: Optional[torch.Tensor] = None,
172
- prompt_embeds: Optional[torch.Tensor] = None,
173
- pooled_prompt_embeds: Optional[torch.Tensor] = None,
174
- prompt_attention_mask: Optional[torch.Tensor] = None,
175
- output_type: Optional[str] = "pil",
176
- return_dict: bool = True,
177
- attention_kwargs: Optional[Dict[str, Any]] = None,
178
- callback_on_step_end: Optional[Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks]] = None,
179
- callback_on_step_end_tensor_inputs: List[str] = ["latents"],
180
- prompt_template: Dict[str, Any] = DEFAULT_PROMPT_TEMPLATE,
181
- max_sequence_length: int = 256,
182
- image_latents: Optional[torch.Tensor] = None,
183
- ):
184
-
185
- if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):
186
- callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs
187
-
188
- # 1. Check inputs. Raise error if not correct
189
- pipe.check_inputs(
190
- prompt,
191
- prompt_2,
192
- height,
193
- width,
194
- prompt_embeds,
195
- callback_on_step_end_tensor_inputs,
196
- prompt_template,
197
- )
198
-
199
- pipe._guidance_scale = guidance_scale
200
- pipe._attention_kwargs = attention_kwargs
201
- pipe._current_timestep = None
202
- pipe._interrupt = False
203
-
204
- device = pipe._execution_device
205
-
206
- # 2. Define call parameters
207
- if prompt is not None and isinstance(prompt, str):
208
- batch_size = 1
209
- elif prompt is not None and isinstance(prompt, list):
210
- batch_size = len(prompt)
211
- else:
212
- batch_size = prompt_embeds.shape[0]
213
-
214
- # 3. Encode input prompt
215
- prompt_embeds, pooled_prompt_embeds, prompt_attention_mask = pipe.encode_prompt(
216
- prompt=prompt,
217
- prompt_2=prompt_2,
218
- prompt_template=prompt_template,
219
- num_videos_per_prompt=num_videos_per_prompt,
220
- prompt_embeds=prompt_embeds,
221
- pooled_prompt_embeds=pooled_prompt_embeds,
222
- prompt_attention_mask=prompt_attention_mask,
223
- device=device,
224
- max_sequence_length=max_sequence_length,
225
- )
226
-
227
- transformer_dtype = pipe.transformer.dtype
228
- prompt_embeds = prompt_embeds.to(transformer_dtype)
229
- prompt_attention_mask = prompt_attention_mask.to(transformer_dtype)
230
- if pooled_prompt_embeds is not None:
231
- pooled_prompt_embeds = pooled_prompt_embeds.to(transformer_dtype)
232
-
233
- # 4. Prepare timesteps
234
- sigmas = np.linspace(1.0, 0.0, num_inference_steps + 1)[:-1] if sigmas is None else sigmas
235
- timesteps, num_inference_steps = retrieve_timesteps(
236
- pipe.scheduler,
237
- num_inference_steps,
238
- device,
239
- sigmas=sigmas,
240
- )
241
-
242
- # 5. Prepare latent variables
243
- num_channels_latents = pipe.transformer.config.in_channels
244
- num_latent_frames = (num_frames - 1) // pipe.vae_scale_factor_temporal + 1
245
- latents = pipe.prepare_latents(
246
- batch_size * num_videos_per_prompt,
247
- num_channels_latents,
248
- height,
249
- width,
250
- num_latent_frames,
251
- torch.float32,
252
- device,
253
- generator,
254
- latents,
255
- )
256
-
257
- # 6. Prepare guidance condition
258
- guidance = torch.tensor([guidance_scale] * latents.shape[0], dtype=transformer_dtype, device=device) * 1000.0
259
-
260
- # 7. Denoising loop
261
- num_warmup_steps = len(timesteps) - num_inference_steps * pipe.scheduler.order
262
- pipe._num_timesteps = len(timesteps)
263
-
264
- with pipe.progress_bar(total=num_inference_steps) as progress_bar:
265
- for i, t in enumerate(timesteps):
266
- if pipe.interrupt:
267
- continue
268
-
269
- pipe._current_timestep = t
270
- latent_model_input = latents.to(transformer_dtype)
271
- timestep = t.expand(latents.shape[0]).to(latents.dtype)
272
-
273
- noise_pred = pipe.transformer(
274
- hidden_states=torch.cat([latent_model_input, image_latents], dim=1),
275
- timestep=timestep,
276
- encoder_hidden_states=prompt_embeds,
277
- encoder_attention_mask=prompt_attention_mask,
278
- pooled_projections=pooled_prompt_embeds,
279
- guidance=guidance,
280
- attention_kwargs=attention_kwargs,
281
- return_dict=False,
282
- )[0]
283
-
284
- # compute the previous noisy sample x_t -> x_t-1
285
- latents = pipe.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
286
-
287
- if callback_on_step_end is not None:
288
- callback_kwargs = {}
289
- for k in callback_on_step_end_tensor_inputs:
290
- callback_kwargs[k] = locals()[k]
291
- callback_outputs = callback_on_step_end(pipe, i, t, callback_kwargs)
292
-
293
- latents = callback_outputs.pop("latents", latents)
294
- prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
295
-
296
- # call the callback, if provided
297
- if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % pipe.scheduler.order == 0):
298
- progress_bar.update()
299
- pipe._current_timestep = None
300
-
301
- if not output_type == "latent":
302
- latents = latents.to(pipe.vae.dtype) / pipe.vae.config.scaling_factor
303
- video = pipe.vae.decode(latents, return_dict=False)[0]
304
- video = pipe.video_processor.postprocess_video(video, output_type=output_type)
305
- else:
306
- video = latents
307
-
308
- # Offload all models
309
- pipe.maybe_free_model_hooks()
310
-
311
- if not return_dict:
312
- return (video,)
313
-
314
- return HunyuanVideoPipelineOutput(frames=video)
315
-
316
-
317
- video = call_pipe(
318
- pipe,
319
- prompt=prompt,
320
- num_frames=n_frames,
321
- num_inference_steps=50,
322
- image_latents=cond_latents,
323
- width=width,
324
- height=height,
325
- guidance_scale=6.0,
326
- generator=torch.Generator(device="cuda").manual_seed(0),
327
- ).frames[0]
328
-
329
- export_to_video(video, "output.mp4", fps=24)
330
- ```
 
30
  HunyuanVideo Keyframe Control Lora can be used directly from Diffusers. Install the latest version of Diffusers.
31
 
32
 
33
+ ## Inference
34
+ While the included `inference.py` script can be used to run inference. We would encourage folks to visit out [github repo](https://github.com/dashtoon/hunyuan-video-keyframe-control-lora/blob/main/hv_control_lora_inference.py) which contains a much optimized version of this inference script.