From 38fd791a775dee0f5577add90e1a09cc6a9ecab6 Mon Sep 17 00:00:00 2001
From: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat, 13 Sep 2025 16:55:28 +0300
Subject: [PATCH] Squashed commit of the following:
commit fda0fe6e0c21eb10276ae302cd88b6cbcf5b36b5
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Sep 13 16:55:00 2025 +0300
Create wanvideo_HuMo_example_01.json
commit cffe3039c3d2fbacd4803329bf31b5fdc45215ba
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Sep 13 16:30:49 2025 +0300
Update model.py
commit ddce018a5a6ffeb926860342889b12efb7343ec0
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Sep 13 16:29:27 2025 +0300
cleanup
commit 8c021b8b3f66144804e500e74aa6f2be52f9f9fc
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Sep 13 16:23:27 2025 +0300
avoid compile graph break
commit ef9c7732042261581b4bba6d980def78633f56dc
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Sep 13 16:16:13 2025 +0300
Allow using whisper model without decoder layers
commit 8d0ba29ee84d14be6084ecbcee1cbc9414128fb3
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Sep 13 15:55:26 2025 +0300
start/end percent for HuMo audio
commit bfe0d358a8820240f262351e61cfb979cb9a47ff
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Sep 13 15:37:11 2025 +0300
cleanup
commit e563ae317f24a7f5751b43cdef4bffbaeaea5114
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Sat Sep 13 14:02:21 2025 +0300
Make audio work
commit 95855196c51b1124a19079b746c5d12ec70d9026
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Fri Sep 12 18:10:04 2025 +0300
cfg
commit d5a18b090fe719b7f0b00a0f68e6824685598313
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Fri Sep 12 03:10:15 2025 +0300
wrong way around
commit 34c8c4842c14002fe4694dfa23c24a65b7ea39d0
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Fri Sep 12 03:01:45 2025 +0300
Update nodes.py
commit 47d1e2ab5f3e1483782d0739f5b51cdd33707c36
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Fri Sep 12 02:47:03 2025 +0300
update
image inputs are working but audio still doesn't do anything
commit 67890d816a64459944091cb01478c1e0ec4c4a82
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Thu Sep 11 21:13:12 2025 +0300
update
commit dbcef53405bb78feae4c5d2c6b310b76e4ef9949
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Thu Sep 11 17:09:37 2025 +0300
Update model.py
commit 92c9aac51f4d37988757510a4e57179834cc5de2
Author: kijai <40791699+kijai@users.noreply.github.com>
Date: Thu Sep 11 16:15:39 2025 +0300
init
untested as no weights released as of yet
---
HuMo/audio_proj.py | 87 +
HuMo/nodes.py | 249 +++
HuMo/whisper_config.json | 50 +
__init__.py | 9 +
.../wanvideo_HuMo_example_01.json | 1771 +++++++++++++++++
nodes.py | 175 +-
nodes_model_loading.py | 5 +-
wanvideo/modules/model.py | 132 +-
8 files changed, 2368 insertions(+), 110 deletions(-)
create mode 100644 HuMo/audio_proj.py
create mode 100644 HuMo/nodes.py
create mode 100644 HuMo/whisper_config.json
create mode 100644 example_workflows/wanvideo_HuMo_example_01.json
diff --git a/HuMo/audio_proj.py b/HuMo/audio_proj.py
new file mode 100644
index 0000000..8483a3f
--- /dev/null
+++ b/HuMo/audio_proj.py
@@ -0,0 +1,87 @@
+import torch
+from einops import rearrange
+from torch import nn
+from einops import rearrange
+
+class WanRMSNorm(nn.Module):
+
+ def __init__(self, dim, eps=1e-5):
+ super().__init__()
+ self.dim = dim
+ self.eps = eps
+ self.weight = nn.Parameter(torch.ones(dim))
+
+ def forward(self, x):
+ r"""
+ Args:
+ x(Tensor): Shape [B, L, C]
+ """
+ return self._norm(x.float()).type_as(x) * self.weight
+
+ def _norm(self, x):
+ return x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
+
+
+class DummyAdapterLayer(nn.Module):
+ def __init__(self, layer):
+ super().__init__()
+ self.layer = layer
+
+ def forward(self, *args, **kwargs):
+ return self.layer(*args, **kwargs)
+
+
+class AudioProjModel(nn.Module):
+ def __init__(
+ self,
+ seq_len=5,
+ blocks=13, # add a new parameter blocks
+ channels=768, # add a new parameter channels
+ intermediate_dim=512,
+ output_dim=1536,
+ context_tokens=16,
+ ):
+ super().__init__()
+
+ self.seq_len = seq_len
+ self.blocks = blocks
+ self.channels = channels
+ self.input_dim = seq_len * blocks * channels # update input_dim to be the product of blocks and channels.
+ self.intermediate_dim = intermediate_dim
+ self.context_tokens = context_tokens
+ self.output_dim = output_dim
+
+ # define multiple linear layers
+ self.audio_proj_glob_1 = DummyAdapterLayer(nn.Linear(self.input_dim, intermediate_dim))
+ self.audio_proj_glob_2 = DummyAdapterLayer(nn.Linear(intermediate_dim, intermediate_dim))
+ self.audio_proj_glob_3 = DummyAdapterLayer(nn.Linear(intermediate_dim, context_tokens * output_dim))
+
+ self.audio_proj_glob_norm = DummyAdapterLayer(nn.LayerNorm(output_dim))
+
+ self.initialize_weights()
+
+ def initialize_weights(self):
+ # Initialize transformer layers:
+ def _basic_init(module):
+ if isinstance(module, nn.Linear):
+ torch.nn.init.xavier_uniform_(module.weight)
+ if module.bias is not None:
+ nn.init.constant_(module.bias, 0)
+
+ self.apply(_basic_init)
+
+ def forward(self, audio_embeds):
+ video_length = audio_embeds.shape[1]
+ audio_embeds = rearrange(audio_embeds, "bz f w b c -> (bz f) w b c")
+ batch_size, window_size, blocks, channels = audio_embeds.shape
+ audio_embeds = audio_embeds.view(batch_size, window_size * blocks * channels)
+
+ audio_embeds = torch.relu(self.audio_proj_glob_1(audio_embeds))
+ audio_embeds = torch.relu(self.audio_proj_glob_2(audio_embeds))
+
+ context_tokens = self.audio_proj_glob_3(audio_embeds).reshape(batch_size, self.context_tokens, self.output_dim)
+
+ context_tokens = self.audio_proj_glob_norm(context_tokens)
+ context_tokens = rearrange(context_tokens, "(bz f) m c -> bz f m c", f=video_length)
+
+ return context_tokens
\ No newline at end of file
diff --git a/HuMo/nodes.py b/HuMo/nodes.py
new file mode 100644
index 0000000..dfc5fd3
--- /dev/null
+++ b/HuMo/nodes.py
@@ -0,0 +1,249 @@
+import folder_paths
+import torch
+import torch.nn.functional as F
+import os
+import json
+import torchaudio
+
+from comfy.utils import load_torch_file
+import comfy.model_management as mm
+
+from accelerate import init_empty_weights
+from ..utils import set_module_tensor_to_device
+from ..nodes import WanVideoEncodeLatentBatch
+
+script_directory = os.path.dirname(os.path.abspath(__file__))
+device = mm.get_torch_device()
+offload_device = mm.unet_offload_device()
+
+def linear_interpolation_fps(features, input_fps, output_fps, output_len=None):
+ features = features.transpose(1, 2) # [1, C, T]
+ seq_len = features.shape[2] / float(input_fps)
+ if output_len is None:
+ output_len = int(seq_len * output_fps)
+ output_features = F.interpolate(features, size=output_len, align_corners=True, mode='linear')
+ return output_features.transpose(1, 2)
+
+def get_audio_emb_window(audio_emb, frame_num, frame0_idx, audio_shift=2):
+ zero_audio_embed = torch.zeros((audio_emb.shape[1], audio_emb.shape[2]), dtype=audio_emb.dtype, device=audio_emb.device)
+ zero_audio_embed_3 = torch.zeros((3, audio_emb.shape[1], audio_emb.shape[2]), dtype=audio_emb.dtype, device=audio_emb.device)
+ iter_ = 1 + (frame_num - 1) // 4
+ audio_emb_wind = []
+ for lt_i in range(iter_):
+ if lt_i == 0:
+ st = frame0_idx + lt_i - 2
+ ed = frame0_idx + lt_i + 3
+ wind_feat = torch.stack([
+ audio_emb[i] if (0 <= i < audio_emb.shape[0]) else zero_audio_embed
+ for i in range(st, ed)
+ ], dim=0)
+ wind_feat = torch.cat((zero_audio_embed_3, wind_feat), dim=0)
+ else:
+ st = frame0_idx + 1 + 4 * (lt_i - 1) - audio_shift
+ ed = frame0_idx + 1 + 4 * lt_i + audio_shift
+ wind_feat = torch.stack([
+ audio_emb[i] if (0 <= i < audio_emb.shape[0]) else zero_audio_embed
+ for i in range(st, ed)
+ ], dim=0)
+ audio_emb_wind.append(wind_feat)
+ audio_emb_wind = torch.stack(audio_emb_wind, dim=0)
+
+ return audio_emb_wind, ed - audio_shift
+
+class WhisperModelLoader:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": (folder_paths.get_filename_list("audio_encoders"), {"tooltip": "These models are loaded from the 'ComfyUI/models/wav2vec2' or 'ComfyUI/models/audio_encoders' folder",}),
+ "base_precision": (["fp32", "bf16", "fp16"], {"default": "fp16"}),
+ "load_device": (["main_device", "offload_device"], {"default": "main_device", "tooltip": "Initial device to load the model to, NOT recommended with the larger models unless you have 48GB+ VRAM"}),
+ },
+ }
+
+ RETURN_TYPES = ("WHISPERMODEL",)
+ RETURN_NAMES = ("whisper_model", )
+ FUNCTION = "loadmodel"
+ CATEGORY = "WanVideoWrapper"
+
+ def loadmodel(self, model, base_precision, load_device):
+ from transformers import WhisperConfig, WhisperModel, WhisperFeatureExtractor
+
+ base_dtype = {"fp8_e4m3fn": torch.float8_e4m3fn, "fp8_e4m3fn_fast": torch.float8_e4m3fn, "bf16": torch.bfloat16, "fp16": torch.float16, "fp16_fast": torch.float16, "fp32": torch.float32}[base_precision]
+
+ if load_device == "offload_device":
+ transformer_load_device = offload_device
+ else:
+ transformer_load_device = device
+
+ config_path = os.path.join(script_directory, "whisper_config.json")
+ whisper_config = WhisperConfig(**json.load(open(config_path)))
+
+ with init_empty_weights():
+ whisper = WhisperModel(whisper_config).eval()
+ whisper.decoder = None # we only need the encoder
+
+ feature_extractor_config = {
+ "chunk_length": 30,
+ "feature_extractor_type": "WhisperFeatureExtractor",
+ "feature_size": 128,
+ "hop_length": 160,
+ "n_fft": 400,
+ "n_samples": 480000,
+ "nb_max_frames": 3000,
+ "padding_side": "right",
+ "padding_value": 0.0,
+ "processor_class": "WhisperProcessor",
+ "return_attention_mask": False,
+ "sampling_rate": 16000
+ }
+
+ feature_extractor = WhisperFeatureExtractor(**feature_extractor_config)
+
+ model_path = folder_paths.get_full_path_or_raise("audio_encoders", model)
+ sd = load_torch_file(model_path, device=transformer_load_device, safe_load=True)
+
+ for name, param in whisper.named_parameters():
+ key = "model." + name
+ value=sd[key]
+ set_module_tensor_to_device(whisper, name, device=offload_device, dtype=base_dtype, value=value)
+
+ whisper_model = {
+ "feature_extractor": feature_extractor,
+ "model": whisper,
+ "dtype": base_dtype,
+ }
+
+ return (whisper_model,)
+
+class HuMoEmbeds:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {"required": {
+ "whisper_model": ("WHISPERMODEL",),
+ "vae": ("WANVAE", ),
+ "num_frames": ("INT", {"default": 81, "min": -1, "max": 10000, "step": 1, "tooltip": "The total frame count to generate."}),
+ "reference_images": ("IMAGE", {"tooltip": "reference images for the humo model"}),
+ "audio_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01, "tooltip": "Strength of the audio conditioning"}),
+ "audio_cfg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01, "tooltip": "When not 1.0, an extra model pass without audio conditioning is done: slower inference but more motion is allowed"}),
+ "audio_start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The percent of the video to start applying audio conditioning"}),
+ "audio_end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The percent of the video to stop applying audio conditioning"})
+ },
+ "optional" : {
+ "audio": ("AUDIO",),
+ }
+ }
+
+ RETURN_TYPES = ("WANVIDIMAGE_EMBEDS", )
+ RETURN_NAMES = ("image_embeds", )
+ FUNCTION = "process"
+ CATEGORY = "WanVideoWrapper"
+
+ def process(self, whisper_model, vae, reference_images, num_frames, audio_scale, audio_cfg_scale, audio_start_percent, audio_end_percent, audio=None):
+ model = whisper_model["model"]
+ feature_extractor = whisper_model["feature_extractor"]
+ dtype = whisper_model["dtype"]
+
+ sampling_rate = 16000
+
+ if audio is not None:
+ audio_input = audio["waveform"][0]
+ sample_rate = audio["sample_rate"]
+
+ if sample_rate != sampling_rate:
+ audio_input = torchaudio.functional.resample(audio_input, sample_rate, sampling_rate)
+ if audio_input.shape[1] == 2:
+ audio_input = audio_input.mean(dim=0, keepdim=False)
+ else:
+ audio_input = audio_input[0]
+
+ model.to(device)
+ audio_len = len(audio_input) // 640
+
+ # feature extraction
+ audio_features = []
+ window = 750*640
+ for i in range(0, len(audio_input), window):
+ audio_feature = feature_extractor(audio_input[i:i+window], sampling_rate=sampling_rate, return_tensors="pt").input_features
+ audio_features.append(audio_feature)
+ audio_features = torch.cat(audio_features, dim=-1).to(device, dtype)
+
+ # preprocess
+ window = 3000
+ audio_prompts = []
+ for i in range(0, audio_features.shape[-1], window):
+ audio_prompt = model.encoder(audio_features[:,:,i:i+window], output_hidden_states=True).hidden_states
+ audio_prompt = torch.stack(audio_prompt, dim=2)
+ audio_prompts.append(audio_prompt)
+
+ model.to(offload_device)
+
+ audio_prompts = torch.cat(audio_prompts, dim=1)
+ audio_prompts = audio_prompts[:,:audio_len*2]
+
+ feat0 = linear_interpolation_fps(audio_prompts[:, :, 0: 8].mean(dim=2), 50, 25)
+ feat1 = linear_interpolation_fps(audio_prompts[:, :, 8: 16].mean(dim=2), 50, 25)
+ feat2 = linear_interpolation_fps(audio_prompts[:, :, 16: 24].mean(dim=2), 50, 25)
+ feat3 = linear_interpolation_fps(audio_prompts[:, :, 24: 32].mean(dim=2), 50, 25)
+ feat4 = linear_interpolation_fps(audio_prompts[:, :, 32], 50, 25)
+ audio_emb = torch.stack([feat0, feat1, feat2, feat3, feat4], dim=2)[0] # [T, 5, 1280]
+ else:
+ audio_emb = torch.zeros(num_frames, 5, 1280, device=device)
+ audio_len = num_frames
+
+ frame_num = num_frames if num_frames != -1 else audio_len
+ frame_num = 4 * ((frame_num - 1) // 4) + 1
+ audio_emb, _ = get_audio_emb_window(audio_emb, frame_num, frame0_idx=0)
+
+ samples, = WanVideoEncodeLatentBatch.encode(self, vae, reference_images, False, 0, 0, 0, 0)
+ samples = samples["samples"].transpose(0, 2).squeeze(0)
+
+ C, T, H, W = samples.shape
+
+ target_shape = (16, (num_frames - 1) // 4 + 1 + T,
+ H * 8 // 8,
+ W * 8 // 8)
+
+ vae.to(device)
+ zero_frames = torch.zeros(1, 3, num_frames + 4*T, H * 8, W * 8, device=device, dtype=vae.dtype)
+ zero_latents = vae.encode(zero_frames, device=device)[0].to(samples.device)
+ vae.model.clear_cache()
+ vae.to(offload_device)
+ mm.soft_empty_cache()
+
+ mask = torch.ones(4, target_shape[1], target_shape[2], target_shape[3], device=samples.device, dtype=vae.dtype)
+ mask[:,:-T] = 0
+ image_cond = torch.cat([zero_latents[:, :(target_shape[1]-T)], samples], dim=1)
+ image_cond = torch.cat([mask, image_cond], dim=0)
+ image_cond_neg = torch.cat([mask, zero_latents], dim=0)
+
+ zero_audio_pad = torch.zeros(T, *audio_emb.shape[1:]).to(audio_emb.device)
+ audio_emb = torch.cat([audio_emb, zero_audio_pad], dim=0)
+ audio_emb_neg = torch.zeros_like(audio_emb, dtype=audio_emb.dtype, device=audio_emb.device)
+
+ embeds = {
+ "humo_audio_emb": audio_emb,
+ "humo_audio_emb_neg": audio_emb_neg,
+ "humo_image_cond": image_cond,
+ "humo_image_cond_neg": image_cond_neg,
+ "humo_reference_count": T,
+ "target_shape": target_shape,
+ "num_frames": num_frames,
+ "humo_audio_scale": audio_scale,
+ "humo_audio_cfg_scale": audio_cfg_scale,
+ "humo_start_percent": audio_start_percent,
+ "humo_end_percent": audio_end_percent,
+ }
+
+ return (embeds, )
+
+
+NODE_CLASS_MAPPINGS = {
+ "WhisperModelLoader": WhisperModelLoader,
+ "HuMoEmbeds": HuMoEmbeds,
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "WhisperModelLoader": "Whisper Model Loader",
+ "HuMoEmbeds": "HuMo Embeds",
+}
\ No newline at end of file
diff --git a/HuMo/whisper_config.json b/HuMo/whisper_config.json
new file mode 100644
index 0000000..14c6c8c
--- /dev/null
+++ b/HuMo/whisper_config.json
@@ -0,0 +1,50 @@
+{
+ "_name_or_path": "openai/whisper-large-v3",
+ "activation_dropout": 0.0,
+ "activation_function": "gelu",
+ "apply_spec_augment": false,
+ "architectures": [
+ "WhisperForConditionalGeneration"
+ ],
+ "attention_dropout": 0.0,
+ "begin_suppress_tokens": [
+ 220,
+ 50257
+ ],
+ "bos_token_id": 50257,
+ "classifier_proj_size": 256,
+ "d_model": 1280,
+ "decoder_attention_heads": 20,
+ "decoder_ffn_dim": 5120,
+ "decoder_layerdrop": 0.0,
+ "decoder_layers": 32,
+ "decoder_start_token_id": 50258,
+ "dropout": 0.0,
+ "encoder_attention_heads": 20,
+ "encoder_ffn_dim": 5120,
+ "encoder_layerdrop": 0.0,
+ "encoder_layers": 32,
+ "eos_token_id": 50257,
+ "init_std": 0.02,
+ "is_encoder_decoder": true,
+ "mask_feature_length": 10,
+ "mask_feature_min_masks": 0,
+ "mask_feature_prob": 0.0,
+ "mask_time_length": 10,
+ "mask_time_min_masks": 2,
+ "mask_time_prob": 0.05,
+ "max_length": 448,
+ "max_source_positions": 1500,
+ "max_target_positions": 448,
+ "median_filter_width": 7,
+ "model_type": "whisper",
+ "num_hidden_layers": 32,
+ "num_mel_bins": 128,
+ "pad_token_id": 50256,
+ "scale_embedding": false,
+ "torch_dtype": "float16",
+ "transformers_version": "4.36.0.dev0",
+ "use_cache": true,
+ "use_weighted_layer_sum": false,
+ "vocab_size": 51866
+}
diff --git a/__init__.py b/__init__.py
index 8ea20c2..0882734 100644
--- a/__init__.py
+++ b/__init__.py
@@ -43,6 +43,13 @@ except Exception as e:
MTV_NODE_CLASS_MAPPINGS = {}
MTV_NODE_DISPLAY_NAME_MAPPINGS = {}
+try:
+ from .HuMo.nodes import NODE_CLASS_MAPPINGS as HUMO_NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as HUMO_NODE_DISPLAY_NAME_MAPPINGS
+except Exception as e:
+ print(f"HuMo nodes not available due to error in importing them: {e}")
+ HUMO_NODE_CLASS_MAPPINGS = {}
+ HUMO_NODE_DISPLAY_NAME_MAPPINGS = {}
+
NODE_CLASS_MAPPINGS.update(RECAM_MASTER_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(UNIANIMATE_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(SKYREELS_NODE_CLASS_MAPPINGS)
@@ -60,6 +67,7 @@ NODE_CLASS_MAPPINGS.update(DEPRECATED_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(QWEN_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(MTV_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(S2V_NODE_CLASS_MAPPINGS)
+NODE_CLASS_MAPPINGS.update(HUMO_NODE_CLASS_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(RECAM_MASTER_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(UNIANIMATE_NODE_DISPLAY_NAME_MAPPINGS)
@@ -78,5 +86,6 @@ NODE_DISPLAY_NAME_MAPPINGS.update(DEPRECATED_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(QWEN_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(MTV_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(S2V_NODE_DISPLAY_NAME_MAPPINGS)
+NODE_DISPLAY_NAME_MAPPINGS.update(HUMO_NODE_DISPLAY_NAME_MAPPINGS)
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
\ No newline at end of file
diff --git a/example_workflows/wanvideo_HuMo_example_01.json b/example_workflows/wanvideo_HuMo_example_01.json
new file mode 100644
index 0000000..8fdfd8d
--- /dev/null
+++ b/example_workflows/wanvideo_HuMo_example_01.json
@@ -0,0 +1,1771 @@
+{
+ "id": "c6e410bc-5e2c-460b-ae81-c91b6094fbb1",
+ "revision": 0,
+ "last_node_id": 121,
+ "last_link_id": 221,
+ "nodes": [
+ {
+ "id": 36,
+ "type": "Note",
+ "pos": [
+ 714.1553955078125,
+ -685.7478637695312
+ ],
+ "size": [
+ 374.3061828613281,
+ 171.9547576904297
+ ],
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [],
+ "properties": {},
+ "widgets_values": [
+ "fp8_fast seems to cause huge quality degradation\n\nfp_16_fast enables \"Full FP16 Accmumulation in FP16 GEMMs\" feature available in the very latest pytorch nightly, this is around 20% speed boost. \n\nSageattn if you have it installed can be used for almost double inference speed"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 84,
+ "type": "WanVideoSetBlockSwap",
+ "pos": [
+ 1203.40625,
+ -451.20733642578125
+ ],
+ "size": [
+ 202.5749969482422,
+ 46
+ ],
+ "flags": {},
+ "order": 25,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "WANVIDEOMODEL",
+ "link": 149
+ },
+ {
+ "name": "block_swap_args",
+ "shape": 7,
+ "type": "BLOCKSWAPARGS",
+ "link": 151
+ }
+ ],
+ "outputs": [
+ {
+ "name": "model",
+ "type": "WANVIDEOMODEL",
+ "links": [
+ 150
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoSetBlockSwap",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "cc3b79ab180e8c81e6125d539662935cf171c87b"
+ },
+ "widgets_values": [],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 81,
+ "type": "ImageResizeKJv2",
+ "pos": [
+ -261.44146728515625,
+ 296.5487976074219
+ ],
+ "size": [
+ 270,
+ 336
+ ],
+ "flags": {},
+ "order": 19,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 202
+ },
+ {
+ "name": "mask",
+ "shape": 7,
+ "type": "MASK",
+ "link": null
+ },
+ {
+ "name": "width",
+ "type": "INT",
+ "widget": {
+ "name": "width"
+ },
+ "link": 139
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "widget": {
+ "name": "height"
+ },
+ "link": 140
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 145,
+ 205,
+ 220
+ ]
+ },
+ {
+ "name": "width",
+ "type": "INT",
+ "links": []
+ },
+ {
+ "name": "height",
+ "type": "INT",
+ "links": []
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "links": null
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageResizeKJv2",
+ "cnr_id": "comfyui-kjnodes",
+ "ver": "0adab07d1ad3d0780afc97319eaf37c3681af37d"
+ },
+ "widgets_values": [
+ 512,
+ 512,
+ "lanczos",
+ "pad",
+ "255,255,255",
+ "center",
+ 16,
+ "cpu",
+ "
| Output: | 1 x 1280 x 720 | 10.55MB |
"
+ ]
+ },
+ {
+ "id": 28,
+ "type": "WanVideoDecode",
+ "pos": [
+ 2345.96484375,
+ -400.7390441894531
+ ],
+ "size": [
+ 315,
+ 198
+ ],
+ "flags": {},
+ "order": 29,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "vae",
+ "type": "WANVAE",
+ "link": 43
+ },
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "link": 33
+ }
+ ],
+ "outputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "slot_index": 0,
+ "links": [
+ 126
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoDecode",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "6099ad393b071728032fd481e96d77d2900eee2c"
+ },
+ "widgets_values": [
+ false,
+ 272,
+ 272,
+ 144,
+ 128,
+ "default"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 79,
+ "type": "ImageConcatMulti",
+ "pos": [
+ 2370.963134765625,
+ -85.9253158569336
+ ],
+ "size": [
+ 315,
+ 170
+ ],
+ "flags": {
+ "collapsed": false
+ },
+ "order": 30,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image_1",
+ "type": "IMAGE",
+ "link": 126
+ },
+ {
+ "name": "image_2",
+ "shape": 7,
+ "type": "IMAGE",
+ "link": 205
+ }
+ ],
+ "outputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "links": [
+ 127
+ ]
+ }
+ ],
+ "properties": {
+ "cnr_id": "comfyui-kjnodes",
+ "ver": "3e3a1a8aac61dc4515f6a7da74e026f05a80299f"
+ },
+ "widgets_values": [
+ 2,
+ "left",
+ true,
+ null
+ ]
+ },
+ {
+ "id": 83,
+ "type": "WanVideoSetLoRAs",
+ "pos": [
+ 1471.3389892578125,
+ -454.92852783203125
+ ],
+ "size": [
+ 175.1999969482422,
+ 46
+ ],
+ "flags": {},
+ "order": 27,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "WANVIDEOMODEL",
+ "link": 150
+ },
+ {
+ "name": "lora",
+ "shape": 7,
+ "type": "WANVIDLORA",
+ "link": 147
+ }
+ ],
+ "outputs": [
+ {
+ "name": "model",
+ "type": "WANVIDEOMODEL",
+ "links": [
+ 148
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoSetLoRAs",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "cc3b79ab180e8c81e6125d539662935cf171c87b"
+ },
+ "widgets_values": [],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 22,
+ "type": "WanVideoModelLoader",
+ "pos": [
+ 617.1249389648438,
+ -441.7767028808594
+ ],
+ "size": [
+ 530.5924072265625,
+ 314
+ ],
+ "flags": {},
+ "order": 21,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "compile_args",
+ "shape": 7,
+ "type": "WANCOMPILEARGS",
+ "link": 208
+ },
+ {
+ "name": "block_swap_args",
+ "shape": 7,
+ "type": "BLOCKSWAPARGS",
+ "link": null
+ },
+ {
+ "name": "lora",
+ "shape": 7,
+ "type": "WANVIDLORA",
+ "link": null
+ },
+ {
+ "name": "vram_management_args",
+ "shape": 7,
+ "type": "VRAM_MANAGEMENTARGS",
+ "link": null
+ },
+ {
+ "name": "extra_model",
+ "shape": 7,
+ "type": "VACEPATH",
+ "link": null
+ },
+ {
+ "name": "fantasytalking_model",
+ "shape": 7,
+ "type": "FANTASYTALKINGMODEL",
+ "link": null
+ },
+ {
+ "name": "multitalk_model",
+ "shape": 7,
+ "type": "MULTITALKMODEL",
+ "link": null
+ },
+ {
+ "name": "fantasyportrait_model",
+ "shape": 7,
+ "type": "FANTASYPORTRAITMODEL",
+ "link": null
+ },
+ {
+ "name": "vace_model",
+ "shape": 7,
+ "type": "VACEPATH",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "model",
+ "type": "WANVIDEOMODEL",
+ "slot_index": 0,
+ "links": [
+ 149
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoModelLoader",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "6099ad393b071728032fd481e96d77d2900eee2c"
+ },
+ "widgets_values": [
+ "WanVideo/HuMo/Wan2_1-HuMo-14B_fp8_e4m3fn_scaled_KJ.safetensors",
+ "fp16_fast",
+ "disabled",
+ "offload_device",
+ "sageattn"
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 39,
+ "type": "WanVideoBlockSwap",
+ "pos": [
+ 1175.088134765625,
+ -351.6418151855469
+ ],
+ "size": [
+ 315,
+ 202
+ ],
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "block_swap_args",
+ "type": "BLOCKSWAPARGS",
+ "slot_index": 0,
+ "links": [
+ 151
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoBlockSwap",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "6099ad393b071728032fd481e96d77d2900eee2c"
+ },
+ "widgets_values": [
+ 20,
+ false,
+ false,
+ true,
+ 0,
+ 1,
+ false
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 38,
+ "type": "WanVideoVAELoader",
+ "pos": [
+ 2340.400390625,
+ -578.1526489257812
+ ],
+ "size": [
+ 315,
+ 82
+ ],
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "compile_args",
+ "shape": 7,
+ "type": "WANCOMPILEARGS",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "vae",
+ "type": "WANVAE",
+ "slot_index": 0,
+ "links": [
+ 43,
+ 176
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoVAELoader",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "6099ad393b071728032fd481e96d77d2900eee2c"
+ },
+ "widgets_values": [
+ "Wan2_1_VAE_bf16.safetensors",
+ "bf16"
+ ],
+ "color": "#322",
+ "bgcolor": "#533"
+ },
+ {
+ "id": 112,
+ "type": "WanVideoTextEncodeCached",
+ "pos": [
+ 672.4511108398438,
+ -51.898902893066406
+ ],
+ "size": [
+ 477.0235900878906,
+ 471.79931640625
+ ],
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "extender_args",
+ "shape": 7,
+ "type": "WANVIDEOPROMPTEXTENDER_ARGS",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "text_embeds",
+ "type": "WANVIDEOTEXTEMBEDS",
+ "links": [
+ 209
+ ]
+ },
+ {
+ "name": "negative_text_embeds",
+ "type": "WANVIDEOTEXTEMBEDS",
+ "links": null
+ },
+ {
+ "name": "positive_prompt",
+ "type": "STRING",
+ "links": null
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoTextEncodeCached"
+ },
+ "widgets_values": [
+ "umt5-xxl-enc-bf16.safetensors",
+ "bf16",
+ "old man is issuing a dire warning",
+ "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
+ "disabled",
+ true,
+ "gpu"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 78,
+ "type": "WanVideoLoraSelect",
+ "pos": [
+ 1191.41259765625,
+ -703.387939453125
+ ],
+ "size": [
+ 596.4673461914062,
+ 150
+ ],
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "prev_lora",
+ "shape": 7,
+ "type": "WANVIDLORA",
+ "link": null
+ },
+ {
+ "name": "blocks",
+ "shape": 7,
+ "type": "SELECTEDBLOCKS",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "lora",
+ "type": "WANVIDLORA",
+ "links": [
+ 147
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoLoraSelect",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "cc3b79ab180e8c81e6125d539662935cf171c87b"
+ },
+ "widgets_values": [
+ "WanVid/Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank64_bf16.safetensors",
+ 1,
+ false,
+ false
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 111,
+ "type": "CreateCFGScheduleFloatList",
+ "pos": [
+ 1608.3153076171875,
+ -152.25692749023438
+ ],
+ "size": [
+ 298.3199157714844,
+ 228
+ ],
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "float_list",
+ "type": "FLOAT",
+ "links": [
+ 207
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "CreateCFGScheduleFloatList",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "95855196c51b1124a19079b746c5d12ec70d9026"
+ },
+ "widgets_values": [
+ 8,
+ 2,
+ 2,
+ "linear",
+ 0,
+ 0.01,
+ "[2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]"
+ ]
+ },
+ {
+ "id": 27,
+ "type": "WanVideoSampler",
+ "pos": [
+ 1968.231201171875,
+ -397.3578796386719
+ ],
+ "size": [
+ 315,
+ 869.9375
+ ],
+ "flags": {},
+ "order": 28,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "WANVIDEOMODEL",
+ "link": 148
+ },
+ {
+ "name": "image_embeds",
+ "type": "WANVIDIMAGE_EMBEDS",
+ "link": 183
+ },
+ {
+ "name": "text_embeds",
+ "shape": 7,
+ "type": "WANVIDEOTEXTEMBEDS",
+ "link": 209
+ },
+ {
+ "name": "samples",
+ "shape": 7,
+ "type": "LATENT",
+ "link": null
+ },
+ {
+ "name": "feta_args",
+ "shape": 7,
+ "type": "FETAARGS",
+ "link": null
+ },
+ {
+ "name": "context_options",
+ "shape": 7,
+ "type": "WANVIDCONTEXT",
+ "link": null
+ },
+ {
+ "name": "cache_args",
+ "shape": 7,
+ "type": "CACHEARGS",
+ "link": null
+ },
+ {
+ "name": "flowedit_args",
+ "shape": 7,
+ "type": "FLOWEDITARGS",
+ "link": null
+ },
+ {
+ "name": "slg_args",
+ "shape": 7,
+ "type": "SLGARGS",
+ "link": null
+ },
+ {
+ "name": "loop_args",
+ "shape": 7,
+ "type": "LOOPARGS",
+ "link": null
+ },
+ {
+ "name": "experimental_args",
+ "shape": 7,
+ "type": "EXPERIMENTALARGS",
+ "link": null
+ },
+ {
+ "name": "sigmas",
+ "shape": 7,
+ "type": "SIGMAS",
+ "link": null
+ },
+ {
+ "name": "unianimate_poses",
+ "shape": 7,
+ "type": "UNIANIMATE_POSE",
+ "link": null
+ },
+ {
+ "name": "fantasytalking_embeds",
+ "shape": 7,
+ "type": "FANTASYTALKING_EMBEDS",
+ "link": null
+ },
+ {
+ "name": "uni3c_embeds",
+ "shape": 7,
+ "type": "UNI3C_EMBEDS",
+ "link": null
+ },
+ {
+ "name": "multitalk_embeds",
+ "shape": 7,
+ "type": "MULTITALK_EMBEDS",
+ "link": null
+ },
+ {
+ "name": "freeinit_args",
+ "shape": 7,
+ "type": "FREEINITARGS",
+ "link": null
+ },
+ {
+ "name": "cfg",
+ "type": "FLOAT",
+ "widget": {
+ "name": "cfg"
+ },
+ "link": 207
+ }
+ ],
+ "outputs": [
+ {
+ "name": "samples",
+ "type": "LATENT",
+ "slot_index": 0,
+ "links": [
+ 33
+ ]
+ },
+ {
+ "name": "denoised_samples",
+ "type": "LATENT",
+ "links": null
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoSampler",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "6099ad393b071728032fd481e96d77d2900eee2c"
+ },
+ "widgets_values": [
+ 8,
+ 1,
+ 5,
+ 42,
+ "fixed",
+ true,
+ "lcm",
+ 0,
+ 1,
+ false,
+ "comfy",
+ 0,
+ -1,
+ false
+ ]
+ },
+ {
+ "id": 115,
+ "type": "MelBandRoFormerSampler",
+ "pos": [
+ 1072.6689453125,
+ 835.3041381835938
+ ],
+ "size": [
+ 223.64999389648438,
+ 46
+ ],
+ "flags": {},
+ "order": 20,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "model",
+ "type": "MELROFORMERMODEL",
+ "link": 213
+ },
+ {
+ "name": "audio",
+ "type": "AUDIO",
+ "link": 217
+ }
+ ],
+ "outputs": [
+ {
+ "name": "vocals",
+ "type": "AUDIO",
+ "links": [
+ 214
+ ]
+ },
+ {
+ "name": "instruments",
+ "type": "AUDIO",
+ "links": null
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "MelBandRoFormerSampler"
+ }
+ },
+ {
+ "id": 116,
+ "type": "NormalizeAudioLoudness",
+ "pos": [
+ 1079.6055908203125,
+ 952.2372436523438
+ ],
+ "size": [
+ 270,
+ 58
+ ],
+ "flags": {},
+ "order": 24,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "audio",
+ "type": "AUDIO",
+ "link": 214
+ }
+ ],
+ "outputs": [
+ {
+ "name": "audio",
+ "type": "AUDIO",
+ "links": [
+ 215
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "NormalizeAudioLoudness"
+ },
+ "widgets_values": [
+ -23
+ ]
+ },
+ {
+ "id": 88,
+ "type": "WhisperModelLoader",
+ "pos": [
+ 709.6544189453125,
+ 661.9351806640625
+ ],
+ "size": [
+ 484.66668701171875,
+ 106
+ ],
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "whisper_model",
+ "type": "WHISPERMODEL",
+ "links": [
+ 175
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WhisperModelLoader",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "9cefe309e3d8eb9ad3afda576cf7098c72e9efad"
+ },
+ "widgets_values": [
+ "whisper_large_v3_encoder_fp16.safetensors",
+ "fp16",
+ "main_device"
+ ]
+ },
+ {
+ "id": 67,
+ "type": "LoadImage",
+ "pos": [
+ -268.9026184082031,
+ -156.98599243164062
+ ],
+ "size": [
+ 340.7649230957031,
+ 379.4031982421875
+ ],
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 202
+ ]
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage",
+ "cnr_id": "comfy-core",
+ "ver": "0.3.29"
+ },
+ "widgets_values": [
+ "oldman_upscaled.png",
+ "image"
+ ]
+ },
+ {
+ "id": 87,
+ "type": "LoadAudio",
+ "pos": [
+ 741.7787475585938,
+ 954.3341674804688
+ ],
+ "size": [
+ 274.080078125,
+ 136
+ ],
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "AUDIO",
+ "type": "AUDIO",
+ "links": [
+ 217,
+ 218
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadAudio",
+ "cnr_id": "comfy-core",
+ "ver": "0.3.57"
+ },
+ "widgets_values": [
+ "one-does-not-simply-walk-into-mordor-its-black-gates-are-guarded-by-more-than-just-orcs.mp3",
+ null,
+ null
+ ]
+ },
+ {
+ "id": 117,
+ "type": "SetNode",
+ "pos": [
+ 1078.615234375,
+ 1067.1883544921875
+ ],
+ "size": [
+ 210,
+ 60
+ ],
+ "flags": {
+ "collapsed": true
+ },
+ "order": 18,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "AUDIO",
+ "type": "AUDIO",
+ "link": 218
+ }
+ ],
+ "outputs": [
+ {
+ "name": "*",
+ "type": "*",
+ "links": null
+ }
+ ],
+ "title": "Set_input_audio",
+ "properties": {
+ "previousName": "input_audio"
+ },
+ "widgets_values": [
+ "input_audio"
+ ],
+ "color": "#323",
+ "bgcolor": "#535"
+ },
+ {
+ "id": 30,
+ "type": "VHS_VideoCombine",
+ "pos": [
+ 2769.601806640625,
+ -589.9664916992188
+ ],
+ "size": [
+ 1915.138671875,
+ 881.0077514648438
+ ],
+ "flags": {},
+ "order": 31,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 127
+ },
+ {
+ "name": "audio",
+ "shape": 7,
+ "type": "AUDIO",
+ "link": 219
+ },
+ {
+ "name": "meta_batch",
+ "shape": 7,
+ "type": "VHS_BatchManager",
+ "link": null
+ },
+ {
+ "name": "vae",
+ "shape": 7,
+ "type": "VAE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "Filenames",
+ "type": "VHS_FILENAMES",
+ "links": null
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "VHS_VideoCombine",
+ "cnr_id": "comfyui-videohelpersuite",
+ "ver": "0a75c7958fe320efcb052f1d9f8451fd20c730a8"
+ },
+ "widgets_values": {
+ "frame_rate": 25,
+ "loop_count": 0,
+ "filename_prefix": "WanVideo21_HuMo",
+ "format": "video/h264-mp4",
+ "pix_fmt": "yuv420p",
+ "crf": 19,
+ "save_metadata": true,
+ "trim_to_audio": false,
+ "pingpong": false,
+ "save_output": false,
+ "videopreview": {
+ "hidden": false,
+ "paused": false,
+ "params": {
+ "filename": "WanVideo21_HuMo_00005-audio.mp4",
+ "subfolder": "",
+ "type": "temp",
+ "format": "video/h264-mp4",
+ "frame_rate": 25,
+ "workflow": "WanVideo21_HuMo_00005.png",
+ "fullpath": "/home/kijai/AI/ComfyUI/temp/WanVideo21_HuMo_00005-audio.mp4"
+ }
+ }
+ }
+ },
+ {
+ "id": 118,
+ "type": "GetNode",
+ "pos": [
+ 2771.7490234375,
+ -643.5535888671875
+ ],
+ "size": [
+ 210,
+ 60
+ ],
+ "flags": {
+ "collapsed": true
+ },
+ "order": 9,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "AUDIO",
+ "type": "AUDIO",
+ "links": [
+ 219
+ ]
+ }
+ ],
+ "title": "Get_input_audio",
+ "properties": {},
+ "widgets_values": [
+ "input_audio"
+ ],
+ "color": "#323",
+ "bgcolor": "#535"
+ },
+ {
+ "id": 61,
+ "type": "INTConstant",
+ "pos": [
+ -543.3141479492188,
+ 214.99432373046875
+ ],
+ "size": [
+ 210,
+ 58
+ ],
+ "flags": {},
+ "order": 10,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "value",
+ "type": "INT",
+ "links": [
+ 139
+ ]
+ }
+ ],
+ "title": "Width",
+ "properties": {
+ "Node name for S&R": "INTConstant",
+ "cnr_id": "comfyui-kjnodes",
+ "ver": "3e3a1a8aac61dc4515f6a7da74e026f05a80299f"
+ },
+ "widgets_values": [
+ 1280
+ ],
+ "color": "#1b4669",
+ "bgcolor": "#29699c"
+ },
+ {
+ "id": 62,
+ "type": "INTConstant",
+ "pos": [
+ -537.188232421875,
+ 340.2867431640625
+ ],
+ "size": [
+ 210,
+ 58
+ ],
+ "flags": {},
+ "order": 11,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "value",
+ "type": "INT",
+ "links": [
+ 140
+ ]
+ }
+ ],
+ "title": "Height",
+ "properties": {
+ "Node name for S&R": "INTConstant",
+ "cnr_id": "comfyui-kjnodes",
+ "ver": "3e3a1a8aac61dc4515f6a7da74e026f05a80299f"
+ },
+ "widgets_values": [
+ 720
+ ],
+ "color": "#1b4669",
+ "bgcolor": "#29699c"
+ },
+ {
+ "id": 75,
+ "type": "MarkdownNote",
+ "pos": [
+ 713.1290283203125,
+ -1113.56005859375
+ ],
+ "size": [
+ 590.143798828125,
+ 328.435791015625
+ ],
+ "flags": {},
+ "order": 12,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [],
+ "title": "Markdown Note: Model Links",
+ "properties": {},
+ "widgets_values": [
+ "Model links:\n\n[https://huggingface.co/Kijai/WanVideo_comfy_fp8_scaled/tree/main/HuMo](https://huggingface.co/Kijai/WanVideo_comfy_fp8_scaled/tree/main/HuMo)\n\nWhisper:\n\n[https://huggingface.co/Kijai/WanVideo_comfy/blob/main/HuMo/whisper_large_v3_encoder_fp16.safetensors](https://huggingface.co/Kijai/WanVideo_comfy/blob/main/HuMo/whisper_large_v3_encoder_fp16.safetensors)\n\nVocal separator:\n\n[https://huggingface.co/Kijai/MelBandRoFormer_comfy/blob/main/MelBandRoformer_fp16.safetensors](https://huggingface.co/Kijai/MelBandRoFormer_comfy/blob/main/MelBandRoformer_fp16.safetensors)\n\nLoRA:\n\n[https://huggingface.co/Kijai/WanVideo_comfy/tree/main/Lightx2v](https://huggingface.co/Kijai/WanVideo_comfy/tree/main/Lightx2v)\n\nText encoder:\n\n[https://huggingface.co/Kijai/WanVideo_comfy/blob/main/umt5-xxl-enc-bf16.safetensors](https://huggingface.co/Kijai/WanVideo_comfy/blob/main/umt5-xxl-enc-bf16.safetensors)\n\nVAE:\n\n[https://huggingface.co/Kijai/WanVideo_comfy/blob/main/Wan2_1_VAE_bf16.safetensors](https://huggingface.co/Kijai/WanVideo_comfy/blob/main/Wan2_1_VAE_bf16.safetensors)"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 119,
+ "type": "Note",
+ "pos": [
+ -544.3449096679688,
+ 26.11699867248535
+ ],
+ "size": [
+ 210,
+ 127.15582275390625
+ ],
+ "flags": {},
+ "order": 13,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [],
+ "properties": {},
+ "widgets_values": [
+ "Seems to work best with original defaults of 1280x720 and 832x480"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 95,
+ "type": "HuMoEmbeds",
+ "pos": [
+ 1597.50732421875,
+ 183.74229431152344
+ ],
+ "size": [
+ 283.1015625,
+ 216
+ ],
+ "flags": {},
+ "order": 26,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "whisper_model",
+ "type": "WHISPERMODEL",
+ "link": 175
+ },
+ {
+ "name": "vae",
+ "type": "WANVAE",
+ "link": 176
+ },
+ {
+ "name": "reference_images",
+ "type": "IMAGE",
+ "link": 221
+ },
+ {
+ "name": "audio",
+ "shape": 7,
+ "type": "AUDIO",
+ "link": 215
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image_embeds",
+ "type": "WANVIDIMAGE_EMBEDS",
+ "links": [
+ 183
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "HuMoEmbeds",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "dbcef53405bb78feae4c5d2c6b310b76e4ef9949"
+ },
+ "widgets_values": [
+ 65,
+ 1,
+ 2.5,
+ 0,
+ 1
+ ]
+ },
+ {
+ "id": 121,
+ "type": "ImageBatchMulti",
+ "pos": [
+ 1265.909423828125,
+ 149.07740783691406
+ ],
+ "size": [
+ 270,
+ 102
+ ],
+ "flags": {},
+ "order": 23,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image_1",
+ "type": "IMAGE",
+ "link": 220
+ },
+ {
+ "name": "image_2",
+ "shape": 7,
+ "type": "IMAGE",
+ "link": null
+ }
+ ],
+ "outputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "links": [
+ 221
+ ]
+ }
+ ],
+ "properties": {},
+ "widgets_values": [
+ 2,
+ null
+ ]
+ },
+ {
+ "id": 120,
+ "type": "Note",
+ "pos": [
+ 1273.8370361328125,
+ 308.62152099609375
+ ],
+ "size": [
+ 230.1770782470703,
+ 88
+ ],
+ "flags": {},
+ "order": 14,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [],
+ "properties": {},
+ "widgets_values": [
+ "You can add more reference images here"
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 42,
+ "type": "Note",
+ "pos": [
+ 1511.07373046875,
+ -344.02752685546875
+ ],
+ "size": [
+ 298.7467956542969,
+ 88
+ ],
+ "flags": {},
+ "order": 15,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [],
+ "properties": {},
+ "widgets_values": [
+ "Adjust the blocks to swap based on your VRAM, this is a tradeoff between speed and memory usage."
+ ],
+ "color": "#432",
+ "bgcolor": "#653"
+ },
+ {
+ "id": 114,
+ "type": "MelBandRoFormerModelLoader",
+ "pos": [
+ 716.1973876953125,
+ 834.9273071289062
+ ],
+ "size": [
+ 317.2749938964844,
+ 58
+ ],
+ "flags": {},
+ "order": 16,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "model",
+ "type": "MELROFORMERMODEL",
+ "links": [
+ 213
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "MelBandRoFormerModelLoader"
+ },
+ "widgets_values": [
+ "MelBandRoformer_fp32.safetensors"
+ ]
+ },
+ {
+ "id": 35,
+ "type": "WanVideoTorchCompileSettings",
+ "pos": [
+ 166.3423309326172,
+ -440.3662414550781
+ ],
+ "size": [
+ 421.6000061035156,
+ 202
+ ],
+ "flags": {},
+ "order": 17,
+ "mode": 0,
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "torch_compile_args",
+ "type": "WANCOMPILEARGS",
+ "slot_index": 0,
+ "links": [
+ 208
+ ]
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "WanVideoTorchCompileSettings",
+ "cnr_id": "ComfyUI-WanVideoWrapper",
+ "ver": "6099ad393b071728032fd481e96d77d2900eee2c"
+ },
+ "widgets_values": [
+ "inductor",
+ false,
+ "default",
+ false,
+ 64,
+ true,
+ 128
+ ],
+ "color": "#223",
+ "bgcolor": "#335"
+ },
+ {
+ "id": 82,
+ "type": "PreviewImage",
+ "pos": [
+ 61.709815979003906,
+ 346.294677734375
+ ],
+ "size": [
+ 441.4006042480469,
+ 281.9812316894531
+ ],
+ "flags": {},
+ "order": 22,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 145
+ }
+ ],
+ "outputs": [],
+ "properties": {
+ "Node name for S&R": "PreviewImage",
+ "cnr_id": "comfy-core",
+ "ver": "0.3.29"
+ },
+ "widgets_values": []
+ }
+ ],
+ "links": [
+ [
+ 33,
+ 27,
+ 0,
+ 28,
+ 1,
+ "LATENT"
+ ],
+ [
+ 43,
+ 38,
+ 0,
+ 28,
+ 0,
+ "VAE"
+ ],
+ [
+ 126,
+ 28,
+ 0,
+ 79,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 127,
+ 79,
+ 0,
+ 30,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 139,
+ 61,
+ 0,
+ 81,
+ 2,
+ "INT"
+ ],
+ [
+ 140,
+ 62,
+ 0,
+ 81,
+ 3,
+ "INT"
+ ],
+ [
+ 145,
+ 81,
+ 0,
+ 82,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 147,
+ 78,
+ 0,
+ 83,
+ 1,
+ "WANVIDLORA"
+ ],
+ [
+ 148,
+ 83,
+ 0,
+ 27,
+ 0,
+ "WANVIDEOMODEL"
+ ],
+ [
+ 149,
+ 22,
+ 0,
+ 84,
+ 0,
+ "WANVIDEOMODEL"
+ ],
+ [
+ 150,
+ 84,
+ 0,
+ 83,
+ 0,
+ "WANVIDEOMODEL"
+ ],
+ [
+ 151,
+ 39,
+ 0,
+ 84,
+ 1,
+ "BLOCKSWAPARGS"
+ ],
+ [
+ 175,
+ 88,
+ 0,
+ 95,
+ 0,
+ "WHISPERMODEL"
+ ],
+ [
+ 176,
+ 38,
+ 0,
+ 95,
+ 1,
+ "WANVAE"
+ ],
+ [
+ 183,
+ 95,
+ 0,
+ 27,
+ 1,
+ "WANVIDIMAGE_EMBEDS"
+ ],
+ [
+ 202,
+ 67,
+ 0,
+ 81,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 205,
+ 81,
+ 0,
+ 79,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 207,
+ 111,
+ 0,
+ 27,
+ 17,
+ "FLOAT"
+ ],
+ [
+ 208,
+ 35,
+ 0,
+ 22,
+ 0,
+ "WANCOMPILEARGS"
+ ],
+ [
+ 209,
+ 112,
+ 0,
+ 27,
+ 2,
+ "WANVIDEOTEXTEMBEDS"
+ ],
+ [
+ 213,
+ 114,
+ 0,
+ 115,
+ 0,
+ "MELROFORMERMODEL"
+ ],
+ [
+ 214,
+ 115,
+ 0,
+ 116,
+ 0,
+ "AUDIO"
+ ],
+ [
+ 215,
+ 116,
+ 0,
+ 95,
+ 3,
+ "AUDIO"
+ ],
+ [
+ 217,
+ 87,
+ 0,
+ 115,
+ 1,
+ "AUDIO"
+ ],
+ [
+ 218,
+ 87,
+ 0,
+ 117,
+ 0,
+ "*"
+ ],
+ [
+ 219,
+ 118,
+ 0,
+ 30,
+ 1,
+ "AUDIO"
+ ],
+ [
+ 220,
+ 81,
+ 0,
+ 121,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 221,
+ 121,
+ 0,
+ 95,
+ 2,
+ "IMAGE"
+ ]
+ ],
+ "groups": [
+ {
+ "id": 1,
+ "title": "Audio input",
+ "bounding": [
+ 666.8719482421875,
+ 520.6751708984375,
+ 1083.3914794921875,
+ 625.0265502929688
+ ],
+ "color": "#a1309b",
+ "font_size": 24,
+ "flags": {}
+ }
+ ],
+ "config": {},
+ "extra": {
+ "ds": {
+ "scale": 0.555991731349262,
+ "offset": [
+ -380.9118726255019,
+ 1233.774132735748
+ ]
+ },
+ "frontendVersion": "1.27.3",
+ "node_versions": {
+ "ComfyUI-WanVideoWrapper": "8d0ba29ee84d14be6084ecbcee1cbc9414128fb3",
+ "ComfyUI-KJNodes": "d8b8c637fbd1fe97b7a8c7ece843150df38f8b21",
+ "ComfyUI-MelBandRoFormer": "b68d9077815387b64d596f8c39607052b95b6eba",
+ "comfy-core": "0.3.59",
+ "comfyui-videohelpersuite": "8e4d79471bf1952154768e8435a9300077b534fa"
+ },
+ "VHS_latentpreview": true,
+ "VHS_latentpreviewrate": 0,
+ "VHS_MetadataImage": true,
+ "VHS_KeepIntermediate": true
+ },
+ "version": 0.4
+}
\ No newline at end of file
diff --git a/nodes.py b/nodes.py
index 5023be7..ea9056e 100644
--- a/nodes.py
+++ b/nodes.py
@@ -1917,6 +1917,7 @@ class WanVideoSampler:
fun_or_fl2v_model = has_ref = drop_last = False
phantom_latents = fun_ref_image = ATI_tracks = None
add_cond = attn_cond = attn_cond_neg = noise_pred_flipped = None
+ humo_audio = humo_audio_neg = None
#I2V
image_cond = image_embeds.get("image_embeds", None)
@@ -2103,6 +2104,24 @@ class WanVideoSampler:
phantom_start_percent = image_embeds.get("phantom_start_percent", 0.0)
phantom_end_percent = image_embeds.get("phantom_end_percent", 1.0)
+
+ #HuMo inputs
+ humo_audio = image_embeds.get("humo_audio_emb", None)
+ if humo_audio is not None:
+ humo_audio = humo_audio.to(device, dtype)
+ humo_audio_neg = image_embeds.get("humo_audio_emb_neg", None)
+ if humo_audio_neg is not None:
+ humo_audio_neg = humo_audio_neg.to(device, dtype)
+ humo_audio_scale = image_embeds.get("humo_audio_scale", 1.0)
+ humo_image_cond = image_embeds.get("humo_image_cond", None)
+ humo_image_cond_neg = image_embeds.get("humo_image_cond_neg", None)
+ humo_reference_count = image_embeds.get("humo_reference_count", 0)
+ humo_audio_cfg_scale = image_embeds.get("humo_audio_cfg_scale", 1.0)
+ humo_start_percent = image_embeds.get("humo_start_percent", 0.0)
+ humo_end_percent = image_embeds.get("humo_end_percent", 1.0)
+ if not isinstance(humo_audio_cfg_scale, list):
+ humo_audio_cfg_scale = [humo_audio_cfg_scale] * (steps + 1)
+
latent_video_length = noise.shape[1]
# Initialize FreeInit filter if enabled
@@ -2654,6 +2673,12 @@ class WanVideoSampler:
elif ATI_tracks is not None and ((ati_start_percent <= current_step_percentage <= ati_end_percent) or
(ati_end_percent > 0 and idx == 0 and current_step_percentage >= ati_start_percent)):
image_cond_input = image_cond_ati.to(z)
+ elif humo_image_cond is not None:
+ if context_window is not None:
+ image_cond_input = humo_image_cond[:, context_window].to(z)
+ image_cond_input[:, -humo_reference_count:] = humo_image_cond[:, -humo_reference_count:]
+ else:
+ image_cond_input = humo_image_cond.to(z)
elif image_cond is not None:
if reverse_time: # Flip the image condition
image_cond_input = torch.cat([
@@ -2759,12 +2784,25 @@ class WanVideoSampler:
(s2v_pose_end_percent > 0 and idx == 0 and current_step_percentage >= s2v_pose_start_percent)):
s2v_pose = None
+
+ if humo_audio is not None and ((humo_start_percent <= current_step_percentage <= humo_end_percent) or \
+ (humo_end_percent > 0 and idx == 0 and current_step_percentage >= humo_start_percent)):
+ humo_audio_input = humo_audio
+ humo_audio_input_neg = humo_audio_neg if humo_audio_neg is not None else None
+ else:
+ humo_audio_input = humo_audio_input_neg = None
+
base_params = {
+ 'x': [z], # latent
+ 'y': [image_cond_input] if image_cond_input is not None else None, # image cond
+ 'clip_fea': clip_fea, # clip features
'seq_len': seq_len, # sequence length
'device': device, # main device
'freqs': freqs, # rope freqs
't': timestep, # current timestep
+ 'is_uncond': False, # is unconditional
'current_step': idx, # current step
+ 'current_step_percentage': current_step_percentage, # current step percentage
'last_step': len(timesteps) - 1 == idx, # is last step
'control_lora_enabled': control_lora_enabled, # control lora toggle for patch embed selection
'enhance_enabled': enhance_enabled, # enhance-a-video toggle
@@ -2796,7 +2834,9 @@ class WanVideoSampler:
"s2v_ref_motion": s2v_ref_motion, # speech-to-video reference motion latent
"s2v_audio_scale": s2v_audio_scale if s2v_audio_input is not None else 1.0, # speech-to-video audio scale
"s2v_pose": s2v_pose if s2v_pose is not None else None, # speech-to-video pose control
- "s2v_motion_frames": s2v_motion_frames, # speech-to-video motion frames
+ "s2v_motion_frames": s2v_motion_frames, # speech-to-video motion frames,
+ "humo_audio": humo_audio_input, # humo audio input
+ "humo_audio_scale": humo_audio_scale if humo_audio is not None else 1.0, # humo audio scale
}
batch_size = 1
@@ -2809,98 +2849,88 @@ class WanVideoSampler:
try:
if not batched_cfg:
- #cond
+ #conditional (positive) pass
noise_pred_cond, cache_state_cond = transformer(
- [z], context=positive_embeds, y=[image_cond_input] if image_cond_input is not None else None,
- clip_fea=clip_fea, is_uncond=False, current_step_percentage=current_step_percentage,
+ context=positive_embeds,
pred_id=cache_state[0] if cache_state else None,
vace_data=vace_data, attn_cond=attn_cond,
**base_params
)
- noise_pred_cond = noise_pred_cond[0].to(intermediate_device)
+ noise_pred_cond = noise_pred_cond[0]
if math.isclose(cfg_scale, 1.0):
if use_fresca:
- noise_pred_cond = fourier_filter(
- noise_pred_cond,
- scale_low=fresca_scale_low,
- scale_high=fresca_scale_high,
- freq_cutoff=fresca_freq_cutoff,
- )
+ noise_pred_cond = fourier_filter(noise_pred_cond, fresca_scale_low, fresca_scale_high, fresca_freq_cutoff)
return noise_pred_cond, [cache_state_cond]
- #uncond
- if fantasytalking_embeds is not None:
- if not math.isclose(audio_cfg_scale[idx], 1.0):
- base_params['audio_proj'] = None
+
+ #unconditional (negative) pass
+ base_params['is_uncond'] = True
+ base_params['clip_fea'] = clip_fea_neg if clip_fea_neg is not None else clip_fea
+ if humo_audio_input_neg is not None:
+ base_params['humo_audio'] = humo_audio_input_neg
+
noise_pred_uncond, cache_state_uncond = transformer(
- [z], context=negative_embeds, clip_fea=clip_fea_neg if clip_fea_neg is not None else clip_fea,
- y=[image_cond_input] if image_cond_input is not None else None,
- is_uncond=True, current_step_percentage=current_step_percentage,
+ context=negative_embeds if humo_audio_input_neg is None else positive_embeds, #ti
pred_id=cache_state[1] if cache_state else None,
vace_data=vace_data, attn_cond=attn_cond_neg,
- **base_params
- )
- noise_pred_uncond = noise_pred_uncond[0].to(intermediate_device)
+ **base_params)
+ noise_pred_uncond = noise_pred_uncond[0]
+
+ # HuMo
+ if humo_audio_input_neg is not None and not math.isclose(humo_audio_cfg_scale[idx], 1.0):
+ if len(cache_state) !=3:
+ cache_state.append(None)
+ if t > 980 and humo_image_cond_neg is not None: # use image cond for first timesteps
+ base_params['y'] = [humo_image_cond_neg.to(z)]
+
+ noise_pred_humo_audio_uncond, cache_state_humo = transformer(
+ context=negative_embeds, pred_id=cache_state[2] if cache_state else None, vace_data=None,
+ **base_params)
+
+ noise_pred = (noise_pred_uncond + humo_audio_cfg_scale[idx] * (noise_pred_cond - noise_pred_humo_audio_uncond[0])
+ + (cfg_scale - 2.0) * (noise_pred_humo_audio_uncond[0] - noise_pred_uncond))
+ return noise_pred, [cache_state_cond, cache_state_uncond, cache_state_humo]
#phantom
if use_phantom and not math.isclose(phantom_cfg_scale[idx], 1.0):
noise_pred_phantom, cache_state_phantom = transformer(
- [z], context=negative_embeds, clip_fea=clip_fea_neg if clip_fea_neg is not None else clip_fea,
- y=[image_cond_input] if image_cond_input is not None else None,
- is_uncond=True, current_step_percentage=current_step_percentage,
- pred_id=cache_state[2] if cache_state else None,
- vace_data=None,
- **base_params
- )
- noise_pred_phantom = noise_pred_phantom[0].to(intermediate_device)
-
- noise_pred = noise_pred_uncond + phantom_cfg_scale[idx] * (noise_pred_phantom - noise_pred_uncond) + cfg_scale * (noise_pred_cond - noise_pred_phantom)
+ context=negative_embeds, pred_id=cache_state[2] if cache_state else None, vace_data=None,
+ **base_params)
+
+ noise_pred = (noise_pred_uncond + phantom_cfg_scale[idx] * (noise_pred_phantom[0] - noise_pred_uncond)
+ + cfg_scale * (noise_pred_cond - noise_pred_phantom[0]))
return noise_pred, [cache_state_cond, cache_state_uncond, cache_state_phantom]
- #fantasytalking
- if fantasytalking_embeds is not None:
+ #audio cfg (fantasytalking and multitalk)
+ if (fantasytalking_embeds is not None or multitalk_audio_embedding is not None):
if not math.isclose(audio_cfg_scale[idx], 1.0):
if cache_state is not None and len(cache_state) != 3:
cache_state.append(None)
- base_params['audio_proj'] = None
+
+ # Set audio parameters to None/zeros based on type
+ if fantasytalking_embeds is not None:
+ base_params['audio_proj'] = None
+ audio_context = positive_embeds
+ else: # multitalk
+ base_params['multitalk_audio'] = torch.zeros_like(multitalk_audio_input)[-1:]
+ audio_context = negative_embeds
+
noise_pred_no_audio, cache_state_audio = transformer(
- [z], context=positive_embeds, y=[image_cond_input] if image_cond_input is not None else None,
- clip_fea=clip_fea, is_uncond=False, current_step_percentage=current_step_percentage,
+ context=audio_context, is_uncond=False,
pred_id=cache_state[2] if cache_state else None,
vace_data=vace_data,
- **base_params
- )
- noise_pred_no_audio = noise_pred_no_audio[0].to(intermediate_device)
- noise_pred = (
- noise_pred_uncond
- + cfg_scale * (noise_pred_no_audio - noise_pred_uncond)
- + audio_cfg_scale[idx] * (noise_pred_cond - noise_pred_no_audio)
- )
- return noise_pred, [cache_state_cond, cache_state_uncond, cache_state_audio]
- elif multitalk_audio_embedding is not None:
- if not math.isclose(audio_cfg_scale[idx], 1.0):
- if cache_state is not None and len(cache_state) != 3:
- cache_state.append(None)
- base_params['multitalk_audio'] = torch.zeros_like(multitalk_audio_input)[-1:]
- noise_pred_no_audio, cache_state_audio = transformer(
- [z], context=negative_embeds, y=[image_cond_input] if image_cond_input is not None else None,
- clip_fea=clip_fea, is_uncond=False, current_step_percentage=current_step_percentage,
- pred_id=cache_state[2] if cache_state else None,
- vace_data=vace_data,
- **base_params
- )
- noise_pred_no_audio = noise_pred_no_audio[0].to(intermediate_device)
- noise_pred = (
- noise_pred_no_audio
- + cfg_scale * (noise_pred_cond - noise_pred_uncond)
- + audio_cfg_scale[idx] * (noise_pred_uncond - noise_pred_no_audio)
- )
+ **base_params)
+
+ noise_pred = (noise_pred_uncond
+ + cfg_scale * (noise_pred_no_audio[0] - noise_pred_uncond)
+ + audio_cfg_scale[idx] * (noise_pred_cond - noise_pred_no_audio[0]))
return noise_pred, [cache_state_cond, cache_state_uncond, cache_state_audio]
#batched
else:
+ base_params['z'] = [z] * 2
+ base_params['y'] = [image_cond_input] * 2 if image_cond_input is not None else None
+ base_params['clip_fea'] = torch.cat([clip_fea, clip_fea], dim=0)
cache_state_uncond = None
[noise_pred_cond, noise_pred_uncond], cache_state_cond = transformer(
- [z] + [z], context=positive_embeds + negative_embeds,
- y=[image_cond_input] + [image_cond_input] if image_cond_input is not None else None,
- clip_fea=clip_fea.repeat(2,1,1), is_uncond=False, current_step_percentage=current_step_percentage,
+ context=positive_embeds + negative_embeds, is_uncond=False,
pred_id=cache_state[0] if cache_state else None,
**base_params
)
@@ -2919,7 +2949,6 @@ class WanVideoSampler:
noise_pred_uncond.view(batch_size, -1)
).view(batch_size, 1, 1, 1)
-
noise_pred_uncond_scaled = noise_pred_uncond * alpha
if use_tangential:
@@ -2932,18 +2961,12 @@ class WanVideoSampler:
#https://github.com/WikiChao/FreSca
if use_fresca:
- filtered_cond = fourier_filter(
- noise_pred_cond - noise_pred_uncond,
- scale_low=fresca_scale_low,
- scale_high=fresca_scale_high,
- freq_cutoff=fresca_freq_cutoff,
- )
+ filtered_cond = fourier_filter(noise_pred_cond - noise_pred_uncond, fresca_scale_low, fresca_scale_high, fresca_freq_cutoff)
noise_pred = noise_pred_uncond_scaled + cfg_scale * filtered_cond * alpha
else:
noise_pred = noise_pred_uncond_scaled + cfg_scale * (noise_pred_cond - noise_pred_uncond_scaled)
del noise_pred_uncond_scaled, noise_pred_cond, noise_pred_uncond
-
return noise_pred, [cache_state_cond, cache_state_uncond]
if args.preview_method in [LatentPreviewMethod.Auto, LatentPreviewMethod.Latent2RGB]: #default for latent2rgb
@@ -4055,6 +4078,8 @@ class WanVideoSampler:
callback_latent = (latent_model_input[:, :orig_noise_len].to(device) - noise_pred[:, :orig_noise_len].to(device) * t.to(device) / 1000).detach()
#elif phantom_latents is not None:
# callback_latent = (latent_model_input[:,:-phantom_latents.shape[1]].to(device) - noise_pred[:,:-phantom_latents.shape[1]].to(device) * t.to(device) / 1000).detach()
+ elif humo_image_cond is not None:
+ callback_latent = (latent_model_input[:,:-humo_reference_count].to(device) - noise_pred[:,:-humo_reference_count].to(device) * t.to(device) / 1000).detach()
else:
callback_latent = (latent_model_input.to(device) - noise_pred.to(device) * t.to(device) / 1000).detach()
callback(idx, callback_latent.permute(1,0,2,3), None, len(timesteps))
@@ -4075,6 +4100,8 @@ class WanVideoSampler:
if phantom_latents is not None:
latent = latent[:,:-phantom_latents.shape[1]]
+ if humo_image_cond is not None:
+ latent = latent[:,:-humo_reference_count]
cache_states = None
if cache_args is not None:
diff --git a/nodes_model_loading.py b/nodes_model_loading.py
index 174bebb..4fe04f7 100644
--- a/nodes_model_loading.py
+++ b/nodes_model_loading.py
@@ -761,7 +761,7 @@ class WanVideoSetLoRAs:
def load_weights(transformer, sd=None, weight_dtype=None, base_dtype=None,
transformer_load_device=None, block_swap_args=None, gguf=False, reader=None, patcher=None):
params_to_keep = {"time_in", "patch_embedding", "time_", "modulation", "text_embedding",
- "adapter", "add", "ref_conv", "casual_audio_encoder", "cond_encoder", "frame_packer"}
+ "adapter", "add", "ref_conv", "casual_audio_encoder", "cond_encoder", "frame_packer", "audio_proj_glob"}
param_count = sum(1 for _ in transformer.named_parameters())
pbar = ProgressBar(param_count)
cnt = 0
@@ -1112,6 +1112,8 @@ class WanVideoModelLoader:
ffn_dim = sd["blocks.0.ffn.0.bias"].shape[0]
ffn2_dim = sd["blocks.0.ffn.2.weight"].shape[1]
+ is_humo = "audio_proj.audio_proj_glob_1.layer.weight" in sd
+
model_type = "t2v"
if "audio_injector.injector.0.k.weight" in sd:
model_type = "s2v"
@@ -1226,6 +1228,7 @@ class WanVideoModelLoader:
"enable_adain": True if "audio_injector.injector_adain_layers.0.linear.weight" in sd else False,
"cond_dim": sd["cond_encoder.weight"].shape[1] if "cond_encoder.weight" in sd else 0,
"zero_timestep": model_type == "s2v",
+ "humo_audio": is_humo,
}
diff --git a/wanvideo/modules/model.py b/wanvideo/modules/model.py
index 6769153..68d1c3e 100644
--- a/wanvideo/modules/model.py
+++ b/wanvideo/modules/model.py
@@ -14,7 +14,6 @@ except:
from .attention import attention
import numpy as np
-from copy import deepcopy
from tqdm import tqdm
import gc
@@ -25,10 +24,7 @@ from ...echoshot.echoshot import rope_apply_z, rope_apply_c, rope_apply_echoshot
from ...MTV.mtv import apply_rotary_emb
-#from .s2v.motioner import MotionerTransformers, FramePackMotioner, rope_precompute
-
-#from comfy.ldm.wan.model import FramePackMotioner
-class FramePackMotioner(nn.Module):
+class FramePackMotioner(nn.Module):#from comfy.ldm.wan.model
def __init__(
self,
inner_dim=1024,
@@ -362,7 +358,8 @@ class WanSelfAttention(nn.Module):
num_heads,
qk_norm=True,
eps=1e-6,
- attention_mode='sdpa'):
+ attention_mode='sdpa',
+ kv_dim=None):
assert out_features % num_heads == 0
super().__init__()
self.dim = out_features
@@ -379,8 +376,12 @@ class WanSelfAttention(nn.Module):
# layers
self.q = nn.Linear(in_features, out_features)
- self.k = nn.Linear(in_features, out_features)
- self.v = nn.Linear(in_features, out_features)
+ if kv_dim is not None:
+ self.k = nn.Linear(kv_dim, out_features)
+ self.v = nn.Linear(kv_dim, out_features)
+ else:
+ self.k = nn.Linear(in_features, out_features)
+ self.v = nn.Linear(in_features, out_features)
self.o = nn.Linear(in_features, out_features)
self.norm_q = WanRMSNorm(out_features, eps=eps) if qk_norm else nn.Identity()
self.norm_k = WanRMSNorm(out_features, eps=eps) if qk_norm else nn.Identity()
@@ -587,8 +588,8 @@ class LoRALinearLayer(nn.Module):
#region crossattn
class WanT2VCrossAttention(WanSelfAttention):
- def __init__(self, in_features, out_features, num_heads, qk_norm=True, eps=1e-6, attention_mode='sdpa'):
- super().__init__(in_features, out_features, num_heads, qk_norm, eps)
+ def __init__(self, in_features, out_features, num_heads, kv_dim=None, qk_norm=True, eps=1e-6, attention_mode='sdpa'):
+ super().__init__(in_features, out_features, num_heads, qk_norm, eps, kv_dim=kv_dim)
self.attention_mode = attention_mode
def forward(self, x, context, grid_sizes=None, clip_embed=None, audio_proj=None, audio_scale=1.0,
@@ -721,6 +722,45 @@ class WanI2VCrossAttention(WanSelfAttention):
x = x + adapter_x * ip_scale
return self.o(x)
+
+class WanHuMoCrossAttention(WanSelfAttention):
+
+ def __init__(self, in_features, out_features, num_heads, kv_dim=None, qk_norm=True, eps=1e-6, attention_mode='sdpa'):
+ super().__init__(in_features, out_features, num_heads, qk_norm, eps, kv_dim=kv_dim)
+ self.attention_mode = attention_mode
+
+ def forward(self, x, context, grid_sizes, **kwargs):
+
+ b, n, d = x.size(0), self.num_heads, self.head_dim
+ q = self.norm_q(self.q(x)).view(b, -1, n, d)
+ k = self.norm_k(self.k(context)).view(b, -1, n, d)
+ v = self.v(context).view(b, -1, n, d)
+
+ # Handle video spatial structure
+ hlen_wlen = grid_sizes[0][1] * grid_sizes[0][2]
+ q = q.reshape(-1, hlen_wlen, n, d)
+
+ # Handle audio temporal structure (16 tokens per frame)
+ k = k.reshape(-1, 16, n, d)
+ v = v.reshape(-1, 16, n, d)
+
+ x_text = attention(q, k, v, attention_mode=self.attention_mode)
+ x_text = x_text.view(b, -1, n, d).flatten(2)
+
+ x = x_text
+
+ return self.o(x)
+
+class AudioCrossAttentionWrapper(nn.Module):
+ def __init__(self, in_features, out_features, num_heads, qk_norm=True, eps=1e-6, kv_dim=None):
+ super().__init__()
+
+ self.audio_cross_attn = WanHuMoCrossAttention(in_features, out_features, num_heads, kv_dim=kv_dim)
+ self.norm1_audio = WanLayerNorm(out_features, eps, elementwise_affine=True)
+
+ def forward(self, x, audio, grid_sizes, humo_audio_scale=1.0):
+ x = x + self.audio_cross_attn(self.norm1_audio(x), audio, grid_sizes) * humo_audio_scale
+ return x
class MTVCrafterMotionAttention(WanSelfAttention):
@@ -768,7 +808,8 @@ class WanAttentionBlock(nn.Module):
eps=1e-6,
attention_mode='sdpa',
rope_func="comfy",
- use_motion_attn=False
+ use_motion_attn=False,
+ use_humo_audio_attn=False,
):
super().__init__()
self.dim = out_features
@@ -813,6 +854,10 @@ class WanAttentionBlock(nn.Module):
self.modulation = nn.Parameter(torch.randn(1, 6, out_features) / in_features**0.5)
self.seg_idx = None
+ # HuMo audio cross-attn
+ if use_humo_audio_attn:
+ self.audio_cross_attn_wrapper = AudioCrossAttentionWrapper(in_features, out_features, num_heads, qk_norm, eps, kv_dim=1536)
+
#@torch.compiler.disable()
def get_mod(self, e):
if e.dim() == 3:
@@ -877,26 +922,21 @@ class WanAttentionBlock(nn.Module):
num_latent_frames=21,
original_seq_len=None,
enhance_enabled=False,
- block_mask=None,
nag_params={},
nag_context=None,
is_uncond=False,
multitalk_audio_embedding=None,
ref_target_masks=None,
human_num=0,
- inner_t=None,
- inner_c=None,
+ inner_t=None, inner_c=None,
cross_freqs=None,
- x_ip=None,
- e_ip=None,
+ x_ip=None, e_ip=None,
freqs_ip=None,
adapter_proj=None,
ip_scale=1.0,
reverse_time=False,
- mtv_motion_tokens=None,
- mtv_motion_rotary_emb=None,
- mtv_strength=1.0,
- mtv_freqs=None
+ mtv_motion_tokens=None, mtv_motion_rotary_emb=None, mtv_strength=1.0, mtv_freqs=None,
+ humo_audio_input=None, humo_audio_scale=1.0,
):
r"""
Args:
@@ -1054,7 +1094,9 @@ class WanAttentionBlock(nn.Module):
audio_proj, audio_scale, num_latent_frames, nag_params, nag_context, is_uncond,
multitalk_audio_embedding, x_ref_attn_map, human_num, inner_t, inner_c, cross_freqs,
adapter_proj=adapter_proj, ip_scale=ip_scale,
- mtv_freqs=mtv_freqs, mtv_motion_tokens=mtv_motion_tokens, mtv_motion_rotary_emb=mtv_motion_rotary_emb, mtv_strength=mtv_strength)
+ mtv_freqs=mtv_freqs, mtv_motion_tokens=mtv_motion_tokens, mtv_motion_rotary_emb=mtv_motion_rotary_emb, mtv_strength=mtv_strength,
+ humo_audio_input=humo_audio_input, humo_audio_scale=humo_audio_scale
+ )
else:
if self.rope_func == "comfy_chunked":
y = self.ffn_chunked(x, shift_mlp, scale_mlp)
@@ -1074,7 +1116,8 @@ class WanAttentionBlock(nn.Module):
def cross_attn_ffn(self, x, context, grid_sizes, shift_mlp, scale_mlp, gate_mlp, clip_embed,
audio_proj, audio_scale, num_latent_frames, nag_params,
nag_context, is_uncond, multitalk_audio_embedding, x_ref_attn_map, human_num,
- inner_t, inner_c, cross_freqs, adapter_proj, ip_scale, mtv_freqs, mtv_motion_tokens, mtv_motion_rotary_emb, mtv_strength):
+ inner_t, inner_c, cross_freqs, adapter_proj, ip_scale, mtv_freqs, mtv_motion_tokens, mtv_motion_rotary_emb, mtv_strength,
+ humo_audio_input, humo_audio_scale):
x = x + self.cross_attn(self.norm3(x), context, grid_sizes, clip_embed=clip_embed,
audio_proj=audio_proj, audio_scale=audio_scale,
@@ -1092,6 +1135,10 @@ class WanAttentionBlock(nn.Module):
x_motion = self.motion_attn(self.norm4(x), mtv_motion_tokens, mtv_motion_rotary_emb, grid_sizes, mtv_freqs)
x = x + x_motion * mtv_strength
+ # HuMo Audio Cross-Attention
+ if humo_audio_input is not None:
+ x = self.audio_cross_attn_wrapper(x, humo_audio_input, grid_sizes, humo_audio_scale)
+
if self.rope_func == "comfy_chunked" and not self.zero_timestep:
y = self.ffn_chunked(x, shift_mlp, scale_mlp)
else:
@@ -1415,7 +1462,8 @@ class WanModel(torch.nn.Module):
enable_adain=False,
adain_mode="attn_norm",
audio_inject_layers=[0, 4, 8, 12, 16, 20, 24, 27, 30, 33, 36, 39],
- zero_timestep=False
+ zero_timestep=False,
+ humo_audio=False,
):
r"""
Initialize the diffusion model backbone.
@@ -1525,6 +1573,8 @@ class WanModel(torch.nn.Module):
self.multitalk_model_type = "none"
+ self.humo_audio = humo_audio
+
# embeddings
self.patch_embedding = nn.Conv3d(
in_dim, dim, kernel_size=patch_size, stride=patch_size)
@@ -1577,7 +1627,7 @@ class WanModel(torch.nn.Module):
self.blocks = nn.ModuleList([
WanAttentionBlock(cross_attn_type, self.in_features, self.out_features, ffn_dim, ffn2_dim, num_heads,
qk_norm, cross_attn_norm, eps,
- attention_mode=self.attention_mode, rope_func=self.rope_func, use_motion_attn=(i % 4 == 0 and use_motion_attn))
+ attention_mode=self.attention_mode, rope_func=self.rope_func, use_motion_attn=(i % 4 == 0 and use_motion_attn), use_humo_audio_attn=self.humo_audio)
for i in range(num_layers)
])
#MTV Crafter
@@ -1620,8 +1670,6 @@ class WanModel(torch.nn.Module):
else:
self.control_adapter = None
- self.block_mask=None
-
#S2V
self.zero_timestep = self.audio_injector = self.trainable_cond_mask =None
if cond_dim > 0:
@@ -1661,6 +1709,12 @@ class WanModel(torch.nn.Module):
self.adain_mode = adain_mode
self.zero_timestep = zero_timestep
+ # HuMo Audio
+ if self.humo_audio:
+ from ...HuMo.audio_proj import AudioProjModel
+ self.audio_proj = AudioProjModel(seq_len=8, blocks=5, channels=1280,
+ intermediate_dim=512, output_dim=1536, context_tokens=16)
+
def block_swap(self, blocks_to_swap, offload_txt_emb=False, offload_img_emb=False, vace_blocks_to_swap=None, prefetch_blocks=0, block_swap_debug=False):
# Clamp blocks_to_swap to valid range
@@ -1893,6 +1947,8 @@ class WanModel(torch.nn.Module):
s2v_ref_motion=None,
s2v_pose=None,
s2v_motion_frames=[1, 0],
+ humo_audio=None,
+ humo_audio_scale=1.0,
):
r"""
@@ -2251,6 +2307,16 @@ class WanModel(torch.nn.Module):
token_ref_target_masks = token_ref_target_masks.view(token_ref_target_masks.shape[0], -1)
token_ref_target_masks = token_ref_target_masks.to(x.dtype).to(device)
+ humo_audio_input = None
+ if humo_audio is not None:
+ humo_audio_input = self.audio_proj(humo_audio.unsqueeze(0)).permute(0, 3, 1, 2)
+
+ humo_audio_seq_len = torch.tensor(humo_audio.shape[2] * humo_audio_input.shape[3], device=device)
+ humo_audio_input = humo_audio_input.flatten(2).transpose(1, 2) # 1, t*32, 1536
+ pad_len = int(humo_audio_seq_len - humo_audio_input.size(1))
+ if pad_len > 0:
+ humo_audio_input = torch.nn.functional.pad(humo_audio_input, (0, 0, 0, pad_len))
+
should_calc = True
#TeaCache
if self.enable_teacache and self.teacache_start_step <= current_step <= self.teacache_end_step:
@@ -2392,25 +2458,21 @@ class WanModel(torch.nn.Module):
original_seq_len=self.original_seq_len,
enhance_enabled=enhance_enabled,
audio_scale=audio_scale,
- block_mask=self.block_mask,
- nag_params=nag_params,
- nag_context=nag_context,
+ nag_params=nag_params, nag_context=nag_context,
is_uncond = is_uncond,
multitalk_audio_embedding=multitalk_audio_embedding if multitalk_audio is not None else None,
ref_target_masks=token_ref_target_masks if multitalk_audio is not None else None,
human_num=human_num if multitalk_audio is not None else 0,
- inner_t=inner_t,
- inner_c=inner_c,
+ inner_t=inner_t, inner_c=inner_c,
cross_freqs=self.cross_freqs if inner_t is not None else None,
freqs_ip=freqs_ip if x_ip is not None else None,
e_ip=e0_ip if x_ip is not None else None,
adapter_proj=adapter_proj,
ip_scale=ip_scale,
reverse_time=reverse_time,
- mtv_motion_tokens=mtv_motion_tokens,
- mtv_motion_rotary_emb=mtv_motion_rotary_emb,
- mtv_strength=mtv_strength,
- mtv_freqs=mtv_freqs
+ mtv_motion_tokens=mtv_motion_tokens, mtv_motion_rotary_emb=mtv_motion_rotary_emb, mtv_strength=mtv_strength, mtv_freqs=mtv_freqs,
+ humo_audio_input=humo_audio_input,
+ humo_audio_scale=humo_audio_scale,
)
if vace_data is not None: