little change

This commit is contained in:
songkey-win
2025-06-02 00:23:11 +08:00
parent deba8b2fc3
commit 9ea08a4411
13 changed files with 2023 additions and 16 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

+1 -1
View File
@@ -10,4 +10,4 @@
from .pipelines import (HMImagePipeline, HMVideoPipeline,
HM3ImagePipeline, HM3VideoPipeline,
HM5ImagePipeline)
HM5ImagePipeline, HM5VideoPipeline)
+1 -1
View File
@@ -13,7 +13,7 @@ from .hm_control import (HMControlNet, HMControlNet2, HMV2ControlNet, HMV2Contro
HMV3ControlNet, HMControlNetBase, HM5ControlNetBase,
HM4SD15ControlProj, HM5SD15ControlProj)
from .hm_adapters import (HMReferenceAdapter, HM3ReferenceAdapter, HM5ReferenceAdapter,
HM3MotionAdapter, HMPipeline)
HM3MotionAdapter, HM5MotionAdapter, HMPipeline)
from .hm_denoising_3d import HMDenoising3D
from .hm3_denoising_3d import HM3Denoising3D
from .hm3_denoising_motion import HM3DenoisingMotion
+6 -2
View File
@@ -193,7 +193,9 @@ class HM3Denoising3D(UNet2DConditionModel, CopyWeights, InsertReferenceAdapter):
if not control_hidden_states is None and f'down3_{idx}' in control_hidden_states:
sample += rearrange(control_hidden_states[f'down3_{idx}'], "b c f h w -> (b f) c h w")
if hasattr(self, 'motion_down') and use_motion:
sample = self.motion_down[idx](sample, motion_pad_hidden_states[f'down_{idx}'], emb, num_frames)
sample = self.motion_down[idx](sample,
None if motion_pad_hidden_states is None else motion_pad_hidden_states[f'down_{idx}'],
emb, num_frames)
down_block_res_samples += res_samples
@@ -251,7 +253,9 @@ class HM3Denoising3D(UNet2DConditionModel, CopyWeights, InsertReferenceAdapter):
if hasattr(self, "reference_modules_up") and not reference_hidden_states is None and f'up_{i}' in reference_hidden_states:
sample = self.reference_modules_up[i](sample, reference_hidden_states[f'up_{i}'], num_frames)
if hasattr(self, 'motion_up') and use_motion:
sample = self.motion_up[i](sample, motion_pad_hidden_states[f'up_{i}'], emb, num_frames)
sample = self.motion_up[i](sample,
None if motion_pad_hidden_states is None else motion_pad_hidden_states[f'up_{i}'],
emb, num_frames)
if hasattr(upsample_block, "has_cross_attention") and upsample_block.has_cross_attention:
sample = upsample_block(
+28 -1
View File
@@ -42,7 +42,8 @@ from diffusers.models.lora import adjust_lora_scale_text_encoder
from .hm_blocks import (SKReferenceAttentionV3,
SKReferenceAttentionV5,
SKReferenceAttention,
SKMotionModule)
SKMotionModule,
SKMotionModuleV5)
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
@@ -183,6 +184,32 @@ class HM3MotionAdapter(ModelMixin, ConfigMixin):
)
class HM5MotionAdapter(ModelMixin, ConfigMixin):
@register_to_config
def __init__(self, block_down_channels: Tuple[int] = (320, 640, 1280, 1280),
block_up_channels: Tuple[int] = (1280, 1280, 1280, 640),
num_attention_heads: int = 8):
super().__init__()
self.motion_down = nn.ModuleList([])
for i, in_channels in enumerate(block_down_channels):
self.motion_down.append(
SKMotionModuleV5(
in_channels=in_channels,
num_attention_heads=num_attention_heads,
)
)
self.motion_up = nn.ModuleList([])
for i, in_channels in enumerate(block_up_channels):
self.motion_up.append(
SKMotionModuleV5(
in_channels=in_channels,
num_attention_heads=num_attention_heads,
)
)
class HMPipeline(StableDiffusionImg2ImgPipeline):
@torch.no_grad()
def load_lora_weights_sk(self, unet, pretrained_model_name_or_path_or_dict: Union[str, Dict[str, torch.Tensor]],
+50 -2
View File
@@ -14,9 +14,12 @@ import torch.utils.checkpoint
from typing import Tuple
from einops import rearrange
from diffusers.models.attention import Attention, FeedForward, BasicTransformerBlock
from diffusers.models.attention import Attention, FeedForward
from diffusers.models.unets.unet_motion_model import AnimateDiffTransformer3D
from torchvision.models.resnet import BasicBlock
from diffusers.models.embeddings import SinusoidalPositionalEmbedding, get_1d_sincos_pos_embed_from_grid, TimestepEmbedding
from diffusers.models.embeddings import (SinusoidalPositionalEmbedding,
get_1d_sincos_pos_embed_from_grid,
TimestepEmbedding)
from diffusers.utils import logging
@@ -725,6 +728,51 @@ class SKMotionModule(nn.Module):
return hidden_states + self.proj(res_temp)
class SKMotionModuleV5(nn.Module):
def __init__(
self,
in_channels: int,
num_attention_heads: int = 8,
):
super().__init__()
self.attn1 = AnimateDiffTransformer3D(
in_channels=in_channels,
num_attention_heads=num_attention_heads,
attention_head_dim=in_channels // num_attention_heads,
dropout=0.0,
double_self_attention=True,
positional_embeddings='sinusoidal',
num_positional_embeddings=32,
)
self.attn_pad = AnimateDiffTransformer3D(
in_channels=in_channels,
num_attention_heads=num_attention_heads,
attention_head_dim=in_channels // num_attention_heads,
dropout=0.0,
double_self_attention=True,
positional_embeddings='sinusoidal',
num_positional_embeddings=32,
)
def forward(self, hidden_states, pad_states, temb, num_frames):
c, h, w = hidden_states.shape[-3:]
res_temp = hidden_states
if not pad_states is None:
res_temp = rearrange(res_temp, "(b f) c h w -> b c f h w", f=num_frames)
res_temp = torch.cat([pad_states[:,:,:1], res_temp, pad_states[:,:,-1:]], dim=2).contiguous()
res_temp = rearrange(res_temp, "b c f h w -> (b f) c h w")
res_temp = self.attn_pad(res_temp, num_frames=num_frames+2)
res_temp = rearrange(res_temp, "(b f) c h w -> b c f h w", f=num_frames+2).contiguous()
res_temp = rearrange(res_temp[:,:,1:-1], "b c f h w -> (b f) c h w", h=h, w=w) + hidden_states
res_temp = self.attn1(res_temp, num_frames=num_frames)
return res_temp
# https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/controlnet.py
def zero_module(module):
for p in module.parameters():
+2 -1
View File
@@ -12,4 +12,5 @@ from .pipline_hm_image import HMImagePipeline
from .pipline_hm_video import HMVideoPipeline
from .pipline_hm3_image import HM3ImagePipeline
from .pipline_hm3_video import HM3VideoPipeline
from .pipline_hm5_image import HM5ImagePipeline
from .pipline_hm5_image import HM5ImagePipeline
from .pipline_hm5_video import HM5VideoPipeline
+510
View File
@@ -0,0 +1,510 @@
# coding: utf-8
"""
@File : hm_pipline_image.py
@Author : Songkey
@Email : songkey@pku.edu.cn
@Date : 5/30/2025
@Desc :
adapted from: https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion_img2img.py
"""
import copy
from typing import Any, Callable, Dict, List, Optional, Union
import torch
from einops import rearrange
from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
from diffusers.image_processor import PipelineImageInput
from diffusers.utils import deprecate
from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img import retrieve_timesteps, retrieve_latents
from diffusers import DPMSolverMultistepScheduler
from diffusers.utils.torch_utils import randn_tensor
from ..models import (HM3Denoising3D, HM5MotionAdapter,
HM5ReferenceAdapter, HMPipeline, HM5ControlNetBase, HM5SD15ControlProj)
class HM5VideoPipeline(HMPipeline):
def caryomitosis(self, version='v5', modelscope=False, **kwargs):
if hasattr(self, "unet_ref"):
del self.unet_ref
self.unet_ref = HM3Denoising3D.from_unet2d(self.unet)
self.unet_ref.cpu()
self.num_frames = 12
if not isinstance(self.unet, HM3Denoising3D):
unet = HM3Denoising3D.from_unet2d(unet=self.unet)
# todo: 不够优雅
del self.unet
self.unet = unet
self.unet.cpu()
self.vae.cpu()
self.vae_decode = copy.deepcopy(self.vae)
self.text_encoder.cpu()
self.text_encoder_ref = copy.deepcopy(self.text_encoder)
self.safety_checker.cpu()
def insert_hm_modules(self, version='v5', dtype=torch.float16, modelscope=False):
self.version = version
if modelscope:
from modelscope import snapshot_download
hm_reference_dir = snapshot_download('songkey/hm5_reference')
hm_control_dir = snapshot_download('songkey/hm5_control_base')
hm_control_proj_dir = snapshot_download('songkey/hm5_control_proj')
hm_motion_dir = snapshot_download('songkey/hm5_motion')
else:
hm_reference_dir = 'songkey/hm5_reference'
hm_control_dir = 'songkey/hm5_control_base'
hm_control_proj_dir = 'songkey/hm5_control_proj'
hm_motion_dir = 'songkey/hm5_motion'
hm_adapter = HM5ReferenceAdapter.from_pretrained(hm_reference_dir)
motion_adapter = HM5MotionAdapter.from_pretrained(hm_motion_dir)
if isinstance(self.unet, HM3Denoising3D):
self.unet.insert_reference_adapter(hm_adapter)
self.unet.insert_reference_adapter(motion_adapter)
self.unet.to(device='cpu', dtype=dtype).eval()
if hasattr(self, "unet_ref"):
self.unet_ref.to(device='cpu', dtype=dtype).eval()
if hasattr(self, "mp_control"):
del self.mp_control
if hasattr(self, "mp_control_proj"):
del self.mp_control_proj
self.mp_control = HM5ControlNetBase.from_pretrained(hm_control_dir)
self.mp_control_proj = HM5SD15ControlProj.from_pretrained(hm_control_proj_dir)
self.mp_control_proj.to(device='cpu', dtype=dtype).eval()
self.mp_control.to(device='cpu', dtype=dtype).eval()
self.vae.to(device='cpu', dtype=dtype).eval()
self.vae_decode.to(device='cpu', dtype=dtype).eval()
self.text_encoder.to(device='cpu', dtype=dtype).eval()
@torch.no_grad()
def gen_ref_cache(self, latent, prompt_embeds, added_cond_kwargs, device, add_axis=False):
self.unet_ref.to(device=device)
latent = latent.to(device=device, dtype=prompt_embeds.dtype)
latent_model_input = torch.cat([torch.zeros_like(latent), latent]) if self.do_classifier_free_guidance else latent
cached_res = self.unet_ref(
latent_model_input.unsqueeze(2),
0,
encoder_hidden_states=prompt_embeds,
added_cond_kwargs=added_cond_kwargs,
return_dict=False,
)[1]
self.unet_ref.cpu()
if add_axis:
for k, v in cached_res.items():
cached_res[k] = rearrange(v, 'b c h w -> b c 1 h w')
return dicts_to_device([cached_res], device='cpu')[0]
@torch.no_grad()
def gen_video(self, indexes, pad_dict, base_noise, depth, control_dict, step, more_params):
device = more_params['device']
timesteps = more_params['timesteps']
timestep_cond = more_params['timestep_cond']
sigmas = more_params['sigmas']
prompt_embeds = more_params['prompt_embeds']
cached_res = more_params['cached_res']
added_cond_kwargs = more_params['added_cond_kwargs']
extra_step_kwargs = more_params['extra_step_kwargs']
num_indexes = len(indexes)
if num_indexes <= self.num_frames:
paded_indexes = indexes
paded_indexes_noise = list(range(len(indexes)))
paded_indexes = copy.deepcopy(paded_indexes)
drive_idx_chunks = [paded_indexes]
drive_idx_chunks_noise = [paded_indexes_noise]
else:
stride = self.num_frames - 1
paded_indexes = indexes
paded_indexes = copy.deepcopy(paded_indexes)
num_paded_indexes = len(paded_indexes)
paded_indexes_noise = list(range(num_paded_indexes))
drive_idx_chunks = [paded_indexes[i:min(i + self.num_frames, num_paded_indexes)] for i in
range(0, num_paded_indexes, stride)]
drive_idx_chunks_noise = [paded_indexes_noise[i:min(i + self.num_frames, num_paded_indexes)] for i in
range(0, num_paded_indexes, stride)]
if len(drive_idx_chunks[-1]) == 1:
drive_idx_chunks[-1] = drive_idx_chunks[:-1]
drive_idx_chunks_noise[-1] = drive_idx_chunks_noise[:-1]
margin_indexes = set()
for chunk in drive_idx_chunks:
margin_indexes.add(chunk[0])
margin_indexes.add(chunk[-1])
margin_indexes = sorted(list(margin_indexes))
self.gen_video(margin_indexes, pad_dict, base_noise, depth+1, control_dict, step, more_params)
## generate latent
scheduler = DPMSolverMultistepScheduler(
num_train_timesteps=1000,
beta_start=0.00085,
beta_end=0.012,
beta_schedule="scaled_linear",
algorithm_type="sde-dpmsolver++",
)
timesteps, _ = retrieve_timesteps(scheduler, step, device, timesteps, sigmas)
N, C, _, H, W = base_noise.shape
latents = base_noise[:,:,:len(paded_indexes)] * scheduler.init_noise_sigma
self.unet.to(device=device)
with self.progress_bar(total=len(timesteps)) as progress_bar:
for i, t in enumerate(timesteps):
progress_bar.set_description(f"D{depth} GEN Frames")
progress_bar.update()
noise_pred = torch.zeros_like(latents)
noise_pred = torch.cat([noise_pred, noise_pred], dim=0) if \
self.do_classifier_free_guidance else noise_pred
noise_pred_counter = torch.zeros([1, 1, noise_pred.size(2), 1, 1], dtype=prompt_embeds.dtype).cpu()
latent_model_input_all = torch.cat([latents] * 2, dim=0) if \
self.do_classifier_free_guidance else latents
latent_model_input_all = scheduler.scale_model_input(latent_model_input_all, t)
for chunk, chunk_noise in zip(drive_idx_chunks, drive_idx_chunks_noise):
if self.interrupt:
continue
control_latent = cat_dicts([control_dict[ix] for ix in chunk], dim=2)
if chunk[0] in pad_dict and chunk[-1] in pad_dict:
pad_latent = cat_dicts([pad_dict[chunk[0]], pad_dict[chunk[-1]]], dim=2)
else:
pad_latent = None
latent_model_input = latent_model_input_all[:, :, chunk_noise].to(device=device)
noise_pred_chunk = self.unet(
latent_model_input,
t,
encoder_hidden_states=prompt_embeds,
reference_hidden_states=dicts_to_device([cached_res], device=device)[0],
control_hidden_states=dicts_to_device([control_latent], device=device)[0],
motion_pad_hidden_states=None if pad_latent is None else dicts_to_device([pad_latent], device=device)[0],
use_motion=True,
timestep_cond=timestep_cond,
cross_attention_kwargs=self.cross_attention_kwargs,
added_cond_kwargs=added_cond_kwargs,
return_dict=False,
)[0].cpu()
noise_pred[:, :, chunk_noise] = noise_pred[:, :, chunk_noise] + noise_pred_chunk
noise_pred_counter[:, :, chunk_noise] = noise_pred_counter[:, :, chunk_noise] + 1
noise_pred = noise_pred / noise_pred_counter
if self.do_classifier_free_guidance:
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond)
latents = scheduler.step(noise_pred.cpu(), t, latents,
**extra_step_kwargs, return_dict=False)[0]
self.unet.cpu()
if depth == 0:
return latents[:,:,:num_indexes]
else:
for chunk, chunk_noise in zip(drive_idx_chunks, drive_idx_chunks_noise):
for idx, idx_noise in zip(chunk, chunk_noise):
# if not idx in pad_dict:
pad_dict[idx] = self.gen_ref_cache(latents[:,:,idx_noise], prompt_embeds,
added_cond_kwargs, device, add_axis=True)
@torch.no_grad()
def __call__(
self,
prompt: Union[str, List[str]] = None,
image: PipelineImageInput = None,
chunk_overlap: int = 4,
drive_params: Dict[str, Any] = None,
strength: float = 0.8,
num_inference_steps: Optional[int] = 50,
timesteps: List[int] = None,
sigmas: List[float] = None,
guidance_scale: Optional[float] = 7.5,
negative_prompt: Optional[Union[str, List[str]]] = None,
eta: Optional[float] = 0.0,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
prompt_embeds: Optional[torch.Tensor] = None,
negative_prompt_embeds: Optional[torch.Tensor] = None,
ip_adapter_image: Optional[PipelineImageInput] = None,
ip_adapter_image_embeds: Optional[List[torch.Tensor]] = None,
output_type: Optional[str] = "pil",
device: Optional[str] = "cpu",
return_dict: bool = True,
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
clip_skip: int = None,
callback_on_step_end: Optional[
Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks]
] = None,
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
**kwargs,
):
callback = kwargs.pop("callback", None)
callback_steps = kwargs.pop("callback_steps", None)
num_images_per_prompt = 1
if callback is not None:
deprecate(
"callback",
"1.0.0",
"Passing `callback` as an input argument to `__call__` is deprecated, consider use `callback_on_step_end`",
)
if callback_steps is not None:
deprecate(
"callback_steps",
"1.0.0",
"Passing `callback_steps` as an input argument to `__call__` is deprecated, consider use `callback_on_step_end`",
)
if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):
callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs
# 1. Check inputs. Raise error if not correct
self.check_inputs(
prompt,
strength,
callback_steps,
negative_prompt,
prompt_embeds,
negative_prompt_embeds,
ip_adapter_image,
ip_adapter_image_embeds,
callback_on_step_end_tensor_inputs,
)
self._guidance_scale = guidance_scale
self._clip_skip = clip_skip
self._cross_attention_kwargs = cross_attention_kwargs
self._interrupt = False
# 2. Define call parameters
if prompt is not None and isinstance(prompt, str):
batch_size = 1
elif prompt is not None and isinstance(prompt, list):
batch_size = len(prompt)
else:
batch_size = prompt_embeds.shape[0]
# device = self.device
# 3. Encode input prompt
text_encoder_lora_scale = (
self.cross_attention_kwargs.get("scale", None) if self.cross_attention_kwargs is not None else None
)
self.text_encoder_ref.to(device=device)
prompt_embeds_ref, negative_prompt_embeds_ref = self.encode_prompt_sk(
self.text_encoder_ref,
prompt,
device,
num_images_per_prompt,
self.do_classifier_free_guidance,
negative_prompt,
prompt_embeds=prompt_embeds,
negative_prompt_embeds=negative_prompt_embeds,
lora_scale=text_encoder_lora_scale,
clip_skip=self.clip_skip,
)
self.text_encoder_ref.cpu()
self.text_encoder.to(device=device)
prompt_embeds, negative_prompt_embeds = self.encode_prompt_sk(
self.text_encoder,
prompt,
device,
num_images_per_prompt,
self.do_classifier_free_guidance,
negative_prompt,
prompt_embeds=prompt_embeds,
negative_prompt_embeds=negative_prompt_embeds,
lora_scale=text_encoder_lora_scale,
clip_skip=self.clip_skip,
)
self.text_encoder.cpu()
# For classifier free guidance, we need to do two forward passes.
# Here we concatenate the unconditional and text embeddings into a single batch
# to avoid doing two forward passes
if self.do_classifier_free_guidance:
prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])
prompt_embeds_ref = torch.cat([negative_prompt_embeds_ref, prompt_embeds_ref])
if ip_adapter_image is not None or ip_adapter_image_embeds is not None:
image_embeds = self.prepare_ip_adapter_image_embeds(
ip_adapter_image,
ip_adapter_image_embeds,
device,
batch_size * num_images_per_prompt,
self.do_classifier_free_guidance,
)
# 4. Preprocess image
image = self.image_processor.preprocess(image).to(device=device, dtype=prompt_embeds.dtype)
raw_video_len = drive_params['condition'].size(2)
# 6. Prepare latent variables
self.vae.to(device=device)
ref_latents = [
retrieve_latents(self.vae.encode(image[i: i + 1].to(device=device)), generator=generator)
for i in range(batch_size)
]
self.vae.cpu()
ref_latents = torch.cat(ref_latents, dim=0)
ref_latents = self.vae.config.scaling_factor * ref_latents
c, h, w = ref_latents.shape[1:]
# 7. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
# 7.1 Add image embeds for IP-Adapter
added_cond_kwargs = (
{"image_embeds": image_embeds}
if ip_adapter_image is not None or ip_adapter_image_embeds is not None
else None
)
# 7.2 Optionally get Guidance Scale Embedding
timestep_cond = None
if self.unet.config.time_cond_proj_dim is not None:
guidance_scale_tensor = torch.tensor(self.guidance_scale - 1).repeat(batch_size * num_images_per_prompt)
timestep_cond = self.get_guidance_scale_embedding(
guidance_scale_tensor, embedding_dim=self.unet.config.time_cond_proj_dim
).to(device=device, dtype=prompt_embeds.dtype)
input_noise = randn_tensor([batch_size, c, raw_video_len + self.num_frames + 1, h, w],
generator=generator, dtype=prompt_embeds.dtype)
cached_res = self.gen_ref_cache(ref_latents, prompt_embeds_ref, added_cond_kwargs, device)
control_latents = []
control_latent1_neg, control_latent2_neg = None, None
self.mp_control.to(device=device)
self.mp_control_proj.to(device=device)
with self.progress_bar(total=raw_video_len) as progress_bar:
for idx in range(raw_video_len):
progress_bar.set_description(f"GEN stage1")
progress_bar.update()
condition = drive_params['condition'][:, :, idx:idx+1].clone().to(device=device)
control_latent_input = {}
if 'drive_coeff' in drive_params:
drive_coeff = drive_params['drive_coeff'][:, idx:idx+1].clone().to(device=device)
face_parts = drive_params['face_parts'][:, idx:idx+1].clone().to(device=device)
if control_latent1_neg is None and self.do_classifier_free_guidance:
control_latent1_neg = self.mp_control(condition=torch.ones_like(condition)*-1,
drive_coeff=torch.zeros_like(drive_coeff),
face_parts=torch.zeros_like(face_parts))
control_latent1_neg = self.mp_control_proj(control_latent1_neg)
control_latent1 = self.mp_control(condition=condition,
drive_coeff=drive_coeff,
face_parts=face_parts)
control_latent1 = self.mp_control_proj(control_latent1)
if self.do_classifier_free_guidance:
control_latent1 = cat_dicts([control_latent1_neg, control_latent1], dim=0)
control_latent_input.update(control_latent1)
else:
pd_fpg = drive_params['pd_fpg'][:, idx:idx+1].clone().to(device=device)
if control_latent2_neg is None and self.do_classifier_free_guidance:
control_latent2_neg = self.mp_control(condition=torch.ones_like(condition)*-1,
emo_embedding=torch.zeros_like(pd_fpg))
control_latent2_neg = self.mp_control_proj(control_latent2_neg)
control_latent2 = self.mp_control(condition=condition, emo_embedding=pd_fpg)
control_latent2 = self.mp_control_proj(control_latent2)
if self.do_classifier_free_guidance:
control_latent2 = cat_dicts([control_latent2_neg, control_latent2], dim=0)
control_latent_input.update(control_latent2)
control_latents.append(dicts_to_device([control_latent_input], device='cpu')[0])
self.mp_control.cpu()
self.mp_control_proj.cpu()
more_params = dict(
timesteps=timesteps,
timestep_cond=timestep_cond,
sigmas=sigmas,
prompt_embeds=prompt_embeds,
cached_res=cached_res,
device=device,
added_cond_kwargs=added_cond_kwargs,
extra_step_kwargs=extra_step_kwargs,
)
indexes = list(range(raw_video_len))
latents = self.gen_video(indexes, dict(), input_noise, 0, control_latents, num_inference_steps, more_params)
latents_res = latents / self.vae_decode.config.scaling_factor
self.vae_decode.to(device=device)
res_frames = []
with self.progress_bar(total=raw_video_len) as progress_bar:
for i in range(raw_video_len):
ret_tensor = self.vae_decode.decode(latents_res[:, :, i, :, :].to(device=device),
return_dict=False, generator=generator)[0]
ret_image = self.image_processor.postprocess(ret_tensor, output_type=output_type)
res_frames.append(ret_image)
progress_bar.set_description(f"VAE DECODE")
progress_bar.update()
self.vae_decode.cpu()
return res_frames, latents_res
def merge_dicts(dictl, dictr, wl=0.5):
res = {}
for k in dictl.keys():
res[k] = dictl[k] * wl + dictr[k] * (1-wl)
return res
def cat_dicts(dicts, dim=0):
res = {}
for k in dicts[0].keys():
res[k] = torch.cat([d[k].clone() for d in dicts], dim=dim)
return res
def cat_dicts_ref(dicts):
res = {}
for k in dicts[0].keys():
res[k] = rearrange(torch.cat([d[k].clone().unsqueeze(1) for d in dicts], dim=1), "b f c h w -> (b f) c h w ")
return res
def dicts_to_device(dicts, device):
ret = []
for d in dicts:
tmpd = {}
for k in d.keys():
tmpd[k] = d[k].clone().to(device)
ret.append(tmpd)
return ret
+394
View File
@@ -0,0 +1,394 @@
# coding: utf-8
"""
@File : sr.py
@Author : Songkey
@Email : songkey@pku.edu.cn
@Date : 5/30/2025
@Desc : adapted from: https://github.com/xinntao/Real-ESRGAN
"""
import torch
from torch import nn as nn
from torch.nn import functional as F
import cv2
import numpy as np
import math
import os.path as osp
def pixel_unshuffle(x, scale):
""" Pixel unshuffle.
Args:
x (Tensor): Input feature with shape (b, c, hh, hw).
scale (int): Downsample ratio.
Returns:
Tensor: the pixel unshuffled feature.
"""
b, c, hh, hw = x.size()
out_channel = c * (scale**2)
assert hh % scale == 0 and hw % scale == 0
h = hh // scale
w = hw // scale
x_view = x.view(b, c, h, scale, w, scale)
return x_view.permute(0, 1, 3, 5, 2, 4).reshape(b, out_channel, h, w)
def make_layer(basic_block, num_basic_block, **kwarg):
"""Make layers by stacking the same blocks.
Args:
basic_block (nn.module): nn.module class for basic block.
num_basic_block (int): number of blocks.
Returns:
nn.Sequential: Stacked blocks in nn.Sequential.
"""
layers = []
for _ in range(num_basic_block):
layers.append(basic_block(**kwarg))
return nn.Sequential(*layers)
class ResidualDenseBlock(nn.Module):
"""Residual Dense Block.
Used in RRDB block in ESRGAN.
Args:
num_feat (int): Channel number of intermediate features.
num_grow_ch (int): Channels for each growth.
"""
def __init__(self, num_feat=64, num_grow_ch=32):
super(ResidualDenseBlock, self).__init__()
self.conv1 = nn.Conv2d(num_feat, num_grow_ch, 3, 1, 1)
self.conv2 = nn.Conv2d(num_feat + num_grow_ch, num_grow_ch, 3, 1, 1)
self.conv3 = nn.Conv2d(num_feat + 2 * num_grow_ch, num_grow_ch, 3, 1, 1)
self.conv4 = nn.Conv2d(num_feat + 3 * num_grow_ch, num_grow_ch, 3, 1, 1)
self.conv5 = nn.Conv2d(num_feat + 4 * num_grow_ch, num_feat, 3, 1, 1)
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
# initialization
# default_init_weights([self.conv1, self.conv2, self.conv3, self.conv4, self.conv5], 0.1)
def forward(self, x):
x1 = self.lrelu(self.conv1(x))
x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1)))
x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1)))
x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1)))
x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1))
# Empirically, we use 0.2 to scale the residual for better performance
return x5 * 0.2 + x
class RRDB(nn.Module):
"""Residual in Residual Dense Block.
Used in RRDB-Net in ESRGAN.
Args:
num_feat (int): Channel number of intermediate features.
num_grow_ch (int): Channels for each growth.
"""
def __init__(self, num_feat, num_grow_ch=32):
super(RRDB, self).__init__()
self.rdb1 = ResidualDenseBlock(num_feat, num_grow_ch)
self.rdb2 = ResidualDenseBlock(num_feat, num_grow_ch)
self.rdb3 = ResidualDenseBlock(num_feat, num_grow_ch)
def forward(self, x):
out = self.rdb1(x)
out = self.rdb2(out)
out = self.rdb3(out)
# Empirically, we use 0.2 to scale the residual for better performance
return out * 0.2 + x
class RRDBNet(nn.Module):
"""Networks consisting of Residual in Residual Dense Block, which is used
in ESRGAN.
ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks.
We extend ESRGAN for scale x2 and scale x1.
Note: This is one option for scale 1, scale 2 in RRDBNet.
We first employ the pixel-unshuffle (an inverse operation of pixelshuffle to reduce the spatial size
and enlarge the channel size before feeding inputs into the main ESRGAN architecture.
Args:
num_in_ch (int): Channel number of inputs.
num_out_ch (int): Channel number of outputs.
num_feat (int): Channel number of intermediate features.
Default: 64
num_block (int): Block number in the trunk network. Defaults: 23
num_grow_ch (int): Channels for each growth. Default: 32.
"""
def __init__(self, num_in_ch, num_out_ch, scale=4, num_feat=64, num_block=23, num_grow_ch=32):
super(RRDBNet, self).__init__()
self.scale = scale
if scale == 2:
num_in_ch = num_in_ch * 4
elif scale == 1:
num_in_ch = num_in_ch * 16
self.conv_first = nn.Conv2d(num_in_ch, num_feat, 3, 1, 1)
self.body = make_layer(RRDB, num_block, num_feat=num_feat, num_grow_ch=num_grow_ch)
self.conv_body = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
# upsample
self.conv_up1 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_up2 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_hr = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1)
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
def forward(self, x):
if self.scale == 2:
feat = pixel_unshuffle(x, scale=2)
elif self.scale == 1:
feat = pixel_unshuffle(x, scale=4)
else:
feat = x
feat = self.conv_first(feat)
body_feat = self.conv_body(self.body(feat))
feat = feat + body_feat
# upsample
feat = self.lrelu(self.conv_up1(F.interpolate(feat, scale_factor=2, mode='nearest')))
feat = self.lrelu(self.conv_up2(F.interpolate(feat, scale_factor=2, mode='nearest')))
out = self.conv_last(self.lrelu(self.conv_hr(feat)))
return out
class RealESRGANer():
def __init__(self,
scale,
tile=0,
tile_pad=10,
pre_pad=10,
half=True,
device=None,
gpu_id=None,
modelscope=False):
self.scale = scale
self.tile_size = tile
self.tile_pad = tile_pad
self.pre_pad = pre_pad
self.mod_scale = None
self.half = half
# initialize model
if gpu_id:
self.device = torch.device(
f'cuda:{gpu_id}' if torch.cuda.is_available() else 'cpu') if device is None else device
else:
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') if device is None else device
if modelscope:
from modelscope import snapshot_download
model_path = osp.join(snapshot_download('songkey/ESRGAN'), 'RealESRGAN_x2plus.pth')
else:
from huggingface_hub import hf_hub_download
model_path = hf_hub_download('songkey/ESRGAN', filename='RealESRGAN_x2plus.pth')
loadnet = torch.load(model_path, map_location=torch.device('cpu'))
# prefer to use params_ema
if 'params_ema' in loadnet:
keyname = 'params_ema'
else:
keyname = 'params'
self.model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2)
self.model.load_state_dict(loadnet[keyname], strict=True)
self.model.eval()
self.model = self.model.to(self.device)
if self.half:
self.model = self.model.half()
def dni(self, net_a, net_b, dni_weight, key='params', loc='cpu'):
"""Deep network interpolation.
``Paper: Deep Network Interpolation for Continuous Imagery Effect Transition``
"""
net_a = torch.load(net_a, map_location=torch.device(loc))
net_b = torch.load(net_b, map_location=torch.device(loc))
for k, v_a in net_a[key].items():
net_a[key][k] = dni_weight[0] * v_a + dni_weight[1] * net_b[key][k]
return net_a
def pre_process(self, img):
"""Pre-process, such as pre-pad and mod pad, so that the images can be divisible
"""
img = torch.from_numpy(np.transpose(img, (2, 0, 1))).float()
self.img = img.unsqueeze(0).to(self.device)
if self.half:
self.img = self.img.half()
# pre_pad
if self.pre_pad != 0:
self.img = F.pad(self.img, (0, self.pre_pad, 0, self.pre_pad), 'reflect')
# mod pad for divisible borders
if self.scale == 2:
self.mod_scale = 2
elif self.scale == 1:
self.mod_scale = 4
if self.mod_scale is not None:
self.mod_pad_h, self.mod_pad_w = 0, 0
_, _, h, w = self.img.size()
if (h % self.mod_scale != 0):
self.mod_pad_h = (self.mod_scale - h % self.mod_scale)
if (w % self.mod_scale != 0):
self.mod_pad_w = (self.mod_scale - w % self.mod_scale)
self.img = F.pad(self.img, (0, self.mod_pad_w, 0, self.mod_pad_h), 'reflect')
def process(self):
# model inference
self.output = self.model(self.img)
def tile_process(self):
"""It will first crop input images to tiles, and then process each tile.
Finally, all the processed tiles are merged into one images.
Modified from: https://github.com/ata4/esrgan-launcher
"""
batch, channel, height, width = self.img.shape
output_height = height * self.scale
output_width = width * self.scale
output_shape = (batch, channel, output_height, output_width)
# start with black image
self.output = self.img.new_zeros(output_shape)
tiles_x = math.ceil(width / self.tile_size)
tiles_y = math.ceil(height / self.tile_size)
# loop over all tiles
for y in range(tiles_y):
for x in range(tiles_x):
# extract tile from input image
ofs_x = x * self.tile_size
ofs_y = y * self.tile_size
# input tile area on total image
input_start_x = ofs_x
input_end_x = min(ofs_x + self.tile_size, width)
input_start_y = ofs_y
input_end_y = min(ofs_y + self.tile_size, height)
# input tile area on total image with padding
input_start_x_pad = max(input_start_x - self.tile_pad, 0)
input_end_x_pad = min(input_end_x + self.tile_pad, width)
input_start_y_pad = max(input_start_y - self.tile_pad, 0)
input_end_y_pad = min(input_end_y + self.tile_pad, height)
# input tile dimensions
input_tile_width = input_end_x - input_start_x
input_tile_height = input_end_y - input_start_y
tile_idx = y * tiles_x + x + 1
input_tile = self.img[:, :, input_start_y_pad:input_end_y_pad, input_start_x_pad:input_end_x_pad]
# upscale tile
try:
with torch.no_grad():
output_tile = self.model(input_tile)
except RuntimeError as error:
print('Error', error)
print(f'\tTile {tile_idx}/{tiles_x * tiles_y}')
# output tile area on total image
output_start_x = input_start_x * self.scale
output_end_x = input_end_x * self.scale
output_start_y = input_start_y * self.scale
output_end_y = input_end_y * self.scale
# output tile area without padding
output_start_x_tile = (input_start_x - input_start_x_pad) * self.scale
output_end_x_tile = output_start_x_tile + input_tile_width * self.scale
output_start_y_tile = (input_start_y - input_start_y_pad) * self.scale
output_end_y_tile = output_start_y_tile + input_tile_height * self.scale
# put tile into output image
self.output[:, :, output_start_y:output_end_y,
output_start_x:output_end_x] = output_tile[:, :, output_start_y_tile:output_end_y_tile,
output_start_x_tile:output_end_x_tile]
def post_process(self):
# remove extra pad
if self.mod_scale is not None:
_, _, h, w = self.output.size()
self.output = self.output[:, :, 0:h - self.mod_pad_h * self.scale, 0:w - self.mod_pad_w * self.scale]
# remove prepad
if self.pre_pad != 0:
_, _, h, w = self.output.size()
self.output = self.output[:, :, 0:h - self.pre_pad * self.scale, 0:w - self.pre_pad * self.scale]
return self.output
@torch.no_grad()
def enhance(self, img, outscale=None, alpha_upsampler='realesrgan'):
h_input, w_input = img.shape[0:2]
# img: numpy
img = img.astype(np.float32)
if np.max(img) > 256: # 16-bit image
max_range = 65535
print('\tInput is a 16-bit image')
else:
max_range = 255
img = img / max_range
if len(img.shape) == 2: # gray image
img_mode = 'L'
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
elif img.shape[2] == 4: # RGBA image with alpha channel
img_mode = 'RGBA'
alpha = img[:, :, 3]
img = img[:, :, 0:3]
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
if alpha_upsampler == 'realesrgan':
alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2RGB)
else:
img_mode = 'RGB'
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# ------------------- process image (without the alpha channel) ------------------- #
self.pre_process(img)
if self.tile_size > 0:
self.tile_process()
else:
self.process()
output_img = self.post_process()
output_img = output_img.data.squeeze().float().cpu().clamp_(0, 1).numpy()
output_img = np.transpose(output_img[[2, 1, 0], :, :], (1, 2, 0))
if img_mode == 'L':
output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2GRAY)
# ------------------- process the alpha channel if necessary ------------------- #
if img_mode == 'RGBA':
if alpha_upsampler == 'realesrgan':
self.pre_process(alpha)
if self.tile_size > 0:
self.tile_process()
else:
self.process()
output_alpha = self.post_process()
output_alpha = output_alpha.data.squeeze().float().cpu().clamp_(0, 1).numpy()
output_alpha = np.transpose(output_alpha[[2, 1, 0], :, :], (1, 2, 0))
output_alpha = cv2.cvtColor(output_alpha, cv2.COLOR_BGR2GRAY)
else: # use the cv2 resize for alpha channel
h, w = alpha.shape[0:2]
output_alpha = cv2.resize(alpha, (w * self.scale, h * self.scale), interpolation=cv2.INTER_LINEAR)
# merge the alpha channel
output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2BGRA)
output_img[:, :, 3] = output_alpha
# ------------------------------ return ------------------------------ #
if max_range == 65535: # 16-bit image
output = (output_img * 65535.0).round().astype(np.uint16)
else:
output = (output_img * 255.0).round().astype(np.uint8)
if outscale is not None and outscale != float(self.scale):
output = cv2.resize(
output, (
int(w_input * outscale),
int(h_input * outscale),
), interpolation=cv2.INTER_LANCZOS4)
return output, img_mode
+5 -5
View File
@@ -1,9 +1,7 @@
import json
import os
import os.path as osp
import random
import sys
import subprocess
import importlib.metadata
cur_dir = osp.dirname(osp.abspath(__file__))
@@ -11,7 +9,7 @@ cur_dir = osp.dirname(osp.abspath(__file__))
installed_packages = [package.name for package in importlib.metadata.distributions()]
REQUIRED = {
'diffusers':'0.31.0', 'transformers':'4.46.3', 'einops':'0.8.0', 'opencv-python':'4.10.0.84', 'tqdm':'4.67.0',
'diffusers':'0.33.1', 'transformers':'4.46.3', 'einops':'0.8.0', 'opencv-python':'4.10.0.84', 'tqdm':'4.67.0',
'pillow':'10.2.0', 'onnxruntime-gpu':'1.18.1', 'onnx':'1.17.0', 'safetensors':'0.4.5',
'accelerate':'1.1.1', 'peft':'0.13.2', 'modelscope':'', 'huggingface-hub':''
}
@@ -54,7 +52,7 @@ from .hellomeme.utils import (get_drive_expression,
)
from .hellomeme import (HMImagePipeline, HMVideoPipeline,
HM3ImagePipeline, HM3VideoPipeline,
HM5ImagePipeline)
HM5ImagePipeline, HM5VideoPipeline)
config_path = osp.join(cur_dir, 'hellomeme', 'model_config.json')
with open(config_path, 'r') as f:
@@ -169,7 +167,7 @@ class HMVideoPipelineLoader:
"checkpoint": (checkpoint_files, ),
"lora": (lora_files, ),
"vae": (vae_files, ),
"version": (['v1', 'v2', 'v3', 'v4'], ),
"version": (['v1', 'v2', 'v3', 'v4', 'v5'], ),
"stylize": (['x1', 'x2'], ),
"deployment": (['huggingface', 'modelscope'], ),
"lora_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1}),
@@ -192,6 +190,8 @@ class HMVideoPipelineLoader:
if version == 'v3' or version == 'v4':
pipeline = HM3VideoPipeline.from_pretrained(sd1_5_dir)
elif version == 'v5':
pipeline = HM5VideoPipeline.from_pretrained(sd1_5_dir)
else:
pipeline = HMVideoPipeline.from_pretrained(sd1_5_dir)
pipeline.to(dtype=dtype)
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui_hellomeme"
description = "This repository is the official implementation of the [a/HelloMeme](https://arxiv.org/pdf/2410.22901) ComfyUI interface"
version = "1.3.3"
version = "1.3.4"
license = {file = "LICENSE"}
[project.urls]