711 lines
31 KiB
Python
711 lines
31 KiB
Python
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": 20, "min": 1, "tooltip": "Sampling steps (10=fast preview, 20=balanced, 30=high quality)"}),
|
||
"use_teacache": (
|
||
"BOOLEAN",
|
||
{"default": False, "tooltip": "Use TeaCache for faster sampling (recommended for 15+ steps)."},
|
||
),
|
||
"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"}),
|
||
},
|
||
"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_latents": (
|
||
"REFERENCE_LATENT_LIST",
|
||
{"tooltip": "List of reference image latents for kisekaeichi mode (up to 8)"},
|
||
),
|
||
"reference_image_embeds": (
|
||
"REFERENCE_EMBEDS_LIST",
|
||
{"tooltip": "List of reference image CLIP embeds for kisekaeichi mode (up to 8)"},
|
||
),
|
||
"reference_masks": (
|
||
"REFERENCE_MASK_LIST",
|
||
{"tooltip": "List of reference masks for selective features (up to 8, optional)"},
|
||
),
|
||
"target_index": (
|
||
"INT",
|
||
{
|
||
"default": 5,
|
||
"min": 0,
|
||
"max": 16,
|
||
"step": 1,
|
||
"tooltip": "Target index for kisekaeichi mode (recommended: 1)",
|
||
},
|
||
),
|
||
"control_index": (
|
||
"STRING",
|
||
{
|
||
"default": "0;10",
|
||
"tooltip": "Control indices separated by semicolon (preferred) or comma: 0;7;8;9;10 or 0,7,8,9,10 (default: 0;10)",
|
||
},
|
||
),
|
||
"input_mask": (
|
||
"MASK",
|
||
{"tooltip": "Input mask for selective application"},
|
||
),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("LATENT",)
|
||
RETURN_NAMES = ("samples",)
|
||
FUNCTION = "process"
|
||
CATEGORY = "FramePackWrapper"
|
||
DESCRIPTION = "Single frame sampler with auto-enabled Kisekaeichi (style transfer) support for multiple references"
|
||
|
||
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,
|
||
reference_latents=None,
|
||
reference_image_embeds=None,
|
||
reference_masks=None,
|
||
target_index=1,
|
||
control_index="0;10",
|
||
input_mask=None,
|
||
):
|
||
print("=== 1フレーム推論モード(musubi-tuner完全互換) ===")
|
||
|
||
# Use control_index parameter directly (no more one_frame_inference)
|
||
|
||
# Auto-enable kisekaeichi when reference inputs are provided
|
||
use_kisekaeichi = reference_latents is not None and len(reference_latents) > 0
|
||
if use_kisekaeichi:
|
||
print("Kisekaeichi(着せ替え)モード有効 (自動検出)")
|
||
print(f"target_index: {target_index}")
|
||
print(f"control_index raw value: '{control_index}'")
|
||
print(f"control_index type: {type(control_index)}")
|
||
|
||
# Parse control indices from parameter (support both semicolon and comma for backward compatibility)
|
||
if ';' in control_index:
|
||
# New format (musubi-tuner compatible): semicolon-separated
|
||
control_indices_list = [int(x.strip()) for x in control_index.split(';') if x.strip()]
|
||
else:
|
||
# Old format: comma-separated for backward compatibility
|
||
control_indices_list = [int(x.strip()) for x in control_index.split(',') if x.strip()]
|
||
|
||
if len(control_indices_list) == 0:
|
||
control_indices_list = [0, 10] # default
|
||
print(f"parsed control_index: {control_indices_list}")
|
||
|
||
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
|
||
# Process reference latents if provided
|
||
processed_reference_latents = []
|
||
if use_kisekaeichi and reference_latents is not None:
|
||
for i, ref_latent in enumerate(reference_latents):
|
||
processed_latent = ref_latent["samples"] * vae_scaling_factor
|
||
processed_reference_latents.append(processed_latent)
|
||
print(f"参照画像latent {i+1}: {processed_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
|
||
|
||
# Process reference image embeds if provided
|
||
processed_reference_embeds = []
|
||
if use_kisekaeichi and reference_image_embeds is not None:
|
||
for i, ref_embeds in enumerate(reference_image_embeds):
|
||
embed_state = ref_embeds["last_hidden_state"].to(device, base_dtype)
|
||
processed_reference_embeds.append(embed_state)
|
||
print(f"参照画像 {i+1} のCLIP embeddingを設定しました")
|
||
|
||
# Validate that latents and embeds have same count
|
||
if use_kisekaeichi:
|
||
if len(processed_reference_latents) != len(processed_reference_embeds):
|
||
raise ValueError(f"Reference latents count ({len(processed_reference_latents)}) must match embeds count ({len(processed_reference_embeds)})")
|
||
if len(processed_reference_latents) == 0:
|
||
raise ValueError("No reference latents provided for kisekaeichi mode")
|
||
|
||
# テキストエンベッディング処理
|
||
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 len(processed_reference_latents) > 0:
|
||
print("=== Kisekaeichi モード設定(完全版) ===")
|
||
|
||
# 一つ目のコードのone_frame_inference処理を完全再現
|
||
one_frame_inference = set()
|
||
one_frame_inference.add(f"target_index={target_index}")
|
||
# control_indexは後でcontrol_index_listから設定されるため、ここでは追加しない
|
||
|
||
# musubi-tuner仕様:latent_indicesを初期化
|
||
latent_indices = torch.zeros((1, 1), dtype=torch.int64) # 1x1 latent index for target image
|
||
latent_indices[:, 0] = latent_window_size # musubi-tunerのデフォルト値
|
||
|
||
# Apply target_index parameter (musubi-tuner仕様)
|
||
latent_indices[:, 0] = target_index
|
||
print(f"target_index設定 (musubi-tuner仕様): {target_index}")
|
||
|
||
# この部分は削除(後でまとめて処理するため)
|
||
|
||
# control_latentsのダミーを作成(一つ目のコードと同じ構造)
|
||
control_latents = torch.zeros(
|
||
size=(1, 16, 1 + 2 + 16, H, W), dtype=torch.float32, device="cpu"
|
||
)
|
||
|
||
# musubi-tuner仕様:control_latentsを直接結合(マスク適用は後で実行)
|
||
control_latents = []
|
||
|
||
# 入力画像をcontrol_latentsに追加
|
||
clean_latents_pre = start_latent.to(torch.float32).cpu()
|
||
if len(clean_latents_pre.shape) < 5:
|
||
clean_latents_pre = clean_latents_pre.unsqueeze(2)
|
||
control_latents.append(clean_latents_pre)
|
||
|
||
# 参照画像をcontrol_latentsに追加
|
||
for i, ref_latent in enumerate(processed_reference_latents):
|
||
clean_latent_post = ref_latent[:, :, 0:1, :, :].to(torch.float32).cpu()
|
||
control_latents.append(clean_latent_post)
|
||
|
||
# musubi-tuner仕様:ゼロlatentをclean_latents_postとして追加
|
||
control_latents.append(torch.zeros((1, 16, 1, H, W), dtype=torch.float32))
|
||
print(f"Add zero latents as clean latents post for one frame inference.")
|
||
|
||
# musubi-tuner仕様:control_latentsを直接結合
|
||
clean_latents = torch.cat(control_latents, dim=2) # (1, 16, num_control_images, H//8, W//8)
|
||
|
||
# musubi-tuner仕様:clean_latent_indicesを設定
|
||
clean_latent_indices = torch.zeros((1, len(control_latents)), dtype=torch.int64)
|
||
if "no_post" not in []: # one_frame_inferenceは空なので常にtrue
|
||
clean_latent_indices[:, -1] = 1 + latent_window_size # default index for clean latents post
|
||
|
||
# musubi-tuner仕様:control_indices_listをclean_latent_indicesに動的適用
|
||
if control_indices_list:
|
||
print(f"control_indices動的適用開始: {control_indices_list}")
|
||
i = 0
|
||
while i < len(control_indices_list) and i < clean_latent_indices.shape[1]:
|
||
control_index = int(control_indices_list[i])
|
||
clean_latent_indices[:, i] = control_index
|
||
print(f"clean_latent_indices[:, {i}] = {control_index}")
|
||
i += 1
|
||
print(f"最終的なclean_latent_indices: {clean_latent_indices}")
|
||
else:
|
||
print("control_indices_listが空のため、デフォルト設定を使用")
|
||
|
||
# musubi-tuner仕様:マスク適用(clean_latents生成後)
|
||
for i in range(len(control_latents)):
|
||
mask_image = None
|
||
|
||
# 入力画像のマスク適用
|
||
if i == 0 and input_mask is not None:
|
||
print("入力画像マスクを適用中(musubi-tuner仕様)...")
|
||
try:
|
||
height_latent, width_latent = clean_latents.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)
|
||
)
|
||
mask_image = input_mask_resized.to(clean_latents.device)[None, None, :, :]
|
||
print("入力画像マスクを適用しました")
|
||
except Exception as e:
|
||
print(f"入力マスク適用エラー: {e}")
|
||
|
||
# 参照画像のマスク適用
|
||
elif i > 0 and i < len(control_latents) - 1: # -1はゼロlatentなので除外
|
||
ref_index = i - 1 # 最初が入力画像なので-1
|
||
if reference_masks is not None and ref_index < len(reference_masks):
|
||
print(f"参照画像 {ref_index+1} マスクを適用中(musubi-tuner仕様)...")
|
||
try:
|
||
height_latent, width_latent = clean_latents.shape[-2:]
|
||
reference_mask = reference_masks[ref_index]
|
||
|
||
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)
|
||
)
|
||
mask_image = reference_mask_resized.to(clean_latents.device)[None, None, :, :]
|
||
print(f"参照画像 {ref_index+1} マスクを適用しました")
|
||
except Exception as e:
|
||
print(f"参照マスク {ref_index+1} 適用エラー: {e}")
|
||
|
||
# マスクを適用
|
||
if mask_image is not None:
|
||
clean_latents[:, :, i : i + 1, :, :] = clean_latents[:, :, i : i + 1, :, :] * mask_image
|
||
|
||
# Always disable 2x/4x processing for kisekaeichi mode
|
||
clean_latents_2x_param = None
|
||
clean_latent_2x_indices = None
|
||
clean_latents_2x = None
|
||
|
||
clean_latents_4x_param = None
|
||
clean_latent_4x_indices = None
|
||
clean_latents_4x = None
|
||
|
||
print("Kisekaeichi: 2x/4xインデックスを無効化しました")
|
||
|
||
# 画像エンベッディングの処理(musubi-tuner仕様:セクション毎の個別処理)
|
||
# musubi-tunerではセクション毎に個別のimage_encoder_last_hidden_stateを使用
|
||
# 複数リファレンス時は最初の参照画像のエンベッディングを使用(musubi-tuner互換)
|
||
if len(processed_reference_embeds) > 0:
|
||
# musubi-tuner互換:最初の参照画像エンベッディングを使用
|
||
image_encoder_last_hidden_state = processed_reference_embeds[0]
|
||
print(f"musubi-tuner互換:最初の参照画像エンベッディングを使用 (参照画像数: {len(processed_reference_embeds)})")
|
||
else:
|
||
image_encoder_last_hidden_state = start_image_encoder_last_hidden_state
|
||
|
||
print(f"Kisekaeichi設定完了(musubi-tuner完全互換):")
|
||
print(f" - clean_latents.shape: {clean_latents.shape} (入力+参照{len(processed_reference_latents)}個+ゼロlatent)")
|
||
print(f" - latent_indices: {latent_indices} (初期値: {latent_window_size} -> target: {target_index})")
|
||
print(f" - clean_latent_indices: {clean_latent_indices} (control_indices適用済み)")
|
||
print(f" - sample_num_frames: {sample_num_frames}")
|
||
print(f" - control_indices適用: {control_indices_list}")
|
||
print(f" - control_latents数: {len(control_latents)}")
|
||
print(f" - マスク適用: clean_latents生成後")
|
||
print(f" - 2x/4x無効化: True")
|
||
|
||
else:
|
||
# 通常モード(参照画像なし)
|
||
# control_indicesは通常モードでは使用しない
|
||
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()
|
||
|
||
# 出力処理
|
||
if use_kisekaeichi:
|
||
mode_info = f"Kisekaeichi(musubi-tuner完全互換、参照{len(processed_reference_latents)}個)"
|
||
else:
|
||
mode_info = "通常"
|
||
print(
|
||
f"=== 1フレーム生成完了 ({mode_info}モード): {generated_latents.shape} ==="
|
||
)
|
||
|
||
return ({"samples": generated_latents / vae_scaling_factor},)
|
||
|
||
|
||
class ReferenceLatentList:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {},
|
||
"optional": {
|
||
"reference_1": ("LATENT", {"tooltip": "Reference latent 1"}),
|
||
"reference_2": ("LATENT", {"tooltip": "Reference latent 2"}),
|
||
"reference_3": ("LATENT", {"tooltip": "Reference latent 3"}),
|
||
"reference_4": ("LATENT", {"tooltip": "Reference latent 4"}),
|
||
"reference_5": ("LATENT", {"tooltip": "Reference latent 5"}),
|
||
"reference_6": ("LATENT", {"tooltip": "Reference latent 6"}),
|
||
"reference_7": ("LATENT", {"tooltip": "Reference latent 7"}),
|
||
"reference_8": ("LATENT", {"tooltip": "Reference latent 8"}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("REFERENCE_LATENT_LIST",)
|
||
RETURN_NAMES = ("reference_latents",)
|
||
FUNCTION = "create_list"
|
||
CATEGORY = "FramePackWrapper/References"
|
||
DESCRIPTION = "Combine up to 8 reference latents into a list for multi-reference Kisekaeichi mode"
|
||
|
||
def create_list(self, **kwargs):
|
||
reference_list = []
|
||
for i in range(1, 9):
|
||
ref_key = f"reference_{i}"
|
||
if ref_key in kwargs and kwargs[ref_key] is not None:
|
||
reference_list.append(kwargs[ref_key])
|
||
|
||
if len(reference_list) == 0:
|
||
return (None,)
|
||
|
||
print(f"Created reference latent list with {len(reference_list)} items")
|
||
return (reference_list,)
|
||
|
||
|
||
class ReferenceEmbedsList:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {},
|
||
"optional": {
|
||
"embeds_1": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 1"}),
|
||
"embeds_2": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 2"}),
|
||
"embeds_3": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 3"}),
|
||
"embeds_4": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 4"}),
|
||
"embeds_5": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 5"}),
|
||
"embeds_6": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 6"}),
|
||
"embeds_7": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 7"}),
|
||
"embeds_8": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 8"}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("REFERENCE_EMBEDS_LIST",)
|
||
RETURN_NAMES = ("reference_embeds",)
|
||
FUNCTION = "create_list"
|
||
CATEGORY = "FramePackWrapper/References"
|
||
DESCRIPTION = "Combine up to 8 reference CLIP vision embeddings into a list for multi-reference Kisekaeichi mode"
|
||
|
||
def create_list(self, **kwargs):
|
||
embeds_list = []
|
||
for i in range(1, 9):
|
||
embeds_key = f"embeds_{i}"
|
||
if embeds_key in kwargs and kwargs[embeds_key] is not None:
|
||
embeds_list.append(kwargs[embeds_key])
|
||
|
||
if len(embeds_list) == 0:
|
||
return (None,)
|
||
|
||
print(f"Created reference embeds list with {len(embeds_list)} items")
|
||
return (embeds_list,)
|
||
|
||
|
||
class ReferenceMaskList:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {},
|
||
"optional": {
|
||
"mask_1": ("MASK", {"tooltip": "Reference mask 1"}),
|
||
"mask_2": ("MASK", {"tooltip": "Reference mask 2"}),
|
||
"mask_3": ("MASK", {"tooltip": "Reference mask 3"}),
|
||
"mask_4": ("MASK", {"tooltip": "Reference mask 4"}),
|
||
"mask_5": ("MASK", {"tooltip": "Reference mask 5"}),
|
||
"mask_6": ("MASK", {"tooltip": "Reference mask 6"}),
|
||
"mask_7": ("MASK", {"tooltip": "Reference mask 7"}),
|
||
"mask_8": ("MASK", {"tooltip": "Reference mask 8"}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("REFERENCE_MASK_LIST",)
|
||
RETURN_NAMES = ("reference_masks",)
|
||
FUNCTION = "create_list"
|
||
CATEGORY = "FramePackWrapper/References"
|
||
DESCRIPTION = "Combine up to 8 reference masks into a list for multi-reference Kisekaeichi mode (optional)"
|
||
|
||
def create_list(self, **kwargs):
|
||
mask_list = []
|
||
for i in range(1, 9):
|
||
mask_key = f"mask_{i}"
|
||
if mask_key in kwargs and kwargs[mask_key] is not None:
|
||
mask_list.append(kwargs[mask_key])
|
||
|
||
if len(mask_list) == 0:
|
||
return (None,)
|
||
|
||
print(f"Created reference mask list with {len(mask_list)} items")
|
||
return (mask_list,)
|
||
|
||
|
||
NODE_CLASS_MAPPINGS = {
|
||
"FramePackSingleFrameSampler": FramePackSingleFrameSampler,
|
||
"ReferenceLatentList": ReferenceLatentList,
|
||
"ReferenceEmbedsList": ReferenceEmbedsList,
|
||
"ReferenceMaskList": ReferenceMaskList,
|
||
}
|
||
|
||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||
"FramePackSingleFrameSampler": "FramePack Single Frame Sampler",
|
||
"ReferenceLatentList": "Reference Latent List",
|
||
"ReferenceEmbedsList": "Reference Embeds List",
|
||
"ReferenceMaskList": "Reference Mask List",
|
||
}
|