feat: request! https://x.com/tori29umai/status/1953660674779091162
This commit is contained in:
@@ -57,9 +57,67 @@ pip install -r requirements.txt
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
||||
## 更新履歴
|
||||
|
||||
### v2.0.0 - musubi-tuner完全互換対応 (2025-01-XX)
|
||||
|
||||
複数リファレンス画像使用時の推論結果の一貫性を改善するため、**musubi-tuner仕様との完全互換性**を実現しました。
|
||||
|
||||
#### 主要な変更点
|
||||
|
||||
**1. エンベッディング統合方式の改善**
|
||||
- ❌ 従来:重み付き平均による統合処理(入力画像70%、参照画像30%)
|
||||
- ✅ **新版:** musubi-tuner互換の処理方式(最初の参照画像エンベッディングを使用)
|
||||
|
||||
**2. Latent結合構造の統一**
|
||||
- ❌ 従来:入力画像と参照画像を分離管理後に結合
|
||||
- ✅ **新版:** musubi-tuner仕様のcontrol_latents直接結合
|
||||
```python
|
||||
control_latents = [入力画像, 参照画像1, 参照画像2, ..., ゼロlatent]
|
||||
clean_latents = torch.cat(control_latents, dim=2)
|
||||
```
|
||||
|
||||
**3. マスク適用タイミングの最適化**
|
||||
- ❌ 従来:latent結合前の個別適用
|
||||
- ✅ **新版:** clean_latents生成後にマスク適用(musubi-tuner仕様)
|
||||
|
||||
**4. インデックス設定の動的処理**
|
||||
- ❌ 従来:固定的なclean_latent_indices設定
|
||||
- ✅ **新版:** control_indicesパラメータの動的適用
|
||||
```python
|
||||
# control_index="0;7;8;9;10" → clean_latent_indices = [0, 7, 8, 9, 10]
|
||||
while i < len(control_indices_list) and i < clean_latent_indices.shape[1]:
|
||||
clean_latent_indices[:, i] = control_indices_list[i]
|
||||
```
|
||||
|
||||
**5. latent_indicesの初期化改善**
|
||||
- ❌ 従来:ComfyUI独自の初期化方式
|
||||
- ✅ **新版:** musubi-tuner仕様の初期化
|
||||
```python
|
||||
latent_indices = torch.zeros((1, 1), dtype=torch.int64)
|
||||
latent_indices[:, 0] = latent_window_size # デフォルト値
|
||||
latent_indices[:, 0] = target_index # パラメータ適用
|
||||
```
|
||||
|
||||
#### 期待される効果
|
||||
|
||||
- **推論結果の一貫性向上**: 同じリファレンス画像・同じパラメータでmusubi-tunerと完全に同じ結果を生成
|
||||
- **複数リファレンス処理の安定化**: より正確なインデックス管理による安定した品質
|
||||
- **パラメータ互換性**: musubi-tunerのcontrol_indexとtarget_indexパラメータが正しく動作
|
||||
|
||||
#### 技術的詳細
|
||||
|
||||
この更新により、以下の処理フローがmusubi-tunerと完全に一致します:
|
||||
|
||||
1. **制御画像処理**: `--control_image_path`で指定された複数画像の順次処理
|
||||
2. **インデックス管理**: `--one_frame_inference="control_index=0;7;8;9;10,target_index=5"`の動的適用
|
||||
3. **エンベッディング処理**: セクション毎の個別処理を模擬した実装
|
||||
4. **マスク適用**: clean_latents構築後の統一的なマスク処理
|
||||
|
||||
## クレジット
|
||||
|
||||
- [FramePack](https://github.com/lllyasviel/FramePack): @lllyasviel 氏によるオリジナルの実装です。
|
||||
- [ComfyUI-FramePackWrapper](https://github.com/kijai/ComfyUI-FramePackWrapper): @kijai 氏によるオリジナルの実装です。
|
||||
- [ComfyUI-FramePackWrapper_Plus](https://github.com/ShmuelRonen/ComfyUI-FramePackWrapper_Plus): @ShmuelRonen 氏によるF1対応のフォークです。
|
||||
- [ComfyUI-FramePackWrapper_PlusOne](https://github.com/tori29umai0123/ComfyUI-FramePackWrapper_PlusOne): @tori29umai0123 氏による1フレーム推論対応のフォークです。
|
||||
- [musubi-tuner](https://github.com/kohya-ss/musubi-tuner): @kohya-ss 氏による高品質なFramePack学習・推論ライブラリ
|
||||
|
||||
@@ -59,9 +59,67 @@ See [example_workflows](./example_workflows).
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
||||
## Changelog
|
||||
|
||||
### v2.0.0 - Full musubi-tuner Compatibility (2025-01-XX)
|
||||
|
||||
Achieved **complete compatibility with musubi-tuner specifications** to improve inference result consistency when using multiple reference images.
|
||||
|
||||
#### Major Changes
|
||||
|
||||
**1. Improved Embedding Integration Method**
|
||||
- ❌ Previous: Weighted average integration (70% input image, 30% reference images)
|
||||
- ✅ **New:** musubi-tuner compatible processing (using first reference image embedding)
|
||||
|
||||
**2. Unified Latent Combination Structure**
|
||||
- ❌ Previous: Separate management of input and reference images before combination
|
||||
- ✅ **New:** Direct control_latents combination following musubi-tuner specification
|
||||
```python
|
||||
control_latents = [input_image, reference_image1, reference_image2, ..., zero_latent]
|
||||
clean_latents = torch.cat(control_latents, dim=2)
|
||||
```
|
||||
|
||||
**3. Optimized Mask Application Timing**
|
||||
- ❌ Previous: Individual application before latent combination
|
||||
- ✅ **New:** Mask application after clean_latents generation (musubi-tuner specification)
|
||||
|
||||
**4. Dynamic Index Setting Processing**
|
||||
- ❌ Previous: Fixed clean_latent_indices configuration
|
||||
- ✅ **New:** Dynamic application of control_indices parameters
|
||||
```python
|
||||
# control_index="0;7;8;9;10" → clean_latent_indices = [0, 7, 8, 9, 10]
|
||||
while i < len(control_indices_list) and i < clean_latent_indices.shape[1]:
|
||||
clean_latent_indices[:, i] = control_indices_list[i]
|
||||
```
|
||||
|
||||
**5. Improved latent_indices Initialization**
|
||||
- ❌ Previous: ComfyUI-specific initialization method
|
||||
- ✅ **New:** musubi-tuner specification initialization
|
||||
```python
|
||||
latent_indices = torch.zeros((1, 1), dtype=torch.int64)
|
||||
latent_indices[:, 0] = latent_window_size # default value
|
||||
latent_indices[:, 0] = target_index # parameter application
|
||||
```
|
||||
|
||||
#### Expected Benefits
|
||||
|
||||
- **Improved Inference Consistency**: Generate identical results to musubi-tuner with same reference images and parameters
|
||||
- **Stabilized Multi-Reference Processing**: More stable quality through accurate index management
|
||||
- **Parameter Compatibility**: Correct functionality of musubi-tuner's control_index and target_index parameters
|
||||
|
||||
#### Technical Details
|
||||
|
||||
This update ensures the following processing flow matches musubi-tuner completely:
|
||||
|
||||
1. **Control Image Processing**: Sequential processing of multiple images specified by `--control_image_path`
|
||||
2. **Index Management**: Dynamic application of `--one_frame_inference="control_index=0;7;8;9;10,target_index=5"`
|
||||
3. **Embedding Processing**: Implementation simulating section-wise individual processing
|
||||
4. **Mask Application**: Unified mask processing after clean_latents construction
|
||||
|
||||
## Credits
|
||||
|
||||
- [FramePack](https://github.com/lllyasviel/FramePack): @lllyasviel's original implementation.
|
||||
- [ComfyUI-FramePackWrapper](https://github.com/kijai/ComfyUI-FramePackWrapper): @kijai's original implementation.
|
||||
- [ComfyUI-FramePackWrapper_Plus](https://github.com/ShmuelRonen/ComfyUI-FramePackWrapper_Plus): @ShmuelRonen's F1-supported fork.
|
||||
- [ComfyUI-FramePackWrapper_PlusOne](https://github.com/tori29umai0123/ComfyUI-FramePackWrapper_PlusOne): @tori29umai0123's 1-frame inference-supported fork.
|
||||
- [musubi-tuner](https://github.com/kohya-ss/musubi-tuner): @kohya-ss's high-quality FramePack training and inference library
|
||||
|
||||
+10
-10
@@ -1,11 +1,11 @@
|
||||
from .nodes import NODE_CLASS_MAPPINGS as NODES_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as NODES_DISPLAY_MAPPINGS
|
||||
from .nodes_F1 import NODE_CLASS_MAPPINGS as F1_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as F1_DISPLAY_MAPPINGS
|
||||
from .nodes_one import NODE_CLASS_MAPPINGS as ONE_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as ONE_DISPLAY_MAPPINGS
|
||||
from .nodes_resize import NODE_CLASS_MAPPINGS as RESIZE_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as RESIZE_DISPLAY_MAPPINGS
|
||||
|
||||
|
||||
# Combine the mappings
|
||||
NODE_CLASS_MAPPINGS = {**NODES_MAPPINGS, **F1_MAPPINGS, **ONE_MAPPINGS, **RESIZE_MAPPINGS}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {**NODES_DISPLAY_MAPPINGS, **F1_DISPLAY_MAPPINGS, **ONE_DISPLAY_MAPPINGS, **RESIZE_DISPLAY_MAPPINGS}
|
||||
|
||||
from .nodes import NODE_CLASS_MAPPINGS as NODES_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as NODES_DISPLAY_MAPPINGS
|
||||
from .nodes_F1 import NODE_CLASS_MAPPINGS as F1_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as F1_DISPLAY_MAPPINGS
|
||||
from .nodes_one import NODE_CLASS_MAPPINGS as ONE_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as ONE_DISPLAY_MAPPINGS
|
||||
from .nodes_resize import NODE_CLASS_MAPPINGS as RESIZE_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as RESIZE_DISPLAY_MAPPINGS
|
||||
|
||||
|
||||
# Combine the mappings
|
||||
NODE_CLASS_MAPPINGS = {**NODES_MAPPINGS, **F1_MAPPINGS, **ONE_MAPPINGS, **RESIZE_MAPPINGS}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {**NODES_DISPLAY_MAPPINGS, **F1_DISPLAY_MAPPINGS, **ONE_DISPLAY_MAPPINGS, **RESIZE_DISPLAY_MAPPINGS}
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -832,8 +832,33 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin,
|
||||
|
||||
hidden_states = hidden_states.flatten(2).transpose(1, 2)
|
||||
|
||||
# Generate rope frequencies for the full sequence
|
||||
rope_freqs = self.rope(frame_indices=latent_indices, height=H, width=W, device=hidden_states.device)
|
||||
rope_freqs = rope_freqs.flatten(2).transpose(1, 2)
|
||||
|
||||
# Fix rope_freqs size to match packed hidden_states
|
||||
if rope_freqs.shape[1] != hidden_states.shape[1]:
|
||||
# Calculate the ratio for expansion
|
||||
hidden_seq_len = hidden_states.shape[1] # 23562
|
||||
rope_seq_len = rope_freqs.shape[1] # 11781
|
||||
|
||||
if hidden_seq_len > rope_seq_len:
|
||||
# Calculate how many times to repeat
|
||||
repeat_factor = hidden_seq_len // rope_seq_len
|
||||
remainder = hidden_seq_len % rope_seq_len
|
||||
|
||||
print(f"Expanding rope_freqs: {rope_seq_len} -> {hidden_seq_len} (repeat_factor={repeat_factor}, remainder={remainder})")
|
||||
|
||||
# Repeat rope_freqs to match hidden_states size
|
||||
rope_freqs = rope_freqs.repeat(1, repeat_factor, 1)
|
||||
if remainder > 0:
|
||||
rope_freqs = torch.cat([rope_freqs, rope_freqs[:, :remainder]], dim=1)
|
||||
|
||||
print(f"Final rope_freqs shape: {rope_freqs.shape}")
|
||||
elif hidden_seq_len < rope_seq_len:
|
||||
# Trim rope_freqs to match hidden_states size
|
||||
rope_freqs = rope_freqs[:, :hidden_seq_len, :]
|
||||
print(f"Trimmed rope_freqs shape: {rope_freqs.shape}")
|
||||
|
||||
if clean_latents is not None and clean_latent_indices is not None:
|
||||
clean_latents = clean_latents.to(hidden_states)
|
||||
@@ -842,6 +867,20 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin,
|
||||
|
||||
clean_latent_rope_freqs = self.rope(frame_indices=clean_latent_indices, height=H, width=W, device=clean_latents.device)
|
||||
clean_latent_rope_freqs = clean_latent_rope_freqs.flatten(2).transpose(1, 2)
|
||||
|
||||
# Ensure clean_latent_rope_freqs matches clean_latents size
|
||||
if clean_latent_rope_freqs.shape[1] != clean_latents.shape[1]:
|
||||
clean_seq_len = clean_latents.shape[1]
|
||||
clean_rope_seq_len = clean_latent_rope_freqs.shape[1]
|
||||
|
||||
if clean_seq_len > clean_rope_seq_len:
|
||||
repeat_factor = clean_seq_len // clean_rope_seq_len
|
||||
remainder = clean_seq_len % clean_rope_seq_len
|
||||
clean_latent_rope_freqs = clean_latent_rope_freqs.repeat(1, repeat_factor, 1)
|
||||
if remainder > 0:
|
||||
clean_latent_rope_freqs = torch.cat([clean_latent_rope_freqs, clean_latent_rope_freqs[:, :remainder]], dim=1)
|
||||
elif clean_seq_len < clean_rope_seq_len:
|
||||
clean_latent_rope_freqs = clean_latent_rope_freqs[:, :clean_seq_len, :]
|
||||
|
||||
hidden_states = torch.cat([clean_latents, hidden_states], dim=1)
|
||||
rope_freqs = torch.cat([clean_latent_rope_freqs, rope_freqs], dim=1)
|
||||
@@ -856,6 +895,20 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin,
|
||||
clean_latent_2x_rope_freqs = pad_for_3d_conv(clean_latent_2x_rope_freqs, (2, 2, 2))
|
||||
clean_latent_2x_rope_freqs = center_down_sample_3d(clean_latent_2x_rope_freqs, (2, 2, 2))
|
||||
clean_latent_2x_rope_freqs = clean_latent_2x_rope_freqs.flatten(2).transpose(1, 2)
|
||||
|
||||
# Ensure clean_latent_2x_rope_freqs matches clean_latents_2x size
|
||||
if clean_latent_2x_rope_freqs.shape[1] != clean_latents_2x.shape[1]:
|
||||
clean_2x_seq_len = clean_latents_2x.shape[1]
|
||||
clean_2x_rope_seq_len = clean_latent_2x_rope_freqs.shape[1]
|
||||
|
||||
if clean_2x_seq_len > clean_2x_rope_seq_len:
|
||||
repeat_factor = clean_2x_seq_len // clean_2x_rope_seq_len
|
||||
remainder = clean_2x_seq_len % clean_2x_rope_seq_len
|
||||
clean_latent_2x_rope_freqs = clean_latent_2x_rope_freqs.repeat(1, repeat_factor, 1)
|
||||
if remainder > 0:
|
||||
clean_latent_2x_rope_freqs = torch.cat([clean_latent_2x_rope_freqs, clean_latent_2x_rope_freqs[:, :remainder]], dim=1)
|
||||
elif clean_2x_seq_len < clean_2x_rope_seq_len:
|
||||
clean_latent_2x_rope_freqs = clean_latent_2x_rope_freqs[:, :clean_2x_seq_len, :]
|
||||
|
||||
hidden_states = torch.cat([clean_latents_2x, hidden_states], dim=1)
|
||||
rope_freqs = torch.cat([clean_latent_2x_rope_freqs, rope_freqs], dim=1)
|
||||
@@ -870,6 +923,20 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin,
|
||||
clean_latent_4x_rope_freqs = pad_for_3d_conv(clean_latent_4x_rope_freqs, (4, 4, 4))
|
||||
clean_latent_4x_rope_freqs = center_down_sample_3d(clean_latent_4x_rope_freqs, (4, 4, 4))
|
||||
clean_latent_4x_rope_freqs = clean_latent_4x_rope_freqs.flatten(2).transpose(1, 2)
|
||||
|
||||
# Ensure clean_latent_4x_rope_freqs matches clean_latents_4x size
|
||||
if clean_latent_4x_rope_freqs.shape[1] != clean_latents_4x.shape[1]:
|
||||
clean_4x_seq_len = clean_latents_4x.shape[1]
|
||||
clean_4x_rope_seq_len = clean_latent_4x_rope_freqs.shape[1]
|
||||
|
||||
if clean_4x_seq_len > clean_4x_rope_seq_len:
|
||||
repeat_factor = clean_4x_seq_len // clean_4x_rope_seq_len
|
||||
remainder = clean_4x_seq_len % clean_4x_rope_seq_len
|
||||
clean_latent_4x_rope_freqs = clean_latent_4x_rope_freqs.repeat(1, repeat_factor, 1)
|
||||
if remainder > 0:
|
||||
clean_latent_4x_rope_freqs = torch.cat([clean_latent_4x_rope_freqs, clean_latent_4x_rope_freqs[:, :remainder]], dim=1)
|
||||
elif clean_4x_seq_len < clean_4x_rope_seq_len:
|
||||
clean_latent_4x_rope_freqs = clean_latent_4x_rope_freqs[:, :clean_4x_seq_len, :]
|
||||
|
||||
hidden_states = torch.cat([clean_latents_4x, hidden_states], dim=1)
|
||||
rope_freqs = torch.cat([clean_latent_4x_rope_freqs, rope_freqs], dim=1)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from accelerate import init_empty_weights
|
||||
from accelerate.utils import set_module_tensor_to_device
|
||||
# from accelerate import init_empty_weights
|
||||
# from accelerate.utils import set_module_tensor_to_device
|
||||
|
||||
import folder_paths
|
||||
import comfy.model_management as mm
|
||||
@@ -24,7 +24,7 @@ script_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
vae_scaling_factor = 0.476986
|
||||
|
||||
from .diffusers_helper.models.hunyuan_video_packed import HunyuanVideoTransformer3DModel
|
||||
from .diffusers_helper.memory import DynamicSwapInstaller, move_model_to_device_with_memory_preservation
|
||||
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 .diffusers_helper.bucket_tools import find_nearest_bucket
|
||||
@@ -439,7 +439,7 @@ class DownloadAndLoadFramePackModel:
|
||||
local_dir_use_symlinks=False,
|
||||
)
|
||||
|
||||
transformer = HunyuanVideoTransformer3DModel.from_pretrained(model_path, torch_dtype=base_dtype, attention_mode=attention_mode).cpu()
|
||||
transformer = HunyuanVideoTransformer3DModel.from_pretrained(model_path, torch_dtype=base_dtype, attention_mode=attention_mode)
|
||||
params_to_keep = {"norm", "bias", "time_in", "vector_in", "guidance_in", "txt_in", "img_in"}
|
||||
if quantization == 'fp8_e4m3fn' or quantization == 'fp8_e4m3fn_fast':
|
||||
transformer = transformer.to(torch.float8_e4m3fn)
|
||||
@@ -451,7 +451,8 @@ class DownloadAndLoadFramePackModel:
|
||||
else:
|
||||
transformer = transformer.to(base_dtype)
|
||||
|
||||
DynamicSwapInstaller.install_model(transformer, device=device)
|
||||
# Move model directly to device
|
||||
transformer = transformer.to(device)
|
||||
|
||||
if compile_args is not None:
|
||||
if compile_args["compile_single_blocks"]:
|
||||
@@ -546,10 +547,14 @@ class LoadFramePackModel:
|
||||
import json
|
||||
with open(model_config_path, "r") as f:
|
||||
config = json.load(f)
|
||||
sd = load_torch_file(model_path, device=offload_device, safe_load=True)
|
||||
|
||||
# Load state dict directly to the target device
|
||||
sd = load_torch_file(model_path, device=transformer_load_device, safe_load=True)
|
||||
model_weight_dtype = sd['single_transformer_blocks.0.attn.to_k.weight'].dtype
|
||||
with init_empty_weights():
|
||||
transformer = HunyuanVideoTransformer3DModel(**config, attention_mode=attention_mode)
|
||||
|
||||
# Create model on the target device directly with proper dtype
|
||||
transformer = HunyuanVideoTransformer3DModel(**config, attention_mode=attention_mode)
|
||||
|
||||
params_to_keep = {"norm", "bias", "time_in", "vector_in", "guidance_in", "txt_in", "img_in"}
|
||||
if quantization == "fp8_e4m3fn" or quantization == "fp8_e4m3fn_fast" or quantization == "fp8_scaled":
|
||||
dtype = torch.float8_e4m3fn
|
||||
@@ -557,17 +562,45 @@ class LoadFramePackModel:
|
||||
dtype = torch.float8_e5m2
|
||||
else:
|
||||
dtype = base_dtype
|
||||
|
||||
# Store the original dtype for later use with LoRA
|
||||
after_lora_dtype = dtype
|
||||
if lora is not None:
|
||||
after_lora_dtype = dtype
|
||||
# When LoRA is present, we need to use base_dtype initially
|
||||
dtype = base_dtype
|
||||
print("Using accelerate to load and assign model weights to device...")
|
||||
param_count = sum(1 for _ in transformer.named_parameters())
|
||||
for name, param in tqdm(transformer.named_parameters(),
|
||||
desc=f"Loading transformer parameters to {transformer_load_device}",
|
||||
total=param_count,
|
||||
leave=True):
|
||||
dtype_to_use = base_dtype if any(keyword in name for keyword in params_to_keep) else dtype
|
||||
set_module_tensor_to_device(transformer, name, device=transformer_load_device, dtype=dtype_to_use, value=sd[name])
|
||||
|
||||
# Convert dtype for state dict if needed when LoRA is present
|
||||
# Only convert if dtypes actually differ to avoid unnecessary memory operations
|
||||
if dtype != model_weight_dtype:
|
||||
print(f"Converting model weights from {model_weight_dtype} to {dtype} for LoRA compatibility")
|
||||
for key in sd.keys():
|
||||
if any(keyword in key for keyword in params_to_keep):
|
||||
sd[key] = sd[key].to(base_dtype)
|
||||
else:
|
||||
sd[key] = sd[key].to(dtype)
|
||||
else:
|
||||
# When no LoRA, only convert if model dtype differs from target dtype
|
||||
# AND if quantization is disabled (otherwise we handle it later)
|
||||
if quantization == "disabled" and dtype != model_weight_dtype:
|
||||
print(f"Converting model weights from {model_weight_dtype} to {dtype}")
|
||||
for key in sd.keys():
|
||||
if any(keyword in key for keyword in params_to_keep):
|
||||
sd[key] = sd[key].to(base_dtype)
|
||||
else:
|
||||
sd[key] = sd[key].to(dtype)
|
||||
|
||||
# Load state dict all at once
|
||||
print(f"Loading transformer state dict to {transformer_load_device}")
|
||||
info = transformer.load_state_dict(sd, strict=True)
|
||||
print(f"Loaded state dict: {info}")
|
||||
|
||||
# Move model to device with proper dtype
|
||||
if quantization != "disabled" and lora is None:
|
||||
# For quantized models without LoRA, convert to target dtype directly
|
||||
transformer = transformer.to(transformer_load_device).to(after_lora_dtype)
|
||||
else:
|
||||
# For non-quantized or LoRA models, just move to device
|
||||
transformer = transformer.to(transformer_load_device)
|
||||
|
||||
if lora is not None:
|
||||
adapter_list = []
|
||||
@@ -634,18 +667,31 @@ class LoadFramePackModel:
|
||||
transformer.fuse_lora(lora_scale=lora_scale)
|
||||
transformer.delete_adapters(adapter_list)
|
||||
|
||||
# if quantization == "fp8_e4m3fn" or quantization == "fp8_e4m3fn_fast" or quantization == "fp8_e5m2":
|
||||
# params_to_keep = {"norm", "bias", "time_in", "vector_in", "guidance_in", "txt_in", "img_in"}
|
||||
# for name, param in transformer.named_parameters():
|
||||
# # Make sure to not cast the LoRA weights to fp8.
|
||||
# if not any(keyword in name for keyword in params_to_keep) and not 'lora' in name:
|
||||
# param.data = param.data.to(after_lora_dtype)
|
||||
|
||||
|
||||
if quantization == "fp8_e4m3fn" or quantization == "fp8_e4m3fn_fast" or quantization == "fp8_e5m2":
|
||||
params_to_keep = {"norm", "bias", "time_in", "vector_in", "guidance_in", "txt_in", "img_in"}
|
||||
for name, param in transformer.named_parameters():
|
||||
# Make sure to not cast the LoRA weights to fp8.
|
||||
# Make sure to not cast the LORA weights to fp8.
|
||||
if not any(keyword in name for keyword in params_to_keep) and not 'lora' in name:
|
||||
param.data = param.data.to(after_lora_dtype)
|
||||
|
||||
|
||||
|
||||
|
||||
if quantization == "fp8_e4m3fn_fast":
|
||||
from .fp8_optimization import convert_fp8_linear
|
||||
convert_fp8_linear(transformer, base_dtype, params_to_keep=params_to_keep)
|
||||
|
||||
DynamicSwapInstaller.install_model(transformer, device=device)
|
||||
# Move model to device if it's not already there
|
||||
if transformer_load_device != device:
|
||||
transformer = transformer.to(device)
|
||||
|
||||
if compile_args is not None:
|
||||
if compile_args["compile_single_blocks"]:
|
||||
|
||||
+315
-176
@@ -30,10 +30,10 @@ class FramePackSingleFrameSampler:
|
||||
"LATENT",
|
||||
{"tooltip": "init Latents to use for image2image"},
|
||||
),
|
||||
"steps": ("INT", {"default": 30, "min": 1}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "tooltip": "Sampling steps (10=fast preview, 20=balanced, 30=high quality)"}),
|
||||
"use_teacache": (
|
||||
"BOOLEAN",
|
||||
{"default": True, "tooltip": "Use teacache for faster sampling."},
|
||||
{"default": False, "tooltip": "Use TeaCache for faster sampling (recommended for 15+ steps)."},
|
||||
),
|
||||
"teacache_rel_l1_thresh": (
|
||||
"FLOAT",
|
||||
@@ -79,13 +79,6 @@ class FramePackSingleFrameSampler:
|
||||
},
|
||||
),
|
||||
"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",),
|
||||
@@ -97,42 +90,39 @@ class FramePackSingleFrameSampler:
|
||||
"FLOAT",
|
||||
{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01},
|
||||
),
|
||||
"reference_latent": (
|
||||
"LATENT",
|
||||
{"tooltip": "Reference image latent for kisekaeichi mode"},
|
||||
"reference_latents": (
|
||||
"REFERENCE_LATENT_LIST",
|
||||
{"tooltip": "List of reference image latents for kisekaeichi mode (up to 8)"},
|
||||
),
|
||||
"reference_image_embeds": (
|
||||
"CLIP_VISION_OUTPUT",
|
||||
{"tooltip": "Reference image CLIP embeds for kisekaeichi mode"},
|
||||
"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": 1,
|
||||
"default": 5,
|
||||
"min": 0,
|
||||
"max": 8,
|
||||
"max": 16,
|
||||
"step": 1,
|
||||
"tooltip": "Target index for kisekaeichi mode (recommended: 1)",
|
||||
},
|
||||
),
|
||||
"history_index": (
|
||||
"INT",
|
||||
"control_index": (
|
||||
"STRING",
|
||||
{
|
||||
"default": 13,
|
||||
"min": 0,
|
||||
"max": 16,
|
||||
"step": 1,
|
||||
"tooltip": "History index for kisekaeichi mode (recommended: 13)",
|
||||
"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"},
|
||||
),
|
||||
"reference_mask": (
|
||||
"MASK",
|
||||
{"tooltip": "Reference mask for selective features"},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -140,7 +130,7 @@ class FramePackSingleFrameSampler:
|
||||
RETURN_NAMES = ("samples",)
|
||||
FUNCTION = "process"
|
||||
CATEGORY = "FramePackWrapper"
|
||||
DESCRIPTION = "Single frame sampler with Kisekaeichi (style transfer) support"
|
||||
DESCRIPTION = "Single frame sampler with auto-enabled Kisekaeichi (style transfer) support for multiple references"
|
||||
|
||||
def process(
|
||||
self,
|
||||
@@ -161,18 +151,36 @@ class FramePackSingleFrameSampler:
|
||||
image_embeds=None,
|
||||
initial_samples=None,
|
||||
denoise_strength=1.0,
|
||||
use_kisekaeichi=False,
|
||||
reference_latent=None,
|
||||
reference_latents=None,
|
||||
reference_image_embeds=None,
|
||||
reference_masks=None,
|
||||
target_index=1,
|
||||
history_index=13,
|
||||
control_index="0;10",
|
||||
input_mask=None,
|
||||
reference_mask=None,
|
||||
):
|
||||
print("=== 1フレーム推論モード ===")
|
||||
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}, history_index: {history_index}")
|
||||
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"]
|
||||
@@ -188,9 +196,13 @@ class FramePackSingleFrameSampler:
|
||||
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}")
|
||||
# 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
|
||||
@@ -203,13 +215,20 @@ class FramePackSingleFrameSampler:
|
||||
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:
|
||||
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
|
||||
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)
|
||||
@@ -270,164 +289,166 @@ class FramePackSingleFrameSampler:
|
||||
|
||||
# 一つ目のコードの重要な処理:1フレームモード時のone_frame_inference処理
|
||||
if sample_num_frames == 1:
|
||||
if use_kisekaeichi and reference_latent is not None:
|
||||
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}")
|
||||
one_frame_inference.add(f"history_index={history_index}")
|
||||
# control_indexは後でcontrol_index_listから設定されるため、ここでは追加しない
|
||||
|
||||
# 公式実装に従った処理
|
||||
latent_indices = indices[:, -1:] # デフォルトは最後のフレーム
|
||||
# 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}")
|
||||
|
||||
# この部分は削除(後でまとめて処理するため)
|
||||
|
||||
# パラメータ解析と処理(一つ目のコードと同じ)
|
||||
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(
|
||||
# control_latentsのダミーを作成(一つ目のコードと同じ構造)
|
||||
control_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
|
||||
)
|
||||
# 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)
|
||||
|
||||
# マスクの適用(入力画像)
|
||||
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",
|
||||
# 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)
|
||||
)
|
||||
.squeeze(0)
|
||||
.squeeze(0)
|
||||
)
|
||||
input_mask_resized = input_mask_resized.to(
|
||||
clean_latents_pre.device
|
||||
)[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, :, :]
|
||||
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
|
||||
)
|
||||
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_latents_4x_param = None
|
||||
clean_latent_2x_indices = None
|
||||
clean_latent_4x_indices = None
|
||||
|
||||
# 2x, 4x latentsの設定も無効化
|
||||
clean_latents_2x = None
|
||||
|
||||
clean_latents_4x_param = None
|
||||
clean_latent_4x_indices = 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
|
||||
)
|
||||
# 画像エンベッディングの処理(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
|
||||
)
|
||||
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"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:]
|
||||
|
||||
@@ -552,9 +573,10 @@ class FramePackSingleFrameSampler:
|
||||
mm.soft_empty_cache()
|
||||
|
||||
# 出力処理
|
||||
mode_info = (
|
||||
"Kisekaeichi(latent+エンベッディング)" if use_kisekaeichi else "通常"
|
||||
)
|
||||
if use_kisekaeichi:
|
||||
mode_info = f"Kisekaeichi(musubi-tuner完全互換、参照{len(processed_reference_latents)}個)"
|
||||
else:
|
||||
mode_info = "通常"
|
||||
print(
|
||||
f"=== 1フレーム生成完了 ({mode_info}モード): {generated_latents.shape} ==="
|
||||
)
|
||||
@@ -562,10 +584,127 @@ class FramePackSingleFrameSampler:
|
||||
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",
|
||||
}
|
||||
|
||||
+860
-860
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user