Files
xhiroga-ComfyUI-FramePackWr…/nodes_one.py
T

572 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import logging
import math
import torch
logger = logging.getLogger(__name__)
import comfy.latent_formats
import comfy.model_base
import comfy.model_management as mm
from comfy.utils import common_upscale
from .diffusers_helper.memory import move_model_to_device_with_memory_preservation
from .diffusers_helper.pipelines.k_diffusion_hunyuan import sample_hunyuan
from .diffusers_helper.utils import crop_or_pad_yield_mask
from .nodes import HyVideoModel, HyVideoModelConfig
vae_scaling_factor = 0.476986
class FramePackSingleFrameSampler:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("FramePackMODEL",),
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"start_latent": (
"LATENT",
{"tooltip": "init Latents to use for image2image"},
),
"steps": ("INT", {"default": 30, "min": 1}),
"use_teacache": (
"BOOLEAN",
{"default": True, "tooltip": "Use teacache for faster sampling."},
),
"teacache_rel_l1_thresh": (
"FLOAT",
{
"default": 0.15,
"min": 0.0,
"max": 1.0,
"step": 0.01,
"tooltip": "The threshold for the relative L1 loss.",
},
),
"cfg": (
"FLOAT",
{"default": 1.0, "min": 0.0, "max": 30.0, "step": 0.01},
),
"guidance_scale": (
"FLOAT",
{"default": 10.0, "min": 0.0, "max": 32.0, "step": 0.01},
),
"shift": (
"FLOAT",
{"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.01},
),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}),
"latent_window_size": (
"INT",
{
"default": 9,
"min": 1,
"max": 33,
"step": 1,
"tooltip": "The size of the latent window to use for sampling.",
},
),
"gpu_memory_preservation": (
"FLOAT",
{
"default": 6.0,
"min": 0.0,
"max": 128.0,
"step": 0.1,
"tooltip": "The amount of GPU memory to preserve.",
},
),
"sampler": (["unipc_bh1", "unipc_bh2"], {"default": "unipc_bh1"}),
"use_kisekaeichi": (
"BOOLEAN",
{
"default": False,
"tooltip": "Enable Kisekaeichi mode for style transfer",
},
),
},
"optional": {
"image_embeds": ("CLIP_VISION_OUTPUT",),
"initial_samples": (
"LATENT",
{"tooltip": "init Latents to use for image2image variation"},
),
"denoise_strength": (
"FLOAT",
{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01},
),
"reference_latent": (
"LATENT",
{"tooltip": "Reference image latent for kisekaeichi mode"},
),
"reference_image_embeds": (
"CLIP_VISION_OUTPUT",
{"tooltip": "Reference image CLIP embeds for kisekaeichi mode"},
),
"target_index": (
"INT",
{
"default": 1,
"min": 0,
"max": 8,
"step": 1,
"tooltip": "Target index for kisekaeichi mode (recommended: 1)",
},
),
"history_index": (
"INT",
{
"default": 13,
"min": 0,
"max": 16,
"step": 1,
"tooltip": "History index for kisekaeichi mode (recommended: 13)",
},
),
"input_mask": (
"MASK",
{"tooltip": "Input mask for selective application"},
),
"reference_mask": (
"MASK",
{"tooltip": "Reference mask for selective features"},
),
},
}
RETURN_TYPES = ("LATENT",)
RETURN_NAMES = ("samples",)
FUNCTION = "process"
CATEGORY = "FramePackWrapper"
DESCRIPTION = "Single frame sampler with Kisekaeichi (style transfer) support"
def process(
self,
model,
shift,
positive,
negative,
latent_window_size,
use_teacache,
teacache_rel_l1_thresh,
steps,
cfg,
guidance_scale,
seed,
sampler,
gpu_memory_preservation,
start_latent=None,
image_embeds=None,
initial_samples=None,
denoise_strength=1.0,
use_kisekaeichi=False,
reference_latent=None,
reference_image_embeds=None,
target_index=1,
history_index=13,
input_mask=None,
reference_mask=None,
):
print("=== 1フレーム推論モード ===")
if use_kisekaeichi:
print("Kisekaeichi(着せ替え)モード有効")
print(f"target_index: {target_index}, history_index: {history_index}")
transformer = model["transformer"]
base_dtype = model["dtype"]
device = mm.get_torch_device()
offload_device = mm.unet_offload_device()
mm.unload_all_models()
mm.cleanup_models()
mm.soft_empty_cache()
# Latent処理
if start_latent is not None:
start_latent = start_latent["samples"] * vae_scaling_factor
if initial_samples is not None:
initial_samples = initial_samples["samples"] * vae_scaling_factor
if use_kisekaeichi and reference_latent is not None:
reference_latent = reference_latent["samples"] * vae_scaling_factor
print(f"参照画像latent: {reference_latent.shape}")
print("start_latent", start_latent.shape)
B, C, T, H, W = start_latent.shape
# 画像エンベッディング処理
if image_embeds is not None:
start_image_encoder_last_hidden_state = image_embeds[
"last_hidden_state"
].to(device, base_dtype)
else:
start_image_encoder_last_hidden_state = None
if use_kisekaeichi and reference_image_embeds is not None:
reference_image_encoder_last_hidden_state = reference_image_embeds[
"last_hidden_state"
].to(device, base_dtype)
print("参照画像のCLIP embeddingを設定しました")
else:
reference_image_encoder_last_hidden_state = None
# テキストエンベッディング処理
llama_vec = positive[0][0].to(device, base_dtype)
clip_l_pooler = positive[0][1]["pooled_output"].to(device, base_dtype)
if not math.isclose(cfg, 1.0):
llama_vec_n = negative[0][0].to(device, base_dtype)
clip_l_pooler_n = negative[0][1]["pooled_output"].to(device, base_dtype)
else:
llama_vec_n = torch.zeros_like(llama_vec, device=device)
clip_l_pooler_n = torch.zeros_like(clip_l_pooler, device=device)
llama_vec, llama_attention_mask = crop_or_pad_yield_mask(llama_vec, length=512)
llama_vec_n, llama_attention_mask_n = crop_or_pad_yield_mask(
llama_vec_n, length=512
)
# シード設定
rnd = torch.Generator("cpu").manual_seed(seed)
# === 一つ目のコードに完全準拠した設定 ===
# 1フレームモード固定設定
sample_num_frames = 1
total_latent_sections = 1
latent_padding = 0
latent_padding_size = latent_padding * latent_window_size # 0
# 一つ目のコードと同じインデックス構造
indices = torch.arange(
0, sum([1, latent_padding_size, latent_window_size, 1, 2, 16])
).unsqueeze(0)
split_sizes = [1, latent_padding_size, latent_window_size, 1, 2, 16]
# latent_padding_sizeが0の場合の分割(一つ目のコードと完全同一)
if latent_padding_size == 0:
clean_latent_indices_pre = indices[:, 0:1]
latent_indices = indices[:, 1 : 1 + latent_window_size]
clean_latent_indices_post = indices[
:, 1 + latent_window_size : 2 + latent_window_size
]
clean_latent_2x_indices = indices[
:, 2 + latent_window_size : 4 + latent_window_size
]
clean_latent_4x_indices = indices[
:, 4 + latent_window_size : 20 + latent_window_size
]
blank_indices = torch.empty((1, 0), dtype=torch.long)
else:
(
clean_latent_indices_pre,
blank_indices,
latent_indices,
clean_latent_indices_post,
clean_latent_2x_indices,
clean_latent_4x_indices,
) = indices.split(split_sizes, dim=1)
# 一つ目のコードの重要な処理:1フレームモード時のone_frame_inference処理
if sample_num_frames == 1:
if use_kisekaeichi and reference_latent is not None:
print("=== Kisekaeichi モード設定(完全版) ===")
# 一つ目のコードのone_frame_inference処理を完全再現
one_frame_inference = set()
one_frame_inference.add(f"target_index={target_index}")
one_frame_inference.add(f"history_index={history_index}")
# 公式実装に従った処理
latent_indices = indices[:, -1:] # デフォルトは最後のフレーム
# パラメータ解析と処理(一つ目のコードと同じ)
for one_frame_param in one_frame_inference:
if one_frame_param.startswith("target_index="):
target_idx = int(one_frame_param.split("=")[1])
latent_indices[:, 0] = target_idx
print(f"latent_indices設定: target_index={target_idx}")
elif one_frame_param.startswith("history_index="):
history_idx = int(one_frame_param.split("=")[1])
clean_latent_indices_post[:, 0] = history_idx
print(
f"clean_latent_indices_post設定: history_index={history_idx}"
)
# history_latentsのダミーを作成(一つ目のコードと同じ構造)
history_latents = torch.zeros(
size=(1, 16, 1 + 2 + 16, H, W), dtype=torch.float32, device="cpu"
)
# clean_latents_preの設定(入力画像)
clean_latents_pre = start_latent.to(history_latents.dtype).to(
history_latents.device
)
if len(clean_latents_pre.shape) < 5:
clean_latents_pre = clean_latents_pre.unsqueeze(2)
# マスクの適用(入力画像)
if input_mask is not None:
print("入力画像マスクを適用中...")
try:
height_latent, width_latent = clean_latents_pre.shape[-2:]
if isinstance(input_mask, torch.Tensor):
input_mask_tensor = input_mask
else:
input_mask_tensor = torch.from_numpy(input_mask)
input_mask_resized = (
common_upscale(
input_mask_tensor.unsqueeze(0).unsqueeze(0),
width_latent,
height_latent,
"bilinear",
"center",
)
.squeeze(0)
.squeeze(0)
)
input_mask_resized = input_mask_resized.to(
clean_latents_pre.device
)[None, None, None, :, :]
clean_latents_pre = clean_latents_pre * input_mask_resized
print("入力画像マスクを適用しました")
except Exception as e:
print(f"入力マスク適用エラー: {e}")
# clean_latents_postの設定(参照画像)
clean_latents_post = (
reference_latent[:, :, 0:1, :, :]
.to(history_latents.dtype)
.to(history_latents.device)
)
# マスクの適用(参照画像)
if reference_mask is not None:
print("参照画像マスクを適用中...")
try:
height_latent, width_latent = clean_latents_post.shape[-2:]
if isinstance(reference_mask, torch.Tensor):
reference_mask_tensor = reference_mask
else:
reference_mask_tensor = torch.from_numpy(reference_mask)
reference_mask_resized = (
common_upscale(
reference_mask_tensor.unsqueeze(0).unsqueeze(0),
width_latent,
height_latent,
"bilinear",
"center",
)
.squeeze(0)
.squeeze(0)
)
reference_mask_resized = reference_mask_resized.to(
clean_latents_post.device
)[None, None, None, :, :]
clean_latents_post = clean_latents_post * reference_mask_resized
print("参照画像マスクを適用しました")
except Exception as e:
print(f"参照マスク適用エラー: {e}")
# clean_latentsを結合
clean_latents = torch.cat(
[clean_latents_pre, clean_latents_post], dim=2
)
# clean_latent_indicesを結合
clean_latent_indices = torch.cat(
[clean_latent_indices_pre, clean_latent_indices_post], dim=1
)
clean_latents_2x_param = None
clean_latents_4x_param = None
clean_latent_2x_indices = None
clean_latent_4x_indices = None
# 2x, 4x latentsの設定も無効化
clean_latents_2x = None
clean_latents_4x = None
print("Kisekaeichi: 2x/4xインデックスを無効化しました")
# 画像エンベッディングの処理(両方を活用)
if (
reference_image_encoder_last_hidden_state is not None
and start_image_encoder_last_hidden_state is not None
):
# 重み付き平均で統合
ref_weight = 0.3 # 参照画像の重み
input_weight = 1.0 - ref_weight
image_encoder_last_hidden_state = (
start_image_encoder_last_hidden_state * input_weight
+ reference_image_encoder_last_hidden_state * ref_weight
)
print(
f"画像エンベッディングを統合 (入力:{input_weight:.2f}, 参照:{ref_weight:.2f})"
)
elif reference_image_encoder_last_hidden_state is not None:
image_encoder_last_hidden_state = (
reference_image_encoder_last_hidden_state
)
else:
image_encoder_last_hidden_state = (
start_image_encoder_last_hidden_state
)
print(f"Kisekaeichi設定完了:")
print(f" - clean_latents.shape: {clean_latents.shape} (入力+参照)")
print(f" - latent_indices: {latent_indices}")
print(f" - clean_latent_indices: {clean_latent_indices}")
print(f" - sample_num_frames: {sample_num_frames}")
print(f" - 2x/4x無効化: True")
else:
# 通常モード(参照画像なし)
all_indices = torch.arange(0, latent_window_size).unsqueeze(0)
latent_indices = all_indices[:, -1:]
clean_latents_pre = start_latent.to(torch.float32).cpu()
if len(clean_latents_pre.shape) < 5:
clean_latents_pre = clean_latents_pre.unsqueeze(2)
clean_latents_post = torch.zeros_like(clean_latents_pre)
clean_latents = torch.cat(
[clean_latents_pre, clean_latents_post], dim=2
)
clean_latent_indices = torch.cat(
[clean_latent_indices_pre, clean_latent_indices_post], dim=1
)
# 通常モードでのインデックス調整
clean_latent_indices = torch.tensor(
[[0]],
dtype=clean_latent_indices.dtype,
device=clean_latent_indices.device,
)
clean_latents = clean_latents[:, :, :1, :, :]
clean_latents_2x_param = None
clean_latents_4x_param = None
clean_latent_2x_indices = None
clean_latent_4x_indices = None
# 2x, 4x latentsの設定も無効化
clean_latents_2x = None
clean_latents_4x = None
print("Kisekaeichi: 2x/4xインデックスを無効化しました")
image_encoder_last_hidden_state = start_image_encoder_last_hidden_state
print("通常モード設定:")
print(f" - clean_latents.shape: {clean_latents.shape}")
print(f" - latent_indices: {latent_indices}")
print(f" - clean_latent_indices: {clean_latent_indices}")
# 初期サンプルの処理
input_init_latents = None
if initial_samples is not None:
input_init_latents = initial_samples[:, :, 0:1, :, :].to(device)
print("初期サンプルを設定しました")
# ComfyUI用のセットアップ
comfy_model = HyVideoModel(
HyVideoModelConfig(base_dtype),
model_type=comfy.model_base.ModelType.FLOW,
device=device,
)
patcher = comfy.model_patcher.ModelPatcher(
comfy_model, device, torch.device("cpu")
)
from latent_preview import prepare_callback
callback = prepare_callback(patcher, steps)
# モデルをGPUに移動
move_model_to_device_with_memory_preservation(
transformer,
target_device=device,
preserved_memory_gb=gpu_memory_preservation,
)
# TeaCacheの設定
if use_teacache:
transformer.initialize_teacache(
enable_teacache=True,
num_steps=steps,
rel_l1_thresh=teacache_rel_l1_thresh,
)
else:
transformer.initialize_teacache(enable_teacache=False)
print("=== サンプリング開始 ===")
print(f"sample_num_frames: {sample_num_frames}")
print(f"clean_latents使用フレーム数: {clean_latents.shape[2]}")
print(f"clean_latent_2x_indices: {clean_latent_2x_indices}")
print(f"clean_latent_4x_indices: {clean_latent_4x_indices}")
with torch.autocast(
device_type=mm.get_autocast_device(device), dtype=base_dtype, enabled=True
):
generated_latents = sample_hunyuan(
transformer=transformer,
sampler=sampler,
initial_latent=input_init_latents,
strength=denoise_strength,
width=W * 8,
height=H * 8,
frames=sample_num_frames, # 1フレーム固定
real_guidance_scale=cfg,
distilled_guidance_scale=guidance_scale,
guidance_rescale=0,
shift=shift if shift != 0 else None,
num_inference_steps=steps,
generator=rnd,
prompt_embeds=llama_vec,
prompt_embeds_mask=llama_attention_mask,
prompt_poolers=clip_l_pooler,
negative_prompt_embeds=llama_vec_n,
negative_prompt_embeds_mask=llama_attention_mask_n,
negative_prompt_poolers=clip_l_pooler_n,
device=device,
dtype=base_dtype,
image_embeddings=image_encoder_last_hidden_state,
latent_indices=latent_indices,
clean_latents=clean_latents,
clean_latent_indices=clean_latent_indices,
clean_latents_2x=clean_latents_2x,
clean_latent_2x_indices=clean_latent_2x_indices,
clean_latents_4x=clean_latents_4x,
clean_latent_4x_indices=clean_latent_4x_indices,
callback=callback,
)
# クリーンアップ
transformer.to(offload_device)
mm.soft_empty_cache()
# 出力処理
mode_info = (
"Kisekaeichi(latent+エンベッディング)" if use_kisekaeichi else "通常"
)
print(
f"=== 1フレーム生成完了 ({mode_info}モード): {generated_latents.shape} ==="
)
return ({"samples": generated_latents / vae_scaling_factor},)
NODE_CLASS_MAPPINGS = {
"FramePackSingleFrameSampler": FramePackSingleFrameSampler,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FramePackSingleFrameSampler": "FramePack Single Frame Sampler",
}