From 3868a9184baf82ab70bce15f69a17258084edfe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Sat, 7 Jun 2025 00:36:08 +0300 Subject: [PATCH 01/85] Fix incorrectly reported lycoris load error --- modules/lora/lora_load.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/lora/lora_load.py b/modules/lora/lora_load.py index f02e4a274..dbeb57630 100644 --- a/modules/lora/lora_load.py +++ b/modules/lora/lora_load.py @@ -148,8 +148,9 @@ def load_safetensors(name, network_on_disk) -> Union[network.Network, None]: if net_module is not None: network_types.append(nettype.__class__.__name__) break - module_errors += 1 if net_module is None: + module_errors += 1 + if l.debug: shared.log.error(f'LoRA unhandled: name={name} key={key} weights={weights.w.keys()}') else: From 26800a1ef90d7071d214917b0b05b1c7514ff9f7 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 17 Jun 2025 02:05:13 +0300 Subject: [PATCH 02/85] Cleanup sdnq --- modules/sdnq/__init__.py | 20 +++++++++++++++--- modules/sdnq/dequantizer.py | 36 +++++++++++++++---------------- modules/sdnq/forward.py | 8 +++---- modules/sdnq/packed_int.py | 42 +++++++++++++------------------------ 4 files changed, 54 insertions(+), 52 deletions(-) diff --git a/modules/sdnq/__init__.py b/modules/sdnq/__init__.py index f5b7179a7..d9db525db 100644 --- a/modules/sdnq/__init__.py +++ b/modules/sdnq/__init__.py @@ -200,7 +200,7 @@ def get_scale_symmetric(weight: torch.FloatTensor, reduction_axes: Union[int, Li return scale -def quantize_weight(weight: torch.FloatTensor, reduction_axes: Union[int, List[int]], weights_dtype: str): +def quantize_weight(weight: torch.FloatTensor, reduction_axes: Union[int, List[int]], weights_dtype: str) -> Tuple[torch.Tensor, torch.FloatTensor, torch.FloatTensor]: if dtype_dict[weights_dtype]["is_unsigned"]: scale, zero_point = get_scale_asymmetric(weight, reduction_axes, weights_dtype) quantized_weight = torch.sub(weight, zero_point).div_(scale) @@ -229,7 +229,7 @@ class SDNQQuantizer(DiffusersQuantizer): required_packages = None torch_dtype = None - def __init__(self, quantization_config, **kwargs): # pylint: disable=useless-parent-delegation + def __init__(self, quantization_config, **kwargs): super().__init__(quantization_config, **kwargs) self.modules_to_not_convert = [] @@ -381,7 +381,21 @@ class SDNQConfig(QuantizationConfigMixin): weights_dtype (`str`, *optional*, defaults to `"int8"`): The target dtype for the weights after quantization. Supported values are: ("int8", "int7", "int6", "int5", "int4", "int3", "int2", "uint8", "uint7", "uint6", "uint5", "uint4", "uint3", "uint2", "uint1", "bool", "float8_e4m3fn", "float8_e4m3fnuz", "float8_e5m2", "float8_e5m2fnuz") - modules_to_not_convert (`list`, *optional*, default to `None`): + weights_dtype (`int`, *optional*, defaults to `0`): + Used to decide how many elements of a tensor will share the same quantization group. + quant_conv (`bool`, *optional*, defaults to `False`): + Enabling this option will quantize the convolutional layers in UNet models too. + use_quantized_matmul (`bool`, *optional*, defaults to `False`): + Enabling this option will use quantized INT8 or FP8 MatMul instead of BF16 / FP16. + use_quantized_matmul_conv (`bool`, *optional*, defaults to `False`): + Same as use_quantized_matmul_conv but for the convolutional layers with UNets like SDXL. + dequantize_fp32 (`bool`, *optional*, defaults to `False`): + Enabling this option will use FP32 on the dequantization step. + quantization_device (`torch.device`, *optional*, defaults to `None`): + Used to set which device will be used for the quantization calculation on model load. + return_device (`torch.device`, *optional*, defaults to `None`): + Used to set which device will the quantized weights be sent back to. + modules_to_not_convert (`list`, *optional*, default to `None`): The list of modules to not quantize, useful for quantizing models that explicitly require to have some modules left in their original precision (e.g. Whisper encoder, Llava encoder, Mixtral gate layers). """ diff --git a/modules/sdnq/dequantizer.py b/modules/sdnq/dequantizer.py index be59019a3..114692f75 100644 --- a/modules/sdnq/dequantizer.py +++ b/modules/sdnq/dequantizer.py @@ -7,43 +7,43 @@ from .common import dtype_dict from .packed_int import pack_int_symetric, unpack_int_symetric, packed_int_function_dict -def dequantize_asymmetric(input: torch.ByteTensor, scale: torch.FloatTensor, zero_point: torch.FloatTensor, dtype: torch.dtype, result_shape: torch.Size) -> torch.FloatTensor: - result = torch.addcmul(zero_point, input.to(dtype=scale.dtype), scale).to(dtype=dtype) +def dequantize_asymmetric(weight: torch.ByteTensor, scale: torch.FloatTensor, zero_point: torch.FloatTensor, dtype: torch.dtype, result_shape: torch.Size) -> torch.FloatTensor: + result = torch.addcmul(zero_point, weight.to(dtype=scale.dtype), scale).to(dtype=dtype) if result_shape is not None: result = result.reshape(result_shape) return result -def dequantize_symmetric(input: torch.CharTensor, scale: torch.FloatTensor, dtype: torch.dtype, result_shape: torch.Size, skip_quantized_matmul: bool = False) -> torch.FloatTensor: +def dequantize_symmetric(weight: torch.CharTensor, scale: torch.FloatTensor, dtype: torch.dtype, result_shape: torch.Size, skip_quantized_matmul: bool = False) -> torch.FloatTensor: if skip_quantized_matmul: - result = input.transpose(0,1).to(dtype=scale.dtype).mul_(scale.transpose(0,1)).to(dtype=dtype) + result = weight.transpose(0,1).to(dtype=scale.dtype).mul_(scale.transpose(0,1)).to(dtype=dtype) else: - result = input.to(dtype=scale.dtype).mul_(scale).to(dtype=dtype) + result = weight.to(dtype=scale.dtype).mul_(scale).to(dtype=dtype) if result_shape is not None: result = result.reshape(result_shape) return result -def dequantize_symmetric_with_bias(input: torch.CharTensor, bias: torch.FloatTensor, scale: torch.FloatTensor, dtype: torch.dtype, result_shape: torch.Size) -> torch.FloatTensor: - return torch.addcmul(bias, input.to(dtype=scale.dtype), scale).to(dtype=dtype).reshape(result_shape) +def dequantize_symmetric_with_bias(weight: torch.CharTensor, scale: torch.FloatTensor, bias: torch.FloatTensor, dtype: torch.dtype, result_shape: torch.Size) -> torch.FloatTensor: + return torch.addcmul(bias, weight.to(dtype=scale.dtype), scale).to(dtype=dtype).reshape(result_shape) -def dequantize_packed_int_asymmetric(input: torch.ByteTensor, scale: torch.FloatTensor, zero_point: torch.FloatTensor, shape: torch.Size, dtype: torch.dtype, result_shape: torch.Size, weights_dtype: str) -> torch.FloatTensor: - return dequantize_asymmetric(packed_int_function_dict[weights_dtype]["unpack"](input, shape), scale, zero_point, dtype, result_shape) +def dequantize_packed_int_asymmetric(weight: torch.ByteTensor, scale: torch.FloatTensor, zero_point: torch.FloatTensor, shape: torch.Size, dtype: torch.dtype, result_shape: torch.Size, weights_dtype: str) -> torch.FloatTensor: + return dequantize_asymmetric(packed_int_function_dict[weights_dtype]["unpack"](weight, shape), scale, zero_point, dtype, result_shape) -def dequantize_packed_int_symmetric(input: torch.ByteTensor, scale: torch.FloatTensor, shape: torch.Size, dtype: torch.dtype, result_shape: torch.Size, weights_dtype: str, skip_quantized_matmul: bool = False) -> torch.FloatTensor: +def dequantize_packed_int_symmetric(weight: torch.ByteTensor, scale: torch.FloatTensor, shape: torch.Size, dtype: torch.dtype, result_shape: torch.Size, weights_dtype: str, skip_quantized_matmul: bool = False) -> torch.FloatTensor: if skip_quantized_matmul: - return dequantize_symmetric(unpack_int_symetric(input, shape, weights_dtype, dtype=scale.dtype), scale.transpose(0,1), dtype, result_shape) + return dequantize_symmetric(unpack_int_symetric(weight, shape, weights_dtype, dtype=scale.dtype), scale.transpose(0,1), dtype, result_shape) else: - return dequantize_symmetric(unpack_int_symetric(input, shape, weights_dtype, dtype=scale.dtype), scale, dtype, result_shape) + return dequantize_symmetric(unpack_int_symetric(weight, shape, weights_dtype, dtype=scale.dtype), scale, dtype, result_shape) class AsymmetricWeightsDequantizer(torch.nn.Module): def __init__( self, - scale: torch.Tensor, - zero_point: torch.Tensor, + scale: torch.FloatTensor, + zero_point: torch.FloatTensor, result_dtype: torch.dtype, result_shape: torch.Size, weights_dtype: str, @@ -67,7 +67,7 @@ class AsymmetricWeightsDequantizer(torch.nn.Module): class SymmetricWeightsDequantizer(torch.nn.Module): def __init__( self, - scale: torch.Tensor, + scale: torch.FloatTensor, result_dtype: torch.dtype, result_shape: torch.Size, weights_dtype: str, @@ -91,8 +91,8 @@ class SymmetricWeightsDequantizer(torch.nn.Module): class PackedINTAsymmetricWeightsDequantizer(torch.nn.Module): def __init__( self, - scale: torch.Tensor, - zero_point: torch.Tensor, + scale: torch.FloatTensor, + zero_point: torch.FloatTensor, quantized_weight_shape: torch.Size, result_dtype: torch.dtype, result_shape: torch.Size, @@ -118,7 +118,7 @@ class PackedINTAsymmetricWeightsDequantizer(torch.nn.Module): class PackedINTSymmetricWeightsDequantizer(torch.nn.Module): def __init__( self, - scale: torch.Tensor, + scale: torch.FloatTensor, quantized_weight_shape: torch.Size, result_dtype: torch.dtype, result_shape: torch.Size, diff --git a/modules/sdnq/forward.py b/modules/sdnq/forward.py index 9caa12f7d..048354cca 100644 --- a/modules/sdnq/forward.py +++ b/modules/sdnq/forward.py @@ -95,7 +95,7 @@ def fp8_matmul_tensorwise( dummy_input_scale = torch.ones(1, device=input.device, dtype=torch.float32) input, scale = quantize_fp8_matmul_input_tensorwise(input, scale) if bias is not None: - return dequantize_symmetric_with_bias(torch._scaled_mm(input, weight, scale_a=dummy_input_scale, scale_b=dummy_input_scale, bias=None, out_dtype=scale.dtype), bias, scale, return_dtype, output_shape) + return dequantize_symmetric_with_bias(torch._scaled_mm(input, weight, scale_a=dummy_input_scale, scale_b=dummy_input_scale, bias=None, out_dtype=scale.dtype), scale, bias, return_dtype, output_shape) else: return dequantize_symmetric(torch._scaled_mm(input, weight, scale_a=dummy_input_scale, scale_b=dummy_input_scale, bias=None, out_dtype=scale.dtype), scale, return_dtype, output_shape) @@ -115,7 +115,7 @@ def int8_matmul( output_shape[-1] = weight.shape[-1] input, scale = quantize_int8_matmul_input(input, scale) if bias is not None: - return dequantize_symmetric_with_bias(torch._int_mm(input, weight), bias, scale, return_dtype, output_shape) + return dequantize_symmetric_with_bias(torch._int_mm(input, weight), scale, bias, return_dtype, output_shape) else: return dequantize_symmetric(torch._int_mm(input, weight), scale, return_dtype, output_shape) @@ -236,7 +236,7 @@ def conv_fp8_matmul_tensorwise( result.append(torch._scaled_mm(input[i], weight[i], scale_a=dummy_input_scale, scale_b=dummy_input_scale, bias=None, out_dtype=scale.dtype)) result = torch.cat(result, dim=-1) if bias is not None: - dequantize_symmetric_with_bias(result, bias, scale, return_dtype, mm_output_shape) + dequantize_symmetric_with_bias(result, scale, bias, return_dtype, mm_output_shape) else: dequantize_symmetric(result, scale, return_dtype, mm_output_shape) @@ -278,7 +278,7 @@ def conv_int8_matmul( result.append(torch._int_mm(input[i], weight[i])) result = torch.cat(result, dim=-1) if bias is not None: - result = dequantize_symmetric_with_bias(result, bias, scale, return_dtype, mm_output_shape) + result = dequantize_symmetric_with_bias(result, scale, bias, return_dtype, mm_output_shape) else: result = dequantize_symmetric(result, scale, return_dtype, mm_output_shape) diff --git a/modules/sdnq/packed_int.py b/modules/sdnq/packed_int.py index e4717086e..b20c61818 100644 --- a/modules/sdnq/packed_int.py +++ b/modules/sdnq/packed_int.py @@ -6,11 +6,11 @@ import torch from .common import dtype_dict -def pack_int_symetric(tensor: torch.ByteTensor, weights_dtype: str) -> torch.ByteTensor: - return packed_int_function_dict[weights_dtype]["pack"](tensor.to(dtype=dtype_dict[weights_dtype]["torch_dtype"]).sub_(dtype_dict[weights_dtype]["min"]).to(dtype=dtype_dict[weights_dtype]["storage_dtype"])) +def pack_int_symetric(tensor: torch.CharTensor, weights_dtype: str) -> torch.ByteTensor: + return packed_int_function_dict[weights_dtype]["pack"](tensor.sub_(dtype_dict[weights_dtype]["min"]).to(dtype=dtype_dict[weights_dtype]["storage_dtype"])) -def unpack_int_symetric(packed_tensor: torch.CharTensor, shape: torch.Size, weights_dtype: str, dtype: Optional[torch.dtype] = None, transpose: Optional[bool] = False) -> torch.ByteTensor: +def unpack_int_symetric(packed_tensor: torch.ByteTensor, shape: torch.Size, weights_dtype: str, dtype: Optional[torch.dtype] = None, transpose: Optional[bool] = False) -> torch.CharTensor: if dtype is None: dtype = dtype_dict[weights_dtype]["torch_dtype"] result = packed_int_function_dict[weights_dtype]["unpack"](packed_tensor, shape).to(dtype=dtype).add_(dtype_dict[weights_dtype]["min"]) @@ -19,9 +19,7 @@ def unpack_int_symetric(packed_tensor: torch.CharTensor, shape: torch.Size, weig return result -def pack_uint7(tensor: torch.Tensor) -> torch.Tensor: - if tensor.dtype != torch.uint8: - raise RuntimeError(f"Invalid tensor dtype {tensor.type}. torch.uint8 type is supported.") +def pack_uint7(tensor: torch.ByteTensor) -> torch.ByteTensor: packed_tensor = tensor.contiguous().reshape(-1, 8) packed_tensor = torch.stack( ( @@ -38,9 +36,7 @@ def pack_uint7(tensor: torch.Tensor) -> torch.Tensor: return packed_tensor -def pack_uint6(tensor: torch.Tensor) -> torch.Tensor: - if tensor.dtype != torch.uint8: - raise RuntimeError(f"Invalid tensor dtype {tensor.type}. torch.uint8 type is supported.") +def pack_uint6(tensor: torch.ByteTensor) -> torch.ByteTensor: packed_tensor = tensor.contiguous().reshape(-1, 4) packed_tensor = torch.stack( ( @@ -53,9 +49,7 @@ def pack_uint6(tensor: torch.Tensor) -> torch.Tensor: return packed_tensor -def pack_uint5(tensor: torch.Tensor) -> torch.Tensor: - if tensor.dtype != torch.uint8: - raise RuntimeError(f"Invalid tensor dtype {tensor.type}. torch.uint8 type is supported.") +def pack_uint5(tensor: torch.ByteTensor) -> torch.ByteTensor: packed_tensor = tensor.contiguous().reshape(-1, 8) packed_tensor = torch.stack( ( @@ -82,17 +76,13 @@ def pack_uint5(tensor: torch.Tensor) -> torch.Tensor: return packed_tensor -def pack_uint4(tensor: torch.Tensor) -> torch.Tensor: - if tensor.dtype != torch.uint8: - raise RuntimeError(f"Invalid tensor dtype {tensor.type}. torch.uint8 type is supported.") +def pack_uint4(tensor: torch.ByteTensor) -> torch.ByteTensor: packed_tensor = tensor.contiguous().reshape(-1, 2) packed_tensor = torch.bitwise_or(packed_tensor[:, 0], torch.bitwise_left_shift(packed_tensor[:, 1], 4)) return packed_tensor -def pack_uint3(tensor: torch.Tensor) -> torch.Tensor: - if tensor.dtype != torch.uint8: - raise RuntimeError(f"Invalid tensor dtype {tensor.type}. torch.uint8 type is supported.") +def pack_uint3(tensor: torch.ByteTensor) -> torch.ByteTensor: packed_tensor = tensor.contiguous().reshape(-1, 8) packed_tensor = torch.stack( ( @@ -117,9 +107,7 @@ def pack_uint3(tensor: torch.Tensor) -> torch.Tensor: return packed_tensor -def pack_uint2(tensor: torch.Tensor) -> torch.Tensor: - if tensor.dtype != torch.uint8: - raise RuntimeError(f"Invalid tensor dtype {tensor.type}. torch.uint8 type is supported.") +def pack_uint2(tensor: torch.ByteTensor) -> torch.ByteTensor: packed_tensor = tensor.contiguous().reshape(-1, 4) packed_tensor = torch.bitwise_or( torch.bitwise_or(packed_tensor[:, 0], torch.bitwise_left_shift(packed_tensor[:, 1], 2)), @@ -128,7 +116,7 @@ def pack_uint2(tensor: torch.Tensor) -> torch.Tensor: return packed_tensor -def unpack_uint7(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor: +def unpack_uint7(packed_tensor: torch.ByteTensor, shape: torch.Size) -> torch.ByteTensor: result = torch.stack( ( torch.bitwise_and(packed_tensor[:, 0], 127), @@ -163,7 +151,7 @@ def unpack_uint7(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor return result -def unpack_uint6(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor: +def unpack_uint6(packed_tensor: torch.ByteTensor, shape: torch.Size) -> torch.ByteTensor: result = torch.stack( ( torch.bitwise_and(packed_tensor[:, 0], 63), @@ -182,7 +170,7 @@ def unpack_uint6(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor return result -def unpack_uint5(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor: +def unpack_uint5(packed_tensor: torch.ByteTensor, shape: torch.Size) -> torch.ByteTensor: result = torch.stack( ( torch.bitwise_and(packed_tensor[:, 0], 31), @@ -211,12 +199,12 @@ def unpack_uint5(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor return result -def unpack_uint4(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor: +def unpack_uint4(packed_tensor: torch.ByteTensor, shape: torch.Size) -> torch.ByteTensor: result = torch.stack((torch.bitwise_and(packed_tensor, 15), torch.bitwise_right_shift(packed_tensor, 4)), dim=-1).reshape(shape) return result -def unpack_uint3(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor: +def unpack_uint3(packed_tensor: torch.ByteTensor, shape: torch.Size) -> torch.ByteTensor: result = torch.stack( ( torch.bitwise_and(packed_tensor[:, 0], 7), @@ -239,7 +227,7 @@ def unpack_uint3(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor return result -def unpack_uint2(packed_tensor: torch.Tensor, shape: torch.Size) -> torch.Tensor: +def unpack_uint2(packed_tensor: torch.ByteTensor, shape: torch.Size) -> torch.ByteTensor: result = torch.stack( ( torch.bitwise_and(packed_tensor, 3), From d8aaffbc2789cf96343d7a397705a588893f2d21 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 17 Jun 2025 19:58:20 +0300 Subject: [PATCH 03/85] IPEX fix DPM2++ FlowMatch --- modules/intel/ipex/hijacks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index d81d7b05c..663d88f56 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -254,8 +254,15 @@ torch.Tensor.original_Tensor_to = torch.Tensor.to @wraps(torch.Tensor.to) def Tensor_to(self, device=None, *args, **kwargs): if check_cuda(device): + if not device_supports_fp64 and kwargs.get("dtype", None) == torch.float64: + kwargs["dtype"] = torch.float32 return self.original_Tensor_to(return_xpu(device), *args, **kwargs) else: + if not device_supports_fp64: + if kwargs.get("dtype", None) == torch.float64: + kwargs["dtype"] = torch.float32 + elif device == torch.float64 and self.device.type == "xpu": + device = torch.float32 return self.original_Tensor_to(device, *args, **kwargs) original_Tensor_cuda = torch.Tensor.cuda From 71c2714edf73022aed5152f6297113302dcd5783 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 17 Jun 2025 20:04:15 +0300 Subject: [PATCH 04/85] Cleanup --- modules/intel/ipex/hijacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index 663d88f56..2a3b9e06a 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -259,7 +259,7 @@ def Tensor_to(self, device=None, *args, **kwargs): return self.original_Tensor_to(return_xpu(device), *args, **kwargs) else: if not device_supports_fp64: - if kwargs.get("dtype", None) == torch.float64: + if kwargs.get("dtype", None) == torch.float64 and torch.device(device).type == "xpu": kwargs["dtype"] = torch.float32 elif device == torch.float64 and self.device.type == "xpu": device = torch.float32 From e657cf790d2dd876510ff48b4c68c66425965af6 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 18 Jun 2025 02:12:34 +0300 Subject: [PATCH 05/85] SDNQ fix int8 matmul with qwen --- modules/sdnq/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/sdnq/__init__.py b/modules/sdnq/__init__.py index d9db525db..998e0a467 100644 --- a/modules/sdnq/__init__.py +++ b/modules/sdnq/__init__.py @@ -56,8 +56,11 @@ def sdnq_quantize_layer(layer, weights_dtype="int8", torch_dtype=None, group_siz output_channel_size, channel_size = layer.weight.shape if use_quantized_matmul: use_quantized_matmul = weights_dtype in quantized_matmul_dtypes and channel_size >= 32 and output_channel_size >= 32 - if use_quantized_matmul and not dtype_dict[weights_dtype]["is_integer"]: - use_quantized_matmul = output_channel_size % 16 == 0 and channel_size % 16 == 0 + if use_quantized_matmul: + if dtype_dict[weights_dtype]["is_integer"]: + use_quantized_matmul = output_channel_size % 8 == 0 and channel_size % 8 == 0 + else: + use_quantized_matmul = output_channel_size % 16 == 0 and channel_size % 16 == 0 if group_size == 0: if is_linear_type: From 4b3ce06916056909fbea7b1648f7609c3e584c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Wed, 18 Jun 2025 00:38:17 +0000 Subject: [PATCH 06/85] Initial support for Chroma --- cli/model-keys.py | 2 + configs/chroma/model_index.json | 24 + .../chroma/scheduler/scheduler_config.json | 18 + configs/chroma/text_encoder/config.json | 32 + configs/chroma/tokenizer/added_tokens.json | 102 ++ .../chroma/tokenizer/special_tokens_map.json | 125 ++ configs/chroma/tokenizer/spiece.model | Bin 0 -> 791656 bytes .../chroma/tokenizer/tokenizer_config.json | 940 +++++++++++++++ configs/chroma/transformer/config.json | 20 + ...usion_pytorch_model.safetensors.index.json | 1030 +++++++++++++++++ configs/chroma/vae/config.json | 37 + extensions-builtin/Lora/lora_extract.py | 3 + extensions-builtin/Lora/networks.py | 2 +- installer.py | 2 +- modules/interposer.py | 2 +- modules/lora/lora_extract.py | 3 + modules/lora/lora_load.py | 2 +- modules/lora/network.py | 7 + modules/model_chroma.py | 339 ++++++ modules/model_chroma_nf4.py | 202 ++++ modules/model_quant.py | 6 +- modules/modeldata.py | 2 + modules/processing_args.py | 31 +- modules/prompt_parser_diffusers.py | 9 +- modules/prompt_parser_xhinker.py | 74 ++ modules/sd_detect.py | 2 + modules/sd_models.py | 3 + modules/sd_offload.py | 2 +- modules/sd_samplers.py | 2 +- modules/sd_samplers_common.py | 2 +- modules/sd_vae_remote.py | 6 +- modules/sd_vae_taesd.py | 2 +- modules/shared_items.py | 1 + modules/teacache/__init__.py | 3 +- modules/teacache/teacache_chroma.py | 325 ++++++ 35 files changed, 3333 insertions(+), 29 deletions(-) create mode 100644 configs/chroma/model_index.json create mode 100644 configs/chroma/scheduler/scheduler_config.json create mode 100644 configs/chroma/text_encoder/config.json create mode 100644 configs/chroma/tokenizer/added_tokens.json create mode 100644 configs/chroma/tokenizer/special_tokens_map.json create mode 100644 configs/chroma/tokenizer/spiece.model create mode 100644 configs/chroma/tokenizer/tokenizer_config.json create mode 100644 configs/chroma/transformer/config.json create mode 100644 configs/chroma/transformer/diffusion_pytorch_model.safetensors.index.json create mode 100644 configs/chroma/vae/config.json create mode 100644 modules/model_chroma.py create mode 100644 modules/model_chroma_nf4.py create mode 100644 modules/teacache/teacache_chroma.py diff --git a/cli/model-keys.py b/cli/model-keys.py index 45b900bd7..abab842f4 100755 --- a/cli/model-keys.py +++ b/cli/model-keys.py @@ -60,6 +60,8 @@ def guess_dct(dct: dict): if has(dct, 'model.diffusion_model.joint_blocks') and len(list(has(dct, 'model.diffusion_model.joint_blocks'))) == 38: return 'sd35-large' if has(dct, 'model.diffusion_model.double_blocks') and len(list(has(dct, 'model.diffusion_model.double_blocks'))) == 19: + if has(dct, 'model.diffusion_model.distilled_guidance_layer'): + return 'chroma' return 'flux-dev' return None diff --git a/configs/chroma/model_index.json b/configs/chroma/model_index.json new file mode 100644 index 000000000..edab1fdd6 --- /dev/null +++ b/configs/chroma/model_index.json @@ -0,0 +1,24 @@ +{ + "_class_name": "ChromaPipeline", + "_diffusers_version": "0.34.0.dev0", + "scheduler": [ + "diffusers", + "FlowMatchEulerDiscreteScheduler" + ], + "text_encoder": [ + "transformers", + "T5EncoderModel" + ], + "tokenizer": [ + "transformers", + "T5Tokenizer" + ], + "transformer": [ + "diffusers", + "ChromaTransformer2DModel" + ], + "vae": [ + "diffusers", + "AutoencoderKL" + ] +} diff --git a/configs/chroma/scheduler/scheduler_config.json b/configs/chroma/scheduler/scheduler_config.json new file mode 100644 index 000000000..c9ef07f78 --- /dev/null +++ b/configs/chroma/scheduler/scheduler_config.json @@ -0,0 +1,18 @@ +{ + "_class_name": "FlowMatchEulerDiscreteScheduler", + "_diffusers_version": "0.34.0.dev0", + "base_image_seq_len": 256, + "base_shift": 0.5, + "invert_sigmas": false, + "max_image_seq_len": 4096, + "max_shift": 1.15, + "num_train_timesteps": 1000, + "shift": 3.0, + "shift_terminal": null, + "stochastic_sampling": false, + "time_shift_type": "exponential", + "use_beta_sigmas": false, + "use_dynamic_shifting": true, + "use_exponential_sigmas": false, + "use_karras_sigmas": false +} diff --git a/configs/chroma/text_encoder/config.json b/configs/chroma/text_encoder/config.json new file mode 100644 index 000000000..483b38488 --- /dev/null +++ b/configs/chroma/text_encoder/config.json @@ -0,0 +1,32 @@ +{ + "_name_or_path": "google/t5-v1_1-xxl", + "architectures": [ + "T5EncoderModel" + ], + "classifier_dropout": 0.0, + "d_ff": 10240, + "d_kv": 64, + "d_model": 4096, + "decoder_start_token_id": 0, + "dense_act_fn": "gelu_new", + "dropout_rate": 0.1, + "eos_token_id": 1, + "feed_forward_proj": "gated-gelu", + "initializer_factor": 1.0, + "is_encoder_decoder": true, + "is_gated_act": true, + "layer_norm_epsilon": 1e-06, + "model_type": "t5", + "num_decoder_layers": 24, + "num_heads": 64, + "num_layers": 24, + "output_past": true, + "pad_token_id": 0, + "relative_attention_max_distance": 128, + "relative_attention_num_buckets": 32, + "tie_word_embeddings": false, + "torch_dtype": "bfloat16", + "transformers_version": "4.52.4", + "use_cache": true, + "vocab_size": 32128 +} diff --git a/configs/chroma/tokenizer/added_tokens.json b/configs/chroma/tokenizer/added_tokens.json new file mode 100644 index 000000000..3f5132007 --- /dev/null +++ b/configs/chroma/tokenizer/added_tokens.json @@ -0,0 +1,102 @@ +{ + "": 32099, + "": 32089, + "": 32088, + "": 32087, + "": 32086, + "": 32085, + "": 32084, + "": 32083, + "": 32082, + "": 32081, + "": 32080, + "": 32098, + "": 32079, + "": 32078, + "": 32077, + "": 32076, + "": 32075, + "": 32074, + "": 32073, + "": 32072, + "": 32071, + "": 32070, + "": 32097, + "": 32069, + "": 32068, + "": 32067, + "": 32066, + "": 32065, + "": 32064, + "": 32063, + "": 32062, + "": 32061, + "": 32060, + "": 32096, + "": 32059, + "": 32058, + "": 32057, + "": 32056, + "": 32055, + "": 32054, + "": 32053, + "": 32052, + "": 32051, + "": 32050, + "": 32095, + "": 32049, + "": 32048, + "": 32047, + "": 32046, + "": 32045, + "": 32044, + "": 32043, + "": 32042, + "": 32041, + "": 32040, + "": 32094, + "": 32039, + "": 32038, + "": 32037, + "": 32036, + "": 32035, + "": 32034, + "": 32033, + "": 32032, + "": 32031, + "": 32030, + "": 32093, + "": 32029, + "": 32028, + "": 32027, + "": 32026, + "": 32025, + "": 32024, + "": 32023, + "": 32022, + "": 32021, + "": 32020, + "": 32092, + "": 32019, + "": 32018, + "": 32017, + "": 32016, + "": 32015, + "": 32014, + "": 32013, + "": 32012, + "": 32011, + "": 32010, + "": 32091, + "": 32009, + "": 32008, + "": 32007, + "": 32006, + "": 32005, + "": 32004, + "": 32003, + "": 32002, + "": 32001, + "": 32000, + "": 32090 +} diff --git a/configs/chroma/tokenizer/special_tokens_map.json b/configs/chroma/tokenizer/special_tokens_map.json new file mode 100644 index 000000000..17ade346a --- /dev/null +++ b/configs/chroma/tokenizer/special_tokens_map.json @@ -0,0 +1,125 @@ +{ + "additional_special_tokens": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "eos_token": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false + }, + "pad_token": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false + }, + "unk_token": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false + } +} diff --git a/configs/chroma/tokenizer/spiece.model b/configs/chroma/tokenizer/spiece.model new file mode 100644 index 0000000000000000000000000000000000000000..4e28ff6ebdf584f5372d9de68867399142435d9a GIT binary patch literal 791656 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

l4{)H$osCy`k*$ zC2o_heS1lN1FEmm)mX^Bg|X}wXNbg5p)3-5mD2Y+7eS9sa?)lOK|s4_Ovr6<%m91y z?G0tDE~Pp5pjNiZ-=QpH`Y2;uxkq=vndz|poTRRf%6Gc(Ilm%2o0I+%iX7=Xn*08R z+p^zJw_4i&4Mx_}uo3ThOtF;NO_#s-0-9n5Yab=6_lB^-@wrx-t@BB)%|4K(6T3kn zKQwgV)O5?dqXa^$7xn%9{c&6FB725p4uvv_)E+0h>Tyihh<}Oup3q0gUTFrl6iW*g zdMx72V_-}$c2%_3V*)j#Ii%IuQl6eZv89WpRtJh(TDvge<8$E?2#t=1L{c_><%AP( zM?EExhF3UOw}rDfS&2xlJjGg}VGU9xqq~AFw>I+RWuj6R?MQaB!)~Z^ zH#p0Ym9IS6(?x^@qYDq=f-T{p^f!0+1r{gAbmLf?f+UNjX8l}5;fXF^yg@Kp^tlQ- zSKTHZ-9TPm9_etcK0vhRD6nM*n>ORmJ!KFkD;Mi+aeLZ0IBOAII!NwxkY%ve=QIeD zg(;I=pzz+hawmFlmcb$R=F(tgsCF&eNryO2OO{C}>x;&|BlIC8p3A6!uvBU|C4kvi zx+EQ?%LA>+4bv`|KEobJHzh2;F} z>hM#X(b?fOr1^RlEF%;9K3I!zr>x|<^B|G2 zxP-2}1Z?_9a9%t-?y}`FILkdVknlB|4VODgM?Z~z_Ek_OE9N*ZgNO5+^~#z`I65hF zuLs)j-by~a89;e=?j%p&g40wrmB$=p_5!fwiY*t`42Rs|2yTf{F*`1FA%+#0>vX;g zZ0e+oQ&a2S_rOrivERw$2VHRb0jhD&#SoUy>LjJEi{VL2;Y9&xgy7_YE zYAB1J2|CKSLJ87-@nN~~AK&PIDLweAvuGUf2MG^{KCP-H5Ab2Ps}C;u^?TE{aLq zQIi?}10#DyK?eNjKI!>=nXlMH%Vbls>nC8;>h!U29xd4dqxxs&xK-UITCJW9UA7e* z={S0@n&sF-+h9#~Hog4pHwcpF0#LKRa1tBmcchiNttAf3%hP|GCtjM|u}AY_wTOi(pO7omX{9xRgGstOpWn+-RQu4Px_ECWlcG{z-jOF7LRd~q*}$J$;(`qG zG6mh*Yc+N{SFweVIQV|E3XY(;Wp)$(QDB4EKtUx4Bslz%9K69acUxEfZ;!m+QXSj z7!J$z$)yK7Kv~%g@bx%wSkv$8>^%C@Q7%p5C#5XaQTDp>+^#Ulqz_?VdLnMLmSXPD ztmv#>KvRjngcZogeIQI{`V5Jj-p~A#;RN?crw#;K1`NJBCA9 zr1|h7*XA-u)Q0DPmyCs=$0}qvv$tgu0P*t71x}3Nw&ZC(X&Ir|y4Ucf)F4S$-5?HW zS`42{*2&nxKAfe=PCSqHrjig=8&Azjoua74($7t*0&2CT_Z&D4wWtmt5t_d)?RJS@~EI8PUIM zc=sXPk*XQ@w<#nc?$c1;1}t_T%UJ_GrqD*NS@O)I?pr@UCEnveD~D+4lfp766MCd4 zv!4XfZBCPaJ%fw9^H{>xl{l@L^yk%DiYtS~=f)RcqiVzf6i%z5&*zmS6gL-_yol4% z(wAhlS(dJbvTSI#`NJCLgBv?)z?5TNfwD-MwbG;Ly^o2nLz1Ia3t5LRSqrdA8H9b- zfp0-XTI9a6T=6y-z3D{ePjy>?>%n}K6?@0|9__I_JMUw#WxszxhHfxKGo{CPcO!^& z^&q<3{T0BX(sRMo*Sj{s6cc9M+W#AeN2}qq1#pO*h&JfOoFye$x>XMN9CE#R?j^|9Hj4)kHUKOjx{ zV}}fsuN9i~3|m-o|8!xPBWCC{$!hP7&kAsOSJ*y2}UVYXEDyl@(ioEP|JVdd9@hGK2 zi<8kvgD7SPQ!5==2N=^@DO2$9ovLFy!dZ+JDQfA=PGGBd zT|3A_T|guszQ31~kGcXQrRl*DI_-VCnGePcE-A<`g%*6^bY3q3S%L%DqY9U0TTgOa zWVhq$dS5upHbyAy-#7PnM8M>j+3Ro!6O$)QV{+68uw|}{*(ejU-<2`h`turyPZ2ab z4a$UB^|1693!>G8r=4$C5M^7BdXJn0HVNoOsg;AzfS|50`pTK=w#2y44<55|3^*y;?g*RYMlq;$n%I218n=bLLHz+#6t^Awmg z1NAQM!X++rwe2x5bAAlY3WH%mje4yghhdmJL4JG+m+7T=OF1E9mqSL?5R0nLrPe6c=PsD|@)UB(btb~%7MVWuS zWib=X+zIjSUXJJ=J|_8bi6dyJXl=#5 zj?k5~FR$zmVXBGikn79D17S$R&IK%KY>1JhIxD9h46-bO(JH~)`x z0ZMt3@K_KE(}UA8OaWQe;}-y`z-L%3p^B()uKaInj^dU9qLiUk1o0eSlWk zI7&ld^B~sDf5U~X+?T7>ZP8KjxWLjMY$Egzuccf+z&tY+$khX#=LvcP^`=2!%YpX1 zbbogYb-}r3fnmJOa7To8cgc@-VfY&>UZ#{M#yBr}Tt6eLl|bd8@wH(Yzy#D66{UKd z^IaAmBK>YWL^MN<-epY4-xFOZOl!XEGYL#h3s=I@=?svmHT-7)y4MDip{zxPht8RX zs5xlxS&r6&nQSPBmZ-`Ki;k)~Bqaw{gQ8-~spWjdGzerao??5y7HowOzAdgj_3)W+ zCWG2lb<|3q?Y!xNJyBp7SgM?P<6vocGw0=cZ{dVkpIq2u4&EqI-QT=V-4BU6>JQX8e0%W7|OJ_y4MOlI5+6E;`YzjGCscMS7g<>cF(_bU6JnUXu4 zk5WA@()Uj1gF{!kVji&2`QY3+|C@3b*gCKQDVcLW2o1dboSglT^H`x%imMf)I}J2r zv3Z+XkRzAiw1zZ}HCNkZK+9v^j*354&*-5GH2=|4_%Y>h%9O%MvmAYGxeJKNQY%&b zX&B2bo6dpsc@Av#2OZPBf24$^#n`R)%FcfU#>zB^vr@p*$4VhK!?@i0s*3?h(0Avx z5SIBaCraI$=2Z`H-TW5rVods<9y}HOHk6GC3>$e*U%|;szjvHRJqaJ@USae9T0HvC zOt*c6+l2PQA+=%ygjI4@Pb_)3EwDp56Jx9=TRsI@w%D{%F)#QWCaT@4LW*&>FC5h` z`ajG$N}0a6y_S?^8^KmGTuzq%eFcov$8A!Mkbe(h`LX9lLOc4D&9Igh^Ib0Q(U&M? z5mhl_&8+vq5ECwpFICD!jwi#nY8UJ)wghk0DLjS&D@_~*CFa7wEY3vxDveH{uWe34nv4juZ>Or?aCMauhh5Hl< zxmOu9T$7R;4zB;G>;6DqI|Rmbu73!k-R7Z(LRp5nj}k~mTp%{w^uY7f!(pr%j_xmO z6q$cEqS(MX(tYihlD(U`uPnlNlA{IK5{xnC=`4B2KPZpgeNq^HEWT)ry2`#t-Ik*a z6JYws*N$^>GQHv-qS}D9N7bol#lPIgA-WR%L$M_o&F$p(j*imXi#l!Jr<0>-HhL3J z#&mWOF{iGQL%V^km^pZ>Cxa{GsP1qk8*`8h6S;*aTBuYIZg|S`iqSsXqgV9CX_C;w z>A7-p=*i}rfv-~ko_(ca8#YU$>7;6CeP3uxW|Td-`BFdUQM(xa2P?Gh{?s3u)Vg(- zAp>w)u?*J%;!DMm9_Vl@khSYzC~L30c`nsMfGE8N_vB%?t%yfpz*J;$vBFHq$Pr+x zdeP)jrTku5^A(=8Su)CD7Hk|N9S7DzWlj72c-)pF9j+1PjMJTOY*Kl7X`=JNF(Xy> z5of@dOcPS_^;sYjeuNj7{o~HNod}Vs3CHj}r$+En1&pa+aP%zW<4Gu!*$iQdi?OEV zu#}6V%>Dv$+EkcmOmdgQR!xJn7EQgNM&<$tvZ~KVzOHVIvWmNQ3-iFJT-0R#Z9Uda zS2@0x?!?}_6$X6f4Pz-%EdDbHOD4yy*6l9%JQ7{k904&=>iU@ZFs zXiOMXm7p3{rsSh*and%!P1^J40W8`W#$|b;9`k~FAPcXzh-j!x{}HRlO%RqA8!)=+ z|93s3n=VZ0ZlCWW)pz&grv+fuJ#Dz(Z5AQz%VYJNkXg4wn0ECoeXSM$zT5fXcslns zb}3=Hz}g}C``vdA@?&H;;34-t=2ggUi-AR5@pljhC^l`BVu#|Y`oqox2lXnY@e;6Q zDx5CoWWrJyME2PP?YIoLbu*am=_t@k?bPp+{L_6*NTMV178 zqHIuTN$8n`&>&j+vg1xG{!7o(%2`6ydb+9NH5U!DWJ$m7;5dpr_7<>M3G^VgeDI!Q zLR%aAMeoB<&BC{dmPTNP?)Pr~fG?KCEvc% zjJ&Ok6)HAUn&D<8ta-0m#1&<0-px0{nh@9l_=n0b!B+H4H^TXC^RJ;uNB^X?^(New z$+2ucy}H@?W_`>3JeCVTI3Ib==X_TWdAC#^{E@Gg+(dsq`;_uin#r#KMXlq50nSk#R|usv8sG7XzQT7k3HCh(%VT{xlx>-Dqc9(QFd~ZdMZ5J8%X=Zpsdb~_2vG$bz6>E#fGb>4lpFnD~%00xsUqi8|15*GS=Q~I;-1^^E*SD z{PfK9dsW?BxI%UKRCvSgFebuS{R`u(dV-@W$e^Pg;oOtzDgC=>=Dlw!MMYE1%kRAb zmZ#dRoOlYzY8m~vWcoVDMP2^84QRR%$S=xJTJ1Z_{82bfB{7U(i^td(E(ZUuPAZ!o z$bN9Fx%Jjh={Vdb50^Yrx=fm;6kfS(A`ef*ZK>(iMH=(_lVOU*h&;WffK6>`-(V_m zCWOUk{|X&Q?OBcr-`v61r%I7XB}#1U;U)uPN5~oKwAj3Kj0a$b`Vgj^ER^a+oMoGb+rfCm6Z8DE@ej3&A?OLMGL^I5}Lc&G*ddFz9pO0O>sgr{yNR z-ma~z!Pzc?1c$=qa>Z6)RD8}U*U9X2oR{G#?$M7t7aZx`%i-ZXB`l1dQqw~LO=d$` z-dMt`nPJWxn5cpB?{CW$S|mJlPSuW|?}!QEDr<)l(Tq2t^B&z#?t395`s09{w7A&Z zdTaI?bz5}U%4i2IFR_o9=H%H+ahrThRKmTnotHa`9SXKCiqD$IF~IIn<%5BKi%znhoegA zfpy!V&7CgP4BiHjjf$h1VjB}3?{YpEfs^w0-C*>RUA0{PbsugMRR3EikO!48!7!k6 zzl)Kr+<8R<0K9zdtT zGjJ*yMLX|h+_p9R&nui<*?f%;QFY^n_r%Jfw_O3(U)Ld1O79++YyC`xBfd zR2O=e<2L|{I@dwwu+Lo3QFwEu{tP6!bJ@%^&kyy{)-b z8M0H&bmHDGaa%)*{?`C&Oxgb+tw{TEhfFtK7JLI`;^;pWW~Ar05GHPnX-%u|fR>8B zwLl~LSsAj{tCQTl8K>pC+~Y8f{NOxej#umah96;~N`xl$AKXs+2?{A!pI;?!tJ^+v z43xW)z8t&-%Hrg3A^&x_6>I`pln4F_VFC{4Hsx-=0j&j}mXdD2gQDI}bEq?umz1*@ z<5^w;t=<7916*+YW2ZS;QLNvE6S3%VDkt9988_PgOBc zovu_=-NJe6RrSQGht**4v=XF&nOTn8C4#)>o0BeS~Ea#a`SD|{B4{k*O)S~RS%E?2jyG2Dmu z^<8>4gS$bS+&X?!OBsBRIK{&03$h3H#ckr)!={0Zsi#ITCV6zvla#W8*Q=({Wxacq zwa9h2^V#q5Wk4xpv3KF9K5thlV`aq4vh>Rr^@E^n!-?G^{oPki^3#A18w_CzqjT*g zj|>A_+U!c`OB!l2KKE4EXp|o+Lq>osQGNHCGHx~!#>$Vwz4FOuAjQ+QyUZJd)5Ku_ zA#*LuT;T8@9{Hz-agO2EgZ7Z`OmKlt(_T$3I~~H}@5l7|r-?3vp1)yVr{iQ8%Lf0k z%Q4?G9Uh%#A%D_;z@~U=RM@ottCW>GCL(QpT#NHuu$*e=uNc?{N(@x)@7)5~Jaz!9HQMs`fRY(i&5e1?X+_ok!0JFe<7F)VRddKFS}g;veY@c6`q2-R4I#cIK8Yq zeWAl6)%tSPMfE{=hDsIj%f(PpnNO#&>DjGWSNwz1H^LZ054#dBs?XT^uDA+lDb?y+ zzVEdT4_dV%C;zz)Y?&F$@p>MDy9vT%=fBKOz14l@H3#mE3|Ig``{>+Lo{pS7JIRf= z!&(-p7);19klqP}g7fV9zo2du>~T*;j$H^aiH3Qs&?*#1666Y8>%X(!15cXsA1HU> z!6K(dh1)kT-VbF$mQ_oKMJ@&oQqbm(dJuxNdEH5#RJSEpp5;v32Z}8=eWX%VyY68Z zKH;e?uIUmB&$&Zs{0MF`{PrVqa^&pXO?o{FYqcbNwUGybmpZ1Dhc%g`^?w2;N?HFA zW7rCZS*_;f^k?g_TI>#Z-r>Qz2e)2v)VH(S6}}j_W+hxSux2yNvck#1Z_1hqd2$cs zw|@Zw{qR3;REgJDf$X?WG}Ku)a`$R@OMp_-D<}!sZw;Ic4~&35lhW`4WbQR+YlcG^ zEjh|VDjzCo$qozuuwlqr2-AwTQ|4bi-+-Yc7@4HOo90e2U)rK>D}`hs2oDM#@sMH+^P)_4$MK>b5#~Dt8FK08z!m z*T+te0AnbZp}yoxvC|Cy7z#D-P%_F7hk;sb0$REp=}*f&itV$W82jIMKqA*3R9b!y zhzgrh@Z{_tAx!I|dCP@5``k}3CR(*8r)&YK8c&q#)M+I?Ahed#Kpv~-nBeNUJRb1e zia*mJLn-4Lk4pUtWvOr@MeA$t?Jlwyi_~0}jO_Rg&N31j0sh8CP#aeKxn7eS(>TS+6N|lJ(?WO zGQLon7Q)7~J4QOs)6=>Vumgm4G*%Yz`Sgt^-;Z4L#R5aItyF`m~mY;^0v zzXz0w;s}-7TOh16p#7@FR3|)_HGx%OPn2)f87Vi&b7M!=AK~JNg>RfpI2yugggu18 zQt`)Q;HW;S@Oj{o&0Qq?!Ke0@XSQ$*I@i_bwaRGrwez`NfknxTikTQ=Xx~l3%_t{5)onhR1IHLFYg;=e{NLa9-SZ!JRXh&^qQP^0&e8>^dIFPR$#@Y4L~0i`m8M!8qgQucp1K(^H9lY6ji~d z_E44|=AI|YpB*d!vznkFZ*+74VyOx`0UjdheOV<1wU1_ z_gli@M6OUFpZQ?xP_#P<#b-8K4r>LghkuVvx}>m{Q}mC{#jPdTLmJLDTHiDst8UYx9(d6st2Ij5_+7l&&T{~B zAgywQ|4OeThkr=J=v3fWbmF#;7U&(eq*5B&1 zk*xkEOdw|~ZGwAoY##a+;3DNrQnja(l>;t?iPWxF#_qWcjN0|y<-BWfTgw>D-uiVQ z)2JT2*XITwQOb>>NNMdPGY=Zsx*sR&Zop{?hBNv=j<^xV667ob(|X0`<21d%nwO?G z!amxbc)&LWG;7Ji>+o;UB^-~DUObLX8obH>k{Inx_Y2(nD; zIDsz2y8>Vx-vcrGB8S`96~3pi)2rn`ps2&Ky34@K1s1ylqUQ=|*9Uz;ie{v>%2oM? z9Dx-o-n!aq_t0jR=pU9#!jd1})T+?n@6y8df>Vyy=pQOfK|3Y(2-azj4)L1N{qbt~gpyHdZkE_n!}$wWXGLQ#bEWGJbNlOBPFEA2A}KAuNrH$~Ea z65t9QMXO3rb^amD*`Z#Kt)75%Rj|U+q(d*^ARnyE3SVA$i+up zGBb+L=HDc1?Kq|ckZ(_8{P+1hk>qNhVQcjl^F&z6^pAH+bd$_Aetwz%EfZY@V9oy0 zD|t3}uV!1c&8q;erO7=}DGKSxNb4|#Exx2~md-Q|d>v3@@1UrDlb}n$%Dg51cpKU& zVQM_~-JBYO6(!v>8c+9%t5tZe2HCu%Avv>7QXcE zH-64Pt6^`|9m9Xk@ogNkVVX@yoVw3>fb;tw`6Co#ZAl?XkkszNmb=y+w_%J z_GU?&_;+DuO8zc1%ipsE5aNFBuGn@-2PYI_x`NJu(xi4>jH3lw66K8Y$1at}$yQes zXy~$lmIF_k&;y!EfcAjw+Xa}<_5x=UgO-aK$(=ZBm846Xj*_4)u%-MU#Cuy zuNQ45iicv3&I9XfgTO@ci&7VL>*gOc7H|N1*?Of9WPH~h$E^?I7Tehp^&3N(Y2R?C zT&r#hW*%!%Y-k?YdNY5-ywB(`cnc_VTq!g8A4FRaHRSNqR{8hS5 zaSF2P=*KUv6+v0CmWpS$CF}~esjbu`pX>x^0kWP%`~2I^0M4ro_4nEXAv1v#@RPtJ z)`LJY33L1?yU}Zh0+9a5jCeo8J{gHZpoXY)`_L^1u;sZ5dS7(WMDhD}S*P%qeojOHFP&I*-)O71`uL z^k`w03F9F)8M4~hcMPCcz1b;`++RH-)PiYeHaTo8r0a-F54gGA3)&2g>A|iirz$4J zB2j%^t4~KaeNqlGwV(sgQB7qvV5ZLN^D|J4ev?5hE;czT+yWskFrMf90^m>kftiEB z{i5?gD9f)VyDD?fK|p4s66Kypu}vBQT@Kw>i7Q3Ze3F~R?@ND{$y8|EbpFg*O9%IF z<1hhE5-D)*DDBA98GvR4RqzI_P-Zm^DlOA{qpcmxvfLJHqyZg}(yTjoC7H3QQ z3~fZeJrcx8qvCCeJ&(#`pz`ctk22+G5ND=z#oMuhoY}NOBOO5<`Cos@Q>xe9z|&8F zcIGVP=upR=2%sK`7ntq;N;pe&HXh1p_Cp2ncQ+mOtWyX(11-ogrQ~??3xSeVfg$nU zQz4vsa)lGio(7vI~NJTHFSoYS*3TwNjB33?e3SKR_)-jZ<+9{w;rx)o6MgukHre-L)zJjUbs z?Kc0-02!Am>=b!7f(<}?eFunBl(jXEh*_k+d#68P4-$X5i?ErlmfMvd?}qjYL^oo! z6PLBv_v|!1ot9DxcKO)p0Pwzp+xAFbZu!{Lnyq1N#S=Q7%V4TyWxL@I{sUSmxiW+|93%ShKuqx^N^nCp6(?svyQZ{&!z_3`oBIf9-*7oMJa2P9Q41}qQ13*jUg zgE3@#58BzLWvl9IwfDiYs*u-%#&p+y=0{G3!3_&q5iS&SG)-^J#{gz6y;j;7X9}^9 zSe(t`!GxIk36L|VtxS)5MEfIb5t3Evr#WJm!O{5{h&#k6Mx53TKO(lr=fHhc(1qHv z9lLoTwOj7dy`(T@*5irswg~EG6yE&5BIvr|ZeVQrHKbefR91YVh*wq}OMOGooF-G4 zlfQ>{c9}*nE*50w$IygWsXSgn(u%YYEB!zm*jR84eR#MCr^)*bUGaH|u9S*y(og(v z-tgfXCzZ(Yu)4z{{)M>(mqqs<1mQYanMBo++x8_pS1c{V%zlxVjH7NPXtx}O`4(&nczHsY<4lvA zug%whc3!zwG4@;wFq5UaI)yth^6zkN%u&ud(8#S>7jO3yLCGggj)H>b3YSzeV7g&_ z5GR(FM=eCZ< zy8HdYW}q~)Zd8!3XJK9Ze}D5l#Wws=7CHC;aO0|-6dP>?Wv-^zp&Mw3AB9jz-5-s? zTSK_=%V{hB-6sFGz9TN)7Rq9+M9mQIZx4+`>U-iZ0}1}GKZX|iBkGZ-g{L;6sjqo3 z6j`NHq92Aqn3Hq>(;gcS2eB|}xP#UjQv^^(UGsZhVM~}JgycQ!xm|$FPg8+WX!G52 zn#;Z|vBF4br$^UE6dUaU;7-f{gF4+Wa;~u_uzDJb`iP0>?jU0Nm!P8e*r~Jo+eA z!)>DZmt!|rdT&bpudc4du2Z3@4SX`jXocMzE7xF`@IOl*oN_BWa-*8N<(pX5ftu*4T&Fn9IUK}s@eI+t*Dio;}8MPURU?oCVwb|5Bm>^rREW& zC2>tJR#(`S0`JM5X|JPzY@e1M0Ms6feRnZ_)gMlDOhL!XvC;7$mLK*GEInAVY$J>w zk(Qe)odA*53CpyrN@NW~#LFW4T8U;Ls^qcLz$}+?ZnnfXqTOXG%A)QZC`$<=Vh%@N zJ1>te8@xE^LTHL92fqqBrD6yBrQ$`xcf<_sZyjY~rrCzVaH z{^g)q#@r6Y*UXgw8XURe)c-2N<`L_}GEvSKS6_vw675e}s&R+lqpJa&XSzP$5xZOu zk_m$81~mb;#y8}jp?_x=P%glqmD^DB$QwZ{U5ry=(9Mv}7=7`Oc;c4)gCZIQW~SmL4b;<8UT{a#2XUc<&AM%)MOQWZHyX6<%A00Pmm z)L$QPmO!!534HO6N1^ElJwLusUFmmdYL zM08*&#$;Jmo+R2Vk6{J3%fpav+j8ZgYFzozJPkcgMs+_R#O09MkafZ10A>Ymlzqnv zd8CF`Y3omV0>DYzIEK5Xf}Hs%(yesI%Pr zkWMojAmZi^fLx6&)jR=Gh)q7szZs-sj*lRnsg?;X9#A*>I8UiAoz558ujCOK;FFJJ zWc2N4pyo*C4KeR?XjjD4j(A6u8Pc*?#%wdc%s=SjJ=j?PD`<@FN5)`<{OOpMxLK5o z#)(KQ_!<&XZnoKG@wCFOyDB+lijMyPxDjC{)|ejm+2~s^my0Pedy+Y}63T!zZemPS z(2|*r3sI`;U*CbaEZ8FT#+pAsJD)n-G4Xn)0KP``I{znvFv}v4R$TZED(ft=>uHSN z{tJ>-1)B(M=N4N8y+bkmbumI&~VKI{5x8fsp#>mN;-_f(&iR4cQ?gNMym4UGYd3mM5xL6t+~f zD%G8UOIHDMt&_@{t3sK_=^OyXO``p8#i+P!btsb626yW<2%EQAn5xbcY2Gw`^X~L& zYdQi4gg7LQ`68Wixn9}(T(nt{Izg_;uU{*V(#dmGvBuiaWG7$t2P*9JStzI1JJ-z- zb};Qttjg8{DNT7={uN@=4FH{QQ%hHCOcU*D%idkUi`#~IoUuC3{7y7tt*nWW8xf?K zom}*!n7eKaX*R|)2GMB8Yzm_KB7Bj;E|Z)b=}Os?g4~3#V!(9f<<0UGu>6-KcG)8T zik>!8H~RJx;aKePjwp5r?NUm7pv{IyW z(#oS0(X=Cx;7l{nd;JJIhbkEZb;Zv@5OGgCUAY+B?VP8mHvus`8VJA$wPj3J$R*|} zx2CvXwA0cU&Oq?I5ZGbbwYqQCAOKYl8;89I6Lw>s(iy{sK)JFiWN(Td|HffJeKJaV ze3*Z5S3qZ4lnunccF*Y=OY+;+>%D^=W$?WLMDzHH}jP$5-*kx+|=XZ6wUf`+rNl-??p?#6Pt05hG&V z$ppP`XF9f<1CBqwnhNZ(TFXToIjRD#p9bn8Vk{|lf4p40FBmdN=X?`IWR;HJJT(rl z2roH^KU@y37}4sl5#Xe%bl+4R<1S(9Hq5`LACiAigGmwIVCw+LhtljGr`AKb>hRIV zx2UiMB|b>0u>m3*D(ZTRaYSP&zD84K$HoGX1(~-0=&gm2Lb~E{f`~qbxg8jDoAM;G zyFYPBGf>vVbhd!iZ6QvY3Fz9bJ8aIVQGG3n1h6eT%jSSTWE|H*3z zeSMeX!PL-(#6BkxRNrJ%eUHKxN+XSgY2(vV{O?TE5!ang*agw*DyE;2(-U+w_sW?$ zjSX%wem)D@snua!a7{55I|sz9>eXhM&<+t~L34Tr@8i$SztRgfDQ@|7Uw$4^(#i_R40 z{A%!Ae6dLLBSnQ4I{P~4 ztP{#G3W4?9kVE3L4O_~01UZ=n7wW}}MFRTd)>fZHzwqy1@R_;rc`G9k1nyyC=#BjA zvid=}xa1}fXU#a>szbVqZUtf{n9bW#&GEP>3WW)HOZ5H&ffleG5aUP5h?ovG8&Cz0XtUzmj(+s6YU$y(K7w$P}(&$+74k$QDcWpKh zk#+ZU9pU6V{jYSvvGQFI7I3Ymz%J1)_qbx0u6dqaLKwufjo$U{JPvz)t-u?FSib3D zL$Uh#eR;}qofJR*6Pm_FFOHM$Cv2YNfv6a7JqVET-^{BQP+8l1Ie;mw->s=^K3>0&| zP=BduCysmu+G)4gvgJb|j)7C6Vr=#-bk-fvX{K$$RSUsfvdSrO@bgeEI1fIuQ(fkT z9H>r{)Uz+;0B-nc%+;>|P?;!x8sqU-A)JGqd6Db$!knh8+lz7h>i|gl{XgFj-zn^( zX|5$p9Trr*;TTvf#*uI4G3-GgS-_p}7N`@|4K1#cz6}5y4f5l%NQ5g0byllhii&sh z4<<9dvau3iacLbwcUkj&5O;)0^yRfafV5=Ts$TI46il;djz<-y^9&ggk9|teIThNm zCtxH${c|8EtdWVP^^_1t;?TN9cXNL%MXJ)aygiz}BvRH!3-x~cyG58;7*!d+mST)n zC+5aU%>0@du*KFSKKzETWm2uzr()`V09^lFGAR2%G!cBCo~_Ke-vXFbw&!xid65u* zGFckrA4Mb9WS-poJp}C3re)WxAN`lE<;C*9=HIckm=nAH25nxrgo(H({};f;lZr7_ z;tRt1D!+ZYG*587!|(a0dvRw6(?0=y#XwRI|B=&8J*;FNTx|Qkys+1js$PKQ%|b0{ ziLP1#$aSk`_vfV`o#g12*0wlu>6|9Vx|p>bw7KQIaTbqEMYDxz$Q&BKl>SafFJgP) ziu|nx($Nz?txnjvwWIldVl7DLR;^{mT5Cfi$BN|b{tZ#)b|O}cQv9v4E|8@sM>u#$ z72rV`J2uYZHL9(-Q67t>ClAklv?+kIEt@zhHrXsED;U&v-2&2F)bmbMj27)oWNXIl zz-ejm+aV z;QcVR+5_54c@0)w1vazEGHT?W{9(T7TRJ~v@>oD;Ih83E#m)~xvNqe^J3AR3Cd@M9buu1N z8b=)RtgVc1_995NG-C}mZ-T?R{W!zi!fp9))kGVs@hiRqJpoC{|qEvZH9S({^c z6mCskg^w0S3AE6QC#FJ>ms;(q(+GQag@bkPLV@oUOnm9BW0`#*oG-S1oU6CTIVH%N zGA;uNvef8wX!Azz>omAdS{KXj2SJ`JGNRuBge`dd+>R6F)HU*Jt^K?pBFnGYiwaw2 ztR7lp@dKgZj2oKm@zz0(h_QLxbTHw*bjY8HXYG0*k7*q- zPn7xRk^s}**QHPT9-@tBXA%j{y4Y?k*Pef%LANpH2;fiI3oTvD|7=UIUmYaC>>&^O z1MYjf9lr_l_DgyQd$1^{Fb|6CD@;2T(;dIh&LNqKY0-4doE%~soAgx6%>{Bfu^g)7 z}$K_lXZqChQV0gtHr|JT(XOL|#`+KP^we zuX|FgPGJ@Uh6*wHbV!$aR0qq>>q_)^`5qs0CjY0#&=&1k7$dZJBn!*w^cLsjNlxPC z{y`$m*A#7FW8rxK7F&f58*K3Mve2wGaHsR17ebnmiSjJ7@kKd}EeW?=MfbHW=}{Wv z2SLtA<54m8xFiP{p%K6>mjSr-b18G=6{dVEWwt0M*xexyIK8p-RX`NBi51jx3j0&; z7w#>J<~4L^oT{LP8CfHoaJBJ@=2+s|{DUTZnu&6Ca-C9p-?2jYj!U;K@so%?8KuGI z2LIb44-B1uhj88lcZ@G?f$A&y^fd8(bQ_RU$4XsxWeaZyvG}ARj@#~lw#@b38zR?X zMB#2w5@t(6y*zl&|D>YFJYv^-^HeZD?TFU;*~p z7J-(e6o&D!Xs5|~SJ$gndKH9bS2-c-Ud#W)N>TSC3qqVBa~gBrIj;j`nPg`}Os)c* z2?y3g&TXvJT?dFJA?YZu`IvCV9);RpkA43IaM~;h(i`W^i}E;KytLXFp9pZA8fo9@IbW9`E}*21M%(ZCXB_o1 zldbj#fZGu}J*LdKObAk#z>0FQ9s1IQvGf-E>Wc%pVw$j5r^$1I)gv(LS=cnuyv|!9 zkA^W?whk>@641SWBA#cTTMAN*VHP*~r*hVKvv&mjSZ)P@H#h6w}4LFU`^7 z%khty;G8|Xb+Cg#OHu2l*hw_aiD{N|Ny2KE%%xE+!V#;PZDZLLp{a~1X$^6)D3=Ap zW(|GEtptKV1`m#9S0(6XkqHF2Lu6T2G0H@F0gSJ?54QLplOU20wlh%S8 zIZGty4GH{mn4dkc@M?gY0otkTF%MiD#0gf&A^f-NLR(hTycdsi*8?KmNi}hwg65|V zc8c+F2}q70WB&U2_h_K1ybcdas>uBn?X3YnS6Qv=KgZ%@VH7re>t7= zGM(quYyp(bRvc5QsWaGovo)x>X;cC3*fys}GZn=q+vPM~9O`1KXpaun&GBbZmN{-) zwF10(dk~kn)31gXV46Y$ z3bFADyU+`+u8TkVLAqFMqxzf-&E6T*Y%qEIMUGR2nOP<-6>iV~5Eq`MJnq*9 zOkgJTj-I+0I1C!0a!^8>8Y0S>l%b$uh!Q}FliLm~wuk5c)0xQkG66`ZFqemeM>sB% zY-cO}p?86VKUzymEVdhj1;AQNv*V>BLCkRtXTs?=z(afb6PDvG@wEt7H;M0G24%jn zo$8Dc<iDtDC_uCC>mz4W0>B=ZeWG;?;l6sY&NDtA3r4Egte^qyirT&M z1W;cw@SIQrXrs*ZUE_&iuEzJoJW#aZ)7*b=|GHccMNPz=-^S)0SUJ(?3$xl zgtM&IHRCqX?!)pDD>WFo{z<57dF-e-N|dUW70|f~lUTBg8rccq#Om~DL`xCcC5KsV z{P#wo2guwqoW%LFAiWZ1mDr;jEq;*csyY1O*4fut~O+a+E}+I8C`WGm|1N^QLBqGTO9-Biq!Us`?Lbw z8qkMv_SV5V=*wgCSS+(x2fuzCfKz8HBDF~|j{PebtjyA^Gy8847IFTB8iw8{gVIC? z4UZE}$y3md9@p@NxLueFi)KVyo|R9{k;#R0uCvb>AT9&8Yxtzc7WYgb*9b566yiA1 zj)G-(GH;r97Es2oY$i09u6%a>SyL-h(HPOx8gfAQ6q}v{i7-+5#a;?qN>X9-oc6f@ zPR}REr1#Nzc_gm9F|drh5Wsw}aZTKxAjo~fkD4-v(ezFhoYg`HN|u;=5dfJtGIi~G zF=2!@p)&rZpi^vHa9=w48FL8`Qm1RTN6n?#XN1pIn9qg`i7yl+`DERs+Bxtt0Jotn z2gakK$V~d@6D}ug2E9A>Kk4GYZ{vyW`-S0APP+Z@jFaOFd0$QI5~Y-<(HhXUX_; zq5xM&+k!iqbW13jZpkCHw96UZMFPxuYaRNCZEuBkTTg8e%U;y#w*$I3JcTQ1?I^_E zc?#|w2a0qmEX8|cSJ8-%^+$YI`Z(R{_uxHC0hXIY@4-iIaYMm>~yt>b=R4K{8@IM##!#e`Dbj@+T-^}pq&#M zy=Z+L5;={g)cu|y3`e*fY>G)w=J9mhbjzn8ojdBv))+228?D=$P`jiFY`V~_)oDoL z*}oDpp-{(pUaa*ralCZ*iD#yPIPG8HS-tCgJ>6sbbs-=up~@=6zn+J1p4e+@(XWe( ztG)nA2{09na};*rc*`oT$o|43e%zz5#l>f7{CQH4tE8cBE=#|0F9EopF>7E~YBS^m zUIupA%Zlg*k9swyX&uec`Z_cm@Yr>{pm5fw(wx1=8#y4iti_lwz~yILcH zGsv@HC}7)Tz*|5r4hI2oqDX{+V!k)t6p{6d&tS%bv`MRSb)DF1+p>tUvXjky9TyH1F2 z``BUJF~=645+JTj%=tHeTTZx8i-})Cy1wvp(#_4woNg>&k@=oz=dVMTq;5a%n>-FzN9y9P z|3JITkE@OozJ+oIY%61z?;x|@tv5xBafT2xs5_0Sj5*%}x!CD4Mm+yxo=7<}+wDK) zpV+r6SJ!^dA)DwGV^9gg4vU`I9p4Kw`%({dNzZrLFJRdemY(tEfgU>X_P+sB3YC$! zD}Fa+W4*=xFqO`o;wVurKsrOGnLjDWVw@`zvM(0jv9HvvJ#*yXVCoVeS(9b8rX1H? zvIL>u6SU1zKyGNc9YEYFklMw;Qv9GWT>4<|CsCGLy%bfk&e8w~A00|3!arUlEvup< zmR-jGu+Qm0Pd8Dd#|KU+`yPS0WLeO@cC2aq#O3m@YFI{{w|xG8atIp_3vjs`J5V64 zuwqW5g%}ijh_?Le3(eduVCWnrjN){%G^kjKFbobI6hA2FyvO$FsC@fX0kUCpj=F!k zl6u{mpl(m?1r8Lrzq=NQWhUn>I93ZOm10>!<-we>%s6DXzSaEX@4~aT+m!=9uABq6sZeQcT zrJ6W;i~M8ynbr^23NZ(5+|Zf6B_z@xH95++b#VBIxN19sS^w10By1eDgZ~{dJQgTu z*=fn2_VK6gn13Pf+jYF^@YcW_lE=|>hkP&)RU*som^u`~rLDp12A6gj8O{@E_P7lk zKb7LJx{^O)9N`jzN8$auZ=Tvbg(GdcvK!M z8Rxb~^Joxrh2ckhD>54ns92sMj^)PWF(!03#@b^covbb>FopNVq7p{KwbFmqc;Zos z!-mIx69}5wDd`;cb^*?X0Rw*kt#M~54)uao^4zNYqfRxtxKKd$5iZOm;6W3C+!s>q zb%=IhunUgIOJwz^(XqlL{>?gW=E?%~{eWsv*Emv3TH<2V3RM3e5l<@UJecTtk9Tiq z7iKCZF&$Bu4B&AabrcovmHl;sT_Mv8xO?Npx*8DY&oHQMi^_0QZJwU}s%$tV|1ce; zqs|f{Q4Wo+sRYgKbj~fM>0w{C4tBLfHJHdDRfX%y(ZMAc@Q5F1ia4ZeQ7pLB)7zdI{@gxRL9yRa(HzFL!ks zqhTg=U-MuHKySQ88;~<&P0xTK-Fat69*-L!8op!iP9Rq)_Zb-ThIi!vb~VVXsvE$W zabnRCyU&7l%4zORtHLh?Qfyu+R>jVr4e5e3vkK)n5QSkc7$s#F&YK}~A)Kw8pS8y> zhXFX31|6(zbvSg^oY@0SI0DioWEe&x!k+3#Akx#c8xM+bdPOu#952oTFbBNqV2iZF zjsnwkz!~qo3cHHuUl9xc-1iU5t)mG$DP2p%b;46e+9@2E0r`ge3c+GHKPY7 zY*GI=C&9GB_U61(pePv!sqsk`W{i&uPX{-nZAJK=v*+;#pm;L3R+R6KAv9Q zY1gc=!uk9G&rPg3PEgqCHuAVZI(k|AA}}+{;tcbFS(oGh4sNjrnkT@e!`d!B5=nx( zA^WYuZltX|=uqtLMXvWvb!Q~&LdQj&04oYIvd>`NNbNxg!v;)DXhslg=B*U+r?P+wwxZV z`)u&hkq;9l zrJ)03*GCB=)dtxv$1S3=!K9f3#{C|HrU)83VzI~l)$rkQt%6xRsr_$%654{)=^bsN z7`HwJ<{H8$QT8g&T2BL-QJhA#G!)|$A!NZ3XmcF!4207yV>uLSJ_`-gx~n}+VgJXw z6K!$zbI|U0qY6FUjAqI8U-Lp>(o1`>9ubykMdhSeDAGdFVsmCpc|HfGC4>6iU+~xT z;X{oVyQNSs|DhDC zphK~!euG#R3w}0b%+eslSxl~s)7~`Y?)cSPP?kgwrVer1+tAK*43{!-cK;p#yy^v( z1qwSd?mW0l{XVp_(wNMOaruuxXboi(;%NmPnNg}MUi%n2KW^2g9lH_MZsR`zca9zE zpHcBC0PLz8HpXtB<$vROj+x+P0a-_5YG9j;Ezy_$giTz391MO9<($iOed0*b6oEsa zxH$`VEx0efEyZI~HY_gtj(9NN)D+h#n2AM3en)-}?UF9I78{=KX!rr7Z+_@Zr{XvM z4}|FydTx9s<>}0p!tJP^pj}wZ_c2R-|K}Xg$rp!qzW_L=-9`a!QaEe9$oQf~kmi0eiV__TzAA`p1`UhT7Tc*W zas!L@UqqTE&J|l(M*OxoNLKhRZcZWO|63A>&xa0);Y$;w0$CrjUY#k*qHIz-m?PTt zr(IIAiukiIv)(PeRV-Q-0M>a=A|79XFv&?fSd67t%0Fj{L<`Ny$%vI5g9G6!yW|)8M8GY&WH5_qNN8G2Nxx|WVPTHp~6ow4# z?Js`GV%JQGvNid~D{Giy|AB2YN7zDLa!l9p5WN<(M;QiTZczCm|Gt9Fhm9uV3?XnS zH$H2xP1vPD^EIMhRF(ieM8zTIdinRCHP{dGG7NZq({iSUH z-&`li`Sf(jD0Z2R04#xux^~Ru!Tv4Gt*D$C+mk zY(j*-qPB5^J0=S8Cny&v@7V&f=E1ptau_mWQ!o!;RXwr6W>97xRUBIkbn%<#ANd6m z5_)DUFz1yN>g};mh?5zOGxe>Z5Xao!SbG}>2M>(P6*NC=W9h;3wgqr9*$L#~+d1~c zj@Wbuf))Tvu0qu8!F3Ys-9T|{di2JPmRogpNB`f9^V3F;hXAg`NMXmZo$SYNc`@3yOPNq!9fpK%58PGs8b7zsRI<@_a9nnkv9Do{Nu5y}A;|2(w70%JcW?qoFBm{}Hi=g8qvs zgqyyiDMdf*iRO=Sv_XSn;#h*_n2ntbn=TPTM5!o-jw9?M&h26Lp}VvRa)G$8k2%8C z0{SNSh7K0$+)SFcmlNS&D}GYgS!k8V6g+%92yE8W#>gsyu7COjUcfq2MIJlRF{)~! zWl|QoD!LRjJG@jx?!9rDAWKCPLb}p(uQ0ld?%s6AqkBV{VHrpJ7{k@f@&A{uGO?_*P-Z#Nk43eAfi+oG{Z*qr}_IK-u(VOGX8zOx+Q*Ex){U`V zCy<-mfFb>3Zg-xN^lTh=JFckPQ&F3d@56Q$+GpJoF&>1w|E zi%51Gv??y}&Vit6Fr@jWs_r11^YU6LjnI_6!%(z{y+&9qUmn{7E@9*%3%FUVJ<5}OnS5OvXg;ao@C0( zE8C}nm{m^C>$vLx?UZM-@WxL1EC5N58Fy=i{U>jn@B-C81Z2%i{>QtMTY(kN26e`G zgh&_Y))(YPT-Fd5oRfc+EAx0$_gv7dDYoF3qbuGP;)J+2mr8KVc|Zt!R9Or;pP(gE zn1iKUYC<=bz>cICmTlVQ7Z5X(jtsr!P2#!|R>R8#X^4JKm@5}|J<)z4Bt@@aO?QRD z{)-y|@$*HHu<`f>QFXC{{RhU83c8e>t6q5pRF*3P6(=i=@u)DDu^g9lw2eQ6kkYtO z(eFxvRW<4oL`*PIJG5R_PbDZvj%3;LV>%rVWnmTxmc%2)dUB>bBt-nLL4&>L} zL-DZ!ow00k=~BN4al?@V!X4x08v!iNBATx|MUrTDi>!*q-UQ{@mm$W5V<7(OW?&>W zVptSzA?O@xSl7}co)Kb3Z2K+nKeqz9ywo8buQj^<0fYo+v3)vQVaphMJz2iXNba9P z{n2!sw&5ZoJ`;qHutF%rzePB2x|Qq#I&TND*jOsCJR_CW?*Mb!eQqRaMP~_h+T$?y z)Bv~Yoj^*Pu`1d{SlA797~y?)SDuQTH5qUnBfH>jmqWLZ;;+4llas7~gVcNBKU zu#Y&Ca&pD{LCv&`pqLJqdmu+<>*S#;&V3LB`3~4Io>q|h=#k}Ld?L!SO^+v`Cg8cD zhXCPQZZzYN`4E->mW{0_ziYjcG7fn>Pi1Joc=U-p6>5Zy|MMjP4Ki!$wVotKA1`&9*L7O>sZdKB zdmA3!dMZzcd!JlvY<(KQ+}M=5G4_21$lPH0q)u6k6NMpu?b|L<*bRFUNme}vY0+7! zDkJu30-dZ~BXd?K2-#Ov#%2o%k{TwPah1ZB&ve}A@qk$g;1GcP%AO~NE0+C@xaoNz z|AtprW;e!l0?cwvcaK(|#rXV1u&m$OeF&01^d&%z1zKnQD#BmMG8^lmu4s7`%wnWJ z^}TV?`x>ZODEheMv^Vl^?f8tu-+nX4vgIF>&s$)w0C`}D!%H*=n2oXjyZrCQQqdC2 zzXyekCRfHI3Yz_N?L4haCcmG9C@WeUXESm{n?ODqIylzhhZVo)WmP|VL_;;Q{D?=i>;o2`j5C!MhR7A~JP!=5OwgFN2 zCQpL<3(4|h^0z={9CJfXX&CH}5oX5NR!OOLqmVqI!SUJuM-Pn0zstXn4J*2>&%Ot7 z;nM>%apDg-kdy=QjR1{kxV4A}euO|Y@U3;q?;>4RtOHV0T;f+CWYgU;GcHtEpUN6y z^Z!Cnbp!jw4T}i6AxxPVgMWuI(=+sR^p_=?BkSh4aPfY9)iEA{-?ap!r;`S3!t?;U zCWk+k1kM^btf)K1V@rd%iD=)?IHzT4zhyz;3ZJ%JF>W~s*Y#vB#Jnuhyfn%YY~}LM zPL;(lt(e*GJYi-aotd{6nkjlbFW4zfs2mmFiL_{#sTg^?qHcx!L;2ujq)j*O9$OJO zyG+z~(BsAV6D#o#A{#y=s#eaE!-quD$(^?fka-=WQG1hBq1_y~>>-8lq}4zuDeuQM zMR!JUMXx(nT!VjH?JQB5a~59{fUJg%ho2%l{h17A1CD-+v7o5Um>$ zbatcI9gp1@(rsvT2l_UszD_qCV}L?T+ziM&OL_-0O{VNC3U}^hw4OLgv_*ljaAA6! zzXgEHKeq4jqldT5BWVT0#iy+REbLy4Bse+g6haBHuZu-n=Si>-s7n?RW3~Y^i#Q8x z&@H$}w*xXOwNe`VbNf6BMic2|wR#62=aU>H)Wv>6%&%PRG{$TJPGy?9^tGb%$v_v< ziS+4?`B$h0Sq-n%4?trWz53vt31>Bqwg3-Y#aJNBspy)J9)HDmfr5Om}dcohvB2oOs#;q%bjiy#{8f#}Y1}xs#`?QJJC$0T>OHe-PEMRu8{6y# zX+d_j$d75e0JC1x9LIfn@;H?}am1cbeWI5CSzS4NS)kLcVHH}4 zSIcrh-od!VRGud?sWV;_04TTvP)>LF?It-Kj9iV3AdfF;gI1NzC~y zxto|8E)i%^*!AGc0Z3JDG z$*kT`EHB%hBds{d;`ZA{9gbcxIo3Ti|C37vIu9H#gtW?QqDnzaf$@vkXO@7h&N*7s zVQ5pdbOB~{Neeu!2x$hYS{fUf3Rdg}A;n>+iWGEQ41;689!P2_y#Vp?Y)2bc6MM|b zqm5?^I;|HHLDsd;jd==Za&GBjgWer;=K?v$3cqUI{U;E~V_58a1VNV}dCrV;1@K?( z)c8O_MkbcI431^<@|e|WJSi7Iwp}=&_(I_Tn16xfWTDeR47js+qE-olE9k0Y!Gi-IPeJU)+$`#0J(e!TwzWXV-A&m4LJ zqzBb0U2(N2K2G($#YqtPxpxt7A#Q8i{1sdUuZhDHbUh~9!_;NI7nm*U=u22^sRT>> z&0h^097~)`&@!my0*chtI|*{Pn5m7{38LM6rB!5`Sr;3gl4JBtw9agBcMz5@A${ev z5KEp4+UMxO)<|Z3mi}d?-RqV{8n# z!Z><`rWk*v@!YhZbcPhi5sCU1q}K+(jDufux!Xn3i0?zhz1O* z@pFZpMGb>}U0i(=fQ9cj>NJS`cr&0GV>>cCR=fqeZ=h<4##&VrLC#m~2c zI%AgZQjScy0|b$zcg0q^(;ropNAa#aW-@~*#tA|!PmJz3nB_g?dw}3t#yvaVOW0hG zVP}gitpNU%N80iC<=^6UJSm!59|UrT!XHs&8&EQ)W&WSAn`jHdc+(N(ct^vV&af5#qR6vS3-|QK-Ih;GdoDxtSmjKxvM^bd36s4ySmJc!f4cOYCn zJP3CB`%o@gxzq)J`w-f>;zO{jzBji07{uag<4lsyP>j=rISuZBkNpJ7tgv`yLvT_F z=t(aM4E>B4ZUla7rw})N0pyG@E7G?4xe~(7I6ZN=!oP_lzR$y4ZmTbyOHU^kEYu~B z{VGo(z4P1{#|eQgl%D95mipQsV1rezJHF8W_?{O{5hvM%-G=FAw~4-s{5UV`ip(*;$l%2Zkeo4+Ir*sUxCDfw!6=W zpnVP4D)#@4pi6@T9$q8f#QC{b@}@?64%1NB(i8H^G@AtM!MQfKJIkju66!Nof@4mHtOX(1_vS z`9j>E(>5)6m3Vq7K$>4)504p3=jqZ^bl3GAA&6kJ&12(b2*R`cBQC!zVZ_v-)A-v& zxD~s*y-)(M*GB)i?(&Y_rgz&95@F`6Fvr5F*$*W|%X=h$@e0JTxG_I&jV)J%hK&&e zqG6@{bG;?Znqc>pK`1*1fNZp$5#^HN=%%fJE*5j9RUC!0H`@PiqMUlQ4juPc723^P zmlF7*F-{TY*37|ltiBqg%UHokR*Yi=WUU?ff4({-0%6}bGky`_|C%@{>sZ78>3}kh z72$-iSD|LMT@%1nzU{y`Lli0oII&6;V3Beo4xJ~%%IWKaxl6Vc+A!x#J$i!;^2D(Goyp*}mk`$=o-TCe z0=U?Qd4fJV2AeI+5gC@SHqyfeQ#JxLE4&iKeE5g}+Ro&%c%k$k2QUn5H*CUxE*P87 zSaDNGq(74;xc}yavv!nJ0%|!k1@=ji_u_RCkD2VFEr2Z$T$uyL23vuUX+>q6rJ#$g z(}-jW)EEP|26g4)cD5xh6m5aY9*}jCDukUpAu@iP55+Hg@asZhVgr zORJm<0uPEb%UnXzrlc!Q+b#b{j>@Q{&34aYsVkzr?Tj6Sk&s`xj-5p!c0J=&K9aE6 zs%dD(tneNIW<$OyDdNIC@+d5AhwT{8m*r7(+(xFSmV=lzHXu?)bj2&9z$}?6c^Fx4 zG&GVKUB;{J1Yw|^ro7D~jBTh)9(k~ke=)P9h+R&jl=*O3!w0vXlRVT;-#xir{ zI43IG$k7V>`^Eyj6&73ea%9w0l<62zSr2P&;gt+XOMxjF&&Nww0b~`exvVP=pOi;w zNar{gR0FttD12weLnXQn6V>>5GXGm9EPL56a$Ej@nmiH8N=v?3fD_^U?z&jK7TSz= z)Td{tV)7Iqv!Z0^Dh~=ll4Vsm74#{Jbb@6^3*)`Vzxw~qWs?m_D?G)g|Lwh(kZC9XAwT_w!b z!ZpeE*sl%R>@r=RlK3^3J^OT!^e! zr8OE&a*!~mYx7-A9Z!}bHnHza^L6K+i0B5|l;%x5DGGwa26#0W6`aj(Y7LM+$La zyY-^W4AD-t(8cj3%jo#~y!CYEkwbY|ta=A)QFnC5UVkBqOWKH;TRNRTT^Ov{<=n^@A6kj_|&)_`fElFAYxRDo5SNNWc=X#m zVSE7)Y)TzGM`8cX(JTt#rud=+(J|6OdH%)3BGYaeB7G%-&)Js6oJ$B>-cxGI<13M_ zHWa!YU3&R$kITR;!=et#{)e7`)Z%FUPudJPbnnX{NV{cvZ+v+LVT88eAMyQF1YJ&A z3~PY5uLf|#%=~b&3*P=3K<88`XIN`p2W??=V*ElqbPIu94436&_;iuv(0@?WTu;!P zb9q$#u{UlJ=EC93HjcZ|q}0zy@hXw<&BCfZ9=gQ@H>V15!>tfhW`P}lAvXMnDOT@X z#~vk$EJu%vmG2tn8E|M04Ib;VhM4=gCK6f zobmKzdiQM~0;OO(4UC<#;MkftP(h0|yBm+PQ<%%$oGub=w*Y{Gv}5(rq_DYUC5M@3 z5pPOBuGR_-QENN`ZH~0l;L!c>M?sw8R7MA0C3r0Vgm;QtV(llOVQ%<m zKbc2E!`>NJiuTB93$2zWYv%WUia(qIukLV0Irtd>^S~ZMOUP@4xN;`bAcj8+>4J~y zX=_Ir-xGTYbCkV03UP->C)$(@x|Vw`|9>ir+G`g=W`nOhZ6-BA`wRI;aum=NV_(dH z8jMA|WBN+~mLN7b$$o3smw_xnO}(+3Xh$h)Z(_RV=IX0Jv}XK=G{=Uo<^N+P!;N&d zNP_skL5^CNcmu*M0DTT?pfpI_{wAn%nW$a+jJKf8KjzUaGNjA@Ss;A_0}VRhB2gA2 ztxe<6{%?a=Ml&&W`duW^ZR)qvJ9%`L18j4m;e8M)7W=_dJ|IZN*3X?Cn}0~y1!N7D z)Uxd#0hzneat&7gF|?(RH4_rp=@S65N>9bb2vL1S)ZO27Ww)5cK~;&E7K$QA%>##h zM%aAI9Dmo(AzgNRhMkKHPZo#_wI+?1zJzemrB%TOdqVm=IfXq3_FV2$EiC?Unh5 z5~1X8ioc3*O3>^N&Ri7$d6Z3zp4ADuw4>PoXc<%{VK^F78LugrwJF2|Lo2Tf z;(ezaLl@=UlTyZf;UvJhRBW>zVYh}fge9fZ`RfC^aE12ioN*Q7@eP2oO3=RVKN~~4 z5~>(WzS$Hq6BRCoi}8a1Ct>TFoCip$5}ih0AOWk##~wR~}au0NdDBNq2wkHu?XPxgXjQ zHwZwULk36FwvI{{*KztEB1xAu5ss<1gRnI8ltWy;J+zC0H{c!yh@XUHb+6^)Mms>8 zlga54_$C4zfhABOJ`?REsu=ij|FP1JIl|2}J&Jfv3E?qxb_j9VPQ=No0jr|D`$1aj zjKs-@#_l`+Pb&0W_K_u|S})TV;_&`?D%DIJ!v{bjgejG=U=Tr?N*kO17Ze7rtc=bf z1f7rEtY|BAk04@lX)kUbO4x<1LIJ?joWlTISQZIQarAI#S8G{k)Qo_#!hl-|vbaf# zor?uK6Ag*7tEt^3PiL~u)%MsG+C7T(3Wecit5tW;!2N17LR8iep$S)$x@ADS+>q4j2pN0^r3qo*D4SNJhf1hcuMuYGA4m0_krM@E;z{z3 zFKY8BC=A%ZS5E<;G0g2@#!=Ycqw}E{FHQBI_>qZM6sFNFxRyIZzlxx=>85^MHVw++ zVBSs+m#^3d$VrL6LOj1OfD6e;h$q$kpv_uxN75D>3vrApI$<0y+AV>*opHKIS3j5C z@bJnMv-AOA&SgfAs0@W1j@ia(u$0L-U-(HG-C2LqW69fDEj9qaN) zI*rrD^{IwDA(R8C&^{AjUTU;T&}L*nzPMcMy&;?genyqWx;i3L?TTjY(}a-=L>z>SsKK`0le{f)71 z2ZV;Xs<>1^l~?A~uXI9~eXhasz*QHt*>6B0g5?sIiiGj)psiwqB0);tO3Qpw;cT#A zMS&#@vRbCwamy;ANkQViKkwX_*F!jy0JG)aM3N#ez$`b5APlfAL8aTyol$646Kj2T z$MU_dWy#_C!%38P%!vy`S_0`RG0w_+K`gTg9a51#Ccw>!jTJg;4h-hzkaStPZ*;#P zr(1zlRP$jui7|4la(GU%S&4l`I!XFAW>LlX{eK{eps{Aa5yUblZLH%OqP_%7Ow;@5 zd3lT(JvxcLSb({x=79Noky!)Q{bNn|*Z&y|p-q|)Pbf(D_RYRGj)pQ*Qs?3VGFChW z$Q(>cs)GM30j%>l&>40tG5UHlo7KN$bmne2CLYKCW@uj;WI-gEFSsNwI)Px;_8vZ3 zY27Bo+)5;AseIsxVCG=7+`q<#Cjrpx&@SObPlR(%PIylL8#HZ2Cs02s>@RWJjX0M( z8NjT__1J2sK*CDJl=$XUf@XzR8T1NQ@iY*#!tC3~<_y1v!kl1vs`6{k0C4WyIP8tv zM0-x^^N*z#x7L~YcWgS@D(o#lJx32dJS~DcV2G!5&!1(A#k?Is9)f(auR#)fl&&o2O9I8!wfp3B9rB`TR!-QYVo}4ib=Mz_tCDCpv2a?d=P$ zWyCmLkYz)o)LLTS3qTMuFHyxh7ZFB?H65{@g20blQHZk@_DAge*n-4;LYxz-u424! zDKwQKGiAo`{+B_jR%Ob2xd<1_1~C{FJ|(Pg=i12nEscmgIdxbO z*AsTB28`gz`@2gt4Y)fN|GU$fT!Vjif-V4c#RCQDM)Sm*K+TW_5}n0Q5#(mt&UX7E zk06CjaGJ zK;wfJ>Nkt%O9X$jOtwos0p#*a8%Tw%Da1+a-BaMJG67j_a6ozNQ;_Ojx|Dp82&afu z3UgOmv_9kiTjnxpJSc(&-qY9;oBfNhYY1EL`=5n!#<&mC2sCXWhy{T@N6&+(L{AB| zAQ;yRvBe9}E_D;Tje@oVe|<4e3&HT9b(|+8TUqIBs!2WwHGs}|i9g&%TWH64SR&2c zUIy%|XWgRbs*NB^p`62#rr6^Z0B>^oTr)U9;Sw-UfW+6YnW&fL>4-NVT*7+hdK&En zA(mt{bJQkpLL!_d_!|5+Vdtj{-<*sHaJg%nwdD~_J{4Hwzd&IF2#%ZXeg{|~H5P3FeM|_J2n6K;Z zQ0b2N021cq+Pm^Y$3q#}9p8v>Nowd}+-m&DAJQ}RX54WfL&7_+53+7O_mey}7pa&d z{`eFCR%zw5YQ2?n&}Sgz#M?M=p2B7l(+LbR8slz3&Tm>$WIQ5(4qQDU3ZD~nlk(h= zj&6Px>S`!s`+e{ij$b)3ZdcHa3%__=q@ZRV6z24qzA4~B0es%lgwKxdnDZs1Wh*@+ zW5f<$1RpFWoPjFtEz#}L?*DCh z{#R$_t)mCBFz&J9Lxr7cD{evlEz%{M+S0{RjH&gE6~MB!h%R{A#OXqzmT4U?SmP^4 zfGW3zBcfAQgvbVAU47=5pcronbY8q?8>g?7lN0HM7l?F`nP?h&aNG2(AOteHBHmNb z*~x4TQ>2yi=vZXR5pyiF3XnMHW{W&QgfqkTFg_M(hKf9@z2I8Ca@VzL9%n2^(>IAk zMvO~w|7s5FU6eN!wg8eQgO)~lKN6a?_CfLZnowky?s1~#UMu^Y;cHigT`)Crj3VN^ z0CH_HNVdoSi6&=xs9C5mg&w$7e4D}YKDhTfz%D>#?&9WkfvzqbVa`m~Ajhohv=-cm zDr7yUHK1RNTc4oiQO5aKb@mq3y8xI+lq5`o zyZ%oE9zTm01emGv%;$cbu$w>Xd-D3M-JzXT8ILV8n4loJU!W_sttqY_31!Y@0Kv!Csf9x{m-??%3*hw-(B2;Upt=nF8tNAjdN4x_4YE z&@%Gm7b{E!$?~adD@qwrHVw%6@bWP-cu|1k@V-SdYOL5NPe_YRW`!3^2=}}+@3-vh zI0!%{R3N_zu?W}+W8p&L>+KJgwS7IZwYz8p&hnMVy$*mtAhqM-eg$1TIbHtbAV?>L zYiFNyo^dc3&6LGq+*{`_%gSOzylz`EpbU!GWHmv#oXM>j-0dz1 zS)@xXD#FAJU}@2WKVKUUy z?I4VZ@g!b|-8&&H|Atlu&mPf;#2uog3yKP;sEPsI1kG_;5HkOk3o^&mC>3Y-JXSvo)-J*%v)6#7&JAGnUJz^a5z4p?h9DmvA-?(CRo8ZH#jd12ea^ z9C^mjKS7(RvB|qwZ|o*WJyxZw5#co0^WZq_|1ove;dND68^>J-7Vc2Ag|@V`*g%>l zZQ7;@OqMf1p-IYaVw5KCCoFI%ZUSdfSB!N9oO%wl z8Tyww9Kx}&@bx0?RdM!2o8{!L>j(%ZS%$upt2swPA@gE#rR6Lk=aJ2<;c@;^8BP>d z?hz(EZ3wXSXuB~h7_S|Z`KwjeKI2%hdjQUF()*Yfi*)w-c@%~kwc2qGLMqlP&%sUh zTBX}{*YTOxx<1zXJ@JSbrwp^#o$=HOnU8#~Nfy_aI}wKZGI~VplR9gs#kMEGrXAHl z>>@<^o6&9EM{b9hBlGcxu#-nSQcJr3WSF#lu?}H?(R^s(Q(#G`tZ~JO0tDFFmWy>y z#qA=H4jZ>+xWp&U5>FISkoXy=Lpjtu8&B;r5mt;##rk7n$tBlQCY%9B;4Bqlw%o*; zyk-2rnE;2%W_7Zk@w+J3ES8J0+dqKL+*FwYsyi#gOikP3bYX(dV=m+~xgB+qY%Ps8 zI2+DYXCe#Gffy~qa%}2~0U=M8^k5PTQlGuvBc3vtP!pfXX>nHY3=6uC>zo7QG)?a$ zOCw5ng!V(eD`9LWHphR3$uieDct`nMfaBk2Yt&gm5<45P*I$6!NlofnEN~%2+Q?AB zntPGI%t_TTmb}=tz;8wmR=T{r4U ztg>$=l8jHQiakpKke;rlI9~*bl|J(5OYLubMZ6+sI!dP2yYsQaWsr`&p-ZQAXNho8 zcWTZrZh6iPAybbQNyIjyAyT*k>-l;j@pbk$<^sVVodMf9Uz*U{OxAn)=N6MT83rJKQ^7 zm;H_5D^~8?Tn|BP-3$nu+<@B=u-hi(_nXByG_2(?Lz{b}y|}ESekcgL1B<$ytoYL^xMmO2|j^{b1tBB`Te> z9?1T!rB{o&!j53m5u3$p{{%XMWdD`$Y94}e1el1Rd+&KT^Hb!*lyZN&^<`qK%>`zm$D}c78tt?Xs_= zU+A1;1p(xgW6b(PZlqaT5##=a)A>gGm&wONuiD@K4DbuQhMOoGbQ6Cm0Rqs&STp5z zHD|I-Z;&iAFBR)F)28WV;+8Z38QzJ}uk*L1hf+;j9Q+2@1u}ld_&DUv?CXXusVT&X zZ^1ZiMK-H?<604(rV!MaZ#o%T3nUpA%zopuWfs z=!x%2ObfP*5X(Qmy$o4e$e-juKnG8EGttbnoC4G&}s^I zwf_KJ!3Iq>(nM|-oAy%l{hG;sEo!jNCHuSImpo}^p#8m5f54NoQ{LXyhrwnMX^~nV zeBB>m$e?WEZShm~Z=H7Gi*eP@SpYiTrQ;JpSuu3dwq6oieW|}``U$C}DO9RJ_U6V<0Z(K)+HT$+1!fpmfK!4kCx z-n5;x#IN{r*7~`NK#5d}ab0AJ*&7S}FAImBFVTOW{09{A_cKA#4s85G1hpY~y>R-U z0GACFHO>~rd@@JECeNE9O zY^e+`K6%p?&Qf7-o|L~N!XG7bgv%ENJ7JR<3f>iT7|9hC=@b357@Wm{DwaCJQy0%Z z8a;eSTvvhY!10lnnK5D6?9<7V65+ozqsD#ojBz4CYlzil6_@UZ_)3Y##oYm0(C(a**|afJMrL zj9CqriqiklaBb%)9sWl&8n{T>E`lVLGae>?Xi0@Jp%?Y+=trw$cs6%)WB96Iw^HhJ zX33&Pv;{tnlZuZ7-N1R>n(H(g$0}EcbT`)66Augd3r4XxX^rg9%Jz7EO%Q!WS$$Nm zg_HKz#suspxt%w}p4?w3t_@{TSF@2A_X*=2OZ2hSI(ExI+YxfRxYaWw*yF|cOLXVZ z>0K9`c1kSRnG91Wwq378Ca*~7Y<=zZA)WMERAhb=bkN;g;bir;@CGmz(%@AG4O?r9 zwnrvwsEx&RF-}8bxca@)hpFyG(pN09A>I(jux+A6PJbbtmv}=Mp;ERacg2lBj#&K- zwht1?PZ!PiTW=Z556Ql-mf4>dga<{+i43)O^^KuC8nR8@$%B-Mm5LZA-sM-{5U9&~@J*|kA&4JNSDA)cb zY}VxCQ4y9QZ%pgtc;#W)SDf5ocAtl`L}{pLiUqgCNdnj|oUP@=R>E#{>CRd)9!mXC z=h3|Dh%2|k4~f*lXiq#PL_tm55s1 zNsk9aILjC|iB(1dUFjH;(lzI%C6YxD%4e!m-6F};i?_$z(*L=3u5`-cY}_p}6%1QI z2xjR~FiuROTNIa-2#$>^0(~cVQh8X9cJ!Bay%E!Cn$Ug?L z;@Gjc9nDnj?Z!PK9BM6>jAZBQLs2gFsk_JY?K2O&KiVpj2KS3{>e;wdmp*z2drBr` zdhL23Zru^eF<~;TbyiIMJ4D)KnM9yOg0FK#Iw)+{%2wMQV*DK^`J=XsaXXnn2NgY9 zYVM^XoJS6S^NFtI#yi6~&Ac79uaKj%2Aan3CCVUOn3GY=x~BvuqKkUVIR16r@6=-N zs&X(wL&GKx9FN;cs!X)8uM$BY&b+H9?n*&&r~IvwSC+MHQ|SI0V$jn~_%(qOrj z)jkQg^HI(9HSRWcMvpiuPFqKuBBx7+8>E^%#IKY6HB;mGdo%#;v8$`H12qO zF6=%m->S)>jM)@pr5VsJRu;ZmtuMPPjEjT{lWa@p<1~>Zic^w4CLwR_miggEZ1P?e zZz1e%KeYD|BZtS6f(U5L$f(#8rv*eVlVM_xC<=x-4J+Nfvj5Sx(LIJZa&IV?WEGXI zJ-*l{BRG_1O?1n?5YDuwfL*$ke$;+&77u$k*sY_bVPrc8>*y1+&;-A?sH}s)s)r>1 zYy+f4R%m9Q;)O)cY0mi99&4TV>#U=<#HR;sTD_N&DLp#Q5KM5JtE!n3s z8(xf4L^yP5)3Y}|rWMBVPu2aPXM~XvE5O#6zCVC!&OvJIB{v@R9-|U|sIY_LjS?+Y zt`{@dt~wL7CJr%6-V$4`y|G>!-Ymm{tmi1O7sL@!IZbcncHGX#q^_1ujjfM~N<~bg z(!&+>X3vYYU}z!qz#o=~@p|?)w(r1)t93ae)-s(yCpoz%!~Tbuv}#i+&|lNwzI-<< z)qnI*ZCIAbyCTye8f~2&c=uLA!Qi56LiHG`gk2No*JuIuy5qW+_eW8-EytYhN}Wr0M4m zhjyZ8vg4tng1ban1QSuw*x?9Z+Q#*SL88kwQKbFs%GBa0Xdzh))pQs z7IbWWBq+8#5r&lYbu`5MlW>#d)?T*7E;<=t$<__t-ajS#y01HV*Lx>1_SDxcrFLr6 zX;5i9o5k!$M`Oo`vo}8-!!Cv>=aQg$<1Aq(bOMvCO-=`rton*+ac}A#HezH9Jp-O3 z%1}rA{TzVHi0216Z!H(Wzp8w3%efAMhfw3`^ZW-(+tzqT0P&0+5_8VSX?c`MIjEn5 z0GPB?WzXTr3&4(p{INWkbs+@BfwBgj>)Ar~&g`u-eh{|z2?f?evGzp}Bq>o#IZwc{ z;qtq$PFyd}6jE18!xDaq&wsT+M7c#$5^~Tolb(;|>AGN>gQveuK;G zyCGeKZzJq{qJ<;vfZEGpTqNCCT9RtaM^`{OC0JG@Ep+pYSeIOx(Rv<;Ie5|yzAPFk zj;o4=uf|DX>7Dl7=HgDv1eMT%Xt@T$d1kC;sz#89h;!sD5!oEBG$eUj4UZDQ z?=F4un4DP=qiE;-o0V_1_vC%)$pVghSETl7`X8%>_Q>7Fe`%|wNg=Jm?J(}tx*BA3 zOT*6TcV^x={l>a{oG*qnOf8Gs?!xK%G?4?sxaaN+V>hHXZoDV^*oPbOofv;XPhNS|XJ53R)O*HYx#W?+w=yf-~&z`qoBs=7R%yS3pLl(+&#W?7EVgFn_`5;)8 zS(E>F1uQKY;L)X833tnfvTs`S;_Ab~X}!|Xi~MZ#2poyv3ZL$4?I}o#QNE0u<#v_R z_7=vQV(WQOX|ZIt?h5hzW6*WfCU&`5k@MV$Qv zKAgbugVu?be=-ZDQH#5!o&u+>g@q%h=Ou#s0`y|fh;Y0Zq$tJ@!p=vHo~xq|n)(cs zlR^iD)|NHgH+&WvVIUZGv|^s1qiUe9EX1cGsQbOiIKBJxAcvb=`{5?VrJ`JRsG%0ANl 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[-|zqhBr(43DLpJ^{F=cclsf(4z-Q9fSP_aK zZ3UUOJzs#@Rbpw?9{T1&O#UPb zqKfl>DovvpJg^ThC8{<-;;XN1h(Wn67fzXz7aQoZt`wVged*QUSJIEVFqu^KZ~k_@ zW2P#T;Dz}5Q#jY|skJks@-v`wOx5A&>{t>02IG_zCJJA^HR)-ICBASllvKsoF(aZ!*m=@69&(&rq%@ShA6VVJXJ^4>x3edD4IJ=Tg{e^SD+hjZ_!t9ul_9E77H0{uiJN ziH#|=0Whr?<3zfmq?h3|A}_JT*dYPqRd9;uxX!fKSAWpbej7X0;D3x)>R=^ zSQ6y4r~1tK*hHLD)|=zl>sDdDAT9>l6_y4e@3F&U6FHrs(G@XI$kK0O)2lN+5aBE^ z`D5#eAvWeO1L+9SbK+o|mh*}@XT6kbi4~Uxy25ZPR(3&m5<>wL*pvN-++>D>$Nu;w z1?Y?x(6!(n7Y!St?C0fUk|>8=bj*HPp|{c2If zJn;DCF}Pe7K9VFmeQ}K_=XpwUo$Bu8Ge3#33hMu_SAeoOCt$=dwidQnCa@`q{cl9@ zsz{YevcP;($T{ibRdH#SzEuu|=S4ZcSP?JIStF%#b{W?3I@x1_YGGVlil+sf0InkU z#J_~e(`-iMUw(rdStBj_0d4bau#y=%CuXENh~9c-7*dlg@>!Qs<%fxKkZo)_#t31{ zfn7P$lNUh(TH1-^S^*;LWxN=>O6IdJS)Pv;T!zV`IJ|AY5T}p@ke@Eye}NR6<4+;+e9iw1a<73~_KL zB1Wy9g_*n>5_^{kR&Q;^=qwX-N?Z>(NrY>F)Godibd=oGOy-%D>p-b%oE~een}y#~ zK>siLML1K5X|mIV)5gkh#{ZvqpR`35K64W{UAi~ z=lbZ42G;j?TvgR1=t)6Kt5#a#{qd?8&)pdRn1c+UJ;nj!2GGRL;TikoC;k@XcdC

kM9h*Ie1)a4tw zEkm2l;ReFSPz2Yf_3S?cT=IOv8S%25M7HpHEG)#@ zn}Zxd9S2Nk=*0?KK)E2s>4Lz?!Y)?yOyU|r*Lv-}GtuLYfH+GKtxwJU_S_OCt&$jq z%g1XXh?XmLXhFR3JCJ13#eUfGTLH*0uf)VuxgFW`hADB9AYVyNJ@LJeqoIvxJ9xuw z{3+%-W2)SQ$y|!wDFt&dc^TgcI5F(^vrcQ+HVdd$+D&~iPmJTOqPO^qpud=~Z~=Q; zpNgTlyZq=<@0d$d-deJ^6^&5zCcsfdFX)s$1-z1t$ zbO@Dc*RkH}eoQnuAV(NKCsx;We<|XT#zo*~P;#n`mL8I{IsC(IZkpzwu zWx-G;strjKcv!T1K5r7US;E6>qhZPA(BgJgVbYU4br`T3&+b zYKtl{#HA$?yK3_V-I|(oheflaTg17KurWXNDc9_lg~a?@Pf~p-hG=IqL-|H-r;(`+ zo44PJa4u(b6#9FZlP|S9RGMtvr{1xQtE7i>k+7veO5zPc2g#&|5#i_Z|CJot?70X3 zyM)=uPSi^Q9@iXiA)*+k$;zOol4#r*~D9FD5RixYzpTh!0v znleKZ&Cv>X9*3WF{y;rS4hwD)=kJo|W=iph7!rd56<0~d8w~U@u+&WeIOV!Wq$OIn zC`7gJx;RTtheX9ImOA1lF(jLtgUK@R!SeSkACqP~W+GmOT0$Y-6tni}>CX(JyO4p@m6LLti3Ye(p0=<~D9sA0vy4-il{?f<)-Y@%W$@LByvR-2U zEIfLFZW_UHe$MOflb2E@HeX3PEA3C4O@N?ldp6Q#+V3S7cffIYW3s&^4ho z(SNQ#5CWl3X^K1KOnWL>Wr$-Bf^a4$atOfQ%JpKh&WZ}aWSwWC56=80_r952x>#S-oOVc}vwHkt*=HI+U(Y}7+%US z>Z&7SpMJ2z*HV*DxL7HU(2@ztxdIk-`GsQAc#U{x6ytu$#=>=?kZLm;rz;HLw!j-O ze6g~ilgv|pbfD2tW;$&&;At_I%Itv{I~(NiXBPaZ>Zmzcn3I`>j1i<26kB+=K{JNK z=0Z5H99op(ph0_JYEon$vm<^K;Z*bv%$r9^tUeEhtfCMyH r;565^6l2rFfKJa2 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≦`29}wc4xEBb_W}V~$=~7Wn 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 zhyNe`fB665|A+q{{(ty?pV0N}=l_TQAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{ z{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K z{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665 z|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&% z|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe` zfB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yK zAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSm zhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y z;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K; z`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{ z{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K z{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665 z|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&% z|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe` zfB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yK zAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSm zhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y z;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K; z`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{ z{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K z{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665 z|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&% z|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe` zfB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yK zAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSm zhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y z;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K; z`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{ z{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K z{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665 z|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&% z|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe` zfB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yK zAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSm zhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y z;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K; z`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{ z{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K z{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665 z|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&% z|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe` zfB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yK zAO3&%|Kb0K{~!K;`2XSmhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSm zhyNe`fB665|A+q{{(t!Y;s1yKAO3&%|Kb0K{~!K;`2XSmhyRcNkN=PVkN=PVkN=PV zkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PV zkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PV zkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PV zkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PV zkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PV zkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PVkN=PV zkN=PVkN=PVkN@v~?C1aE|KtDT|KtDrAN%?L`2YC-`2YC-`2YC-`2YC-`2YC-`2YC- z`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-{?~r~KmI@dKmI@dzyGzL|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|L?&51N#r`Kd}G6{sa3D>_4#o!2SdK59~j%|G@qO z`w#3tu>ZjR1N#r`Kd}G6{sa3D>_4#o!2SdK59~j%|G@qO`w#3tu%G{r|BwHV|BwHV z|BwHV|L?&51N#r`Kd_(wkN@w${sa3D>_4#o!2SdK59~j%|G@qO`w#3tu>ZjR1N#r` zKd}G6{sa5@|M>s-|M>s-|M>s-|M>q7?LV~t(EdaF`TzL;4(;dvJGB4M{zLl@?LV~t z(EdaF5A8p+|Iq$J`w#6uwExilL;DZyKeYeQ{zLl@?LV~t(EdaF5A8p+|Iq$J`w#6u zwExilL;DZyKeYeQ{zLl@?LV~t(EdaF5A8p+|Iq$J`w#6uwExilL;DZyKeGSG{v-R3 z>_4*q$o?bykL*9PpZ|~lkN@w;{v-R3>_4)f|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwIg*#2YtkL^FU|JeRx`;YBEw*T1v zWBd94`2UXWKeqqa{$u<3|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s- z|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>s-|M>qN?0>MI|BwHV|BwHV|BwHV z|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwHV|BwIg(f&vK z`TzL;`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC- z`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-`2YC-_zuW(A|GWL~ z_P^WzZvVUe@Akjj|8D=g{U7#!*#BYwhy5S+f7t(F|A+k__J7#_VgHByANGIP|6%`! z{U7#!*#BYwhy5S+f7t(F|A+k__J7#_VgHByANGIP|6%`!{U7#!*#BYwhy5S+f7t(F z|A+k__J7zf1CR_rANGIP|6%`!{W1W_03-vD3_vmf$p9n+kPJXF0LcI(1CR_rG62Z{ zBmxnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE z16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xn zHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbH zRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowE zST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTx zfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE z16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xn zHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE1K9tu zUkzZ@09FlP)c{rvVATLt4Pey(Rt;d)09FlP)c{rvVATLt4Pey(Rt;d)09FlP)c{rv zVATLt4Pey(Rt;d)09FlP)c{rvVATLt4PgJ*el>tq16VbHRRdTxfK>xnHGowEST%rE z16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xn zHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbH zRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowE zST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTx zfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE z16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xn zHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbH zRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowE zST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTx zfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE z16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xn zHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbH zRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowE zST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTx zfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE z16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xnHGowEST%rE16VbHRRdTxfK>xn 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 z(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G z0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLaw zq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V z0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?W zL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz z1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$ zhz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c z1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh z5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC? z4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1 zAR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ( z8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2 zKs1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4Immo zG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4 zfM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCF zXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks118 z0MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT z(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G z0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLaw zq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V z0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?W zL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz z1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$ zhz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh5Dg$2Ks1180MP)V0Yn3c z1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC?4ImmoG=OLT(Ey?WL<5Kh z5Dg$2Ks1180MP)V0Yn3c1`rJ(8bCCFXaLawq5(t$hz1Z1AR0h4fM@{G0HOgz1BeC? z4ImmoG=O=gP$;~)hPQ$`kMNHm`yxCQtmB3Mui>-c(k^)H&quHPY}bv~zirrYnD3AJ z?QlPL-v7Gu@2@)_>^%O@Q?G~owqfUbq#rxiBR&3f_mr<2cTc(7@sxw=)*LK-1;;vA zNY23(X%2?(=U_5;4(8kDU=U=EZUN@#yf;Vx^m8a*#$o=tPg>?$g= zD-X;rPd~dP>+G_6v%A%r-Mz%@7LT(tB?ND6f9H7n^{wqXv}e~&JG*|O*)@>NNd_soX", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "1": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "2": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32000": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32001": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32002": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32003": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32004": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32005": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32006": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32007": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32008": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32009": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32010": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32011": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32012": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32013": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32014": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32015": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32016": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32017": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32018": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32019": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32020": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32021": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32022": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32023": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32024": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32025": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32026": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32027": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32028": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32029": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32030": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32031": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32032": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32033": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32034": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32035": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32036": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32037": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32038": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32039": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32040": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32041": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32042": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32043": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32044": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32045": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32046": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32047": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32048": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32049": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32050": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32051": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32052": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32053": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32054": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32055": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32056": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32057": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32058": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32059": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32060": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32061": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32062": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32063": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32064": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32065": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32066": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32067": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32068": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32069": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32070": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32071": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32072": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32073": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32074": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32075": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32076": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32077": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32078": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32079": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32080": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32081": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32082": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32083": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32084": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32085": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32086": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32087": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32088": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32089": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32090": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32091": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32092": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32093": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32094": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32095": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32096": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32097": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32098": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "32099": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + } + }, + "additional_special_tokens": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "clean_up_tokenization_spaces": true, + "eos_token": "", + "extra_ids": 100, + "legacy": true, + "model_max_length": 512, + "pad_token": "", + "sp_model_kwargs": {}, + "tokenizer_class": "T5Tokenizer", + "unk_token": "" +} diff --git a/configs/chroma/transformer/config.json b/configs/chroma/transformer/config.json new file mode 100644 index 000000000..86d6daa85 --- /dev/null +++ b/configs/chroma/transformer/config.json @@ -0,0 +1,20 @@ +{ + "_class_name": "ChromaTransformer2DModel", + "_diffusers_version": "0.34.0.dev0", + "approximator_hidden_dim": 5120, + "approximator_in_factor": 16, + "approximator_layers": 5, + "attention_head_dim": 128, + "axes_dims_rope": [ + 16, + 56, + 56 + ], + "in_channels": 64, + "joint_attention_dim": 4096, + "num_attention_heads": 24, + "num_layers": 19, + "num_single_layers": 38, + "out_channels": null, + "patch_size": 1 +} diff --git a/configs/chroma/transformer/diffusion_pytorch_model.safetensors.index.json b/configs/chroma/transformer/diffusion_pytorch_model.safetensors.index.json new file mode 100644 index 000000000..8a52f5e7b --- /dev/null +++ b/configs/chroma/transformer/diffusion_pytorch_model.safetensors.index.json @@ -0,0 +1,1030 @@ +{ + "metadata": { + "total_size": 17799966848 + }, + "weight_map": { + "context_embedder.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "context_embedder.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.in_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.in_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.0.linear_1.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.0.linear_1.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.0.linear_2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.0.linear_2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.1.linear_1.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.1.linear_1.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.1.linear_2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.1.linear_2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.2.linear_1.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.2.linear_1.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.2.linear_2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.2.linear_2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.3.linear_1.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.3.linear_1.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.3.linear_2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.3.linear_2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.4.linear_1.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.4.linear_1.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.4.linear_2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.layers.4.linear_2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.norms.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.norms.1.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.norms.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.norms.3.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.norms.4.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.out_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "distilled_guidance_layer.out_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.0.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.proj_mlp.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.proj_mlp.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.proj_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.0.proj_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.proj_mlp.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.proj_mlp.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.proj_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.1.proj_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.10.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.10.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.11.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.12.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.13.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.14.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.15.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.16.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.17.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.18.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.19.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.2.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.proj_mlp.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.proj_mlp.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.proj_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.2.proj_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.20.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.20.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.21.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.22.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.23.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.24.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.25.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.26.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.27.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.28.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.29.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.proj_mlp.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.3.proj_mlp.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "single_transformer_blocks.3.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.3.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.30.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.31.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.32.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.33.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.34.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.35.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.36.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.37.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.4.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.5.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.6.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.7.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.8.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.proj_mlp.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.proj_mlp.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.proj_out.bias": "diffusion_pytorch_model-00002-of-00002.safetensors", + "single_transformer_blocks.9.proj_out.weight": "diffusion_pytorch_model-00002-of-00002.safetensors", + "transformer_blocks.0.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.0.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.1.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.10.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.11.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.12.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.13.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.14.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.15.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.16.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.17.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.18.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.2.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.3.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.4.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.5.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.6.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.7.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.8.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.ff.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.ff.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.ff.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.ff.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.ff_context.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.ff_context.net.0.proj.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.ff_context.net.2.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer_blocks.9.ff_context.net.2.weight": "diffusion_pytorch_model-00001-of-00002.safetensors", + "x_embedder.bias": "diffusion_pytorch_model-00001-of-00002.safetensors", + "x_embedder.weight": "diffusion_pytorch_model-00001-of-00002.safetensors" + } +} diff --git a/configs/chroma/vae/config.json b/configs/chroma/vae/config.json new file mode 100644 index 000000000..188c62140 --- /dev/null +++ b/configs/chroma/vae/config.json @@ -0,0 +1,37 @@ +{ + "_class_name": "AutoencoderKL", + "_diffusers_version": "0.34.0.dev0", + "act_fn": "silu", + "block_out_channels": [ + 128, + 256, + 512, + 512 + ], + "down_block_types": [ + "DownEncoderBlock2D", + "DownEncoderBlock2D", + "DownEncoderBlock2D", + "DownEncoderBlock2D" + ], + "force_upcast": true, + "in_channels": 3, + "latent_channels": 16, + "latents_mean": null, + "latents_std": null, + "layers_per_block": 2, + "mid_block_add_attention": true, + "norm_num_groups": 32, + "out_channels": 3, + "sample_size": 1024, + "scaling_factor": 0.3611, + "shift_factor": 0.1159, + "up_block_types": [ + "UpDecoderBlock2D", + "UpDecoderBlock2D", + "UpDecoderBlock2D", + "UpDecoderBlock2D" + ], + "use_post_quant_conv": false, + "use_quant_conv": false +} diff --git a/extensions-builtin/Lora/lora_extract.py b/extensions-builtin/Lora/lora_extract.py index 85bcc2c95..c6b65fca7 100644 --- a/extensions-builtin/Lora/lora_extract.py +++ b/extensions-builtin/Lora/lora_extract.py @@ -107,6 +107,9 @@ def make_meta(fn, maxrank, rank_ratio): elif shared.sd_model_type == "f1": meta["model_spec.architecture"] = "flux-1-dev/lora" meta["ss_base_model_version"] = "flux1" + elif shared.sd_model_type == "chroma": + meta["model_spec.architecture"] = "chroma/lora" + meta["ss_base_model_version"] = "chroma" elif shared.sd_model_type == "sc": meta["model_spec.architecture"] = "stable-cascade-v1-prior/lora" return meta diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index e59555993..0a4657383 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -139,7 +139,7 @@ def load_network(name, network_on_disk) -> network.Network: net = network.Network(name, network_on_disk) net.mtime = os.path.getmtime(network_on_disk.filename) sd = sd_models.read_state_dict(network_on_disk.filename, what='network') - if shared.sd_model_type == 'f1': # if kohya flux lora, convert state_dict + if shared.sd_model_type in ['f1', 'chroma']: # if kohya flux lora, convert state_dict sd = lora_convert._convert_kohya_flux_lora_to_diffusers(sd) or sd # pylint: disable=protected-access assign_network_names_to_compvis_modules(shared.sd_model) keys_failed_to_match = {} diff --git a/installer.py b/installer.py index bf4d0b408..a634febf3 100644 --- a/installer.py +++ b/installer.py @@ -546,7 +546,7 @@ def check_diffusers(): t_start = time.time() if args.skip_all or args.skip_git or args.experimental: return - sha = '8adc6003ba4dbf5b61bb4f1ce571e9e55e145a99' # diffusers commit hash + sha = '900653c814cfa431877ae46548fe496506bda2ad' # diffusers commit hash pkg = pkg_resources.working_set.by_key.get('diffusers', None) minor = int(pkg.version.split('.')[1] if pkg is not None else 0) cur = opts.get('diffusers_version', '') if minor > 0 else '' diff --git a/modules/interposer.py b/modules/interposer.py index 2bf272706..c248c4f00 100644 --- a/modules/interposer.py +++ b/modules/interposer.py @@ -101,7 +101,7 @@ def map_model_name(name: str): return 'xl' if name == 'sd3': return 'v3' - if name == 'f1': + if name in ['f1', 'chroma']: return 'fx' return name diff --git a/modules/lora/lora_extract.py b/modules/lora/lora_extract.py index 4bd3a3adb..27bfb5c20 100644 --- a/modules/lora/lora_extract.py +++ b/modules/lora/lora_extract.py @@ -107,6 +107,9 @@ def make_meta(fn, maxrank, rank_ratio): elif shared.sd_model_type == "f1": meta["model_spec.architecture"] = "flux-1-dev/lora" meta["ss_base_model_version"] = "flux1" + elif shared.sd_model_type == "chroma": + meta["model_spec.architecture"] = "chroma/lora" + meta["ss_base_model_version"] = "chroma" elif shared.sd_model_type == "sc": meta["model_spec.architecture"] = "stable-cascade-v1-prior/lora" return meta diff --git a/modules/lora/lora_load.py b/modules/lora/lora_load.py index f02e4a274..100235bd1 100644 --- a/modules/lora/lora_load.py +++ b/modules/lora/lora_load.py @@ -85,7 +85,7 @@ def load_safetensors(name, network_on_disk) -> Union[network.Network, None]: net = network.Network(name, network_on_disk) net.mtime = os.path.getmtime(network_on_disk.filename) state_dict = sd_models.read_state_dict(network_on_disk.filename, what='network') - if shared.sd_model_type == 'f1': # if kohya flux lora, convert state_dict + if shared.sd_model_type in ['f1', 'chroma']: # if kohya flux lora, convert state_dict state_dict = lora_convert._convert_kohya_flux_lora_to_diffusers(state_dict) or state_dict # pylint: disable=protected-access if shared.sd_model_type == 'sd3': # if kohya flux lora, convert state_dict try: diff --git a/modules/lora/network.py b/modules/lora/network.py index f6d93009c..41d580e37 100644 --- a/modules/lora/network.py +++ b/modules/lora/network.py @@ -18,6 +18,7 @@ class SdVersion(enum.Enum): SC = 5 F1 = 6 HV = 7 + CHROMA = 8 class NetworkOnDisk: @@ -59,6 +60,8 @@ class NetworkOnDisk: return 'f1' if base.startswith("hunyuan_video"): return 'hv' + if base.startswith("chroma"): + return 'chroma' if arch.startswith("stable-diffusion-v1"): return 'sd1' @@ -70,6 +73,8 @@ class NetworkOnDisk: return 'f1' if arch.startswith("hunyuan-video"): return 'hv' + if arch.startswith("chroma"): + return 'chroma' if "v1-5" in str(self.metadata.get('ss_sd_model_name', "")): return 'sd1' @@ -79,6 +84,8 @@ class NetworkOnDisk: return 'f1' if 'xl' in self.name.lower(): return 'xl' + if 'chroma' in self.name.lower(): + return 'chroma' return '' diff --git a/modules/model_chroma.py b/modules/model_chroma.py new file mode 100644 index 000000000..b722837fc --- /dev/null +++ b/modules/model_chroma.py @@ -0,0 +1,339 @@ +import os +import json +import torch +import diffusers +import transformers +from safetensors.torch import load_file +from huggingface_hub import hf_hub_download, auth_check +from modules import shared, errors, devices, modelloader, sd_models, sd_unet, model_te, model_quant, sd_hijack_te + + +debug = shared.log.trace if os.environ.get('SD_LOAD_DEBUG', None) is not None else lambda *args, **kwargs: None + + +def load_chroma_quanto(checkpoint_info): + transformer, text_encoder = None, None + quanto = model_quant.load_quanto('Load model: type=Chroma') + + if isinstance(checkpoint_info, str): + repo_path = checkpoint_info + else: + repo_path = checkpoint_info.path + + try: + quantization_map = os.path.join(repo_path, "transformer", "quantization_map.json") + debug(f'Load model: type=Chroma quantization map="{quantization_map}" repo="{checkpoint_info.name}" component="transformer"') + if not os.path.exists(quantization_map): + repo_id = sd_models.path_to_repo(checkpoint_info.name) + quantization_map = hf_hub_download(repo_id, subfolder='transformer', filename='quantization_map.json', cache_dir=shared.opts.diffusers_dir) + with open(quantization_map, "r", encoding='utf8') as f: + quantization_map = json.load(f) + state_dict = load_file(os.path.join(repo_path, "transformer", "diffusion_pytorch_model.safetensors")) + dtype = state_dict['context_embedder.bias'].dtype + with torch.device("meta"): + transformer = diffusers.ChromaTransformer2DModel.from_config(os.path.join(repo_path, "transformer", "config.json")).to(dtype=dtype) + quanto.requantize(transformer, state_dict, quantization_map, device=torch.device("cpu")) + if shared.opts.diffusers_eval: + transformer.eval() + transformer_dtype = transformer.dtype + if transformer_dtype != devices.dtype: + try: + transformer = transformer.to(dtype=devices.dtype) + except Exception: + shared.log.error(f"Load model: type=Chroma Failed to cast transformer to {devices.dtype}, set dtype to {transformer_dtype}") + except Exception as e: + shared.log.error(f"Load model: type=Chroma failed to load Quanto transformer: {e}") + if debug: + errors.display(e, 'Chroma Quanto:') + + try: + quantization_map = os.path.join(repo_path, "text_encoder", "quantization_map.json") + debug(f'Load model: type=Chroma quantization map="{quantization_map}" repo="{checkpoint_info.name}" component="text_encoder"') + if not os.path.exists(quantization_map): + repo_id = sd_models.path_to_repo(checkpoint_info.name) + quantization_map = hf_hub_download(repo_id, subfolder='text_encoder', filename='quantization_map.json', cache_dir=shared.opts.diffusers_dir) + with open(quantization_map, "r", encoding='utf8') as f: + quantization_map = json.load(f) + with open(os.path.join(repo_path, "text_encoder", "config.json"), encoding='utf8') as f: + t5_config = transformers.T5Config(**json.load(f)) + state_dict = load_file(os.path.join(repo_path, "text_encoder", "model.safetensors")) + dtype = state_dict['encoder.block.0.layer.0.SelfAttention.relative_attention_bias.weight'].dtype + with torch.device("meta"): + text_encoder = transformers.T5EncoderModel(t5_config).to(dtype=dtype) + quanto.requantize(text_encoder, state_dict, quantization_map, device=torch.device("cpu")) + if shared.opts.diffusers_eval: + text_encoder.eval() + text_encoder_dtype = text_encoder.dtype + if text_encoder_dtype != devices.dtype: + try: + text_encoder = text_encoder.to(dtype=devices.dtype) + except Exception: + shared.log.error(f"Load model: type=Chroma Failed to cast text encoder to {devices.dtype}, set dtype to {text_encoder_dtype}") + except Exception as e: + shared.log.error(f"Load model: type=Chroma failed to load Quanto text encoder: {e}") + if debug: + errors.display(e, 'Chroma Quanto:') + + return transformer, text_encoder + + +def load_chroma_bnb(checkpoint_info, diffusers_load_config): # pylint: disable=unused-argument + transformer, text_encoder = None, None + if isinstance(checkpoint_info, str): + repo_path = checkpoint_info + else: + repo_path = checkpoint_info.path + model_quant.load_bnb('Load model: type=Chroma') + quant = model_quant.get_quant(repo_path) + try: + # we ignore the distilled guidance layer because it degrades quality too much + # see: https://github.com/huggingface/diffusers/pull/11698#issuecomment-2969717180 for more details + if quant == 'fp8': + quantization_config = transformers.BitsAndBytesConfig(load_in_8bit=True, llm_int8_skip_modules=["distilled_guidance_layer"], bnb_4bit_compute_dtype=devices.dtype) + debug(f'Quantization: {quantization_config}') + transformer = diffusers.ChromaTransformer2DModel.from_single_file(repo_path, **diffusers_load_config, quantization_config=quantization_config) + elif quant == 'fp4': + quantization_config = transformers.BitsAndBytesConfig(load_in_4bit=True, llm_int8_skip_modules=["distilled_guidance_layer"], bnb_4bit_compute_dtype=devices.dtype, bnb_4bit_quant_type= 'fp4') + debug(f'Quantization: {quantization_config}') + transformer = diffusers.ChromaTransformer2DModel.from_single_file(repo_path, **diffusers_load_config, quantization_config=quantization_config) + elif quant == 'nf4': + quantization_config = transformers.BitsAndBytesConfig(load_in_4bit=True, llm_int8_skip_modules=["distilled_guidance_layer"], bnb_4bit_compute_dtype=devices.dtype, bnb_4bit_quant_type= 'nf4') + debug(f'Quantization: {quantization_config}') + transformer = diffusers.ChromaTransformer2DModel.from_single_file(repo_path, **diffusers_load_config, quantization_config=quantization_config) + else: + transformer = diffusers.ChromaTransformer2DModel.from_single_file(repo_path, **diffusers_load_config) + except Exception as e: + shared.log.error(f"Load model: type=Chroma failed to load BnB transformer: {e}") + transformer, text_encoder = None, None + if debug: + errors.display(e, 'Chroma:') + return transformer, text_encoder + + +def load_quants(kwargs, pretrained_model_name_or_path, cache_dir, allow_quant): + try: + if 'transformer' not in kwargs and model_quant.check_nunchaku('Transformer'): + raise NotImplementedError('Nunchaku does not support Chroma Model yet. See https://github.com/mit-han-lab/nunchaku/issues/167') + elif 'transformer' not in kwargs and model_quant.check_quant('Transformer'): + quant_args = model_quant.create_config(allow=allow_quant, module='Transformer') + if quant_args: + if os.path.isfile(pretrained_model_name_or_path): + kwargs['transformer'] = diffusers.ChromaTransformer2DModel.from_single_file(pretrained_model_name_or_path, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) + pass + else: + kwargs['transformer'] = diffusers.ChromaTransformer2DModel.from_pretrained(pretrained_model_name_or_path, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) + if 'text_encoder' not in kwargs and model_quant.check_nunchaku('TE'): + import nunchaku + nunchaku_precision = nunchaku.utils.get_precision() + nunchaku_repo = 'mit-han-lab/nunchaku-t5/awq-int4-flux.1-t5xxl.safetensors' + shared.log.debug(f'Load module: quant=Nunchaku module=t5 repo="{nunchaku_repo}" precision={nunchaku_precision}') + kwargs['text_encoder'] = nunchaku.NunchakuT5EncoderModel.from_pretrained(nunchaku_repo, torch_dtype=devices.dtype) + elif 'text_encoder' not in kwargs and model_quant.check_quant('TE'): + quant_args = model_quant.create_config(allow=allow_quant, module='TE') + if quant_args: + if os.path.isfile(pretrained_model_name_or_path): + kwargs['text_encoder'] = transformers.T5EncoderModel.from_single_file(pretrained_model_name_or_path, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) + else: + kwargs['text_encoder'] = transformers.T5EncoderModel.from_pretrained(pretrained_model_name_or_path, subfolder="text_encoder", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) + except Exception as e: + shared.log.error(f'Quantization: {e}') + errors.display(e, 'Quantization:') + return kwargs + + +def load_transformer(file_path): # triggered by opts.sd_unet change + if file_path is None or not os.path.exists(file_path): + return None + transformer = None + quant = model_quant.get_quant(file_path) + diffusers_load_config = { + "low_cpu_mem_usage": True, + "torch_dtype": devices.dtype, + "cache_dir": shared.opts.hfcache_dir, + } + if quant is not None and quant != 'none': + shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} prequant={quant} dtype={devices.dtype}') + if 'gguf' in file_path.lower(): + from modules import ggml + _transformer = ggml.load_gguf(file_path, cls=diffusers.ChromaTransformer2DModel, compute_dtype=devices.dtype) + if _transformer is not None: + transformer = _transformer + elif quant == 'qint8' or quant == 'qint4': + _transformer, _text_encoder = load_chroma_quanto(file_path) + if _transformer is not None: + transformer = _transformer + elif quant == 'fp8' or quant == 'fp4' or quant == 'nf4': + _transformer, _text_encoder = load_chroma_bnb(file_path, diffusers_load_config) + if _transformer is not None: + transformer = _transformer + elif 'nf4' in quant: # TODO chroma: loader for civitai nf4 models + from modules.model_chroma_nf4 import load_chroma_nf4 + _transformer, _text_encoder = load_chroma_nf4(file_path, prequantized=True) + if _transformer is not None: + transformer = _transformer + else: + quant_args = model_quant.create_bnb_config({}) + if quant_args: + shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=bnb dtype={devices.dtype}') + from modules.model_chroma_nf4 import load_chroma_nf4 + transformer, _text_encoder = load_chroma_nf4(file_path, prequantized=False) + if transformer is not None: + return transformer + quant_args = model_quant.create_config(module='Transformer') + if quant_args: + shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=torchao dtype={devices.dtype}') + transformer = diffusers.ChromaTransformer2DModel.from_single_file(file_path, **diffusers_load_config, **quant_args) + if transformer is not None: + return transformer + shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=none dtype={devices.dtype}') + # TODO chroma transformer from-single-file with quant + # shared.log.warning('Load module: type=UNet/Transformer does not support load-time quantization') + transformer = diffusers.ChromaTransformer2DModel.from_single_file(file_path, **diffusers_load_config) + if transformer is None: + shared.log.error('Failed to load UNet model') + shared.opts.sd_unet = 'Default' + return transformer + + +def load_chroma(checkpoint_info, diffusers_load_config): # triggered by opts.sd_checkpoint change + fn = checkpoint_info.path + repo_id = sd_models.path_to_repo(checkpoint_info.name) + login = modelloader.hf_login() + try: + auth_check(repo_id) + except Exception as e: + repo_id = None + if not os.path.exists(fn): + shared.log.error(f'Load model: repo="{repo_id}" login={login} {e}') + return None + + prequantized = model_quant.get_quant(checkpoint_info.path) + shared.log.debug(f'Load model: type=Chroma model="{checkpoint_info.name}" repo={repo_id or "none"} unet="{shared.opts.sd_unet}" te="{shared.opts.sd_text_encoder}" vae="{shared.opts.sd_vae}" quant={prequantized} offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype}') + debug(f'Load model: type=Chroma config={diffusers_load_config}') + + transformer = None + text_encoder = None + vae = None + + # unload current model + sd_models.unload_model_weights() + shared.sd_model = None + devices.torch_gc(force=True) + + if shared.opts.teacache_enabled: + from modules import teacache + shared.log.debug(f'Transformers cache: type=teacache patch=forward cls={diffusers.ChromaTransformer2DModel.__name__}') + diffusers.ChromaTransformer2DModel.forward = teacache.teacache_chroma_forward # patch must be done before transformer is loaded + + # load overrides if any + if shared.opts.sd_unet != 'Default': + try: + debug(f'Load model: type=Chroma unet="{shared.opts.sd_unet}"') + transformer = load_transformer(sd_unet.unet_dict[shared.opts.sd_unet]) + if transformer is None: + shared.opts.sd_unet = 'Default' + sd_unet.failed_unet.append(shared.opts.sd_unet) + except Exception as e: + shared.log.error(f"Load model: type=Chroma failed to load UNet: {e}") + shared.opts.sd_unet = 'Default' + if debug: + errors.display(e, 'Chroma UNet:') + if shared.opts.sd_text_encoder != 'Default': + try: + debug(f'Load model: type=Chroma te="{shared.opts.sd_text_encoder}"') + from modules.model_te import load_t5 + text_encoder = load_t5(name=shared.opts.sd_text_encoder, cache_dir=shared.opts.diffusers_dir) + except Exception as e: + shared.log.error(f"Load model: type=Chroma failed to load T5: {e}") + shared.opts.sd_text_encoder = 'Default' + if debug: + errors.display(e, 'Chroma T5:') + if shared.opts.sd_vae != 'Default' and shared.opts.sd_vae != 'Automatic': + try: + debug(f'Load model: type=Chroma vae="{shared.opts.sd_vae}"') + from modules import sd_vae + # vae = sd_vae.load_vae_diffusers(None, sd_vae.vae_dict[shared.opts.sd_vae], 'override') + vae_file = sd_vae.vae_dict[shared.opts.sd_vae] + if os.path.exists(vae_file): + vae_config = os.path.join('configs', 'chroma', 'vae', 'config.json') + vae = diffusers.AutoencoderKL.from_single_file(vae_file, config=vae_config, **diffusers_load_config) + except Exception as e: + shared.log.error(f"Load model: type=Chroma failed to load VAE: {e}") + shared.opts.sd_vae = 'Default' + if debug: + errors.display(e, 'Chroma VAE:') + + # load quantized components if any + if prequantized == 'nf4': + try: + from modules.model_chroma_nf4 import load_chroma_nf4 + _transformer, _text_encoder = load_chroma_nf4(checkpoint_info) + if _transformer is not None: + transformer = _transformer + if _text_encoder is not None: + text_encoder = _text_encoder + except Exception as e: + shared.log.error(f"Load model: type=Chroma failed to load NF4 components: {e}") + if debug: + errors.display(e, 'Chroma NF4:') + if prequantized == 'qint8' or prequantized == 'qint4': + try: + _transformer, _text_encoder = load_chroma_quanto(checkpoint_info) + if _transformer is not None: + transformer = _transformer + if _text_encoder is not None: + text_encoder = _text_encoder + except Exception as e: + shared.log.error(f"Load model: type=Chroma failed to load Quanto components: {e}") + if debug: + errors.display(e, 'Chroma Quanto:') + + # initialize pipeline with pre-loaded components + kwargs = {} + if transformer is not None: + kwargs['transformer'] = transformer + sd_unet.loaded_unet = shared.opts.sd_unet + if text_encoder is not None: + kwargs['text_encoder'] = text_encoder + model_te.loaded_te = shared.opts.sd_text_encoder + if vae is not None: + kwargs['vae'] = vae + + # Todo: atm only ChromaPipeline is implemented in diffusers. + # Need to add ChromaFillPipeline, ChromaControlPipeline, ChromaImg2ImgPipeline etc when available. + # Chroma will support inpainting *after* its training has finished: + # https://huggingface.co/lodestones/Chroma/discussions/28#6826dd2ed86f53ff983add5c + cls = diffusers.ChromaPipeline + shared.log.debug(f'Load model: type=Chroma cls={cls.__name__} preloaded={list(kwargs)} revision={diffusers_load_config.get("revision", None)}') + for c in kwargs: + if getattr(kwargs[c], 'quantization_method', None) is not None or getattr(kwargs[c], 'gguf', None) is not None: + shared.log.debug(f'Load model: type=Chroma component={c} dtype={kwargs[c].dtype} quant={getattr(kwargs[c], "quantization_method", None) or getattr(kwargs[c], "gguf", None)}') + if kwargs[c].dtype == torch.float32 and devices.dtype != torch.float32: + try: + kwargs[c] = kwargs[c].to(dtype=devices.dtype) + shared.log.warning(f'Load model: type=Chroma component={c} dtype={kwargs[c].dtype} cast dtype={devices.dtype} recast') + except Exception: + pass + + allow_quant = 'gguf' not in (sd_unet.loaded_unet or '') and (prequantized is None or prequantized == 'none') + if (fn is None) or (not os.path.exists(fn) or os.path.isdir(fn)): + kwargs = load_quants(kwargs, repo_id or fn, cache_dir=shared.opts.diffusers_dir, allow_quant=allow_quant) + # kwargs = model_quant.create_config(kwargs, allow_quant) + if fn.endswith('.safetensors') and os.path.isfile(fn): + pipe = diffusers.ChromaPipeline.from_single_file(fn, cache_dir=shared.opts.diffusers_dir, **kwargs, **diffusers_load_config) + else: + pipe = cls.from_pretrained(repo_id or fn, cache_dir=shared.opts.diffusers_dir, **kwargs, **diffusers_load_config) + + if shared.opts.teacache_enabled and model_quant.check_nunchaku('Transformer'): + from nunchaku.caching.diffusers_adapters import apply_cache_on_pipe + apply_cache_on_pipe(pipe, residual_diff_threshold=0.12) + + # release memory + transformer = None + text_encoder = None + vae = None + for k in kwargs.keys(): + kwargs[k] = None + sd_hijack_te.init_hijack(pipe) + devices.torch_gc(force=True) + return pipe diff --git a/modules/model_chroma_nf4.py b/modules/model_chroma_nf4.py new file mode 100644 index 000000000..c72d624ab --- /dev/null +++ b/modules/model_chroma_nf4.py @@ -0,0 +1,202 @@ +""" +Copied from: https://github.com/huggingface/diffusers/issues/9165 ++ adjusted for Chroma by skipping distilled guidance layer +""" + +import os +import torch +import torch.nn as nn +from transformers.quantizers.quantizers_utils import get_module_from_name +from huggingface_hub import hf_hub_download +from accelerate import init_empty_weights +from accelerate.utils import set_module_tensor_to_device +from diffusers.loaders.single_file_utils import convert_chroma_transformer_checkpoint_to_diffusers +import safetensors.torch +from modules import shared, devices, model_quant + + +debug = os.environ.get('SD_LOAD_DEBUG', None) is not None + + +def _replace_with_bnb_linear( + model, + method="nf4", + has_been_replaced=False, +): + """ + Private method that wraps the recursion for module replacement. + Returns the converted model and a boolean that indicates if the conversion has been successfull or not. + """ + bnb = model_quant.load_bnb('Load model: type=Chroma') + for name, module in model.named_children(): + # we ignore the distilled guidance layer because it degrades quality too much + # see: https://github.com/huggingface/diffusers/pull/11698#issuecomment-2969717180 for more details + if "distilled_guidance_layer" in name: + continue + if isinstance(module, nn.Linear): + with init_empty_weights(): + in_features = module.in_features + out_features = module.out_features + + if method == "llm_int8": + model._modules[name] = bnb.nn.Linear8bitLt( # pylint: disable=protected-access + in_features, + out_features, + module.bias is not None, + has_fp16_weights=False, + threshold=6.0, + ) + has_been_replaced = True + else: + model._modules[name] = bnb.nn.Linear4bit( # pylint: disable=protected-access + in_features, + out_features, + module.bias is not None, + compute_dtype=devices.dtype, + compress_statistics=False, + quant_type="nf4", + ) + has_been_replaced = True + # Store the module class in case we need to transpose the weight later + model._modules[name].source_cls = type(module) # pylint: disable=protected-access + # Force requires grad to False to avoid unexpected errors + model._modules[name].requires_grad_(False) # pylint: disable=protected-access + + if len(list(module.children())) > 0: + _, has_been_replaced = _replace_with_bnb_linear( + module, + has_been_replaced=has_been_replaced, + ) + # Remove the last key for recursion + return model, has_been_replaced + + +def check_quantized_param( + model, + param_name: str, +) -> bool: + bnb = model_quant.load_bnb('Load model: type=Chroma') + module, tensor_name = get_module_from_name(model, param_name) + if isinstance(module._parameters.get(tensor_name, None), bnb.nn.Params4bit): # pylint: disable=protected-access + # Add here check for loaded components' dtypes once serialization is implemented + return True + elif isinstance(module, bnb.nn.Linear4bit) and tensor_name == "bias": + # bias could be loaded by regular set_module_tensor_to_device() from accelerate, + # but it would wrongly use uninitialized weight there. + return True + else: + return False + + +def create_quantized_param( + model, + param_value: "torch.Tensor", + param_name: str, + target_device: "torch.device", + state_dict=None, + unexpected_keys=None, + pre_quantized=False +): + bnb = model_quant.load_bnb('Load model: type=Chroma') + module, tensor_name = get_module_from_name(model, param_name) + + if tensor_name not in module._parameters: # pylint: disable=protected-access + raise ValueError(f"{module} does not have a parameter or a buffer named {tensor_name}.") + + old_value = getattr(module, tensor_name) + + if tensor_name == "bias": + if param_value is None: + new_value = old_value.to(target_device) + else: + new_value = param_value.to(target_device) + new_value = torch.nn.Parameter(new_value, requires_grad=old_value.requires_grad) + module._parameters[tensor_name] = new_value # pylint: disable=protected-access + return + + if not isinstance(module._parameters[tensor_name], bnb.nn.Params4bit): # pylint: disable=protected-access + raise ValueError("this function only loads `Linear4bit components`") + if ( + old_value.device == torch.device("meta") + and target_device not in ["meta", torch.device("meta")] + and param_value is None + ): + raise ValueError(f"{tensor_name} is on the meta device, we need a `value` to put in on {target_device}.") + + if pre_quantized: + if (param_name + ".quant_state.bitsandbytes__fp4" not in state_dict) and (param_name + ".quant_state.bitsandbytes__nf4" not in state_dict): + raise ValueError(f"Supplied state dict for {param_name} does not contain `bitsandbytes__*` and possibly other `quantized_stats` components.") + quantized_stats = {} + for k, v in state_dict.items(): + # `startswith` to counter for edge cases where `param_name` + # substring can be present in multiple places in the `state_dict` + if param_name + "." in k and k.startswith(param_name): + quantized_stats[k] = v + if unexpected_keys is not None and k in unexpected_keys: + unexpected_keys.remove(k) + new_value = bnb.nn.Params4bit.from_prequantized( + data=param_value, + quantized_stats=quantized_stats, + requires_grad=False, + device=target_device, + ) + else: + new_value = param_value.to("cpu") + kwargs = old_value.__dict__ + new_value = bnb.nn.Params4bit(new_value, requires_grad=False, **kwargs).to(target_device) + module._parameters[tensor_name] = new_value # pylint: disable=protected-access + + +def load_chroma_nf4(checkpoint_info, prequantized: bool = True): + transformer = None + text_encoder = None + if isinstance(checkpoint_info, str): + repo_path = checkpoint_info + else: + repo_path = checkpoint_info.path + if os.path.exists(repo_path) and os.path.isfile(repo_path): + ckpt_path = repo_path + elif os.path.exists(repo_path) and os.path.isdir(repo_path) and os.path.exists(os.path.join(repo_path, "diffusion_pytorch_model.safetensors")): + ckpt_path = os.path.join(repo_path, "diffusion_pytorch_model.safetensors") + else: + ckpt_path = hf_hub_download(repo_path, filename="diffusion_pytorch_model.safetensors", cache_dir=shared.opts.diffusers_dir) + original_state_dict = safetensors.torch.load_file(ckpt_path) + + try: + converted_state_dict = convert_chroma_transformer_checkpoint_to_diffusers(original_state_dict) + except Exception as e: + shared.log.error(f"Load model: type=Chroma Failed to convert UNET: {e}") + if debug: + from modules import errors + errors.display(e, 'Chroma convert:') + converted_state_dict = original_state_dict + + with init_empty_weights(): + from diffusers import ChromaTransformer2DModel + config = ChromaTransformer2DModel.load_config(os.path.join('configs', 'chroma'), subfolder="transformer") + transformer = ChromaTransformer2DModel.from_config(config).to(devices.dtype) + expected_state_dict_keys = list(transformer.state_dict().keys()) + + _replace_with_bnb_linear(transformer, "nf4") + + try: + for param_name, param in converted_state_dict.items(): + if param_name not in expected_state_dict_keys: + continue + is_param_float8_e4m3fn = hasattr(torch, "float8_e4m3fn") and param.dtype == torch.float8_e4m3fn + if torch.is_floating_point(param) and not is_param_float8_e4m3fn: + param = param.to(devices.dtype) + if not check_quantized_param(transformer, param_name): + set_module_tensor_to_device(transformer, param_name, device=0, value=param) + else: + create_quantized_param(transformer, param, param_name, target_device=0, state_dict=original_state_dict, pre_quantized=prequantized) + except Exception as e: + transformer, text_encoder = None + shared.log.error(f"Load model: type=Chroma failed to load UNET: {e}") + if debug: + from modules import errors + errors.display(e, 'Chroma:') + + del original_state_dict + devices.torch_gc(force=True) + return transformer, text_encoder diff --git a/modules/model_quant.py b/modules/model_quant.py index 3c57ac67c..74bd5f822 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -427,8 +427,12 @@ def optimum_quanto_model(model, op=None, sd_model=None, weights=None, activation from modules import devices, shared quanto = load_quanto('Quantize model: type=Optimum Quanto') global quant_last_model_name, quant_last_model_device # pylint: disable=global-statement - if sd_model is not None and "Flux" in sd_model.__class__.__name__: # LayerNorm is not supported + if sd_model is not None and "Flux" in sd_model.__class__.__name__ or "Chroma" in sd_model.__class__.__name__: # LayerNorm is not supported exclude_list = ["transformer_blocks.*.norm1.norm", "transformer_blocks.*.norm2", "transformer_blocks.*.norm1_context.norm", "transformer_blocks.*.norm2_context", "single_transformer_blocks.*.norm.norm", "norm_out.norm"] + if "Chroma" in sd_model.__class__.__name__: + # we ignore the distilled guidance layer because it degrades quality too much + # see: https://github.com/huggingface/diffusers/pull/11698#issuecomment-2969717180 for more details + exclude_list.append("distilled_guidance_layer.*") else: exclude_list = None weights = getattr(quanto, weights) if weights is not None else getattr(quanto, shared.opts.optimum_quanto_weights_type) diff --git a/modules/modeldata.py b/modules/modeldata.py index 688cff23d..0aaaaeb63 100644 --- a/modules/modeldata.py +++ b/modules/modeldata.py @@ -27,6 +27,8 @@ def get_model_type(pipe): model_type = 'sc' elif "AuraFlow" in name: model_type = 'auraflow' + elif 'Chroma' in name: + model_type = 'chroma' elif "Flux" in name or "Flex1" in name or "Flex2" in name: model_type = 'f1' elif "Lumina2" in name: diff --git a/modules/processing_args.py b/modules/processing_args.py index 6ef43667b..fca0115de 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -150,6 +150,7 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t 'StableDiffusion' in model.__class__.__name__ or 'StableCascade' in model.__class__.__name__ or 'Flux' in model.__class__.__name__ or + 'Chroma' in model.__class__.__name__ or 'HiDreamImagePipeline' in model.__class__.__name__ # hidream-e1 has different embeds ): try: @@ -174,15 +175,14 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t args['prompt_embeds_llama3'] = prompt_embeds[1] elif hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and 'prompt_embeds' in possible and prompt_parser_diffusers.embedder is not None: args['prompt_embeds'] = prompt_parser_diffusers.embedder('prompt_embeds') - if prompt_parser_diffusers.embedder is not None: - if 'StableCascade' in model.__class__.__name__: - args['prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('positive_pooleds').unsqueeze(0) - elif 'XL' in model.__class__.__name__: - args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') - elif 'StableDiffusion3' in model.__class__.__name__: - args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') - elif 'Flux' in model.__class__.__name__: - args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') + if 'StableCascade' in model.__class__.__name__: + args['prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('positive_pooleds').unsqueeze(0) + elif 'XL' in model.__class__.__name__: + args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') + elif 'StableDiffusion3' in model.__class__.__name__: + args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') + elif 'Flux' in model.__class__.__name__: + args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') else: args['prompt'] = prompts if 'negative_prompt' in possible: @@ -193,13 +193,12 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t args['negative_prompt_embeds_llama3'] = negative_prompt_embeds[1] elif hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and 'negative_prompt_embeds' in possible and prompt_parser_diffusers.embedder is not None: args['negative_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_prompt_embeds') - if prompt_parser_diffusers.embedder is not None: - if 'StableCascade' in model.__class__.__name__: - args['negative_prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('negative_pooleds').unsqueeze(0) - elif 'XL' in model.__class__.__name__: - args['negative_pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_pooleds') - elif 'StableDiffusion3' in model.__class__.__name__: - args['negative_pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_pooleds') + if 'StableCascade' in model.__class__.__name__: + args['negative_prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('negative_pooleds').unsqueeze(0) + elif 'XL' in model.__class__.__name__: + args['negative_pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_pooleds') + elif 'StableDiffusion3' in model.__class__.__name__: + args['negative_pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_pooleds') else: if 'PixArtSigmaPipeline' in model.__class__.__name__: # pixart-sigma pipeline throws list-of-list for negative prompt args['negative_prompt'] = negative_prompts[0] diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index ad80e3af0..1e4f5246a 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -7,7 +7,7 @@ import torch from compel.embeddings_provider import BaseTextualInversionManager, EmbeddingsProvider from transformers import PreTrainedTokenizer from modules import shared, prompt_parser, devices, sd_models -from modules.prompt_parser_xhinker import get_weighted_text_embeddings_sd15, get_weighted_text_embeddings_sdxl_2p, get_weighted_text_embeddings_sd3, get_weighted_text_embeddings_flux1 +from modules.prompt_parser_xhinker import get_weighted_text_embeddings_sd15, get_weighted_text_embeddings_sdxl_2p, get_weighted_text_embeddings_sd3, get_weighted_text_embeddings_flux1, get_weighted_text_embeddings_chroma debug_enabled = os.environ.get('SD_PROMPT_DEBUG', None) debug = shared.log.trace if debug_enabled else lambda *args, **kwargs: None @@ -27,6 +27,7 @@ def prompt_compatible(pipe = None): 'DemoFusion' not in pipe.__class__.__name__ and 'StableCascade' not in pipe.__class__.__name__ and 'Flux' not in pipe.__class__.__name__ and + 'Chroma' not in pipe.__class__.__name__ and 'HiDreamImage' not in pipe.__class__.__name__ ): shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") @@ -510,6 +511,10 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c prompt_embeds, pooled_prompt_embeds, _ = pipe.encode_prompt(prompt=prompt, prompt_2=prompt_2, device=device, num_images_per_prompt=1) return prompt_embeds, pooled_prompt_embeds, None, None # no negative support + if "Chroma" in pipe.__class__.__name__: # does not use clip and has no pooled embeds + prompt_embeds, _, _, negative_prompt_embeds, _, _ = pipe.encode_prompt(prompt=prompt, negative_prompt=neg_prompt, device=device, num_images_per_prompt=1) + return prompt_embeds, None, negative_prompt_embeds, None + if "HiDreamImage" in pipe.__class__.__name__: # clip is only used for the pooled embeds prompt_embeds_t5, negative_prompt_embeds_t5, prompt_embeds_llama3, negative_prompt_embeds_llama3, pooled_prompt_embeds, negative_pooled_prompt_embeds = pipe.encode_prompt( prompt=prompt, prompt_2=prompt_2, prompt_3=prompt_3, prompt_4=prompt_4, @@ -662,6 +667,8 @@ def get_xhinker_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", cl prompt_embed, negative_embed, positive_pooled, negative_pooled = get_weighted_text_embeddings_sd3(pipe=pipe, prompt=prompt, neg_prompt=neg_prompt, use_t5_encoder=bool(pipe.text_encoder_3)) elif 'Flux' in pipe.__class__.__name__: prompt_embed, positive_pooled = get_weighted_text_embeddings_flux1(pipe=pipe, prompt=prompt, prompt2=prompt_2, device=devices.device) + elif 'Chroma' in pipe.__class__.__name__: + prompt_embed, negative_embed = get_weighted_text_embeddings_chroma(pipe=pipe, prompt=prompt, neg_prompt=neg_prompt, device=devices.device) elif 'XL' in pipe.__class__.__name__: prompt_embed, negative_embed, positive_pooled, negative_pooled = get_weighted_text_embeddings_sdxl_2p(pipe=pipe, prompt=prompt, prompt_2=prompt_2, neg_prompt=neg_prompt, neg_prompt_2=neg_prompt_2) else: diff --git a/modules/prompt_parser_xhinker.py b/modules/prompt_parser_xhinker.py index c0ddc9bc7..d2cc1971d 100644 --- a/modules/prompt_parser_xhinker.py +++ b/modules/prompt_parser_xhinker.py @@ -17,11 +17,13 @@ ## ----------------------------------------------------------------------------- import torch +import torch.nn.functional as F from transformers import CLIPTokenizer, T5Tokenizer from diffusers import StableDiffusionPipeline from diffusers import StableDiffusionXLPipeline from diffusers import StableDiffusion3Pipeline from diffusers import FluxPipeline +from diffusers import ChromaPipeline from modules.prompt_parser import parse_prompt_attention # use built-in A1111 parser @@ -1424,3 +1426,75 @@ def get_weighted_text_embeddings_flux1( t5_prompt_embeds = t5_prompt_embeds.to(dtype=pipe.text_encoder_2.dtype, device=device) return t5_prompt_embeds, prompt_embeds + + +def get_weighted_text_embeddings_chroma( + pipe: ChromaPipeline, + prompt: str = "", + neg_prompt: str = "", + device=None +): + """ + This function can process long prompt with weights for Chroma model + + Args: + pipe (ChromaPipeline) + prompt (str) + neg_prompt (str) + device (torch.device, optional): Device to run the embeddings on. + Returns: + prompt_embeds (T5 prompt embeds as torch.Tensor) + neg_prompt_embeds (T5 prompt embeds as torch.Tensor) + """ + if device is None: + device = pipe.text_encoder.device + + # prompt + prompt_tokens, prompt_weights = get_prompts_tokens_with_weights_t5( + pipe.tokenizer, prompt + ) + + prompt_tokens = torch.tensor([prompt_tokens], dtype=torch.long) + + t5_prompt_embeds = pipe.text_encoder(prompt_tokens.to(device))[0].squeeze(0) + t5_prompt_embeds = t5_prompt_embeds.to(device=device) + + # add weight to t5 prompt + for z in range(len(prompt_weights)): + if prompt_weights[z] != 1.0: + t5_prompt_embeds[z] = t5_prompt_embeds[z] * prompt_weights[z] + t5_prompt_embeds = t5_prompt_embeds.unsqueeze(0) + t5_prompt_embeds = t5_prompt_embeds.to(dtype=pipe.text_encoder.dtype, device=device) + + # negative prompt + neg_prompt_tokens, neg_prompt_weights = get_prompts_tokens_with_weights_t5( + pipe.tokenizer, neg_prompt + ) + + neg_prompt_tokens = torch.tensor([neg_prompt_tokens], dtype=torch.long) + + t5_neg_prompt_embeds = pipe.text_encoder(neg_prompt_tokens.to(device))[0].squeeze(0) + t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(device=device) + + # add weight to neg t5 embeddings + for z in range(len(neg_prompt_weights)): + if neg_prompt_weights[z] != 1.0: + t5_neg_prompt_embeds[z] = t5_neg_prompt_embeds[z] * neg_prompt_weights[z] + t5_neg_prompt_embeds = t5_neg_prompt_embeds.unsqueeze(0) + t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(dtype=pipe.text_encoder.dtype, device=device) + + def pad_prompt_embeds_to_same_size(prompt_embeds_a, prompt_embeds_b): + size_a = prompt_embeds_a.size(1) + size_b = prompt_embeds_b.size(1) + + if size_a < size_b: + pad_size = size_b - size_a + prompt_embeds_a = F.pad(prompt_embeds_a, (0, 0, 0, pad_size)) # Pad dim=1 + elif size_b < size_a: + pad_size = size_a - size_b + prompt_embeds_b = F.pad(prompt_embeds_b, (0, 0, 0, pad_size)) # Pad dim=1 + + return prompt_embeds_a, prompt_embeds_b + + # chroma needs positive and negative prompt embeddings to have the same length (for now) + return pad_prompt_embeds_to_same_size(t5_prompt_embeds, t5_neg_prompt_embeds) diff --git a/modules/sd_detect.py b/modules/sd_detect.py index 3c2c1be72..9a360e922 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -92,6 +92,8 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): guess = 'Stable Diffusion 3' if 'hidream' in f.lower(): guess = 'HiDream' + if 'chroma' in f.lower(): + guess = 'Chroma' if 'flux' in f.lower() or 'flex.1' in f.lower() or 'lodestones' in f.lower(): guess = 'FLUX' if size > 11000 and size < 16000: diff --git a/modules/sd_models.py b/modules/sd_models.py index d8aad13a3..2c908e5ba 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -329,6 +329,9 @@ def load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op=' elif model_type in ['FLEX']: from modules.model_flex import load_flex sd_model = load_flex(checkpoint_info, diffusers_load_config) + elif model_type in ['Chroma']: + from modules.model_chroma import load_chroma + sd_model = load_chroma(checkpoint_info, diffusers_load_config) elif model_type in ['Lumina 2']: from modules.model_lumina import load_lumina2 sd_model = load_lumina2(checkpoint_info, diffusers_load_config) diff --git a/modules/sd_offload.py b/modules/sd_offload.py index c57165fb5..d2a56c349 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -11,7 +11,7 @@ from modules.timer import process as process_timer debug = os.environ.get('SD_MOVE_DEBUG', None) is not None debug_move = log.trace if debug else lambda *args, **kwargs: None -offload_warn = ['sc', 'sd3', 'f1', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'cogview4'] +offload_warn = ['sc', 'sd3', 'f1', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'cogview4', 'chroma'] offload_post = ['h1'] offload_hook_instance = None balanced_offload_exclude = ['OmniGenPipeline', 'CogView4Pipeline'] diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index f73520027..aa3e4d21a 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -12,7 +12,7 @@ samplers = all_samplers samplers_for_img2img = all_samplers samplers_map = {} loaded_config = None -flow_models = ['Flux', 'StableDiffusion3', 'Lumina', 'AuraFlow', 'Sana', 'CogView4', 'HiDream'] +flow_models = ['Flux', 'StableDiffusion3', 'Lumina', 'AuraFlow', 'Sana', 'CogView4', 'HiDream', 'Chroma'] flow_models += ['Hunyuan', 'LTX', 'Mochi'] diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 52fd6313e..21e01a45b 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -9,7 +9,7 @@ from modules import shared, devices, processing, images, sd_vae_approx, sd_vae_t SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options']) approximation_indexes = { "Simple": 0, "Approximate": 1, "TAESD": 2, "Full VAE": 3 } -flow_models = ['f1', 'sd3', 'lumina', 'auraflow', 'sana', 'lumina2', 'cogview4', 'h1'] +flow_models = ['f1', 'sd3', 'lumina', 'auraflow', 'sana', 'lumina2', 'cogview4', 'h1', 'chroma'] warned = False queue_lock = threading.Lock() diff --git a/modules/sd_vae_remote.py b/modules/sd_vae_remote.py index 741d349bc..5ee5522d4 100644 --- a/modules/sd_vae_remote.py +++ b/modules/sd_vae_remote.py @@ -12,6 +12,7 @@ hf_decode_endpoints = { 'sd': 'https://q1bj3bpq6kzilnsu.us-east-1.aws.endpoints.huggingface.cloud', 'sdxl': 'https://x2dmsqunjd6k9prw.us-east-1.aws.endpoints.huggingface.cloud', 'f1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', + 'chroma': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', 'h1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', 'hunyuanvideo': 'https://o7ywnmrahorts457.us-east-1.aws.endpoints.huggingface.cloud', } @@ -19,6 +20,7 @@ hf_encode_endpoints = { 'sd': 'https://qc6479g0aac6qwy9.us-east-1.aws.endpoints.huggingface.cloud', 'sdxl': 'https://xjqqhmyn62rog84g.us-east-1.aws.endpoints.huggingface.cloud', 'f1': 'https://ptccx55jz97f9zgo.us-east-1.aws.endpoints.huggingface.cloud', + 'chroma': 'https://ptccx55jz97f9zgo.us-east-1.aws.endpoints.huggingface.cloud', } dtypes = { "float16": torch.float16, @@ -48,7 +50,7 @@ def remote_decode(latents: torch.Tensor, width: int = 0, height: int = 0, model_ params = {} try: latent = latent_copy[i] - if model_type != 'f1': + if model_type not in ['f1', 'chroma']: latent = latent.unsqueeze(0) params = { "input_tensor_type": "binary", @@ -74,7 +76,7 @@ def remote_decode(latents: torch.Tensor, width: int = 0, height: int = 0, model_ params["output_type"] = "pt" params["output_tensor_type"] = "binary" headers["Accept"] = "tensor/binary" - if (model_type == 'f1' or model_type == 'h1') and (width > 0) and (height > 0): + if (model_type in ['f1', 'h1', 'chroma']) and (width > 0) and (height > 0): params['width'] = width params['height'] = height if shared.sd_model.vae is not None and shared.sd_model.vae.config is not None: diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index 3bcd1b322..1eca9ae1f 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -55,7 +55,7 @@ def get_model(model_type = 'decoder', variant = None): cls = shared.sd_model_type if cls in {'ldm', 'pixartalpha'}: cls = 'sd' - elif cls in {'h1', 'lumina2'}: + elif cls in {'h1', 'lumina2', 'chroma'}: cls = 'f1' elif cls == 'pixartsigma': cls = 'sdxl' diff --git a/modules/shared_items.py b/modules/shared_items.py index 9a09b8c7f..8d64f947b 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -27,6 +27,7 @@ pipelines = { 'DeepFloyd IF': getattr(diffusers, 'IFPipeline', None), 'FLUX': getattr(diffusers, 'FluxPipeline', None), 'FLEX': getattr(diffusers, 'AutoPipelineForText2Image', None), + 'Chroma': getattr(diffusers, 'ChromaPipeline', None), 'Sana': getattr(diffusers, 'SanaPipeline', None), 'Lumina-Next': getattr(diffusers, 'LuminaText2ImgPipeline', None), 'Lumina 2': getattr(diffusers, 'Lumina2Pipeline', None), diff --git a/modules/teacache/__init__.py b/modules/teacache/__init__.py index 2fb81b7c9..56a261241 100644 --- a/modules/teacache/__init__.py +++ b/modules/teacache/__init__.py @@ -4,9 +4,10 @@ from .teacache_lumina2 import teacache_lumina2_forward from .teacache_ltx import teacache_ltx_forward from .teacache_mochi import teacache_mochi_forward from .teacache_cogvideox import teacache_cog_forward +from .teacache_chroma import teacache_chroma_forward -supported_models = ['Flux', 'CogVideoX', 'Mochi', 'LTX', 'HiDream', 'Lumina2'] +supported_models = ['Flux', 'Chroma', 'CogVideoX', 'Mochi', 'LTX', 'HiDream', 'Lumina2'] def apply_teacache(p): diff --git a/modules/teacache/teacache_chroma.py b/modules/teacache/teacache_chroma.py new file mode 100644 index 000000000..999b3095b --- /dev/null +++ b/modules/teacache/teacache_chroma.py @@ -0,0 +1,325 @@ +from typing import Any, Dict, Optional, Union +import torch +import numpy as np +from diffusers.models.modeling_outputs import Transformer2DModelOutput +from diffusers.utils import USE_PEFT_BACKEND, is_torch_version, logging, scale_lora_layers, unscale_lora_layers + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +def teacache_chroma_forward( + self, + hidden_states: torch.Tensor, + encoder_hidden_states: torch.Tensor = None, + timestep: torch.LongTensor = None, + img_ids: torch.Tensor = None, + txt_ids: torch.Tensor = None, + joint_attention_kwargs: Optional[Dict[str, Any]] = None, + controlnet_block_samples=None, + controlnet_single_block_samples=None, + return_dict: bool = True, + controlnet_blocks_repeat: bool = False, + ) -> Union[torch.Tensor, Transformer2DModelOutput]: + """ + The [`ChromaTransformer2DModel`] forward method. + Args: + hidden_states (`torch.Tensor` of shape `(batch_size, image_sequence_length, in_channels)`): + Input `hidden_states`. + encoder_hidden_states (`torch.Tensor` of shape `(batch_size, text_sequence_length, joint_attention_dim)`): + Conditional embeddings (embeddings computed from the input conditions such as prompts) to use. + timestep ( `torch.LongTensor`): + Used to indicate denoising step. + block_controlnet_hidden_states: (`list` of `torch.Tensor`): + A list of tensors that if specified are added to the residuals of transformer blocks. + joint_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~models.transformer_2d.Transformer2DModelOutput`] instead of a plain + tuple. + Returns: + If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a + `tuple` where the first element is the sample tensor. + """ + if joint_attention_kwargs is not None: + joint_attention_kwargs = joint_attention_kwargs.copy() + lora_scale = joint_attention_kwargs.pop("scale", 1.0) + else: + lora_scale = 1.0 + + if USE_PEFT_BACKEND: + # weight the lora layers by setting `lora_scale` for each PEFT layer + scale_lora_layers(self, lora_scale) + else: + if joint_attention_kwargs is not None and joint_attention_kwargs.get("scale", None) is not None: + logger.warning( + "Passing `scale` via `joint_attention_kwargs` when not using the PEFT backend is ineffective." + ) + + hidden_states = self.x_embedder(hidden_states) + + timestep = timestep.to(hidden_states.dtype) * 1000 + + input_vec = self.time_text_embed(timestep) + pooled_temb = self.distilled_guidance_layer(input_vec) + + encoder_hidden_states = self.context_embedder(encoder_hidden_states) + + if txt_ids.ndim == 3: + logger.warning( + "Passing `txt_ids` 3d torch.Tensor is deprecated." + "Please remove the batch dimension and pass it as a 2d torch Tensor" + ) + txt_ids = txt_ids[0] + if img_ids.ndim == 3: + logger.warning( + "Passing `img_ids` 3d torch.Tensor is deprecated." + "Please remove the batch dimension and pass it as a 2d torch Tensor" + ) + img_ids = img_ids[0] + + ids = torch.cat((txt_ids, img_ids), dim=0) + image_rotary_emb = self.pos_embed(ids) + + if joint_attention_kwargs is not None and "ip_adapter_image_embeds" in joint_attention_kwargs: + ip_adapter_image_embeds = joint_attention_kwargs.pop("ip_adapter_image_embeds") + ip_hidden_states = self.encoder_hid_proj(ip_adapter_image_embeds) + joint_attention_kwargs.update({"ip_hidden_states": ip_hidden_states}) + + if self.enable_teacache: + inp = hidden_states.clone() + input_vec_ = input_vec.clone() + modulated_inp, _gate_msa, _shift_mlp, _scale_mlp, _gate_mlp = self.transformer_blocks[0].norm1(inp, emb=input_vec_) + if self.cnt == 0 or self.cnt == self.num_steps-1: + should_calc = True + self.accumulated_rel_l1_distance = 0 + else: + coefficients = [4.98651651e+02, -2.83781631e+02, 5.58554382e+01, -3.82021401e+00, 2.64230861e-01] + rescale_func = np.poly1d(coefficients) + self.accumulated_rel_l1_distance += rescale_func(((modulated_inp-self.previous_modulated_input).abs().mean() / self.previous_modulated_input.abs().mean()).cpu().item()) + if self.accumulated_rel_l1_distance < self.rel_l1_thresh: + should_calc = False + else: + should_calc = True + self.accumulated_rel_l1_distance = 0 + self.previous_modulated_input = modulated_inp + self.cnt += 1 + if self.cnt == self.num_steps: + self.cnt = 0 + + if self.enable_teacache: + if not should_calc: + hidden_states += self.previous_residual + else: + ori_hidden_states = hidden_states.clone() + for index_block, block in enumerate(self.transformer_blocks): + img_offset = 3 * len(self.single_transformer_blocks) + txt_offset = img_offset + 6 * len(self.transformer_blocks) + img_modulation = img_offset + 6 * index_block + text_modulation = txt_offset + 6 * index_block + temb = torch.cat( + ( + pooled_temb[:, img_modulation : img_modulation + 6], + pooled_temb[:, text_modulation : text_modulation + 6], + ), + dim=1, + ) + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward4(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + encoder_hidden_states, hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward4(block), + hidden_states, + encoder_hidden_states, + temb, + image_rotary_emb, + **ckpt_kwargs, + ) + + else: + encoder_hidden_states, hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + joint_attention_kwargs=joint_attention_kwargs, + ) + + # controlnet residual + if controlnet_block_samples is not None: + interval_control = len(self.transformer_blocks) / len(controlnet_block_samples) + interval_control = int(np.ceil(interval_control)) + # For Xlabs ControlNet. + if controlnet_blocks_repeat: + hidden_states = ( + hidden_states + controlnet_block_samples[index_block % len(controlnet_block_samples)] + ) + else: + hidden_states = hidden_states + controlnet_block_samples[index_block // interval_control] + hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1) + + for index_block, block in enumerate(self.single_transformer_blocks): + start_idx = 3 * index_block + temb = pooled_temb[:, start_idx : start_idx + 3] + + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward2(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward2(block), + hidden_states, + temb, + image_rotary_emb, + **ckpt_kwargs, + ) + + else: + hidden_states = block( + hidden_states=hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + joint_attention_kwargs=joint_attention_kwargs, + ) + + # controlnet residual + if controlnet_single_block_samples is not None: + interval_control = len(self.single_transformer_blocks) / len(controlnet_single_block_samples) + interval_control = int(np.ceil(interval_control)) + hidden_states[:, encoder_hidden_states.shape[1] :, ...] = ( + hidden_states[:, encoder_hidden_states.shape[1] :, ...] + + controlnet_single_block_samples[index_block // interval_control] + ) + + hidden_states = hidden_states[:, encoder_hidden_states.shape[1] :, ...] + self.previous_residual = hidden_states - ori_hidden_states + else: + for index_block, block in enumerate(self.transformer_blocks): + img_offset = 3 * len(self.single_transformer_blocks) + txt_offset = img_offset + 6 * len(self.transformer_blocks) + img_modulation = img_offset + 6 * index_block + text_modulation = txt_offset + 6 * index_block + temb = torch.cat( + ( + pooled_temb[:, img_modulation : img_modulation + 6], + pooled_temb[:, text_modulation : text_modulation + 6], + ), + dim=1, + ) + + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward1(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + encoder_hidden_states, hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward1(block), + hidden_states, + encoder_hidden_states, + temb, + image_rotary_emb, + **ckpt_kwargs, + ) + + else: + encoder_hidden_states, hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + joint_attention_kwargs=joint_attention_kwargs, + ) + + # controlnet residual + if controlnet_block_samples is not None: + interval_control = len(self.transformer_blocks) / len(controlnet_block_samples) + interval_control = int(np.ceil(interval_control)) + # For Xlabs ControlNet. + if controlnet_blocks_repeat: + hidden_states = ( + hidden_states + controlnet_block_samples[index_block % len(controlnet_block_samples)] + ) + else: + hidden_states = hidden_states + controlnet_block_samples[index_block // interval_control] + hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1) + + for index_block, block in enumerate(self.single_transformer_blocks): + start_idx = 3 * index_block + temb = pooled_temb[:, start_idx : start_idx + 3] + + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward3(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward3(block), + hidden_states, + temb, + image_rotary_emb, + **ckpt_kwargs, + ) + + else: + hidden_states = block( + hidden_states=hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + joint_attention_kwargs=joint_attention_kwargs, + ) + + # controlnet residual + if controlnet_single_block_samples is not None: + interval_control = len(self.single_transformer_blocks) / len(controlnet_single_block_samples) + interval_control = int(np.ceil(interval_control)) + hidden_states[:, encoder_hidden_states.shape[1] :, ...] = ( + hidden_states[:, encoder_hidden_states.shape[1] :, ...] + + controlnet_single_block_samples[index_block // interval_control] + ) + hidden_states = hidden_states[:, encoder_hidden_states.shape[1] :, ...] + + temb = pooled_temb[:, -2:] + hidden_states = self.norm_out(hidden_states, temb) + output = self.proj_out(hidden_states) + + if USE_PEFT_BACKEND: + # remove `lora_scale` from each PEFT layer + unscale_lora_layers(self, lora_scale) + + if not return_dict: + return (output,) + + return Transformer2DModelOutput(sample=output) \ No newline at end of file From 86cd272b961af0f317f4faf4b7b6e5046e4c3b76 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 18 Jun 2025 16:24:42 +0300 Subject: [PATCH 07/85] SDNQ fix Dora --- CHANGELOG.md | 9 +++++++++ modules/lora/lora_apply.py | 5 ++++- modules/lora/network.py | 5 ++++- modules/model_quant.py | 2 +- modules/sdnq/__init__.py | 2 ++ modules/sdnq/dequantizer.py | 8 ++++++++ 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed2ebb762..f11448519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log for SD.Next +## Update for 2025-06-18 + +- **SDNQ Quantization** + - Fix Qwen 2.5 with int8 matmul + - Fix Dora loading + +- **Fixes** + - IPEX with DPM2++ FlowMatch samplers + ## Update for 2025-06-16 - **Feature** diff --git a/modules/lora/lora_apply.py b/modules/lora/lora_apply.py index b971541cd..2fcea174c 100644 --- a/modules/lora/lora_apply.py +++ b/modules/lora/lora_apply.py @@ -79,7 +79,9 @@ def network_calc_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn. continue try: t0 = time.time() - if hasattr(self, "sdnq_dequantizer"): + if hasattr(self, "sdnq_dequantizer_backup"): + weight = self.sdnq_dequantizer_backup.to(devices.device)(self.weight.to(devices.device), skip_quantized_matmul=self.sdnq_dequantizer_backup.use_quantized_matmul) + elif hasattr(self, "sdnq_dequantizer"): weight = self.sdnq_dequantizer.to(devices.device)(self.weight.to(devices.device), skip_quantized_matmul=self.sdnq_dequantizer.use_quantized_matmul) else: weight = self.weight.to(devices.device) # must perform calc on gpu due to performance @@ -228,6 +230,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn self.weight = torch.nn.Parameter(weights_backup.to(device), requires_grad=False) if hasattr(self, "sdnq_dequantizer_backup"): self.sdnq_dequantizer = self.sdnq_dequantizer_backup.to(device) + del self.sdnq_dequantizer_backup if bias_backup is not None: self.bias = None diff --git a/modules/lora/network.py b/modules/lora/network.py index f6d93009c..44e5a64b9 100644 --- a/modules/lora/network.py +++ b/modules/lora/network.py @@ -124,7 +124,10 @@ class NetworkModule: self.sd_key = weights.sd_key self.sd_module = weights.sd_module if hasattr(self.sd_module, 'weight'): - self.shape = self.sd_module.weight.shape + if hasattr(self.sd_module, "sdnq_dequantizer"): + self.shape = self.sd_module.sdnq_dequantizer.original_shape + else: + self.shape = self.sd_module.weight.shape self.dim = None self.bias = weights.w.get("bias") self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None diff --git a/modules/model_quant.py b/modules/model_quant.py index 3c57ac67c..75632c020 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -402,7 +402,7 @@ def sdnq_quantize_weights(sd_model): try: t0 = time.time() from modules import shared, devices, sd_models - log.info(f"Quantization: type=SDNQ modules={shared.opts.sdnq_quantize_weights}") + log.info(f"Quantization: type=SDNQ dtype={shared.opts.sdnq_quantize_weights_mode} dtype_te={shared.opts.sdnq_quantize_weights_mode_te} modules={shared.opts.sdnq_quantize_weights}") global quant_last_model_name, quant_last_model_device # pylint: disable=global-statement sd_model = sd_models.apply_function_to_model(sd_model, sdnq_quantize_model, shared.opts.sdnq_quantize_weights, op="sdnq") diff --git a/modules/sdnq/__init__.py b/modules/sdnq/__init__.py index 998e0a467..cd9fc7759 100644 --- a/modules/sdnq/__init__.py +++ b/modules/sdnq/__init__.py @@ -21,6 +21,7 @@ def sdnq_quantize_layer(layer, weights_dtype="int8", torch_dtype=None, group_siz is_conv_transpose_type = False is_linear_type = False result_shape = None + original_shape = layer.weight.shape if torch_dtype is None: torch_dtype = devices.dtype @@ -139,6 +140,7 @@ def sdnq_quantize_layer(layer, weights_dtype="int8", torch_dtype=None, group_siz quantized_weight_shape=layer.weight.shape, result_dtype=torch_dtype, result_shape=result_shape, + original_shape=original_shape, weights_dtype=weights_dtype, use_quantized_matmul=use_quantized_matmul, ) diff --git a/modules/sdnq/dequantizer.py b/modules/sdnq/dequantizer.py index 114692f75..45cedfa79 100644 --- a/modules/sdnq/dequantizer.py +++ b/modules/sdnq/dequantizer.py @@ -46,11 +46,13 @@ class AsymmetricWeightsDequantizer(torch.nn.Module): zero_point: torch.FloatTensor, result_dtype: torch.dtype, result_shape: torch.Size, + original_shape: torch.Size, weights_dtype: str, **kwargs, # pylint: disable=unused-argument ): super().__init__() self.weights_dtype = weights_dtype + self.original_shape = original_shape self.use_quantized_matmul = False self.result_dtype = result_dtype self.result_shape = result_shape @@ -70,12 +72,14 @@ class SymmetricWeightsDequantizer(torch.nn.Module): scale: torch.FloatTensor, result_dtype: torch.dtype, result_shape: torch.Size, + original_shape: torch.Size, weights_dtype: str, use_quantized_matmul: bool = False, **kwargs, # pylint: disable=unused-argument ): super().__init__() self.weights_dtype = weights_dtype + self.original_shape = original_shape self.use_quantized_matmul = use_quantized_matmul self.result_dtype = result_dtype self.result_shape = result_shape @@ -96,12 +100,14 @@ class PackedINTAsymmetricWeightsDequantizer(torch.nn.Module): quantized_weight_shape: torch.Size, result_dtype: torch.dtype, result_shape: torch.Size, + original_shape: torch.Size, weights_dtype: str, **kwargs, # pylint: disable=unused-argument ): super().__init__() self.weights_dtype = weights_dtype self.use_quantized_matmul = False + self.original_shape = original_shape self.quantized_weight_shape = quantized_weight_shape self.result_dtype = result_dtype self.result_shape = result_shape @@ -122,12 +128,14 @@ class PackedINTSymmetricWeightsDequantizer(torch.nn.Module): quantized_weight_shape: torch.Size, result_dtype: torch.dtype, result_shape: torch.Size, + original_shape: torch.Size, weights_dtype: str, use_quantized_matmul: bool = False, **kwargs, # pylint: disable=unused-argument ): super().__init__() self.weights_dtype = weights_dtype + self.original_shape = original_shape self.use_quantized_matmul = use_quantized_matmul self.quantized_weight_shape = quantized_weight_shape self.result_dtype = result_dtype From 2c4850cc2ba771364d8ffc91d340858e2c4bae4e Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 18 Jun 2025 18:03:46 +0300 Subject: [PATCH 08/85] Log more info with SDNQ --- modules/model_quant.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/model_quant.py b/modules/model_quant.py index 75632c020..ac8186cce 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -142,7 +142,7 @@ def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Mo quantization_device=quantization_device, return_device=return_device, ) - log.debug(f'Quantization: module="{module}" type=sdnq dtype={weights_dtype}') + log.debug(f'Quantization: module="{module}" type=sdnq dtype={weights_dtype} matmul={shared.opts.sdnq_use_quantized_matmul} group_size={shared.opts.sdnq_quantize_weights_group_size} quant_conv={shared.opts.sdnq_quantize_conv_layers} matmul_conv={shared.opts.sdnq_use_quantized_matmul_conv} dequantize_fp32={shared.opts.sdnq_dequantize_fp32} quantize_with_gpu={shared.opts.sdnq_quantize_with_gpu} quantization_device={quantization_device} return_device={return_device}') if kwargs is None: return sdnq_config else: @@ -402,7 +402,7 @@ def sdnq_quantize_weights(sd_model): try: t0 = time.time() from modules import shared, devices, sd_models - log.info(f"Quantization: type=SDNQ dtype={shared.opts.sdnq_quantize_weights_mode} dtype_te={shared.opts.sdnq_quantize_weights_mode_te} modules={shared.opts.sdnq_quantize_weights}") + log.info(f"Quantization: type=SDNQ dtype={shared.opts.sdnq_quantize_weights_mode} dtype_te={shared.opts.sdnq_quantize_weights_mode_te} matmul={shared.opts.sdnq_use_quantized_matmul} group_size={shared.opts.sdnq_quantize_weights_group_size} quant_conv={shared.opts.sdnq_quantize_conv_layers} matmul_conv={shared.opts.sdnq_use_quantized_matmul_conv} quantize_with_gpu={shared.opts.sdnq_quantize_with_gpu} dequantize_fp32={shared.opts.sdnq_dequantize_fp32} modules={shared.opts.sdnq_quantize_weights}") global quant_last_model_name, quant_last_model_device # pylint: disable=global-statement sd_model = sd_models.apply_function_to_model(sd_model, sdnq_quantize_model, shared.opts.sdnq_quantize_weights, op="sdnq") From ff5d7977a9c6ffc8e9bc8a229c9833547c40bdd8 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 18 Jun 2025 19:35:57 +0300 Subject: [PATCH 09/85] Fix Controlnet pipeline check on set atten --- modules/sd_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index d8aad13a3..88644b17e 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -920,7 +920,7 @@ def set_diffusers_attention(pipe, quiet:bool=False): # if hasattr(pipe, 'pipe'): # set_diffusers_attention(pipe.pipe) - if 'ControlNet' in pipe.__class__.__name__ or not (pipe.__class__.__name__.startswith("StableDiffusion") and hasattr(pipe, "unet")): + if 'Control' in pipe.__class__.__name__ or 'Adapter' in pipe.__class__.__name__ or not (pipe.__class__.__name__.startswith("StableDiffusion") and hasattr(pipe, "unet")): if shared.opts.cross_attention_optimization not in {"Scaled-Dot-Product", "Disabled"}: shared.log.warning(f"Attention: {shared.opts.cross_attention_optimization} is not compatible with {pipe.__class__.__name__}") else: From 7fc7797a1dd536d03516f07b7632306ba1f54380 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 19 Jun 2025 11:36:41 +0300 Subject: [PATCH 10/85] SDNQ fix group size calc on odd shapes --- modules/sdnq/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sdnq/__init__.py b/modules/sdnq/__init__.py index cd9fc7759..b58721004 100644 --- a/modules/sdnq/__init__.py +++ b/modules/sdnq/__init__.py @@ -77,13 +77,13 @@ def sdnq_quantize_layer(layer, weights_dtype="int8", torch_dtype=None, group_siz num_of_groups = 1 else: num_of_groups = channel_size // group_size - while channel_size % group_size != 0: # find something divisible + while num_of_groups * group_size != channel_size: # find something divisible num_of_groups -= 1 if num_of_groups <= 1: group_size = channel_size num_of_groups = 1 break - group_size = channel_size / num_of_groups + group_size = channel_size // num_of_groups group_size = int(group_size) num_of_groups = int(num_of_groups) From 4491d26a184818fad3cf7cbbd1d7d1b801f10184 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 19 Jun 2025 22:55:28 +0300 Subject: [PATCH 11/85] IPEX fix fp64 hijack --- modules/intel/ipex/hijacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index 2a3b9e06a..e2a04a662 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -259,7 +259,7 @@ def Tensor_to(self, device=None, *args, **kwargs): return self.original_Tensor_to(return_xpu(device), *args, **kwargs) else: if not device_supports_fp64: - if kwargs.get("dtype", None) == torch.float64 and torch.device(device).type == "xpu": + if kwargs.get("dtype", None) == torch.float64 and ((device is None and self.device.type == "xpu") or (device is not None and torch.device(device).type == "xpu")): kwargs["dtype"] = torch.float32 elif device == torch.float64 and self.device.type == "xpu": device = torch.float32 From 87e6d3f4fc9da29b2ee738951c1ee3c892436112 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 20 Jun 2025 20:41:11 +0300 Subject: [PATCH 12/85] SDNQ add modules_to_not_convert and don't quant _keep_in_fp32_modules layers in post mode --- modules/model_quant.py | 15 +++++---------- modules/sdnq/__init__.py | 25 ++++--------------------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/modules/model_quant.py b/modules/model_quant.py index ac8186cce..b3db3f311 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -328,16 +328,6 @@ def sdnq_quantize_model(model, op=None, sd_model=None, do_gc=True): from modules.sdnq import apply_sdnq_to_module model.eval() - - if model.__class__.__name__ in {"T5EncoderModel", "UMT5EncoderModel"}: - import torch - from modules.sdnq import SDNQ_T5DenseGatedActDense # T5DenseGatedActDense uses fp32 - for i in range(len(model.encoder.block)): - model.encoder.block[i].layer[1].DenseReluDense = SDNQ_T5DenseGatedActDense( - model.encoder.block[i].layer[1].DenseReluDense, - dtype=torch.float32 if devices.dtype != torch.bfloat16 else torch.bfloat16 - ) - backup_embeddings = None if hasattr(model, "get_input_embeddings"): backup_embeddings = copy.deepcopy(model.get_input_embeddings()) @@ -357,6 +347,10 @@ def sdnq_quantize_model(model, op=None, sd_model=None, do_gc=True): quantization_device = None return_device = None + modules_to_not_convert = getattr(model, "_keep_in_fp32_modules", []) + if modules_to_not_convert is None: + modules_to_not_convert = [] + model = apply_sdnq_to_module( model, weights_dtype=weights_dtype, @@ -369,6 +363,7 @@ def sdnq_quantize_model(model, op=None, sd_model=None, do_gc=True): quantization_device=quantization_device, return_device=return_device, param_name=op, + modules_to_not_convert=modules_to_not_convert, ) model.quantization_method = 'SDNQ' diff --git a/modules/sdnq/__init__.py b/modules/sdnq/__init__.py index b58721004..ceb19366c 100644 --- a/modules/sdnq/__init__.py +++ b/modules/sdnq/__init__.py @@ -153,11 +153,13 @@ def sdnq_quantize_layer(layer, weights_dtype="int8", torch_dtype=None, group_siz return layer -def apply_sdnq_to_module(model, weights_dtype="int8", torch_dtype=None, group_size=0, quant_conv=False, use_quantized_matmul=False, use_quantized_matmul_conv=False, dequantize_fp32=False, quantization_device=None, return_device=None, param_name=None): # pylint: disable=unused-argument +def apply_sdnq_to_module(model, weights_dtype="int8", torch_dtype=None, group_size=0, quant_conv=False, use_quantized_matmul=False, use_quantized_matmul_conv=False, dequantize_fp32=False, quantization_device=None, return_device=None, param_name=None, modules_to_not_convert: List[str] = []): # pylint: disable=unused-argument has_children = list(model.children()) if not has_children: return model for module_param_name, module in model.named_children(): + if module_param_name in modules_to_not_convert: + continue if hasattr(module, "weight") and module.weight is not None: module = sdnq_quantize_layer( module, @@ -184,6 +186,7 @@ def apply_sdnq_to_module(model, weights_dtype="int8", torch_dtype=None, group_si quantization_device=quantization_device, return_device=return_device, param_name=module_param_name, + modules_to_not_convert=modules_to_not_convert, ) return model @@ -440,23 +443,3 @@ class SDNQConfig(QuantizationConfigMixin): raise ValueError(f"Only support weights in {accepted_weights} but found {self.weights_dtype}") if not isinstance(self.modules_to_not_convert, list): self.modules_to_not_convert = [self.modules_to_not_convert] - - -class SDNQ_T5DenseGatedActDense(torch.nn.Module): # forward can't find what self is without creating a class - def __init__(self, T5DenseGatedActDense, dtype): - super().__init__() - self.wi_0 = T5DenseGatedActDense.wi_0 - self.wi_1 = T5DenseGatedActDense.wi_1 - self.wo = T5DenseGatedActDense.wo - self.dropout = T5DenseGatedActDense.dropout - self.act = T5DenseGatedActDense.act - self.torch_dtype = dtype - - def forward(self, hidden_states): - hidden_gelu = self.act(self.wi_0(hidden_states)) - hidden_linear = self.wi_1(hidden_states) - hidden_states = hidden_gelu * hidden_linear - hidden_states = self.dropout(hidden_states) - hidden_states = hidden_states.to(self.torch_dtype) # this line needs to be forced - hidden_states = self.wo(hidden_states) - return hidden_states From 5e9c6bfd4e72ad69ce21563fe74e4cc7059be258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Mon, 23 Jun 2025 01:19:53 +0000 Subject: [PATCH 13/85] update diffusers --- installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer.py b/installer.py index a634febf3..47f69f525 100644 --- a/installer.py +++ b/installer.py @@ -546,7 +546,7 @@ def check_diffusers(): t_start = time.time() if args.skip_all or args.skip_git or args.experimental: return - sha = '900653c814cfa431877ae46548fe496506bda2ad' # diffusers commit hash + sha = '7fc53b5d66867f9e59398f5a14dd3dd9fbc700dd' # diffusers commit hash pkg = pkg_resources.working_set.by_key.get('diffusers', None) minor = int(pkg.version.split('.')[1] if pkg is not None else 0) cur = opts.get('diffusers_version', '') if minor > 0 else '' From 954138bfc1685fd31a06d67a4113b534c28b9907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Mon, 23 Jun 2025 01:27:17 +0000 Subject: [PATCH 14/85] add attention masks --- modules/processing_args.py | 4 ++ modules/processing_class.py | 4 ++ modules/prompt_parser_diffusers.py | 53 ++++++++++---- modules/prompt_parser_xhinker.py | 111 +++++++++++++++++++---------- 4 files changed, 124 insertions(+), 48 deletions(-) diff --git a/modules/processing_args.py b/modules/processing_args.py index fca0115de..7e9be06e9 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -183,6 +183,8 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') elif 'Flux' in model.__class__.__name__: args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') + elif 'Chroma' in model.__class__.__name__: + args['prompt_attention_mask'] = prompt_parser_diffusers.embedder('prompt_attention_masks') else: args['prompt'] = prompts if 'negative_prompt' in possible: @@ -199,6 +201,8 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t args['negative_pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_pooleds') elif 'StableDiffusion3' in model.__class__.__name__: args['negative_pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_pooleds') + elif 'Chroma' in model.__class__.__name__: + args['negative_prompt_attention_mask'] = prompt_parser_diffusers.embedder('negative_prompt_attention_masks') else: if 'PixArtSigmaPipeline' in model.__class__.__name__: # pixart-sigma pipeline throws list-of-list for negative prompt args['negative_prompt'] = negative_prompts[0] diff --git a/modules/processing_class.py b/modules/processing_class.py index 6b10f07a7..fc697d997 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -152,6 +152,8 @@ class StableDiffusionProcessing: self.positive_pooleds = [] self.negative_embeds = [] self.negative_pooleds = [] + self.prompt_attention_masks = [] + self.negative_prompt_attention_masks = [] self.disable_extra_networks = False self.iteration = 0 self.network_data = {} @@ -321,6 +323,8 @@ class StableDiffusionProcessing: self.positive_pooleds = [] self.negative_embeds = [] self.negative_pooleds = [] + self.prompt_attention_masks = [] + self.negative_prompt_attention_mask = [] def __str__(self): return f'{self.__class__.__name__}: {self.__dict__}' diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 1e4f5246a..37d6fe9ae 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -62,6 +62,8 @@ class PromptEmbedder: self.positive_pooleds = [[]] * self.batchsize self.negative_prompt_embeds = [[]] * self.batchsize self.negative_pooleds = [[]] * self.batchsize + self.prompt_attention_masks = [[]] * self.batchsize + self.negative_prompt_attention_masks = [[]] * self.batchsize self.positive_schedule = None self.negative_schedule = None self.scheduled_prompt = False @@ -109,13 +111,17 @@ class PromptEmbedder: if not any(flatten(emb) for emb in [self.prompt_embeds, self.negative_prompt_embeds, self.positive_pooleds, - self.negative_pooleds]): + self.negative_pooleds, + self.prompt_attention_masks, + self.negative_prompt_attention_masks]): return False else: cache[key] = {'prompt_embeds': self.prompt_embeds, 'negative_prompt_embeds': self.negative_prompt_embeds, 'positive_pooleds': self.positive_pooleds, 'negative_pooleds': self.negative_pooleds, + 'prompt_attention_masks': self.prompt_attention_masks, + 'negative_prompt_attention_masks': self.negative_prompt_attention_masks, } debug(f"Prompt cache: add={key}") while len(cache) > int(shared.opts.sd_textencoder_cache_size): @@ -129,6 +135,8 @@ class PromptEmbedder: self.positive_pooleds = [self.positive_pooleds[0]] * self.batchsize self.negative_prompt_embeds = [self.negative_prompt_embeds[0]] * self.batchsize self.negative_pooleds = [self.negative_pooleds[0]] * self.batchsize + self.prompt_attention_masks = [self.prompt_attention_masks[0]] * self.batchsize + self.negative_prompt_attention_masks = [self.negative_prompt_attention_masks[0]] * self.batchsize debug(f"Prompt cache: get={key}") return True @@ -167,15 +175,33 @@ class PromptEmbedder: self.positive_pooleds[batchidx].append(self.positive_pooleds[batchidx][idx]) if len(self.negative_pooleds[batchidx]) > 0: self.negative_pooleds[batchidx].append(self.negative_pooleds[batchidx][idx]) + if len(self.prompt_attention_masks[batchidx]) > 0: + self.prompt_attention_masks[batchidx].append(self.prompt_attention_masks[batchidx][idx]) + if len(self.negative_prompt_attention_masks[batchidx]) > 0: + self.negative_prompt_attention_masks[batchidx].append(self.negative_prompt_attention_masks[batchidx][idx]) def encode(self, pipe, positive_prompt, negative_prompt, batchidx): global last_attention # pylint: disable=global-statement self.attention = shared.opts.prompt_attention last_attention = self.attention if self.attention == "xhinker": - prompt_embed, positive_pooled, negative_embed, negative_pooled = get_xhinker_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip) + ( + prompt_embed, + positive_pooled, + prompt_attention_mask, + negative_embed, + negative_pooled, + negative_prompt_attention_mask + ) = get_xhinker_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip) else: - prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip) + ( + prompt_embed, + positive_pooled, + prompt_attention_mask, + negative_embed, + negative_pooled, + negative_prompt_attention_mask + ) = get_weighted_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip) if prompt_embed is not None: self.prompt_embeds[batchidx].append(prompt_embed) if negative_embed is not None: @@ -184,7 +210,10 @@ class PromptEmbedder: self.positive_pooleds[batchidx].append(positive_pooled) if negative_pooled is not None: self.negative_pooleds[batchidx].append(negative_pooled) - + if prompt_attention_mask is not None: + self.prompt_attention_masks[batchidx].append(prompt_attention_mask) + if negative_prompt_attention_mask is not None: + self.negative_prompt_attention_masks[batchidx].append(negative_prompt_attention_mask) if debug_enabled: get_tokens(pipe, 'positive', positive_prompt) get_tokens(pipe, 'negative', negative_prompt) @@ -509,11 +538,11 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c if "Flux" in pipe.__class__.__name__: # clip is only used for the pooled embeds prompt_embeds, pooled_prompt_embeds, _ = pipe.encode_prompt(prompt=prompt, prompt_2=prompt_2, device=device, num_images_per_prompt=1) - return prompt_embeds, pooled_prompt_embeds, None, None # no negative support + return prompt_embeds, pooled_prompt_embeds, None, None, None, None # no negative support if "Chroma" in pipe.__class__.__name__: # does not use clip and has no pooled embeds - prompt_embeds, _, _, negative_prompt_embeds, _, _ = pipe.encode_prompt(prompt=prompt, negative_prompt=neg_prompt, device=device, num_images_per_prompt=1) - return prompt_embeds, None, negative_prompt_embeds, None + prompt_embeds, _, prompt_attention_mask, negative_prompt_embeds, _, negative_prompt_attention_mask = pipe.encode_prompt(prompt=prompt, negative_prompt=neg_prompt, device=device, num_images_per_prompt=1) + return prompt_embeds, None, prompt_attention_mask, negative_prompt_embeds, None, negative_prompt_attention_mask if "HiDreamImage" in pipe.__class__.__name__: # clip is only used for the pooled embeds prompt_embeds_t5, negative_prompt_embeds_t5, prompt_embeds_llama3, negative_prompt_embeds_llama3, pooled_prompt_embeds, negative_pooled_prompt_embeds = pipe.encode_prompt( @@ -523,7 +552,7 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c ) prompt_embeds = [prompt_embeds_t5, prompt_embeds_llama3] negative_prompt_embeds = [negative_prompt_embeds_t5, negative_prompt_embeds_llama3] - return prompt_embeds, pooled_prompt_embeds, negative_prompt_embeds, negative_pooled_prompt_embeds + return prompt_embeds, pooled_prompt_embeds, None, negative_prompt_embeds, negative_pooled_prompt_embeds, None if prompt != prompt_2: ps = [get_prompts_with_weights(pipe, p) for p in [prompt, prompt_2]] @@ -636,7 +665,7 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c negative_prompt_embeds, (0, t5_negative_prompt_embed.shape[-1] - negative_prompt_embeds.shape[-1]) ).to(device) negative_prompt_embeds = torch.cat([negative_prompt_embeds, t5_negative_prompt_embed], dim=-2) - return prompt_embeds, pooled_prompt_embeds, negative_prompt_embeds, negative_pooled_prompt_embeds + return prompt_embeds, pooled_prompt_embeds, None, negative_prompt_embeds, negative_pooled_prompt_embeds, None def get_xhinker_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", clip_skip: int = None): @@ -650,7 +679,7 @@ def get_xhinker_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", cl neg_prompt_2 = pipe.maybe_convert_prompt(neg_prompt_2, pipe.tokenizer_2) except Exception: pass - prompt_embed = positive_pooled = negative_embed = negative_pooled = None + prompt_embed = positive_pooled = negative_embed = negative_pooled = prompt_attention_mask = negative_prompt_attention_mask = None te1_device, te2_device, te3_device = None, None, None if hasattr(pipe, "text_encoder") and pipe.text_encoder.device != devices.device: @@ -668,7 +697,7 @@ def get_xhinker_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", cl elif 'Flux' in pipe.__class__.__name__: prompt_embed, positive_pooled = get_weighted_text_embeddings_flux1(pipe=pipe, prompt=prompt, prompt2=prompt_2, device=devices.device) elif 'Chroma' in pipe.__class__.__name__: - prompt_embed, negative_embed = get_weighted_text_embeddings_chroma(pipe=pipe, prompt=prompt, neg_prompt=neg_prompt, device=devices.device) + prompt_embed, prompt_attention_mask, negative_embed, negative_prompt_attention_mask = get_weighted_text_embeddings_chroma(pipe=pipe, prompt=prompt, neg_prompt=neg_prompt, device=devices.device) elif 'XL' in pipe.__class__.__name__: prompt_embed, negative_embed, positive_pooled, negative_pooled = get_weighted_text_embeddings_sdxl_2p(pipe=pipe, prompt=prompt, prompt_2=prompt_2, neg_prompt=neg_prompt, neg_prompt_2=neg_prompt_2) else: @@ -681,4 +710,4 @@ def get_xhinker_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", cl if te3_device is not None: sd_models.move_model(pipe.text_encoder_3, te1_device, force=True) - return prompt_embed, positive_pooled, negative_embed, negative_pooled + return prompt_embed, positive_pooled, prompt_attention_mask, negative_embed, negative_pooled, negative_prompt_attention_mask diff --git a/modules/prompt_parser_xhinker.py b/modules/prompt_parser_xhinker.py index d2cc1971d..ed31fc5d4 100644 --- a/modules/prompt_parser_xhinker.py +++ b/modules/prompt_parser_xhinker.py @@ -88,8 +88,8 @@ def get_prompts_tokens_with_weights( def get_prompts_tokens_with_weights_t5( - t5_tokenizer: T5Tokenizer - , prompt: str + t5_tokenizer: T5Tokenizer, + prompt: str ): """ Get prompt token ids and weights, this function works for both prompt and negative prompt @@ -98,18 +98,21 @@ def get_prompts_tokens_with_weights_t5( prompt = "empty" texts_and_weights = parse_prompt_attention(prompt) - text_tokens, text_weights = [], [] + text_tokens, text_weights, text_masks = [], [], [] for word, weight in texts_and_weights: # tokenize and discard the starting and the ending token - token = t5_tokenizer( - word - , truncation=False # so that tokenize whatever length prompt - , add_special_tokens=True - ).input_ids - # the returned token is a 1d list: [320, 1125, 539, 320] + inputs = t5_tokenizer( + word, + truncation=False, # so that tokenize whatever length prompt + add_special_tokens=True, + ) + + token = inputs.input_ids + mask = inputs.attention_mask # merge the new tokens to the all tokens holder: text_tokens text_tokens = [*text_tokens, *token] + text_masks = [*text_masks, *mask] # each token chunk will come with one weight, like ['red cat', 2.0] # need to expand weight for each token. @@ -117,7 +120,7 @@ def get_prompts_tokens_with_weights_t5( # append the weight back to the weight holder: text_weights text_weights = [*text_weights, *chunk_weights] - return text_tokens, text_weights + return text_tokens, text_weights, text_masks def group_tokens_and_weights( @@ -1070,11 +1073,11 @@ def get_weighted_text_embeddings_sd3( ) # tokenizer 3 - prompt_tokens_3, prompt_weights_3 = get_prompts_tokens_with_weights_t5( + prompt_tokens_3, prompt_weights_3, _ = get_prompts_tokens_with_weights_t5( pipe.tokenizer_3, prompt ) - neg_prompt_tokens_3, neg_prompt_weights_3 = get_prompts_tokens_with_weights_t5( + neg_prompt_tokens_3, neg_prompt_weights_3, _ = get_prompts_tokens_with_weights_t5( pipe.tokenizer_3, neg_prompt ) @@ -1366,7 +1369,7 @@ def get_weighted_text_embeddings_flux1( ) # tokenizer 2 - google/t5-v1_1-xxl - prompt_tokens_2, prompt_weights_2 = get_prompts_tokens_with_weights_t5( + prompt_tokens_2, prompt_weights_2, _ = get_prompts_tokens_with_weights_t5( pipe.tokenizer_2, prompt2 ) @@ -1443,23 +1446,26 @@ def get_weighted_text_embeddings_chroma( neg_prompt (str) device (torch.device, optional): Device to run the embeddings on. Returns: - prompt_embeds (T5 prompt embeds as torch.Tensor) - neg_prompt_embeds (T5 prompt embeds as torch.Tensor) + prompt_embeds (torch.Tensor) + prompt_attention_mask (torch.Tensor) + neg_prompt_embeds (torch.Tensor) + neg_prompt_attention_mask (torch.Tensor) """ if device is None: device = pipe.text_encoder.device - # prompt - prompt_tokens, prompt_weights = get_prompts_tokens_with_weights_t5( + # positive prompt + prompt_tokens, prompt_weights, prompt_masks = get_prompts_tokens_with_weights_t5( pipe.tokenizer, prompt ) - prompt_tokens = torch.tensor([prompt_tokens], dtype=torch.long) + prompt_tokens = torch.tensor([prompt_tokens], dtype=torch.long).to(device) + prompt_masks = torch.tensor([prompt_masks], dtype=torch.long).to(device) - t5_prompt_embeds = pipe.text_encoder(prompt_tokens.to(device))[0].squeeze(0) + t5_prompt_embeds = pipe.text_encoder(prompt_tokens, output_hidden_states=False, attention_mask=prompt_masks)[0].squeeze(0) t5_prompt_embeds = t5_prompt_embeds.to(device=device) - # add weight to t5 prompt + # add weight to t5 positive embeddings for z in range(len(prompt_weights)): if prompt_weights[z] != 1.0: t5_prompt_embeds[z] = t5_prompt_embeds[z] * prompt_weights[z] @@ -1467,34 +1473,67 @@ def get_weighted_text_embeddings_chroma( t5_prompt_embeds = t5_prompt_embeds.to(dtype=pipe.text_encoder.dtype, device=device) # negative prompt - neg_prompt_tokens, neg_prompt_weights = get_prompts_tokens_with_weights_t5( + neg_prompt_tokens, neg_prompt_weights, neg_prompt_masks = get_prompts_tokens_with_weights_t5( pipe.tokenizer, neg_prompt ) - neg_prompt_tokens = torch.tensor([neg_prompt_tokens], dtype=torch.long) + neg_prompt_tokens = torch.tensor([neg_prompt_tokens], dtype=torch.long).to(device) + neg_prompt_masks = torch.tensor([neg_prompt_masks], dtype=torch.long).to(device) - t5_neg_prompt_embeds = pipe.text_encoder(neg_prompt_tokens.to(device))[0].squeeze(0) + t5_neg_prompt_embeds = pipe.text_encoder(neg_prompt_tokens, output_hidden_states=False, attention_mask=neg_prompt_masks)[0].squeeze(0) t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(device=device) - # add weight to neg t5 embeddings + # add weight to negative t5 embeddings for z in range(len(neg_prompt_weights)): if neg_prompt_weights[z] != 1.0: t5_neg_prompt_embeds[z] = t5_neg_prompt_embeds[z] * neg_prompt_weights[z] t5_neg_prompt_embeds = t5_neg_prompt_embeds.unsqueeze(0) t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(dtype=pipe.text_encoder.dtype, device=device) - def pad_prompt_embeds_to_same_size(prompt_embeds_a, prompt_embeds_b): - size_a = prompt_embeds_a.size(1) - size_b = prompt_embeds_b.size(1) + embeds, masks = pad_prompt_embeds_to_same_size_chroma( + pipe, + [t5_prompt_embeds, t5_neg_prompt_embeds], + [prompt_masks, neg_prompt_masks] + ) - if size_a < size_b: - pad_size = size_b - size_a - prompt_embeds_a = F.pad(prompt_embeds_a, (0, 0, 0, pad_size)) # Pad dim=1 - elif size_b < size_a: - pad_size = size_a - size_b - prompt_embeds_b = F.pad(prompt_embeds_b, (0, 0, 0, pad_size)) # Pad dim=1 + return embeds[0], masks[0], embeds[1], masks[1] - return prompt_embeds_a, prompt_embeds_b - # chroma needs positive and negative prompt embeddings to have the same length (for now) - return pad_prompt_embeds_to_same_size(t5_prompt_embeds, t5_neg_prompt_embeds) +def pad_prompt_embeds_to_same_size_chroma(pipe, embeds, masks): + """ + Implementation of Chroma's padding for prompt embeddings. + Pads the embeddings to the maximum length found in the batch, while ensuring + that the padding tokens are masked correctly and keeping one padding token unmasked. + + https://huggingface.co/lodestones/Chroma#tldr-masking-t5-padding-tokens-enhanced-fidelity-and-increased-stability-during-training + """ + pad_token_id = pipe.tokenizer.pad_token_id + + max_token_count = max([embed.shape[1] for embed in embeds]) + + padded_embeds = [] + padded_masks = [] + + for embed, mask in zip(embeds, masks): + current_length = embed.shape[1] + if current_length < max_token_count: + pad_length = max_token_count - current_length + embed_pad = torch.full( + (1, pad_length, embed.shape[-1]), + fill_value=pad_token_id, + dtype=embed.dtype, + device=embed.device + ) + padded_embed = torch.cat([embed, embed_pad], dim=1) + + mask_pad = torch.ones(1, pad_length, device=mask.device) + mask_pad[0, 0] = 0 # keep one padding token unmasked, see linked Chroma docs + padded_mask = torch.cat([mask, mask_pad], dim=1) + + padded_embeds.append(padded_embed) + padded_masks.append(padded_mask) + else: + padded_embeds.append(embed) + padded_masks.append(mask) + + return padded_embeds, padded_masks From 106f6a9bc19617ba9f37ff0555315ebc6179e290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Mon, 23 Jun 2025 02:18:50 +0000 Subject: [PATCH 15/85] xhinker padding fixes --- modules/prompt_parser_xhinker.py | 91 +++++++++++++++++++------------- 1 file changed, 54 insertions(+), 37 deletions(-) diff --git a/modules/prompt_parser_xhinker.py b/modules/prompt_parser_xhinker.py index ed31fc5d4..69727e2c3 100644 --- a/modules/prompt_parser_xhinker.py +++ b/modules/prompt_parser_xhinker.py @@ -1454,14 +1454,40 @@ def get_weighted_text_embeddings_chroma( if device is None: device = pipe.text_encoder.device - # positive prompt prompt_tokens, prompt_weights, prompt_masks = get_prompts_tokens_with_weights_t5( pipe.tokenizer, prompt ) + neg_prompt_tokens, neg_prompt_weights, neg_prompt_masks = get_prompts_tokens_with_weights_t5( + pipe.tokenizer, neg_prompt + ) + + padded_tokens, padded_masks = pad_prompt_tokens_to_same_size_chroma( + pipe, + [prompt_tokens, neg_prompt_tokens], + [prompt_masks, neg_prompt_masks] + ) + + prompt_tokens = padded_tokens[0] + prompt_masks = padded_masks[0] + prompt_tokens = torch.tensor([prompt_tokens], dtype=torch.long).to(device) prompt_masks = torch.tensor([prompt_masks], dtype=torch.long).to(device) + seq_lengths = prompt_masks.sum(dim=1) + mask_indices = torch.arange(prompt_masks.size(1), device=device).unsqueeze(0) + prompt_masks = (mask_indices <= seq_lengths.unsqueeze(1)).long().to(device) + + neg_prompt_tokens = padded_tokens[1] + neg_prompt_masks = padded_masks[1] + + neg_prompt_tokens = torch.tensor([neg_prompt_tokens], dtype=torch.long).to(device) + neg_prompt_masks = torch.tensor([neg_prompt_masks], dtype=torch.long).to(device) + + seq_lengths = neg_prompt_masks.sum(dim=1) + mask_indices = torch.arange(neg_prompt_masks.size(1), device=device).unsqueeze(0) + neg_prompt_masks = (mask_indices <= seq_lengths.unsqueeze(1)).long().to(device) + t5_prompt_embeds = pipe.text_encoder(prompt_tokens, output_hidden_states=False, attention_mask=prompt_masks)[0].squeeze(0) t5_prompt_embeds = t5_prompt_embeds.to(device=device) @@ -1472,14 +1498,6 @@ def get_weighted_text_embeddings_chroma( t5_prompt_embeds = t5_prompt_embeds.unsqueeze(0) t5_prompt_embeds = t5_prompt_embeds.to(dtype=pipe.text_encoder.dtype, device=device) - # negative prompt - neg_prompt_tokens, neg_prompt_weights, neg_prompt_masks = get_prompts_tokens_with_weights_t5( - pipe.tokenizer, neg_prompt - ) - - neg_prompt_tokens = torch.tensor([neg_prompt_tokens], dtype=torch.long).to(device) - neg_prompt_masks = torch.tensor([neg_prompt_masks], dtype=torch.long).to(device) - t5_neg_prompt_embeds = pipe.text_encoder(neg_prompt_tokens, output_hidden_states=False, attention_mask=neg_prompt_masks)[0].squeeze(0) t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(device=device) @@ -1490,16 +1508,10 @@ def get_weighted_text_embeddings_chroma( t5_neg_prompt_embeds = t5_neg_prompt_embeds.unsqueeze(0) t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(dtype=pipe.text_encoder.dtype, device=device) - embeds, masks = pad_prompt_embeds_to_same_size_chroma( - pipe, - [t5_prompt_embeds, t5_neg_prompt_embeds], - [prompt_masks, neg_prompt_masks] - ) - - return embeds[0], masks[0], embeds[1], masks[1] + return t5_prompt_embeds, prompt_masks, t5_neg_prompt_embeds, neg_prompt_masks -def pad_prompt_embeds_to_same_size_chroma(pipe, embeds, masks): +def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_masks): """ Implementation of Chroma's padding for prompt embeddings. Pads the embeddings to the maximum length found in the batch, while ensuring @@ -1507,33 +1519,38 @@ def pad_prompt_embeds_to_same_size_chroma(pipe, embeds, masks): https://huggingface.co/lodestones/Chroma#tldr-masking-t5-padding-tokens-enhanced-fidelity-and-increased-stability-during-training """ + + input_tokens = input_tokens.copy() + input_masks = input_masks.copy() + pad_token_id = pipe.tokenizer.pad_token_id - max_token_count = max([embed.shape[1] for embed in embeds]) + for i, (tokens, mask) in enumerate(zip(input_tokens, input_masks)): + if pad_token_id in tokens: + first_pad_token_index = tokens.index(pad_token_id) + mask[first_pad_token_index] = 1 + else: + input_tokens[i] = tokens + [pad_token_id] + input_masks[i] = mask + [1] - padded_embeds = [] + max_token_count = max([len(x) for x in input_tokens]) + + padded_tokens = [] padded_masks = [] - for embed, mask in zip(embeds, masks): - current_length = embed.shape[1] + for tokens, mask in zip(input_tokens, input_masks): + current_length = len(tokens) + if current_length < max_token_count: pad_length = max_token_count - current_length - embed_pad = torch.full( - (1, pad_length, embed.shape[-1]), - fill_value=pad_token_id, - dtype=embed.dtype, - device=embed.device - ) - padded_embed = torch.cat([embed, embed_pad], dim=1) - mask_pad = torch.ones(1, pad_length, device=mask.device) - mask_pad[0, 0] = 0 # keep one padding token unmasked, see linked Chroma docs - padded_mask = torch.cat([mask, mask_pad], dim=1) + token_pad = [pad_token_id] * pad_length + mask_pad = [0] * pad_length - padded_embeds.append(padded_embed) - padded_masks.append(padded_mask) - else: - padded_embeds.append(embed) - padded_masks.append(mask) + tokens = tokens + token_pad + mask = mask + mask_pad - return padded_embeds, padded_masks + padded_tokens.append(tokens) + padded_masks.append(mask) + + return padded_tokens, padded_masks From 405416871f18ab889c26f2947f855e5ae163846c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Mon, 23 Jun 2025 08:35:05 +0000 Subject: [PATCH 16/85] fix chroma teacache --- modules/teacache/teacache_chroma.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/teacache/teacache_chroma.py b/modules/teacache/teacache_chroma.py index 999b3095b..685054bf9 100644 --- a/modules/teacache/teacache_chroma.py +++ b/modules/teacache/teacache_chroma.py @@ -7,7 +7,7 @@ from diffusers.utils import USE_PEFT_BACKEND, is_torch_version, logging, scale_l logger = logging.get_logger(__name__) # pylint: disable=invalid-name - +# todo: add def teacache_chroma_forward( self, hidden_states: torch.Tensor, @@ -15,6 +15,7 @@ def teacache_chroma_forward( timestep: torch.LongTensor = None, img_ids: torch.Tensor = None, txt_ids: torch.Tensor = None, + attention_mask: torch.Tensor = None, joint_attention_kwargs: Optional[Dict[str, Any]] = None, controlnet_block_samples=None, controlnet_single_block_samples=None, @@ -144,6 +145,7 @@ def teacache_chroma_forward( encoder_hidden_states, temb, image_rotary_emb, + attention_mask, **ckpt_kwargs, ) @@ -153,6 +155,7 @@ def teacache_chroma_forward( encoder_hidden_states=encoder_hidden_states, temb=temb, image_rotary_emb=image_rotary_emb, + attention_mask=attention_mask, joint_attention_kwargs=joint_attention_kwargs, ) @@ -244,6 +247,7 @@ def teacache_chroma_forward( encoder_hidden_states, temb, image_rotary_emb, + attention_mask=attention_mask, **ckpt_kwargs, ) @@ -253,6 +257,7 @@ def teacache_chroma_forward( encoder_hidden_states=encoder_hidden_states, temb=temb, image_rotary_emb=image_rotary_emb, + attention_mask=attention_mask, joint_attention_kwargs=joint_attention_kwargs, ) @@ -290,6 +295,7 @@ def teacache_chroma_forward( hidden_states, temb, image_rotary_emb, + attention_mask=attention_mask, **ckpt_kwargs, ) @@ -298,6 +304,7 @@ def teacache_chroma_forward( hidden_states=hidden_states, temb=temb, image_rotary_emb=image_rotary_emb, + attention_mask=attention_mask, joint_attention_kwargs=joint_attention_kwargs, ) From 71869a31815b812b84b186b16c7dc5c9778d843f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Mon, 23 Jun 2025 08:41:15 +0000 Subject: [PATCH 17/85] xhinker improvements --- modules/prompt_parser_xhinker.py | 117 +++++++++++++++++----------- modules/teacache/teacache_chroma.py | 2 +- 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/modules/prompt_parser_xhinker.py b/modules/prompt_parser_xhinker.py index 69727e2c3..689c4cf55 100644 --- a/modules/prompt_parser_xhinker.py +++ b/modules/prompt_parser_xhinker.py @@ -105,6 +105,7 @@ def get_prompts_tokens_with_weights_t5( word, truncation=False, # so that tokenize whatever length prompt add_special_tokens=True, + return_length=False, ) token = inputs.input_ids @@ -1454,6 +1455,8 @@ def get_weighted_text_embeddings_chroma( if device is None: device = pipe.text_encoder.device + dtype = pipe.text_encoder.dtype + prompt_tokens, prompt_weights, prompt_masks = get_prompts_tokens_with_weights_t5( pipe.tokenizer, prompt ) @@ -1462,56 +1465,59 @@ def get_weighted_text_embeddings_chroma( pipe.tokenizer, neg_prompt ) - padded_tokens, padded_masks = pad_prompt_tokens_to_same_size_chroma( + padded_tokens, padded_weights, padded_masks = pad_prompt_tokens_to_same_size_chroma( pipe, [prompt_tokens, neg_prompt_tokens], + [prompt_weights, neg_prompt_weights], [prompt_masks, neg_prompt_masks] ) - + prompt_tokens = padded_tokens[0] + prompt_weights = padded_weights[0] prompt_masks = padded_masks[0] - prompt_tokens = torch.tensor([prompt_tokens], dtype=torch.long).to(device) - prompt_masks = torch.tensor([prompt_masks], dtype=torch.long).to(device) - - seq_lengths = prompt_masks.sum(dim=1) - mask_indices = torch.arange(prompt_masks.size(1), device=device).unsqueeze(0) - prompt_masks = (mask_indices <= seq_lengths.unsqueeze(1)).long().to(device) + prompt_embeds, prompt_masks = get_weighted_prompt_embeds_with_attention_mask_chroma( + pipe, + prompt_tokens, + prompt_weights, + prompt_masks, + device=device, + dtype=dtype) neg_prompt_tokens = padded_tokens[1] + neg_prompt_weights = padded_weights[1] neg_prompt_masks = padded_masks[1] - neg_prompt_tokens = torch.tensor([neg_prompt_tokens], dtype=torch.long).to(device) - neg_prompt_masks = torch.tensor([neg_prompt_masks], dtype=torch.long).to(device) + neg_prompt_embeds, neg_prompt_masks = get_weighted_prompt_embeds_with_attention_mask_chroma( + pipe, + neg_prompt_tokens, + neg_prompt_weights, + neg_prompt_masks, + device=device, + dtype=dtype) - seq_lengths = neg_prompt_masks.sum(dim=1) - mask_indices = torch.arange(neg_prompt_masks.size(1), device=device).unsqueeze(0) - neg_prompt_masks = (mask_indices <= seq_lengths.unsqueeze(1)).long().to(device) - - t5_prompt_embeds = pipe.text_encoder(prompt_tokens, output_hidden_states=False, attention_mask=prompt_masks)[0].squeeze(0) - t5_prompt_embeds = t5_prompt_embeds.to(device=device) - - # add weight to t5 positive embeddings - for z in range(len(prompt_weights)): - if prompt_weights[z] != 1.0: - t5_prompt_embeds[z] = t5_prompt_embeds[z] * prompt_weights[z] - t5_prompt_embeds = t5_prompt_embeds.unsqueeze(0) - t5_prompt_embeds = t5_prompt_embeds.to(dtype=pipe.text_encoder.dtype, device=device) - - t5_neg_prompt_embeds = pipe.text_encoder(neg_prompt_tokens, output_hidden_states=False, attention_mask=neg_prompt_masks)[0].squeeze(0) - t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(device=device) - - # add weight to negative t5 embeddings - for z in range(len(neg_prompt_weights)): - if neg_prompt_weights[z] != 1.0: - t5_neg_prompt_embeds[z] = t5_neg_prompt_embeds[z] * neg_prompt_weights[z] - t5_neg_prompt_embeds = t5_neg_prompt_embeds.unsqueeze(0) - t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(dtype=pipe.text_encoder.dtype, device=device) - - return t5_prompt_embeds, prompt_masks, t5_neg_prompt_embeds, neg_prompt_masks + return prompt_embeds, prompt_masks, neg_prompt_embeds, neg_prompt_masks -def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_masks): +def get_weighted_prompt_embeds_with_attention_mask_chroma( + pipe: ChromaPipeline, + tokens, + weights, + masks, + device, + dtype +): + prompt_tokens = torch.tensor([tokens], dtype=torch.long, device=device) + prompt_masks = torch.tensor([masks], dtype=torch.long, device=device) + prompt_embeds = pipe.text_encoder(prompt_tokens, output_hidden_states=False, attention_mask=prompt_masks)[0].squeeze(0) + for z in range(len(weights)): + if weights[z] != 1.0: + prompt_embeds[z] = prompt_embeds[z] * weights[z] + prompt_embeds = prompt_embeds.unsqueeze(0).to(dtype=dtype, device=device) + return prompt_embeds, prompt_masks + + +def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_weights, input_masks): """ Implementation of Chroma's padding for prompt embeddings. Pads the embeddings to the maximum length found in the batch, while ensuring @@ -1521,36 +1527,57 @@ def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_masks): """ input_tokens = input_tokens.copy() + input_weights = input_weights.copy() input_masks = input_masks.copy() pad_token_id = pipe.tokenizer.pad_token_id - for i, (tokens, mask) in enumerate(zip(input_tokens, input_masks)): - if pad_token_id in tokens: - first_pad_token_index = tokens.index(pad_token_id) - mask[first_pad_token_index] = 1 - else: - input_tokens[i] = tokens + [pad_token_id] - input_masks[i] = mask + [1] + for tokens, mask in zip(input_tokens, input_masks): + for j, token in enumerate(tokens): + if token == pad_token_id: + mask[j] = 0 max_token_count = max([len(x) for x in input_tokens]) padded_tokens = [] + padded_weights = [] padded_masks = [] - for tokens, mask in zip(input_tokens, input_masks): + for tokens, weights, mask in zip(input_tokens, input_weights, input_masks): current_length = len(tokens) if current_length < max_token_count: pad_length = max_token_count - current_length token_pad = [pad_token_id] * pad_length + weight_pad = [1.0] * pad_length mask_pad = [0] * pad_length tokens = tokens + token_pad + weights = weights + weight_pad mask = mask + mask_pad padded_tokens.append(tokens) + padded_weights.append(weights) padded_masks.append(mask) - return padded_tokens, padded_masks + for i, (tokens, weights, mask) in enumerate(zip(padded_tokens, padded_weights, padded_masks)): + if pad_token_id in tokens: + if tokens[-1] == pad_token_id: + mask[-1] = 1 + continue + + padded_tokens[i] = tokens + [pad_token_id] + padded_weights[i] = weights + [1.0] + padded_masks[i] = mask + [1] + max_token_count = max(max_token_count, len(padded_tokens[i])) + + for i, tokens in enumerate(padded_tokens): + if len(tokens) < max_token_count: + pad_length = max_token_count - len(tokens) + tokens += [pad_token_id] * pad_length + padded_weights[i] += [1.0] * pad_length + padded_masks[i][-1] = 0 + padded_masks[i] += [0] * (pad_length - 1) + [1] + + return padded_tokens, padded_weights, padded_masks diff --git a/modules/teacache/teacache_chroma.py b/modules/teacache/teacache_chroma.py index 685054bf9..102ae7f9b 100644 --- a/modules/teacache/teacache_chroma.py +++ b/modules/teacache/teacache_chroma.py @@ -7,7 +7,7 @@ from diffusers.utils import USE_PEFT_BACKEND, is_torch_version, logging, scale_l logger = logging.get_logger(__name__) # pylint: disable=invalid-name -# todo: add + def teacache_chroma_forward( self, hidden_states: torch.Tensor, From 9238150a56660cc0e25483c2d5ce8a8cf539fd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:15:35 +0000 Subject: [PATCH 18/85] fix eos token not added correctly --- modules/prompt_parser_xhinker.py | 55 +++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/modules/prompt_parser_xhinker.py b/modules/prompt_parser_xhinker.py index 689c4cf55..5a3ac0eb1 100644 --- a/modules/prompt_parser_xhinker.py +++ b/modules/prompt_parser_xhinker.py @@ -89,7 +89,8 @@ def get_prompts_tokens_with_weights( def get_prompts_tokens_with_weights_t5( t5_tokenizer: T5Tokenizer, - prompt: str + prompt: str, + add_special_tokens: bool = True ): """ Get prompt token ids and weights, this function works for both prompt and negative prompt @@ -104,7 +105,7 @@ def get_prompts_tokens_with_weights_t5( inputs = t5_tokenizer( word, truncation=False, # so that tokenize whatever length prompt - add_special_tokens=True, + add_special_tokens=add_special_tokens, return_length=False, ) @@ -1458,20 +1459,21 @@ def get_weighted_text_embeddings_chroma( dtype = pipe.text_encoder.dtype prompt_tokens, prompt_weights, prompt_masks = get_prompts_tokens_with_weights_t5( - pipe.tokenizer, prompt + pipe.tokenizer, prompt, add_special_tokens=False ) neg_prompt_tokens, neg_prompt_weights, neg_prompt_masks = get_prompts_tokens_with_weights_t5( - pipe.tokenizer, neg_prompt + pipe.tokenizer, neg_prompt, add_special_tokens=False ) padded_tokens, padded_weights, padded_masks = pad_prompt_tokens_to_same_size_chroma( pipe, [prompt_tokens, neg_prompt_tokens], [prompt_weights, neg_prompt_weights], - [prompt_masks, neg_prompt_masks] + [prompt_masks, neg_prompt_masks], + add_eos_token=True ) - + prompt_tokens = padded_tokens[0] prompt_weights = padded_weights[0] prompt_masks = padded_masks[0] @@ -1496,9 +1498,21 @@ def get_weighted_text_embeddings_chroma( device=device, dtype=dtype) + # debug, will be removed later + prompt_with_mask, prompt_without_mask = debug_masked_tokens(pipe, prompt_tokens, prompt_masks.detach().tolist()[0]) + neg_prompt_with_mask, neg_prompt_without_mask = debug_masked_tokens(pipe, neg_prompt_tokens, neg_prompt_masks.detach().tolist()[0]) + return prompt_embeds, prompt_masks, neg_prompt_embeds, neg_prompt_masks +# debug, will be removed later +def debug_masked_tokens(pipe, prompt_tokens, prompt_masks): + prompt_with_mask = pipe.tokenizer.decode([token for token, mask in zip(prompt_tokens, prompt_masks) if mask == 1], skip_special_tokens=False) + prompt_without_mask = pipe.tokenizer.decode(prompt_tokens, skip_special_tokens=False) + + return prompt_with_mask, prompt_without_mask + + def get_weighted_prompt_embeds_with_attention_mask_chroma( pipe: ChromaPipeline, tokens, @@ -1517,7 +1531,7 @@ def get_weighted_prompt_embeds_with_attention_mask_chroma( return prompt_embeds, prompt_masks -def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_weights, input_masks): +def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_weights, input_masks, min_length=3, add_eos_token=True): """ Implementation of Chroma's padding for prompt embeddings. Pads the embeddings to the maximum length found in the batch, while ensuring @@ -1531,13 +1545,14 @@ def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_weights, inp input_masks = input_masks.copy() pad_token_id = pipe.tokenizer.pad_token_id + eos_token_id = pipe.tokenizer.eos_token_id for tokens, mask in zip(input_tokens, input_masks): for j, token in enumerate(tokens): if token == pad_token_id: mask[j] = 0 - max_token_count = max([len(x) for x in input_tokens]) + max_token_count = max([len(x) for x in input_tokens] + [min_length]) padded_tokens = [] padded_weights = [] @@ -1546,9 +1561,15 @@ def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_weights, inp for tokens, weights, mask in zip(input_tokens, input_weights, input_masks): current_length = len(tokens) + pad_length = 0 + if current_length < max_token_count: pad_length = max_token_count - current_length + elif pad_token_id not in tokens: + pad_length = 1 + + if pad_length > 0: token_pad = [pad_token_id] * pad_length weight_pad = [1.0] * pad_length mask_pad = [0] * pad_length @@ -1561,6 +1582,8 @@ def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_weights, inp padded_weights.append(weights) padded_masks.append(mask) + max_token_count = max([len(x) for x in padded_tokens]) + for i, (tokens, weights, mask) in enumerate(zip(padded_tokens, padded_weights, padded_masks)): if pad_token_id in tokens: if tokens[-1] == pad_token_id: @@ -1572,12 +1595,20 @@ def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_weights, inp padded_masks[i] = mask + [1] max_token_count = max(max_token_count, len(padded_tokens[i])) - for i, tokens in enumerate(padded_tokens): - if len(tokens) < max_token_count: - pad_length = max_token_count - len(tokens) - tokens += [pad_token_id] * pad_length + if add_eos_token: + max_token_count += 1 # eos token + + for i in range(len(padded_tokens)): + if len(padded_tokens[i]) < max_token_count: + pad_length = max_token_count - len(padded_tokens[i]) padded_weights[i] += [1.0] * pad_length padded_masks[i][-1] = 0 padded_masks[i] += [0] * (pad_length - 1) + [1] + if add_eos_token: + padded_tokens[i] += [pad_token_id] * (pad_length - 1) + [eos_token_id] + padded_masks[i][-2] = 1 + else: + padded_tokens[i] += [pad_token_id] * pad_length + return padded_tokens, padded_weights, padded_masks From 4870605f4a6d12a8f8f04731e41e929fae1e1183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Mon, 23 Jun 2025 13:41:49 +0300 Subject: [PATCH 19/85] Update prompt_parser_xhinker.py --- modules/prompt_parser_xhinker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/prompt_parser_xhinker.py b/modules/prompt_parser_xhinker.py index 5a3ac0eb1..e2f40fc26 100644 --- a/modules/prompt_parser_xhinker.py +++ b/modules/prompt_parser_xhinker.py @@ -1535,7 +1535,7 @@ def pad_prompt_tokens_to_same_size_chroma(pipe, input_tokens, input_weights, inp """ Implementation of Chroma's padding for prompt embeddings. Pads the embeddings to the maximum length found in the batch, while ensuring - that the padding tokens are masked correctly and keeping one padding token unmasked. + that the padding tokens are masked correctly while keeping at least one padding and one eos token unmasked. https://huggingface.co/lodestones/Chroma#tldr-masking-t5-padding-tokens-enhanced-fidelity-and-increased-stability-during-training """ From bbf986d3a581fd2b357341bba53d8d155a4b1c7a Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 23 Jun 2025 17:37:20 +0300 Subject: [PATCH 20/85] Fix LTXVideo --- CHANGELOG.md | 4 +++- modules/sdnq/__init__.py | 2 +- modules/video_models/video_load.py | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f11448519..a9938fed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,15 @@ # Change Log for SD.Next -## Update for 2025-06-18 +## Update for 2025-06-123 - **SDNQ Quantization** - Fix Qwen 2.5 with int8 matmul - Fix Dora loading + - Remove per layer GC - **Fixes** - IPEX with DPM2++ FlowMatch samplers + - LTXVideo default scheduler ## Update for 2025-06-16 diff --git a/modules/sdnq/__init__.py b/modules/sdnq/__init__.py index ceb19366c..c76d1e9f9 100644 --- a/modules/sdnq/__init__.py +++ b/modules/sdnq/__init__.py @@ -149,7 +149,7 @@ def sdnq_quantize_layer(layer, weights_dtype="int8", torch_dtype=None, group_siz layer.forward = get_forward_func(layer_class_name, use_quantized_matmul, dtype_dict[weights_dtype]["is_integer"], use_tensorwise_fp8_matmul) layer.forward = layer.forward.__get__(layer, layer.__class__) - devices.torch_gc(force=False, reason=f"SDNQ param_name: {param_name}") + #devices.torch_gc(force=False, reason=f"SDNQ param_name: {param_name}") return layer diff --git a/modules/video_models/video_load.py b/modules/video_models/video_load.py index 33813df82..d607b8fc3 100644 --- a/modules/video_models/video_load.py +++ b/modules/video_models/video_load.py @@ -1,4 +1,5 @@ import os +import copy import time from modules import shared, errors, sd_models, sd_checkpoint, model_quant, devices, sd_hijack_te from modules.video_models import models_def, video_utils, video_vae, video_overrides, video_cache @@ -70,6 +71,9 @@ def load_model(selected: models_def.Model): errors.display(e, 'video') t1 = time.time() + if shared.sd_model.__class__.__name__.startswith("LTX"): + shared.sd_model.scheduler.config.use_dynamic_shifting = False + shared.sd_model.default_scheduler = copy.deepcopy(shared.sd_model.scheduler) if hasattr(shared.sd_model, "scheduler") else None shared.sd_model.sd_checkpoint_info = sd_checkpoint.CheckpointInfo(selected.repo) shared.sd_model.sd_model_hash = None sd_models.set_diffuser_options(shared.sd_model) From 8ef348d9edb9a972cad74692aac0af22647a8df5 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 23 Jun 2025 17:39:39 +0300 Subject: [PATCH 21/85] Update changelog --- CHANGELOG.md | 4 +++- wiki | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9938fed8..0c6094039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,16 @@ # Change Log for SD.Next -## Update for 2025-06-123 +## Update for 2025-06-23 - **SDNQ Quantization** + - Add modules_to_not_convert support for post mode - Fix Qwen 2.5 with int8 matmul - Fix Dora loading - Remove per layer GC - **Fixes** - IPEX with DPM2++ FlowMatch samplers + - Invalid attention processor with ControlNet - LTXVideo default scheduler ## Update for 2025-06-16 diff --git a/wiki b/wiki index 2ca67daca..5e97702f2 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 2ca67dacaccb7ef4c0595b19407fc5a93167008b +Subproject commit 5e97702f219b879c035057204303ae649e1edcf7 From eaa130463cd0a2a20f6165153566d5a26f8977b9 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 24 Jun 2025 13:09:11 +0300 Subject: [PATCH 22/85] Use Diffusers with OmniGen --- CHANGELOG.md | 8 +- html/reference.json | 2 +- modules/model_omnigen.py | 60 +++-- modules/omnigen/__init__.py | 4 - modules/omnigen/model.py | 390 --------------------------------- modules/omnigen/pipeline.py | 219 ------------------ modules/omnigen/processor.py | 312 -------------------------- modules/omnigen/scheduler.py | 55 ----- modules/omnigen/transformer.py | 164 -------------- modules/omnigen/utils.py | 105 --------- modules/processing_args.py | 2 +- modules/sd_offload.py | 2 +- modules/sd_vae_remote.py | 15 +- modules/sd_vae_taesd.py | 4 +- modules/shared_items.py | 2 +- 15 files changed, 67 insertions(+), 1277 deletions(-) delete mode 100644 modules/omnigen/__init__.py delete mode 100644 modules/omnigen/model.py delete mode 100644 modules/omnigen/pipeline.py delete mode 100644 modules/omnigen/processor.py delete mode 100644 modules/omnigen/scheduler.py delete mode 100644 modules/omnigen/transformer.py delete mode 100644 modules/omnigen/utils.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c6094039..5faadfa04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log for SD.Next -## Update for 2025-06-23 +## Update for 2025-06-24 + +- **Changes** + - Use Diffusers version of OmniGen + - Support Remote VAE with Omnigen, Lumina 2 and PixArt - **SDNQ Quantization** - Add modules_to_not_convert support for post mode @@ -12,6 +16,8 @@ - IPEX with DPM2++ FlowMatch samplers - Invalid attention processor with ControlNet - LTXVideo default scheduler + - Balanced offload with OmniGen + - Quantization with OmniGen ## Update for 2025-06-16 diff --git a/html/reference.json b/html/reference.json index 2441112e6..fe89f47ac 100644 --- a/html/reference.json +++ b/html/reference.json @@ -239,7 +239,7 @@ }, "VectorSpaceLab OmniGen v1": { - "path": "Shitao/OmniGen-v1", + "path": "Shitao/OmniGen-v1-diffusers", "desc": "OmniGen is a unified image generation model that can generate a wide range of images from multi-modal prompts. It is designed to be simple, flexible and easy to use.", "preview": "Shitao--OmniGen-v1.jpg", "skip": true diff --git a/modules/model_omnigen.py b/modules/model_omnigen.py index b7eb4684e..0df4948a6 100644 --- a/modules/model_omnigen.py +++ b/modules/model_omnigen.py @@ -1,25 +1,47 @@ -def load_omnigen(checkpoint_info, diffusers_load_config={}): # pylint: disable=unused-argument - from modules import shared, devices, sd_models, shared_items - repo_id = sd_models.path_to_repo(checkpoint_info.name) +import os +import diffusers +from modules import errors, shared, devices, sd_models, model_quant - # load - from modules.omnigen import OmniGenPipeline - shared_items.pipelines['OmniGen'] = OmniGenPipeline - pipe = OmniGenPipeline.from_pretrained( - model_name=repo_id, - vae_path='madebyollin/sdxl-vae-fp16-fix', +debug = shared.log.trace if os.environ.get('SD_LOAD_DEBUG', None) is not None else lambda *args, **kwargs: None + + +def load_omnigen(checkpoint_info, diffusers_load_config={}): # pylint: disable=unused-argument + repo_id = sd_models.path_to_repo(checkpoint_info.name) + vae = None + + if shared.opts.sd_vae != 'Default' and shared.opts.sd_vae != 'Automatic': + try: + debug(f'Load model: type=OmniGen vae="{shared.opts.sd_vae}"') + from modules import sd_vae + # vae = sd_vae.load_vae_diffusers(None, sd_vae.vae_dict[shared.opts.sd_vae], 'override') + vae_file = sd_vae.vae_dict[shared.opts.sd_vae] + if os.path.exists(vae_file): + vae_config = os.path.join('configs', 'sdxl', 'vae', 'config.json') + vae = diffusers.AutoencoderKL.from_single_file(vae_file, config=vae_config, **diffusers_load_config) + except Exception as e: + shared.log.error(f"Load model: type=OmniGen failed to load VAE: {e}") + shared.opts.sd_vae = 'Default' + if debug: + errors.display(e, 'OmniGen VAE:') + + load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, module='Transformer') + transformer = diffusers.OmniGenTransformer2DModel.from_pretrained( + repo_id, + subfolder="transformer", cache_dir=shared.opts.diffusers_dir, + **load_config, + **quant_config, + ) + + load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, allow_quant=False) + if vae is not None: + load_config['vae'] = vae + pipe = diffusers.OmniGenPipeline.from_pretrained( + repo_id, + transformer=transformer, + cache_dir=shared.opts.diffusers_dir, + **load_config, ) - # init - pipe.device = devices.device - pipe.dtype = devices.dtype - pipe.model.device = devices.device - pipe.separate_cfg_infer = True - pipe.use_kv_cache = False - pipe.model.to(device=devices.device, dtype=devices.dtype) - if shared.opts.diffusers_eval: - pipe.model.eval() - pipe.vae.to(devices.device, dtype=devices.dtype) devices.torch_gc(force=True) return pipe diff --git a/modules/omnigen/__init__.py b/modules/omnigen/__init__.py deleted file mode 100644 index 40315a6f3..000000000 --- a/modules/omnigen/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .model import OmniGen -from .processor import OmniGenProcessor -from .scheduler import OmniGenScheduler -from .pipeline import OmniGenPipeline diff --git a/modules/omnigen/model.py b/modules/omnigen/model.py deleted file mode 100644 index 17d696b53..000000000 --- a/modules/omnigen/model.py +++ /dev/null @@ -1,390 +0,0 @@ -# The code is revised from DiT -import os -import math -import torch -import torch.nn as nn -import numpy as np -from safetensors.torch import load_file -from diffusers.loaders import PeftAdapterMixin -from huggingface_hub import snapshot_download -from .transformer import Phi3Config, Phi3Transformer - - -def modulate(x, shift, scale): - return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1) - - -class TimestepEmbedder(nn.Module): - """ - Embeds scalar timesteps into vector representations. - """ - def __init__(self, hidden_size, frequency_embedding_size=256): - super().__init__() - self.mlp = nn.Sequential( - nn.Linear(frequency_embedding_size, hidden_size, bias=True), - nn.SiLU(), - nn.Linear(hidden_size, hidden_size, bias=True), - ) - self.frequency_embedding_size = frequency_embedding_size - - @staticmethod - def timestep_embedding(t, dim, max_period=10000): - """ - Create sinusoidal timestep embeddings. - :param t: a 1-D Tensor of N indices, one per batch element. - These may be fractional. - :param dim: the dimension of the output. - :param max_period: controls the minimum frequency of the embeddings. - :return: an (N, D) Tensor of positional embeddings. - """ - # https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py - half = dim // 2 - freqs = torch.exp( - -math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half - ).to(device=t.device) - args = t[:, None].float() * freqs[None] - embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) - if dim % 2: - embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1) - return embedding - - def forward(self, t, dtype=torch.float32): - t_freq = self.timestep_embedding(t, self.frequency_embedding_size).to(dtype) - t_emb = self.mlp(t_freq) - return t_emb - - -class FinalLayer(nn.Module): - """ - The final layer of DiT. - """ - def __init__(self, hidden_size, patch_size, out_channels): - super().__init__() - self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6) - self.linear = nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True) - self.adaLN_modulation = nn.Sequential( - nn.SiLU(), - nn.Linear(hidden_size, 2 * hidden_size, bias=True) - ) - - def forward(self, x, c): - shift, scale = self.adaLN_modulation(c).chunk(2, dim=1) - x = modulate(self.norm_final(x), shift, scale) - x = self.linear(x) - return x - - -def get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=False, extra_tokens=0, interpolation_scale=1.0, base_size=1): - """ - grid_size: int of the grid height and width return: pos_embed: [grid_size*grid_size, embed_dim] or - [1+grid_size*grid_size, embed_dim] (w/ or w/o cls_token) - """ - if isinstance(grid_size, int): - grid_size = (grid_size, grid_size) - - grid_h = np.arange(grid_size[0], dtype=np.float32) / (grid_size[0] / base_size) / interpolation_scale - grid_w = np.arange(grid_size[1], dtype=np.float32) / (grid_size[1] / base_size) / interpolation_scale - grid = np.meshgrid(grid_w, grid_h) # here w goes first - grid = np.stack(grid, axis=0) - - grid = grid.reshape([2, 1, grid_size[1], grid_size[0]]) - pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid) - if cls_token and extra_tokens > 0: - pos_embed = np.concatenate([np.zeros([extra_tokens, embed_dim]), pos_embed], axis=0) - return pos_embed - - -def get_2d_sincos_pos_embed_from_grid(embed_dim, grid): - assert embed_dim % 2 == 0 - - # use half of dimensions to encode grid_h - emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2) - emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2) - - emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D) - return emb - - -def get_1d_sincos_pos_embed_from_grid(embed_dim, pos): - """ - embed_dim: output dimension for each position - pos: a list of positions to be encoded: size (M,) - out: (M, D) - """ - assert embed_dim % 2 == 0 - omega = np.arange(embed_dim // 2, dtype=np.float64) - omega /= embed_dim / 2. - omega = 1. / 10000**omega # (D/2,) - - pos = pos.reshape(-1) # (M,) - out = np.einsum('m,d->md', pos, omega) # (M, D/2), outer product - - emb_sin = np.sin(out) # (M, D/2) - emb_cos = np.cos(out) # (M, D/2) - - emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D) - return emb - - -class PatchEmbedMR(nn.Module): - """ 2D Image to Patch Embedding - """ - def __init__( - self, - patch_size: int = 2, - in_chans: int = 4, - embed_dim: int = 768, - bias: bool = True, - ): - super().__init__() - self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size, bias=bias) - - def forward(self, x): - x = self.proj(x) - x = x.flatten(2).transpose(1, 2) # NCHW -> NLC - return x - - -class OmniGen(nn.Module, PeftAdapterMixin): - """ - Diffusion model with a Transformer backbone. - """ - def __init__( - self, - transformer_config: Phi3Config, - patch_size=2, - in_channels=4, - pe_interpolation: float = 1.0, - pos_embed_max_size: int = 192, - ): - super().__init__() - self.in_channels = in_channels - self.out_channels = in_channels - self.patch_size = patch_size - self.pos_embed_max_size = pos_embed_max_size - hidden_size = transformer_config.hidden_size - self.x_embedder = PatchEmbedMR(patch_size, in_channels, hidden_size, bias=True) - self.input_x_embedder = PatchEmbedMR(patch_size, in_channels, hidden_size, bias=True) - self.time_token = TimestepEmbedder(hidden_size) - self.t_embedder = TimestepEmbedder(hidden_size) - self.pe_interpolation = pe_interpolation - pos_embed = get_2d_sincos_pos_embed(hidden_size, pos_embed_max_size, interpolation_scale=self.pe_interpolation, base_size=64) - self.register_buffer("pos_embed", torch.from_numpy(pos_embed).float().unsqueeze(0), persistent=True) - self.final_layer = FinalLayer(hidden_size, patch_size, self.out_channels) - self.initialize_weights() - self.llm = Phi3Transformer(config=transformer_config) - self.llm.config.use_cache = False - - @classmethod - def from_pretrained(cls, model_name: str, cache_dir: str=None): - if not os.path.exists(os.path.join(model_name, 'model.pt')) and not os.path.exists(os.path.join(model_name, 'model.safetensors')): - cache_dir = cache_dir or os.getenv('HF_HUB_CACHE') - model_name = snapshot_download(repo_id=model_name, - cache_dir=cache_dir, - ignore_patterns=['flax_model.msgpack', 'rust_model.ot', 'tf_model.h5']) - config = Phi3Config.from_pretrained(model_name) - model = cls(config) - if os.path.exists(os.path.join(model_name, 'model.pt')): - state_dict = torch.load(os.path.join(model_name, 'model.pt'), map_location='cpu') - elif os.path.exists(os.path.join(model_name, 'model.safetensors')): - state_dict = load_file(os.path.join(model_name, 'model.safetensors')) - else: - raise ValueError(f"OmniGen: Could not find model file in {model_name}") - model.load_state_dict(state_dict) - return model - - def initialize_weights(self): - assert not hasattr(self, "llama") - - # 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) - - # Initialize patch_embed like nn.Linear (instead of nn.Conv2d): - w = self.x_embedder.proj.weight.data - nn.init.xavier_uniform_(w.view([w.shape[0], -1])) - nn.init.constant_(self.x_embedder.proj.bias, 0) - - w = self.input_x_embedder.proj.weight.data - nn.init.xavier_uniform_(w.view([w.shape[0], -1])) - nn.init.constant_(self.x_embedder.proj.bias, 0) - - - # Initialize timestep embedding MLP: - nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02) - nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02) - nn.init.normal_(self.time_token.mlp[0].weight, std=0.02) - nn.init.normal_(self.time_token.mlp[2].weight, std=0.02) - - # Zero-out output layers: - nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0) - nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0) - nn.init.constant_(self.final_layer.linear.weight, 0) - nn.init.constant_(self.final_layer.linear.bias, 0) - - def unpatchify(self, x, h, w): - """ - x: (N, T, patch_size**2 * C) - imgs: (N, H, W, C) - """ - c = self.out_channels - - x = x.reshape(shape=(x.shape[0], h//self.patch_size, w//self.patch_size, self.patch_size, self.patch_size, c)) - x = torch.einsum('nhwpqc->nchpwq', x) - imgs = x.reshape(shape=(x.shape[0], c, h, w)) - return imgs - - - def cropped_pos_embed(self, height, width): - """Crops positional embeddings for SD3 compatibility.""" - if self.pos_embed_max_size is None: - raise ValueError("`pos_embed_max_size` must be set for cropping.") - - height = height // self.patch_size - width = width // self.patch_size - if height > self.pos_embed_max_size: - raise ValueError( - f"Height ({height}) cannot be greater than `pos_embed_max_size`: {self.pos_embed_max_size}." - ) - if width > self.pos_embed_max_size: - raise ValueError( - f"Width ({width}) cannot be greater than `pos_embed_max_size`: {self.pos_embed_max_size}." - ) - - top = (self.pos_embed_max_size - height) // 2 - left = (self.pos_embed_max_size - width) // 2 - spatial_pos_embed = self.pos_embed.reshape(1, self.pos_embed_max_size, self.pos_embed_max_size, -1) - spatial_pos_embed = spatial_pos_embed[:, top : top + height, left : left + width, :] - spatial_pos_embed = spatial_pos_embed.reshape(1, -1, spatial_pos_embed.shape[-1]) - return spatial_pos_embed - - - def patch_multiple_resolutions(self, latents, padding_latent=None, is_input_images:bool=False): - if isinstance(latents, list): - return_list = False - if padding_latent is None: - padding_latent = [None] * len(latents) - return_list = True - patched_latents, num_tokens, shapes = [], [], [] - for latent, padding in zip(latents, padding_latent): - height, width = latent.shape[-2:] - if is_input_images: - latent = self.input_x_embedder(latent) - else: - latent = self.x_embedder(latent) - pos_embed = self.cropped_pos_embed(height, width) - latent = latent + pos_embed - if padding is not None: - latent = torch.cat([latent, padding], dim=-2) - patched_latents.append(latent) - - num_tokens.append(pos_embed.size(1)) - shapes.append([height, width]) - if not return_list: - latents = torch.cat(patched_latents, dim=0) - else: - latents = patched_latents - else: - height, width = latents.shape[-2:] - if is_input_images: - latents = self.input_x_embedder(latents) - else: - latents = self.x_embedder(latents) - pos_embed = self.cropped_pos_embed(height, width) - latents = latents + pos_embed - num_tokens = latents.size(1) - shapes = [height, width] - return latents, num_tokens, shapes - - def forward(self, x, timestep, input_ids, input_img_latents, input_image_sizes, attention_mask, position_ids, padding_latent=None, past_key_values=None, return_past_key_values=True): - input_is_list = isinstance(x, list) - x, num_tokens, shapes = self.patch_multiple_resolutions(x, padding_latent) - time_token = self.time_token(timestep, dtype=x[0].dtype).unsqueeze(1) - if input_img_latents is not None: - input_latents, _, _ = self.patch_multiple_resolutions(input_img_latents, is_input_images=True) - if input_ids is not None: - condition_embeds = self.llm.embed_tokens(input_ids).clone() - input_img_inx = 0 - for b_inx in input_image_sizes.keys(): - for start_inx, end_inx in input_image_sizes[b_inx]: - condition_embeds[b_inx, start_inx: end_inx] = input_latents[input_img_inx] - input_img_inx += 1 - if input_img_latents is not None: - assert input_img_inx == len(input_latents) - - input_emb = torch.cat([condition_embeds, time_token, x], dim=1) - else: - input_emb = torch.cat([time_token, x], dim=1) - output = self.llm(inputs_embeds=input_emb, attention_mask=attention_mask, position_ids=position_ids, past_key_values=past_key_values) - output, past_key_values = output.last_hidden_state, output.past_key_values - if input_is_list: - image_embedding = output[:, -max(num_tokens):] - time_emb = self.t_embedder(timestep, dtype=x.dtype) - x = self.final_layer(image_embedding, time_emb) - latents = [] - for i in range(x.size(0)): - latent = x[i:i+1, :num_tokens[i]] - latent = self.unpatchify(latent, shapes[i][0], shapes[i][1]) - latents.append(latent) - else: - image_embedding = output[:, -num_tokens:] - time_emb = self.t_embedder(timestep, dtype=x.dtype) - x = self.final_layer(image_embedding, time_emb) - latents = self.unpatchify(x, shapes[0], shapes[1]) - - if return_past_key_values: - return latents, past_key_values - return latents - - @torch.no_grad() - def forward_with_cfg(self, x, timestep, input_ids, input_img_latents, input_image_sizes, attention_mask, position_ids, cfg_scale, use_img_cfg, img_cfg_scale, past_key_values, use_kv_cache): - """ - Forward pass of DiT, but also batches the unconditional forward pass for classifier-free guidance. - """ - self.llm.config.use_cache = use_kv_cache - model_out, past_key_values = self.forward(x, timestep, input_ids, input_img_latents, input_image_sizes, attention_mask, position_ids, past_key_values=past_key_values, return_past_key_values=True) - if use_img_cfg: - cond, uncond, img_cond = torch.split(model_out, len(model_out) // 3, dim=0) - cond = uncond + img_cfg_scale * (img_cond - uncond) + cfg_scale * (cond - img_cond) - model_out = [cond, cond, cond] - else: - cond, uncond = torch.split(model_out, len(model_out) // 2, dim=0) - cond = uncond + cfg_scale * (cond - uncond) - model_out = [cond, cond] - return torch.cat(model_out, dim=0), past_key_values - - - @torch.no_grad() - def forward_with_separate_cfg(self, x, timestep, input_ids, input_img_latents, input_image_sizes, attention_mask, position_ids, cfg_scale, use_img_cfg, img_cfg_scale, past_key_values, use_kv_cache, return_past_key_values=True): - """ - Forward pass of DiT, but also batches the unconditional forward pass for classifier-free guidance. - """ - self.llm.config.use_cache = use_kv_cache - if past_key_values is None: - past_key_values = [None] * len(attention_mask) - - x = torch.split(x, len(x) // len(attention_mask), dim=0) - timestep = timestep.to(x[0].dtype) - timestep = torch.split(timestep, len(timestep) // len(input_ids), dim=0) - - model_out, pask_key_values = [], [] - for i in range(len(input_ids)): - temp_out, temp_pask_key_values = self.forward(x[i], timestep[i], input_ids[i], input_img_latents[i], input_image_sizes[i], attention_mask[i], position_ids[i], past_key_values[i]) - model_out.append(temp_out) - pask_key_values.append(temp_pask_key_values) - - if len(model_out) == 3: - cond, uncond, img_cond = model_out - cond = uncond + img_cfg_scale * (img_cond - uncond) + cfg_scale * (cond - img_cond) - model_out = [cond, cond, cond] - elif len(model_out) == 2: - cond, uncond = model_out - cond = uncond + cfg_scale * (cond - uncond) - model_out = [cond, cond] - else: - return model_out[0] - return torch.cat(model_out, dim=0), pask_key_values diff --git a/modules/omnigen/pipeline.py b/modules/omnigen/pipeline.py deleted file mode 100644 index a07467543..000000000 --- a/modules/omnigen/pipeline.py +++ /dev/null @@ -1,219 +0,0 @@ -import os -from typing import List, Union -from PIL import Image -import torch -from huggingface_hub import snapshot_download -from peft import PeftModel -from diffusers.models import AutoencoderKL -from diffusers.utils import replace_example_docstring -from .model import OmniGen -from .processor import OmniGenProcessor -from .scheduler import OmniGenScheduler - - -EXAMPLE_DOC_STRING = """ - Examples: - ```py - >>> from OmniGen import OmniGenPipeline - >>> pipe = FluxControlNetPipeline.from_pretrained( - ... base_model - ... ) - >>> prompt = "A woman holds a bouquet of flowers and faces the camera" - >>> image = pipe( - ... prompt, - ... guidance_scale=3.0, - ... num_inference_steps=50, - ... ).images[0] - >>> image.save("t2i.png") - ``` -""" - - -class OmniGenPipeline(): - def __init__( - self, - vae: AutoencoderKL, - model: OmniGen, - processor: OmniGenProcessor, - ): - super().__init__() - self.vae = vae - self.model = model - self.processor = processor - self.device = None - self.dtype: None - self.separate_cfg_infer: bool = True - self.use_kv_cache: bool = False - # omnigen does not inherit from diffusionpipeline so we hack it - self._internal_dict = { # pylint: disable=protected-access - 'vae': self.vae, - 'model': self.model, - 'processor': self.processor, - } - - @classmethod - def from_pretrained(cls, model_name, vae_path: str=None, cache_dir: str=None): - if not os.path.exists(model_name): - cache_dir = cache_dir or os.getenv('HF_HUB_CACHE') - model_name = snapshot_download(repo_id=model_name, - cache_dir=cache_dir, - ignore_patterns=['flax_model.msgpack', 'rust_model.ot', 'tf_model.h5']) - model = OmniGen.from_pretrained(model_name) - processor = OmniGenProcessor.from_pretrained(model_name) - if os.path.exists(os.path.join(model_name, "vae")): - vae = AutoencoderKL.from_pretrained(os.path.join(model_name, "vae")) - else: - vae = AutoencoderKL.from_pretrained(vae_path or "stabilityai/sdxl-vae") - return cls(vae, model, processor) - - def merge_lora(self, lora_path: str): - model = PeftModel.from_pretrained(self.model, lora_path) - model.merge_and_unload() - self.model = model - - def to(self, device: Union[str, torch.device]): - if isinstance(device, str): - device = torch.device(device) - self.model.to(device) - self.vae.to(device) - - def vae_encode(self, x, dtype): - x = x.to(dtype) - if self.vae.config.shift_factor is not None: - x = self.vae.encode(x).latent_dist.sample() - x = (x - self.vae.config.shift_factor) * self.vae.config.scaling_factor - else: - x = self.vae.encode(x).latent_dist.sample().mul_(self.vae.config.scaling_factor) - x = x.to(dtype) - return x - - def move_to_device(self, data): - if isinstance(data, list): - return [x.to(self.device) for x in data] - return data.to(self.device) - - - @torch.no_grad() - @replace_example_docstring(EXAMPLE_DOC_STRING) - def __call__( - self, - prompt: Union[str, List[str]], - input_images: Union[List[str], List[List[str]]] = None, - height: int = 1024, - width: int = 1024, - num_inference_steps: int = 50, - guidance_scale: float = 3, - use_img_guidance: bool = True, - img_guidance_scale: float = 1.6, - output_type: str = 'latent', - seed: int = None, - ): - r""" - Function invoked when calling the pipeline for generation. - - Args: - prompt (`str` or `List[str]`): - The prompt or prompts to guide the image generation. - input_images (`List[str]` or `List[List[str]]`, *optional*): - The list of input images. We will replace the "<|image_i|>" in prompt with the 1-th image in list. - height (`int`, *optional*, defaults to 1024): - The height in pixels of the generated image. The number must be a multiple of 16. - width (`int`, *optional*, defaults to 1024): - The width in pixels of the generated image. The number must be a multiple of 16. - num_inference_steps (`int`, *optional*, defaults to 50): - The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference. - guidance_scale (`float`, *optional*, defaults to 4.0): - Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). - `guidance_scale` is defined as `w` of equation 2. of [Imagen - Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > - 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, - usually at the expense of lower image quality. - use_img_guidance (`bool`, *optional*, defaults to True): - Defined as equation 3 in [Instrucpix2pix](https://arxiv.org/pdf/2211.09800). - img_guidance_scale (`float`, *optional*, defaults to 1.6): - Defined as equation 3 in [Instrucpix2pix](https://arxiv.org/pdf/2211.09800). - self.separate_cfg_infer (`bool`, *optional*, defaults to False): - Perform inference on images with different guidance separately; this can save memory when generating images of large size at the expense of slower inference. - self.use_kv_cache (`bool`, *optional*, defaults to True): enable kv cache to speed up the inference - generator (`torch.Generator` or `List[torch.Generator]`, *optional*): - One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) - to make generation deterministic. - Examples: - - Returns: - A list with the generated images. - """ - assert height%16 == 0 and width%16 == 0 - if self.separate_cfg_infer: - self.use_kv_cache = False - # raise "Currently, don't support both self.use_kv_cache and self.separate_cfg_infer" - if input_images is None: - use_img_guidance = False - if isinstance(prompt, str): - prompt = [prompt] - input_images = [input_images] if input_images is not None else None - - input_data = self.processor(prompt, input_images, height=height, width=width, use_img_cfg=use_img_guidance, separate_cfg_input=self.separate_cfg_infer) - - num_prompt = len(prompt) - num_cfg = 2 if use_img_guidance else 1 - latent_size_h, latent_size_w = height//8, width//8 - - if seed is not None: - generator = torch.Generator(device=self.device).manual_seed(int(seed)) - else: - generator = None - latents = torch.randn(num_prompt, 4, latent_size_h, latent_size_w, device=self.device, generator=generator) - latents = torch.cat([latents]*(1+num_cfg), 0).to(self.dtype) - - input_img_latents = [] - if self.separate_cfg_infer: - for temp_pixel_values in input_data['input_pixel_values']: - temp_input_latents = [] - for img in temp_pixel_values: - img = self.vae_encode(img.to(self.device), self.dtype) - temp_input_latents.append(img) - input_img_latents.append(temp_input_latents) - else: - for img in input_data['input_pixel_values']: - img = self.vae_encode(img.to(self.device), self.dtype) - input_img_latents.append(img) - - model_kwargs = dict(input_ids=self.move_to_device(input_data['input_ids']), - input_img_latents=input_img_latents, - input_image_sizes=input_data['input_image_sizes'], - attention_mask=self.move_to_device(input_data["attention_mask"]), - position_ids=self.move_to_device(input_data["position_ids"]), - cfg_scale=guidance_scale, - img_cfg_scale=img_guidance_scale, - use_img_cfg=use_img_guidance, - use_kv_cache=self.use_kv_cache) - - if self.separate_cfg_infer: - func = self.model.forward_with_separate_cfg - else: - func = self.model.forward_with_cfg - self.model.to(self.dtype) - - scheduler = OmniGenScheduler(num_steps=num_inference_steps) - samples = scheduler(latents, func, model_kwargs, use_kv_cache=self.use_kv_cache) - samples = samples.chunk((1+num_cfg), dim=0)[0] - - if output_type == 'latent': - output_images = { 'images': samples } - return output_images - - samples = samples.to(self.vae.dtype) - if self.vae.config.shift_factor is not None: - samples = samples / self.vae.config.scaling_factor + self.vae.config.shift_factor - else: - samples = samples / self.vae.config.scaling_factor - samples = self.vae.decode(samples).sample - - output_samples = (samples * 0.5 + 0.5).clamp(0, 1)*255 - output_samples = output_samples.permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy() - output_images = [] - for _i, sample in enumerate(output_samples): - output_images.append(Image.fromarray(sample)) - - return output_images diff --git a/modules/omnigen/processor.py b/modules/omnigen/processor.py deleted file mode 100644 index ada813a8b..000000000 --- a/modules/omnigen/processor.py +++ /dev/null @@ -1,312 +0,0 @@ -import os -import re -from typing import Dict, List -import torch -from torchvision import transforms -from transformers import AutoTokenizer -from huggingface_hub import snapshot_download -from .utils import crop_arr - - -class OmniGenProcessor: - def __init__(self, - text_tokenizer, - max_image_size: int=1024): - self.text_tokenizer = text_tokenizer - self.max_image_size = max_image_size - - self.image_transform = transforms.Compose([ - transforms.Lambda(lambda pil_image: crop_arr(pil_image, max_image_size)), - transforms.ToTensor(), - transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True) - ]) - - self.collator = OmniGenCollator() - self.separate_collator = OmniGenSeparateCollator() - - @classmethod - def from_pretrained(cls, model_name): - if not os.path.exists(model_name): - cache_folder = os.getenv('HF_HUB_CACHE') - model_name = snapshot_download(repo_id=model_name, - cache_dir=cache_folder, - allow_patterns="*.json") - text_tokenizer = AutoTokenizer.from_pretrained(model_name) - - return cls(text_tokenizer) - - - def process_image(self, image): - return self.image_transform(image) - - def process_multi_modal_prompt(self, text, input_images): - text = self.add_prefix_instruction(text) - if input_images is None or len(input_images) == 0: - model_inputs = self.text_tokenizer(text) - return {"input_ids": model_inputs.input_ids, "pixel_values": None, "image_sizes": None} - - pattern = r"<\|image_\d+\|>" - prompt_chunks = [self.text_tokenizer(chunk).input_ids for chunk in re.split(pattern, text)] - - for i in range(1, len(prompt_chunks)): - if prompt_chunks[i][0] == 1: - prompt_chunks[i] = prompt_chunks[i][1:] - - image_tags = re.findall(pattern, text) - image_ids = [int(s.split("|")[1].split("_")[-1]) for s in image_tags] - - unique_image_ids = sorted(list(set(image_ids))) - assert unique_image_ids == list(range(1, len(unique_image_ids)+1)), f"image_ids must start from 1, and must be continuous int, e.g. [1, 2, 3], cannot be {unique_image_ids}" - # total images must be the same as the number of image tags - assert len(unique_image_ids) == len(input_images), f"total images must be the same as the number of image tags, got {len(unique_image_ids)} image tags and {len(input_images)} images" - - input_images = [input_images[x-1] for x in image_ids] - - all_input_ids = [] - img_inx = [] - _idx = 0 - for i in range(len(prompt_chunks)): - all_input_ids.extend(prompt_chunks[i]) - if i != len(prompt_chunks) -1: - start_inx = len(all_input_ids) - size = input_images[i].size(-2) * input_images[i].size(-1) // 16 // 16 - img_inx.append([start_inx, start_inx+size]) - all_input_ids.extend([0]*size) - - return {"input_ids": all_input_ids, "pixel_values": input_images, "image_sizes": img_inx} - - - def add_prefix_instruction(self, prompt): - user_prompt = '<|user|>\n' - generation_prompt = 'Generate an image according to the following instructions\n' - assistant_prompt = '<|assistant|>\n<|diffusion|>' - prompt_suffix = "<|end|>\n" - prompt = f"{user_prompt}{generation_prompt}{prompt}{prompt_suffix}{assistant_prompt}" - return prompt - - - def __call__(self, - instructions: List[str], - input_images: List[List[str]] = None, - height: int = 1024, - width: int = 1024, - negative_prompt: str = "low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers.", - use_img_cfg: bool = True, - separate_cfg_input: bool = False, - ) -> Dict: - - if input_images is None: - use_img_cfg = False - if isinstance(instructions, str): - instructions = [instructions] - input_images = [input_images] - - input_data = [] - for i in range(len(instructions)): - cur_instruction = instructions[i] - cur_input_images = None if input_images is None else input_images[i] - if cur_input_images is not None and len(cur_input_images) > 0: - cur_input_images = [self.process_image(x) for x in cur_input_images] - else: - cur_input_images = None - assert "<|image_1|>" not in cur_instruction - - mllm_input = self.process_multi_modal_prompt(cur_instruction, cur_input_images) - - neg_mllm_input, img_cfg_mllm_input = None, None - neg_mllm_input = self.process_multi_modal_prompt(negative_prompt, None) - if use_img_cfg: - if cur_input_images is not None and len(cur_input_images) >= 1: - img_cfg_prompt = [f"<|image_{i+1}|>" for i in range(len(cur_input_images))] - img_cfg_mllm_input = self.process_multi_modal_prompt(" ".join(img_cfg_prompt), cur_input_images) - else: - img_cfg_mllm_input = neg_mllm_input - - input_data.append((mllm_input, neg_mllm_input, img_cfg_mllm_input, [height, width])) - - if separate_cfg_input: - return self.separate_collator(input_data) - return self.collator(input_data) - - -class OmniGenCollator: - def __init__(self, pad_token_id=2, hidden_size=3072): - self.pad_token_id = pad_token_id - self.hidden_size = hidden_size - - def create_position(self, attention_mask, num_tokens_for_output_images): - position_ids = [] - text_length = attention_mask.size(-1) - img_length = max(num_tokens_for_output_images) - for mask in attention_mask: - temp_l = torch.sum(mask) - temp_position = [0]*(text_length-temp_l) + [i for i in range(temp_l+img_length+1)] # we add a time embedding into the sequence, so add one more token - position_ids.append(temp_position) - return torch.LongTensor(position_ids) - - def create_mask(self, attention_mask, num_tokens_for_output_images): - extended_mask = [] - padding_images = [] - text_length = attention_mask.size(-1) - img_length = max(num_tokens_for_output_images) - seq_len = text_length + img_length + 1 # we add a time embedding into the sequence, so add one more token - inx = 0 - for mask in attention_mask: - temp_l = torch.sum(mask) - pad_l = text_length - temp_l - - temp_mask = torch.tril(torch.ones(size=(temp_l+1, temp_l+1))) - - image_mask = torch.zeros(size=(temp_l+1, img_length)) - temp_mask = torch.cat([temp_mask, image_mask], dim=-1) - - image_mask = torch.ones(size=(img_length, temp_l+img_length+1)) - temp_mask = torch.cat([temp_mask, image_mask], dim=0) - - if pad_l > 0: - pad_mask = torch.zeros(size=(temp_l+1+img_length, pad_l)) - temp_mask = torch.cat([pad_mask, temp_mask], dim=-1) - - pad_mask = torch.ones(size=(pad_l, seq_len)) - temp_mask = torch.cat([pad_mask, temp_mask], dim=0) - - true_img_length = num_tokens_for_output_images[inx] - pad_img_length = img_length - true_img_length - if pad_img_length > 0: - temp_mask[:, -pad_img_length:] = 0 - temp_padding_imgs = torch.zeros(size=(1, pad_img_length, self.hidden_size)) - else: - temp_padding_imgs = None - - extended_mask.append(temp_mask.unsqueeze(0)) - padding_images.append(temp_padding_imgs) - inx += 1 - return torch.cat(extended_mask, dim=0), padding_images - - def adjust_attention_for_input_images(self, attention_mask, image_sizes): - for b_inx in image_sizes.keys(): - for start_inx, end_inx in image_sizes[b_inx]: - attention_mask[b_inx][start_inx:end_inx, start_inx:end_inx] = 1 - - return attention_mask - - def pad_input_ids(self, input_ids, image_sizes): - max_l = max([len(x) for x in input_ids]) - padded_ids = [] - attention_mask = [] - _new_image_sizes = [] - - for i in range(len(input_ids)): - temp_ids = input_ids[i] - temp_l = len(temp_ids) - pad_l = max_l - temp_l - if pad_l == 0: - attention_mask.append([1]*max_l) - padded_ids.append(temp_ids) - else: - attention_mask.append([0]*pad_l+[1]*temp_l) - padded_ids.append([self.pad_token_id]*pad_l+temp_ids) - - if i in image_sizes: - new_inx = [] - for old_inx in image_sizes[i]: - new_inx.append([x+pad_l for x in old_inx]) - image_sizes[i] = new_inx - - return torch.LongTensor(padded_ids), torch.LongTensor(attention_mask), image_sizes - - - def process_mllm_input(self, mllm_inputs, target_img_size): - num_tokens_for_output_images = [] - for img_size in target_img_size: - num_tokens_for_output_images.append(img_size[0]*img_size[1]//16//16) - - pixel_values, image_sizes = [], {} - b_inx = 0 - for x in mllm_inputs: - if x['pixel_values'] is not None: - pixel_values.extend(x['pixel_values']) - for size in x['image_sizes']: - if b_inx not in image_sizes: - image_sizes[b_inx] = [size] - else: - image_sizes[b_inx].append(size) - b_inx += 1 - pixel_values = [x.unsqueeze(0) for x in pixel_values] - - input_ids = [x['input_ids'] for x in mllm_inputs] - padded_input_ids, attention_mask, image_sizes = self.pad_input_ids(input_ids, image_sizes) - position_ids = self.create_position(attention_mask, num_tokens_for_output_images) - attention_mask, padding_images = self.create_mask(attention_mask, num_tokens_for_output_images) - attention_mask = self.adjust_attention_for_input_images(attention_mask, image_sizes) - - return padded_input_ids, position_ids, attention_mask, padding_images, pixel_values, image_sizes - - def __call__(self, features): - mllm_inputs = [f[0] for f in features] - cfg_mllm_inputs = [f[1] for f in features] - img_cfg_mllm_input = [f[2] for f in features] - target_img_size = [f[3] for f in features] - - if img_cfg_mllm_input[0] is not None: - mllm_inputs = mllm_inputs + cfg_mllm_inputs + img_cfg_mllm_input - target_img_size = target_img_size + target_img_size + target_img_size - else: - mllm_inputs = mllm_inputs + cfg_mllm_inputs - target_img_size = target_img_size + target_img_size - - - all_padded_input_ids, all_position_ids, all_attention_mask, all_padding_images, all_pixel_values, all_image_sizes = self.process_mllm_input(mllm_inputs, target_img_size) - - data = {"input_ids": all_padded_input_ids, - "attention_mask": all_attention_mask, - "position_ids": all_position_ids, - "input_pixel_values": all_pixel_values, - "input_image_sizes": all_image_sizes, - "padding_images": all_padding_images, - } - return data - - -class OmniGenSeparateCollator(OmniGenCollator): - def __call__(self, features): - mllm_inputs = [f[0] for f in features] - cfg_mllm_inputs = [f[1] for f in features] - img_cfg_mllm_input = [f[2] for f in features] - target_img_size = [f[3] for f in features] - - all_padded_input_ids, all_attention_mask, all_position_ids, all_pixel_values, all_image_sizes, all_padding_images = [], [], [], [], [], [] - - padded_input_ids, position_ids, attention_mask, padding_images, pixel_values, image_sizes = self.process_mllm_input(mllm_inputs, target_img_size) - all_padded_input_ids.append(padded_input_ids) - all_attention_mask.append(attention_mask) - all_position_ids.append(position_ids) - all_pixel_values.append(pixel_values) - all_image_sizes.append(image_sizes) - all_padding_images.append(padding_images) - - if cfg_mllm_inputs[0] is not None: - padded_input_ids, position_ids, attention_mask, padding_images, pixel_values, image_sizes = self.process_mllm_input(cfg_mllm_inputs, target_img_size) - all_padded_input_ids.append(padded_input_ids) - all_attention_mask.append(attention_mask) - all_position_ids.append(position_ids) - all_pixel_values.append(pixel_values) - all_image_sizes.append(image_sizes) - all_padding_images.append(padding_images) - if img_cfg_mllm_input[0] is not None: - padded_input_ids, position_ids, attention_mask, padding_images, pixel_values, image_sizes = self.process_mllm_input(img_cfg_mllm_input, target_img_size) - all_padded_input_ids.append(padded_input_ids) - all_attention_mask.append(attention_mask) - all_position_ids.append(position_ids) - all_pixel_values.append(pixel_values) - all_image_sizes.append(image_sizes) - all_padding_images.append(padding_images) - - data = {"input_ids": all_padded_input_ids, - "attention_mask": all_attention_mask, - "position_ids": all_position_ids, - "input_pixel_values": all_pixel_values, - "input_image_sizes": all_image_sizes, - "padding_images": all_padding_images, - } - return data diff --git a/modules/omnigen/scheduler.py b/modules/omnigen/scheduler.py deleted file mode 100644 index 0764fd8f0..000000000 --- a/modules/omnigen/scheduler.py +++ /dev/null @@ -1,55 +0,0 @@ -import torch -from tqdm import tqdm -from transformers.cache_utils import Cache, DynamicCache - -class OmniGenScheduler: - def __init__(self, num_steps: int=50, time_shifting_factor: int=1): - self.num_steps = num_steps - self.time_shift = time_shifting_factor - - t = torch.linspace(0, 1, num_steps+1) - t = t / (t + time_shifting_factor - time_shifting_factor * t) - self.sigma = t - - def crop_kv_cache(self, past_key_values, num_tokens_for_img): - crop_past_key_values = () - for layer_idx in range(len(past_key_values)): - key_states, value_states = past_key_values[layer_idx][:2] - crop_past_key_values += ((key_states[..., :-(num_tokens_for_img+1), :], value_states[..., :-(num_tokens_for_img+1), :], ),) - return crop_past_key_values - # return DynamicCache.from_legacy_cache(crop_past_key_values) - - def crop_position_ids_for_cache(self, position_ids, num_tokens_for_img): - if isinstance(position_ids, list): - for i in range(len(position_ids)): - position_ids[i] = position_ids[i][:, -(num_tokens_for_img+1):] - else: - position_ids = position_ids[:, -(num_tokens_for_img+1):] - return position_ids - - def crop_attention_mask_for_cache(self, attention_mask, num_tokens_for_img): - if isinstance(attention_mask, list): - return [x[..., -(num_tokens_for_img+1):, :] for x in attention_mask] - return attention_mask[..., -(num_tokens_for_img+1):, :] - - def __call__(self, z, func, model_kwargs, use_kv_cache: bool=True): - past_key_values = None - for i in tqdm(range(self.num_steps)): - timesteps = torch.zeros(size=(len(z), )).to(z.device) + self.sigma[i] - pred, temp_past_key_values = func(z, timesteps, past_key_values=past_key_values, **model_kwargs) - sigma_next = self.sigma[i+1] - sigma = self.sigma[i] - z = z + (sigma_next - sigma) * pred - if i == 0 and use_kv_cache: - num_tokens_for_img = z.size(-1)*z.size(-2) // 4 - if isinstance(temp_past_key_values, list): - past_key_values = [self.crop_kv_cache(x, num_tokens_for_img) for x in temp_past_key_values] - model_kwargs['input_ids'] = [None] * len(temp_past_key_values) - else: - past_key_values = self.crop_kv_cache(temp_past_key_values, num_tokens_for_img) - model_kwargs['input_ids'] = None - - model_kwargs['position_ids'] = self.crop_position_ids_for_cache(model_kwargs['position_ids'], num_tokens_for_img) - model_kwargs['attention_mask'] = self.crop_attention_mask_for_cache(model_kwargs['attention_mask'], num_tokens_for_img) - return z - diff --git a/modules/omnigen/transformer.py b/modules/omnigen/transformer.py deleted file mode 100644 index d166309ca..000000000 --- a/modules/omnigen/transformer.py +++ /dev/null @@ -1,164 +0,0 @@ -import math -import warnings -from typing import List, Optional, Tuple, Union - -import torch -import torch.utils.checkpoint -from torch import nn -from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss -from huggingface_hub import snapshot_download - -from transformers.modeling_outputs import ( - BaseModelOutputWithPast, - CausalLMOutputWithPast, - SequenceClassifierOutputWithPast, - TokenClassifierOutput, -) -from transformers.modeling_utils import PreTrainedModel -from transformers import Phi3Config, Phi3Model -from transformers.cache_utils import Cache, DynamicCache, StaticCache -from transformers.utils import logging - -logger = logging.get_logger(__name__) - - -class Phi3Transformer(Phi3Model): - """ - Transformer decoder consisting of *config.num_hidden_layers* layers. Each layer is a [`Phi3DecoderLayer`] - We only modified the attention mask - Args: - config: Phi3Config - """ - - def forward( - self, - input_ids: torch.LongTensor = None, - attention_mask: Optional[torch.Tensor] = None, - position_ids: Optional[torch.LongTensor] = None, - past_key_values: Optional[List[torch.FloatTensor]] = None, - inputs_embeds: Optional[torch.FloatTensor] = None, - use_cache: Optional[bool] = None, - output_attentions: Optional[bool] = None, - output_hidden_states: Optional[bool] = None, - return_dict: Optional[bool] = None, - cache_position: Optional[torch.LongTensor] = None, - ) -> Union[Tuple, BaseModelOutputWithPast]: - output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions - output_hidden_states = ( - output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states - ) - use_cache = use_cache if use_cache is not None else self.config.use_cache - - return_dict = return_dict if return_dict is not None else self.config.use_return_dict - - if (input_ids is None) ^ (inputs_embeds is not None): - raise ValueError("You must specify exactly one of input_ids or inputs_embeds") - - if self.gradient_checkpointing and self.training: - if use_cache: - logger.warning_once( - "`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`..." - ) - use_cache = False - - # kept for BC (non `Cache` `past_key_values` inputs) - return_legacy_cache = False - if use_cache and not isinstance(past_key_values, Cache): - return_legacy_cache = True - if past_key_values is None: - past_key_values = DynamicCache() - else: - past_key_values = DynamicCache.from_legacy_cache(past_key_values) - logger.warning_once( - "We detected that you are passing `past_key_values` as a tuple of tuples. This is deprecated and " - "will be removed in v4.47. Please convert your cache or use an appropriate `Cache` class " - "(https://huggingface.co/docs/transformers/kv_cache#legacy-cache-format)" - ) - - if inputs_embeds is None: - inputs_embeds = self.embed_tokens(input_ids) - - if cache_position is None: - past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0 - cache_position = torch.arange( - past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device - ) - if position_ids is None: - position_ids = cache_position.unsqueeze(0) - - if attention_mask is not None and attention_mask.dim() == 3: - dtype = inputs_embeds.dtype - min_dtype = torch.finfo(dtype).min - attention_mask = (1 - attention_mask) * min_dtype - attention_mask = attention_mask.unsqueeze(1).to(inputs_embeds.dtype) - else: - raise - # causal_mask = self._update_causal_mask( - # attention_mask, inputs_embeds, cache_position, past_key_values, output_attentions - # ) - - hidden_states = inputs_embeds - - # create position embeddings to be shared across the decoder layers - position_embeddings = self.rotary_emb(hidden_states, position_ids) - - # decoder layers - all_hidden_states = () if output_hidden_states else None - all_self_attns = () if output_attentions else None - next_decoder_cache = None - - for decoder_layer in self.layers: - if output_hidden_states: - all_hidden_states += (hidden_states,) - - if self.gradient_checkpointing and self.training: - layer_outputs = self._gradient_checkpointing_func( - decoder_layer.__call__, - hidden_states, - attention_mask, - position_ids, - past_key_values, - output_attentions, - use_cache, - cache_position, - position_embeddings, - ) - else: - layer_outputs = decoder_layer( - hidden_states, - attention_mask=attention_mask, - position_ids=position_ids, - past_key_value=past_key_values, - output_attentions=output_attentions, - use_cache=use_cache, - cache_position=cache_position, - position_embeddings=position_embeddings, - ) - - hidden_states = layer_outputs[0] - - if use_cache: - next_decoder_cache = layer_outputs[2 if output_attentions else 1] - - if output_attentions: - all_self_attns += (layer_outputs[1],) - - hidden_states = self.norm(hidden_states) - - # add hidden states from the last decoder layer - if output_hidden_states: - all_hidden_states += (hidden_states,) - - next_cache = next_decoder_cache if use_cache else None - if return_legacy_cache: - next_cache = next_cache.to_legacy_cache() - - if not return_dict: - return tuple(v for v in [hidden_states, next_cache, all_hidden_states, all_self_attns] if v is not None) - return BaseModelOutputWithPast( - last_hidden_state=hidden_states, - past_key_values=next_cache, - hidden_states=all_hidden_states, - attentions=all_self_attns, - ) - diff --git a/modules/omnigen/utils.py b/modules/omnigen/utils.py deleted file mode 100644 index bf0a6de62..000000000 --- a/modules/omnigen/utils.py +++ /dev/null @@ -1,105 +0,0 @@ -import logging - -from PIL import Image -import torch -import numpy as np - -def create_logger(logging_dir): - """ - Create a logger that writes to a log file and stdout. - """ - logging.basicConfig( - level=logging.INFO, - format='[\033[34m%(asctime)s\033[0m] %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', - handlers=[logging.StreamHandler(), logging.FileHandler(f"{logging_dir}/log.txt")] - ) - logger = logging.getLogger(__name__) - return logger - - -@torch.no_grad() -def update_ema(ema_model, model, decay=0.9999): - """ - Step the EMA model towards the current model. - """ - ema_params = dict(ema_model.named_parameters()) - for name, param in model.named_parameters(): - ema_params[name].mul_(decay).add_(param.data, alpha=1 - decay) - - -def requires_grad(model, flag=True): - """ - Set requires_grad flag for all parameters in a model. - """ - for p in model.parameters(): - p.requires_grad = flag - - -def center_crop_arr(pil_image, image_size): - """ - Center cropping implementation from ADM. - https://github.com/openai/guided-diffusion/blob/8fb3ad9197f16bbc40620447b2742e13458d2831/guided_diffusion/image_datasets.py#L126 - """ - while min(*pil_image.size) >= 2 * image_size: - pil_image = pil_image.resize( - tuple(x // 2 for x in pil_image.size), resample=Image.Resampling.LANCZOS - ) - - scale = image_size / min(*pil_image.size) - pil_image = pil_image.resize( - tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS - ) - - arr = np.array(pil_image) - crop_y = (arr.shape[0] - image_size) // 2 - crop_x = (arr.shape[1] - image_size) // 2 - return Image.fromarray(arr[crop_y: crop_y + image_size, crop_x: crop_x + image_size]) - - -def crop_arr(pil_image, max_image_size): - while min(*pil_image.size) >= 2 * max_image_size: - pil_image = pil_image.resize( - tuple(x // 2 for x in pil_image.size), resample=Image.Resampling.LANCZOS - ) - - if max(*pil_image.size) > max_image_size: - scale = max_image_size / max(*pil_image.size) - pil_image = pil_image.resize( - tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS - ) - - if min(*pil_image.size) < 16: - scale = 16 / min(*pil_image.size) - pil_image = pil_image.resize( - tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS - ) - - arr = np.array(pil_image) - crop_y1 = (arr.shape[0] % 16) // 2 - crop_y2 = arr.shape[0] % 16 - crop_y1 - - crop_x1 = (arr.shape[1] % 16) // 2 - crop_x2 = arr.shape[1] % 16 - crop_x1 - - arr = arr[crop_y1:arr.shape[0]-crop_y2, crop_x1:arr.shape[1]-crop_x2] - return Image.fromarray(arr) - - -def vae_encode(vae, x, weight_dtype): - if x is not None: - if vae.config.shift_factor is not None: - x = vae.encode(x).latent_dist.sample() - x = (x - vae.config.shift_factor) * vae.config.scaling_factor - else: - x = vae.encode(x).latent_dist.sample().mul_(vae.config.scaling_factor) - x = x.to(weight_dtype) - return x - - -def vae_encode_list(vae, x, weight_dtype): - latents = [] - for img in x: - img = vae_encode(vae, img, weight_dtype) - latents.append(img) - return latents diff --git a/modules/processing_args.py b/modules/processing_args.py index 6ef43667b..d9ad9869f 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -166,7 +166,7 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t extra_networks.activate(p, include=['text_encoder', 'text_encoder_2', 'text_encoder_3']) if 'prompt' in possible: if 'OmniGen' in model.__class__.__name__: - prompts = [p.replace('|image|', '<|image_1|>') for p in prompts] + prompts = [p.replace('|image|', '<|image_1|>') for p in prompts] if 'HiDreamImage' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None: args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') prompt_embeds = prompt_parser_diffusers.embedder('prompt_embeds') diff --git a/modules/sd_offload.py b/modules/sd_offload.py index c57165fb5..57057d233 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -14,7 +14,7 @@ debug_move = log.trace if debug else lambda *args, **kwargs: None offload_warn = ['sc', 'sd3', 'f1', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'cogview4'] offload_post = ['h1'] offload_hook_instance = None -balanced_offload_exclude = ['OmniGenPipeline', 'CogView4Pipeline'] +balanced_offload_exclude = ['CogView4Pipeline'] def get_signature(cls): diff --git a/modules/sd_vae_remote.py b/modules/sd_vae_remote.py index 741d349bc..099c48148 100644 --- a/modules/sd_vae_remote.py +++ b/modules/sd_vae_remote.py @@ -12,14 +12,25 @@ hf_decode_endpoints = { 'sd': 'https://q1bj3bpq6kzilnsu.us-east-1.aws.endpoints.huggingface.cloud', 'sdxl': 'https://x2dmsqunjd6k9prw.us-east-1.aws.endpoints.huggingface.cloud', 'f1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', - 'h1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', 'hunyuanvideo': 'https://o7ywnmrahorts457.us-east-1.aws.endpoints.huggingface.cloud', } +hf_decode_endpoints['pixartalpha'] = hf_decode_endpoints['sd'] +hf_decode_endpoints['pixartsigma'] = hf_decode_endpoints['sdxl'] +hf_decode_endpoints['omnigen'] = hf_decode_endpoints['sdxl'] +hf_decode_endpoints['h1'] = hf_decode_endpoints['f1'] +hf_decode_endpoints['lumina2'] = hf_decode_endpoints['f1'] + hf_encode_endpoints = { 'sd': 'https://qc6479g0aac6qwy9.us-east-1.aws.endpoints.huggingface.cloud', 'sdxl': 'https://xjqqhmyn62rog84g.us-east-1.aws.endpoints.huggingface.cloud', 'f1': 'https://ptccx55jz97f9zgo.us-east-1.aws.endpoints.huggingface.cloud', } +hf_encode_endpoints['pixartalpha'] = hf_encode_endpoints['sd'] +hf_encode_endpoints['pixartsigma'] = hf_encode_endpoints['sdxl'] +hf_encode_endpoints['omnigen'] = hf_encode_endpoints['sdxl'] +hf_encode_endpoints['h1'] = hf_encode_endpoints['f1'] +hf_encode_endpoints['lumina2'] = hf_encode_endpoints['f1'] + dtypes = { "float16": torch.float16, "float32": torch.float32, @@ -74,7 +85,7 @@ def remote_decode(latents: torch.Tensor, width: int = 0, height: int = 0, model_ params["output_type"] = "pt" params["output_tensor_type"] = "binary" headers["Accept"] = "tensor/binary" - if (model_type == 'f1' or model_type == 'h1') and (width > 0) and (height > 0): + if model_type in {'f1', 'h1', 'lumina2'} and (width > 0) and (height > 0): params['width'] = width params['height'] = height if shared.sd_model.vae is not None and shared.sd_model.vae.config is not None: diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index 3bcd1b322..7837c2b14 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -36,7 +36,7 @@ prev_cls = '' prev_type = '' prev_model = '' lock = threading.Lock() -supported = ['sd', 'sdxl', 'f1', 'h1', 'lumina2', 'hunyuanvideo', 'wanvideo', 'mochivideo', 'pixartsigma', 'pixartalpha'] +supported = ['sd', 'sdxl', 'f1', 'h1', 'lumina2', 'hunyuanvideo', 'wanvideo', 'mochivideo', 'pixartsigma', 'pixartalpha', 'omnigen'] def warn_once(msg, variant=None): @@ -57,7 +57,7 @@ def get_model(model_type = 'decoder', variant = None): cls = 'sd' elif cls in {'h1', 'lumina2'}: cls = 'f1' - elif cls == 'pixartsigma': + elif cls in {'pixartsigma', 'omnigen'}: cls = 'sdxl' elif cls not in supported: warn_once(f'cls={shared.sd_model.__class__.__name__} type={cls} unsuppported', variant=variant) diff --git a/modules/shared_items.py b/modules/shared_items.py index 9a09b8c7f..cdc1f1980 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -41,10 +41,10 @@ pipelines = { 'UniDiffuser': getattr(diffusers, 'UniDiffuserPipeline', None), 'Amused': getattr(diffusers, 'AmusedPipeline', None), 'HiDream': getattr(diffusers, 'HiDreamImagePipeline', None), + 'OmniGenPipeline': getattr(diffusers, 'DiffusionPipeline', None), # dynamically imported and redefined later 'Meissonic': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser - 'OmniGenPipeline': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser 'InstaFlow': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser 'SegMoE': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser } From 77db759d88ee23e498d8afe51326986020d302a0 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 24 Jun 2025 13:15:31 +0300 Subject: [PATCH 23/85] Cleanup --- modules/shared_items.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared_items.py b/modules/shared_items.py index cdc1f1980..baf247a35 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -41,7 +41,7 @@ pipelines = { 'UniDiffuser': getattr(diffusers, 'UniDiffuserPipeline', None), 'Amused': getattr(diffusers, 'AmusedPipeline', None), 'HiDream': getattr(diffusers, 'HiDreamImagePipeline', None), - 'OmniGenPipeline': getattr(diffusers, 'DiffusionPipeline', None), + 'OmniGenPipeline': getattr(diffusers, 'OmniGenPipeline', None), # dynamically imported and redefined later 'Meissonic': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser From b650618b274b674588155e7c8d856c6d85969a71 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 09:11:17 -0400 Subject: [PATCH 24/85] fix params.txt Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 8 +++++--- javascript/gallery.js | 2 +- modules/generation_parameters_copypaste.py | 7 +++---- modules/gr_tempdir.py | 5 +++-- modules/images.py | 6 +++--- modules/paths.py | 1 + modules/ui_extra_networks.py | 10 ++++------ 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5faadfa04..25985d83b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Change Log for SD.Next -## Update for 2025-06-24 +## Update for 2025-06-25 - **Changes** - - Use Diffusers version of OmniGen - - Support Remote VAE with Omnigen, Lumina 2 and PixArt + - Use Diffusers version of *OmniGen* + - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* - **SDNQ Quantization** - Add modules_to_not_convert support for post mode @@ -18,6 +18,8 @@ - LTXVideo default scheduler - Balanced offload with OmniGen - Quantization with OmniGen + - Do not save empty `params.txt` file + - Override `params.txt` using `SD_PATH_PARAMS` env variable ## Update for 2025-06-16 diff --git a/javascript/gallery.js b/javascript/gallery.js index 32ae0bde4..54945cf97 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -286,7 +286,7 @@ async function gallerySearch(evt) { const findDuplicates = (arr, key) => { const map = new Map(); - return arr.filter(item => { + return arr.filter((item) => { const value = item[key]; if (map.has(value)) return true; map.set(value, true); diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 98c2b39cd..6393c9051 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -3,7 +3,7 @@ import io import os from PIL import Image import gradio as gr -from modules.paths import data_path +from modules.paths import params_path from modules import shared, gr_tempdir, script_callbacks, images from modules.infotext import parse, mapping, quote, unquote # pylint: disable=unused-import @@ -223,9 +223,8 @@ def connect_paste(button, local_paste_fields, input_comp, override_settings_comp def paste_func(prompt): if prompt is None or len(prompt.strip()) == 0: - filename = os.path.join(data_path, "params.txt") - if os.path.exists(filename): - with open(filename, "r", encoding="utf8") as file: + if os.path.exists(params_path): + with open(params_path, "r", encoding="utf8") as file: prompt = file.read() shared.log.debug(f'Prompt parse: type="params" prompt="{prompt}"') else: diff --git a/modules/gr_tempdir.py b/modules/gr_tempdir.py index bbe2b2192..f19fd53a3 100644 --- a/modules/gr_tempdir.py +++ b/modules/gr_tempdir.py @@ -74,8 +74,9 @@ def pil_to_temp_file(self, img: Image, dir: str, format="png") -> str: # pylint: shared.state.image_history += 1 params = ', '.join([f'{k}: {v}' for k, v in img.info.items()]) params = params[12:] if params.startswith('parameters: ') else params - with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: - file.write(params) + if len(params) > 2: + with open(paths.params_path, "w", encoding="utf8") as file: + file.write(params) return name diff --git a/modules/images.py b/modules/images.py index bd3cd0c70..e8a0fa384 100644 --- a/modules/images.py +++ b/modules/images.py @@ -26,7 +26,6 @@ except Exception: pass - def sanitize_filename_part(text, replace_spaces=True): if text is None: return None @@ -47,8 +46,9 @@ def atomically_save_image(): while True: image, filename, extension, params, exifinfo, filename_txt = save_queue.get() shared.state.image_history += 1 - with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: - file.write(exifinfo) + if len(exifinfo) > 2: + with open(paths.params_path, "w", encoding="utf8") as file: + file.write(exifinfo) fn = filename + extension filename = filename.strip() if extension[0] != '.': # add dot if missing diff --git a/modules/paths.py b/modules/paths.py index 06cf34bff..d3257f043 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -29,6 +29,7 @@ script_path = os.path.dirname(modules_path) data_path = cli.data_dir models_config = cli.models_dir or config.get('models_dir') or 'models' models_path = models_config if os.path.isabs(models_config) else os.path.join(data_path, models_config) +params_path = os.environ.get('SD_PATH_PARAMS', os.path.join(data_path, "params.txt")) extensions_dir = cli.extensions_dir or os.path.join(data_path, "extensions") extensions_builtin_dir = "extensions-builtin" sd_configs_path = os.path.join(script_path, "configs") diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index d2f8e4576..ca4c5c855 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -941,9 +941,8 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): from modules.processing_info import get_last_args params, text = get_last_args() if (not params) or (not text) or (len(text) == 0): - filename = os.path.join(paths.data_path, "params.txt") - if os.path.exists(filename): - with open(filename, "r", encoding="utf8") as file: + if os.path.exists(paths.params_path): + with open(paths.params_path, "r", encoding="utf8") as file: text = file.read() else: text = '' @@ -960,9 +959,8 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): from modules.processing_info import get_last_args params, text = get_last_args() if (not params) or (not text) or (len(text) == 0): - fn = os.path.join(paths.data_path, "params.txt") - if os.path.exists(fn): - with open(fn, "r", encoding="utf8") as file: + if os.path.exists(paths.params_path): + with open(paths.params_path, "r", encoding="utf8") as file: text = file.read() else: text = '' From 82e5848259701bcc08e2fe1a35da46313871f3a7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 09:24:33 -0400 Subject: [PATCH 25/85] add wheel to requirements Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25985d83b..a01e5e35e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Quantization with OmniGen - Do not save empty `params.txt` file - Override `params.txt` using `SD_PATH_PARAMS` env variable + - Add `wheel` to requirements due to `pip` change ## Update for 2025-06-16 diff --git a/requirements.txt b/requirements.txt index f5eb92014..5d88bbf35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ # required for python 3.12 setuptools==69.5.1 +wheel # standard patch-ng From a5d784f073a8bdc561a4f2f13b6a1b758238ab7d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 10:30:59 -0400 Subject: [PATCH 26/85] fix delete file from main gallery view Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 ++ javascript/ui.js | 11 +++++++++ modules/processing_helpers.py | 5 +--- modules/sd_samplers.py | 11 +++++++++ modules/ui_common.py | 43 ++++++++++++++++++----------------- 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a01e5e35e..34d357111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ - Do not save empty `params.txt` file - Override `params.txt` using `SD_PATH_PARAMS` env variable - Add `wheel` to requirements due to `pip` change + - Case-insensitive sampler name matching + - Fix delete file with gallery views ## Update for 2025-06-16 diff --git a/javascript/ui.js b/javascript/ui.js index b8112d523..ea8af5902 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -60,6 +60,17 @@ function selected_gallery_index() { return result; } +function selected_gallery_files() { + let allImages = []; + try { + let allCurrentButtons = gradioApp().querySelectorAll('[style="display: block;"].tabitem div[id$=_gallery].gradio-gallery .thumbnail-item.thumbnail-small'); + if (allCurrentButtons.length === 0) allCurrentButtons = gradioApp().querySelectorAll('.gradio-gallery .thumbnails > .thumbnail-item.thumbnail-small'); + allImages = Array.from(allCurrentButtons).map((v) => v.querySelector('img')?.src); + } catch { /**/ } + const selectedIndex = selected_gallery_index(); + return [allImages, selectedIndex]; +} + function extract_image_from_gallery(gallery) { if (gallery.length === 0) return [null]; if (gallery.length === 1) return [gallery[0]]; diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index 0f2d7bc6c..461e0e9ed 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -572,10 +572,7 @@ def update_sampler(p, sd_model, second_pass=False): if hasattr(sd_model, 'scheduler'): if sampler_selection == 'None': return - if sampler_selection is None: - sampler = sd_samplers.all_samplers_map.get("UniPC") - else: - sampler = sd_samplers.all_samplers_map.get(sampler_selection, None) + sampler = sd_samplers.find_sampler(sampler_selection) if sampler is None: shared.log.warning(f'Sampler: sampler="{sampler_selection}" not found') sampler = sd_samplers.all_samplers_map.get("UniPC") diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index f73520027..16452da23 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -16,6 +16,17 @@ flow_models = ['Flux', 'StableDiffusion3', 'Lumina', 'AuraFlow', 'Sana', 'CogVi flow_models += ['Hunyuan', 'LTX', 'Mochi'] +def find_sampler(name:str): + if name is None or name == 'None': + return all_samplers_map.get("UniPC", None) + for sampler in all_samplers: + if sampler.name.lower() == name.lower() or name in sampler.aliases: + debug(f'Find sampler: name="{name}" found={sampler.name}') + return sampler + debug(f'Find sampler: name="{name}" found=None') + return None + + def list_samplers(): global all_samplers # pylint: disable=global-statement global all_samplers_map # pylint: disable=global-statement diff --git a/modules/ui_common.py b/modules/ui_common.py index d0696461a..84cae441d 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -54,7 +54,7 @@ def infotext_to_html(text): return code -def delete_files(js_data, files, _html_info, index): +def delete_files(js_data, files, all_files, index): try: data = json.loads(js_data) except Exception: @@ -63,25 +63,26 @@ def delete_files(js_data, files, _html_info, index): if index > -1 and shared.opts.save_selected_only and (index >= data['index_of_first_image']): files = [files[index]] start_index = index - filenames = [] - filenames = [] - fullfns = [] + deleted = [] + all_files = [f.split('/file=')[1] if 'file=' in f else f for f in all_files] if isinstance(all_files, list) else [] for _image_index, filedata in enumerate(files, start_index): - if 'name' in filedata and os.path.isfile(filedata['name']): - fullfn = filedata['name'] - filenames.append(os.path.basename(fullfn)) - try: - os.remove(fullfn) - base, _ext = os.path.splitext(fullfn) - desc = f'{base}.txt' - if os.path.exists(desc): - os.remove(desc) - fullfns.append(fullfn) - shared.log.info(f"Deleting image: {fullfn}") - except Exception as e: - shared.log.error(f'Error deleting file: {fullfn} {e}') - files = [image for image in files if image['name'] not in fullfns] - return files, plaintext_to_html(f"Deleted: {filenames[0] if len(filenames) > 0 else 'none'}") + try: + fn = filedata['name'] + if os.path.isfile(fn): + deleted.append(fn) + os.remove(fn) + if fn in all_files: + all_files.remove(fn) + shared.log.info(f'Delete: image="{fn}"') + base, _ext = os.path.splitext(fn) + desc = f'{base}.txt' + if os.path.exists(desc): + os.remove(desc) + shared.log.info(f'Delete: text="{fn}"') + except Exception as e: + shared.log.error(f'Delete: image="{fn}" {e}') + deleted = ', '.join(deleted) if len(deleted) > 0 else 'none' + return all_files, plaintext_to_html(f"Deleted: {deleted}") def save_files(js_data, files, html_info, index): @@ -296,8 +297,8 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None, transfe inputs=[generation_info, result_gallery, html_info, html_info], outputs=[download_files, html_log], ) - delete.click(fn=call_queue.wrap_gradio_call(delete_files),show_progress=False, - _js="(x, y, z, i) => [x, y, z, selected_gallery_index()]", + delete.click(fn=call_queue.wrap_gradio_call(delete_files), show_progress=False, + _js="(x, y, i, j) => [x, y, ...selected_gallery_files()]", inputs=[generation_info, result_gallery, html_info, html_info], outputs=[result_gallery, html_log], ) From 3d52e3fe9f6533b44ec2626f99011d61faa5d2eb Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 10:54:02 -0400 Subject: [PATCH 27/85] add /sdapi/v1/lora endpoint Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 3 +++ modules/api/api.py | 7 ++++--- modules/api/endpoints.py | 10 ---------- modules/api/loras.py | 22 ++++++++++++++++++++++ modules/lora/network.py | 21 +++++++++++++++++++++ 5 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 modules/api/loras.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d357111..a7f02d718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ - Fix Dora loading - Remove per layer GC +- **API** + - Add `/sdapi/v1/lora?lora=` endpoint that returns full lora info and metadata + - **Fixes** - IPEX with DPM2++ FlowMatch samplers - Invalid attention processor with ControlNet diff --git a/modules/api/api.py b/modules/api/api.py index 72a2090a0..06b236d02 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -5,7 +5,7 @@ from fastapi import FastAPI, APIRouter, Depends, Request from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.exceptions import HTTPException from modules import errors, shared, postprocessing -from modules.api import models, endpoints, script, helpers, server, nvml, generate, process, control, gallery, docs +from modules.api import models, endpoints, script, helpers, server, nvml, generate, process, control, gallery, loras, docs errors.install() @@ -100,8 +100,9 @@ class Api: # lora api if shared.native: - self.add_api_route("/sdapi/v1/loras", endpoints.get_loras, methods=["GET"], response_model=List[dict]) - self.add_api_route("/sdapi/v1/refresh-loras", endpoints.post_refresh_loras, methods=["POST"]) + self.add_api_route("/sdapi/v1/lora", loras.get_lora, methods=["GET"], response_model=dict) + self.add_api_route("/sdapi/v1/loras", loras.get_loras, methods=["GET"], response_model=List[dict]) + self.add_api_route("/sdapi/v1/refresh-loras", loras.post_refresh_loras, methods=["POST"]) # gallery api gallery.register_api(self.app) diff --git a/modules/api/endpoints.py b/modules/api/endpoints.py index 80b46f324..9dc94f92e 100644 --- a/modules/api/endpoints.py +++ b/modules/api/endpoints.py @@ -43,12 +43,6 @@ def get_embeddings(): return {"loaded": convert_embeddings(db.word_embeddings), "skipped": convert_embeddings(db.skipped_embeddings)} -def get_loras(): - from modules.lora import network, lora_load - def create_lora_json(obj: network.NetworkOnDisk): - return { "name": obj.name, "alias": obj.alias, "path": obj.filename, "metadata": obj.metadata } - return [create_lora_json(obj) for obj in lora_load.available_networks.values()] - def get_extra_networks(page: Optional[str] = None, name: Optional[str] = None, filename: Optional[str] = None, title: Optional[str] = None, fullname: Optional[str] = None, hash: Optional[str] = None): # pylint: disable=redefined-builtin res = [] for pg in shared.extra_networks: @@ -158,10 +152,6 @@ def post_refresh_vae(): shared.refresh_vaes() return {} -def post_refresh_loras(): - from modules.lora import lora_load - return lora_load.list_available_networks() - def get_extensions_list(): from modules import extensions extensions.list_extensions() diff --git a/modules/api/loras.py b/modules/api/loras.py new file mode 100644 index 000000000..4dc7221d4 --- /dev/null +++ b/modules/api/loras.py @@ -0,0 +1,22 @@ +from fastapi.exceptions import HTTPException + + +def get_lora(lora: str) -> dict: + from modules.lora import lora_load + if lora not in lora_load.available_networks: + raise HTTPException(status_code=404, detail=f"Lora '{lora}' not found") + obj = lora_load.available_networks[lora] + obj.info = obj.get_info() + obj.desc = obj.get_desc() + print('HERE', obj) + return obj.__dict__ + +def get_loras(): + from modules.lora import network, lora_load + def create_lora_json(obj: network.NetworkOnDisk): + return { "name": obj.name, "alias": obj.alias, "path": obj.filename, "metadata": obj.metadata } + return [create_lora_json(obj) for obj in lora_load.available_networks.values()] + +def post_refresh_loras(): + from modules.lora import lora_load + return lora_load.list_available_networks() diff --git a/modules/lora/network.py b/modules/lora/network.py index 44e5a64b9..c22ae7cf9 100644 --- a/modules/lora/network.py +++ b/modules/lora/network.py @@ -90,6 +90,27 @@ class NetworkOnDisk: if not self.hash: self.set_hash(hashes.sha256(self.filename, "lora/" + self.name, use_addnet_hash=self.is_safetensors) or '') + def get_info(self): + data = {} + if shared.cmd_opts.no_metadata: + return data + if self.filename is not None: + fn = os.path.splitext(self.filename)[0] + '.json' + if os.path.exists(fn): + data = shared.readfile(fn, silent=True) + if type(data) is list: + data = data[0] + return data + + def get_desc(self): + if shared.cmd_opts.no_metadata: + return None + if self.filename is not None: + fn = os.path.splitext(self.filename)[0] + '.txt' + if os.path.exists(fn): + return shared.readfile(fn, silent=True) + return None + def get_alias(self): if shared.opts.lora_preferred_name == "filename": return self.name From 6c3f0dd43bcb6dfa9aac8fcea74ac92987a1ab29 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 11:23:39 -0400 Subject: [PATCH 28/85] add `SD_SAVE_DEBUG` env variable Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + javascript/gallery.js | 2 +- modules/images.py | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f02d718..cd9c1a77b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Add `wheel` to requirements due to `pip` change - Case-insensitive sampler name matching - Fix delete file with gallery views + - Add `SD_SAVE_DEBUG` env variable to report all params and metadata save operations as they happen ## Update for 2025-06-16 diff --git a/javascript/gallery.js b/javascript/gallery.js index 54945cf97..eaa646c66 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -131,7 +131,7 @@ class GalleryFile extends HTMLElement { } const ext = this.name.split('.').pop().toLowerCase(); if (!['jpg', 'jpeg', 'png', 'gif', 'webp', 'jxl', 'svg', 'mp4'].includes(ext)) { - console.error(`gallery: type=${ext} file=${this.name} unsupported`); + // console.error(`gallery: type=${ext} file=${this.name} unsupported`); return; } this.hash = await getHash(`${this.folder}/${this.name}/${this.size}/${this.mtime}`); // eslint-disable-line no-use-before-define diff --git a/modules/images.py b/modules/images.py index e8a0fa384..d5c2d3130 100644 --- a/modules/images.py +++ b/modules/images.py @@ -19,6 +19,7 @@ from modules.video import save_video # pylint: disable=unused-import debug = errors.log.trace if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None +debug_save = errors.log.trace if os.environ.get('SD_SAVE_DEBUG', None) is not None else lambda *args, **kwargs: None try: from pi_heif import register_heif_opener register_heif_opener() @@ -73,6 +74,7 @@ def atomically_save_image(): pnginfo_data = PngImagePlugin.PngInfo() for k, v in params.pnginfo.items(): pnginfo_data.add_text(k, str(v)) + debug_save(f'Save pnginfo: {params.pnginfo.items()}') save_args = { 'compress_level': 6, 'pnginfo': pnginfo_data if shared.opts.image_metadata else None } elif image_format == 'JPEG': if image.mode == 'RGBA': @@ -82,12 +84,14 @@ def atomically_save_image(): image = image.point(lambda p: p * 0.0038910505836576).convert("L") save_args = { 'optimize': True, 'quality': shared.opts.jpeg_quality } if shared.opts.image_metadata: + debug_save(f'Save exif: {exifinfo}') save_args['exif'] = piexif.dump({ "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(exifinfo, encoding="unicode") } }) elif image_format == 'WEBP': if image.mode == 'I;16': image = image.point(lambda p: p * 0.0038910505836576).convert("RGB") save_args = { 'optimize': True, 'quality': shared.opts.jpeg_quality, 'lossless': shared.opts.webp_lossless } if shared.opts.image_metadata: + debug_save(f'Save exif: {exifinfo}') save_args['exif'] = piexif.dump({ "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(exifinfo, encoding="unicode") } }) elif image_format == 'JXL': if image.mode == 'I;16': @@ -96,10 +100,12 @@ def atomically_save_image(): image = image.convert("RGBA") save_args = { 'optimize': True, 'quality': shared.opts.jpeg_quality, 'lossless': shared.opts.webp_lossless } if shared.opts.image_metadata: + debug_save(f'Save exif: {exifinfo}') save_args['exif'] = piexif.dump({ "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(exifinfo, encoding="unicode") } }) else: save_args = { 'quality': shared.opts.jpeg_quality } try: + debug_save(f'Save args: {save_args}') image.save(fn, format=image_format, **save_args) except Exception as e: shared.log.error(f'Save failed: file="{fn}" format={image_format} args={save_args} {e}') From 5b486a6ef17d0db34ac2f8f7c922adbd6807555f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 15:32:37 -0400 Subject: [PATCH 29/85] sdnq add xyz grid support, improve offloading compatibility Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 7 +++++- modules/api/loras.py | 1 - modules/hidiffusion/__init__.py | 2 +- modules/interrogate/joycaption.py | 5 +++- modules/interrogate/vqa.py | 5 ++-- modules/ipadapter.py | 2 +- modules/model_quant.py | 19 ++++++++++---- modules/model_te.py | 16 +++++++----- modules/sd_models.py | 5 ++-- modules/sd_offload.py | 21 +++++++++++++++- modules/sd_vae_taesd.py | 42 +++++++++++++++++-------------- modules/shared.py | 5 ++-- modules/timer.py | 5 ++-- scripts/xyz_grid_classes.py | 10 +++++++- scripts/xyz_grid_shared.py | 12 +++++++++ 15 files changed, 112 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd9c1a77b..a4259c5bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,18 @@ ## Update for 2025-06-25 - **Changes** - - Use Diffusers version of *OmniGen* + - Add [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* + - Use Diffusers version of *OmniGen* - **SDNQ Quantization** - Add modules_to_not_convert support for post mode - Fix Qwen 2.5 with int8 matmul - Fix Dora loading - Remove per layer GC + - Improve offload compatibility + - Add support for XYZ grid to test quantization modes + *note*: you need to enable quantization and choose what it applies on, then xyz grid can change quantization mode - **API** - Add `/sdapi/v1/lora?lora=` endpoint that returns full lora info and metadata @@ -27,6 +31,7 @@ - Case-insensitive sampler name matching - Fix delete file with gallery views - Add `SD_SAVE_DEBUG` env variable to report all params and metadata save operations as they happen + - Fix TAESD model type detection ## Update for 2025-06-16 diff --git a/modules/api/loras.py b/modules/api/loras.py index 4dc7221d4..7e65a709d 100644 --- a/modules/api/loras.py +++ b/modules/api/loras.py @@ -8,7 +8,6 @@ def get_lora(lora: str) -> dict: obj = lora_load.available_networks[lora] obj.info = obj.get_info() obj.desc = obj.get_desc() - print('HERE', obj) return obj.__dict__ def get_loras(): diff --git a/modules/hidiffusion/__init__.py b/modules/hidiffusion/__init__.py index ac7dd9627..2e1a500d9 100644 --- a/modules/hidiffusion/__init__.py +++ b/modules/hidiffusion/__init__.py @@ -41,5 +41,5 @@ def apply(p, model_type): def unapply(): pipe = shared.sd_model.pipe if hasattr(shared.sd_model, 'pipe') else shared.sd_model - if hasattr(pipe, 'unet'): + if hasattr(pipe, 'unet') and pipe.unet is not None: hidiffusion.remove_hidiffusion(pipe) diff --git a/modules/interrogate/joycaption.py b/modules/interrogate/joycaption.py index cc316341a..4941f7899 100644 --- a/modules/interrogate/joycaption.py +++ b/modules/interrogate/joycaption.py @@ -58,9 +58,12 @@ opts = JoyOptions() @torch.no_grad() -def predict(question: str, image): +def predict(question: str, image, vqa_model: str = None) -> str: global llava_model, processor # pylint: disable=global-statement opts.max_new_tokens = shared.opts.interrogate_vlm_max_length + if vqa_model is not None and opts.repo != vqa_model: + opts.repo = vqa_model + llava_model = None if llava_model is None: shared.log.info(f'Interrogate: type=vlm model="JoyCaption" {str(opts)}') processor = AutoProcessor.from_pretrained(opts.repo) diff --git a/modules/interrogate/vqa.py b/modules/interrogate/vqa.py index 32820a243..5d74a8b9c 100644 --- a/modules/interrogate/vqa.py +++ b/modules/interrogate/vqa.py @@ -38,7 +38,8 @@ vlm_models = { "ToriiGate 0.4 2B": "Minthy/ToriiGate-v0.4-2B", "ToriiGate 0.4 7B": "Minthy/ToriiGate-v0.4-7B", "ViLT Base": "dandelin/vilt-b32-finetuned-vqa", # 0.5GB - "JoyCaption": "fancyfeast/llama-joycaption-alpha-two-hf-llava", # 17.4GB + "JoyCaption Alpha": "fancyfeast/llama-joycaption-alpha-two-hf-llava", # 17.4GB + "JoyCaption Beta": "fancyfeast/llama-joycaption-beta-one-hf-llava", # 17.4GB "JoyTag": "fancyfeast/joytag", # 0.7GB "AIDC Ovis2 1B": "AIDC-AI/Ovis2-1B", "AIDC Ovis2 2B": "AIDC-AI/Ovis2-2B", @@ -583,7 +584,7 @@ def interrogate(question:str='', system_prompt:str=None, prompt:str=None, image: answer = joytag.predict(image) elif 'joycaption' in vqa_model.lower(): from modules.interrogate import joycaption - answer = joycaption.predict(question, image) + answer = joycaption.predict(question, image, vqa_model) elif 'deepseek' in vqa_model.lower(): from modules.interrogate import deepseek answer = deepseek.predict(question, image, vqa_model) diff --git a/modules/ipadapter.py b/modules/ipadapter.py index a03381a1b..3b2ce3ea9 100644 --- a/modules/ipadapter.py +++ b/modules/ipadapter.py @@ -146,7 +146,7 @@ def unapply(pipe, unload: bool = False): # pylint: disable=arguments-differ if unload: shared.log.debug('IP adapter unload') pipe.unload_ip_adapter() - if hasattr(pipe, 'unet'): + if hasattr(pipe, 'unet') and pipe.unet is not None: module = pipe.unet elif hasattr(pipe, 'transformer'): module = pipe.transformer diff --git a/modules/model_quant.py b/modules/model_quant.py index b3db3f311..1eab2aa64 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -113,11 +113,15 @@ def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Mo diffusers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig transformers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig - if weights_dtype is None: - if shared.opts.sdnq_quantize_weights_mode_te != "default" and module in {"TE", "LLM"}: - weights_dtype = shared.opts.sdnq_quantize_weights_mode_te - else: + if weights_dtype is None and module in {"TE", "LLM"}: + if shared.opts.sdnq_quantize_weights_mode_te == "none": + return None + elif shared.opts.sdnq_quantize_weights_mode_te == "same as model" or shared.opts.sdnq_quantize_weights_mode_te == "default": weights_dtype = shared.opts.sdnq_quantize_weights_mode + else: + weights_dtype = shared.opts.sdnq_quantize_weights_mode_te + if weights_dtype is None: + return None if shared.opts.device_map == "gpu": quantization_device = devices.device @@ -337,6 +341,11 @@ def sdnq_quantize_model(model, op=None, sd_model=None, do_gc=True): else: weights_dtype = shared.opts.sdnq_quantize_weights_mode + if weights_dtype is None or weights_dtype == 'none': + return model + if debug: + log.trace(f'Quantization: type=SDNQ op={op} cls={model.__class__} dtype={weights_dtype} mode{shared.opts.diffusers_offload_mode}') + if shared.opts.diffusers_offload_mode in {"none", "model"}: quantization_device = devices.device if shared.opts.sdnq_quantize_with_gpu else devices.cpu return_device = devices.device @@ -397,7 +406,7 @@ def sdnq_quantize_weights(sd_model): try: t0 = time.time() from modules import shared, devices, sd_models - log.info(f"Quantization: type=SDNQ dtype={shared.opts.sdnq_quantize_weights_mode} dtype_te={shared.opts.sdnq_quantize_weights_mode_te} matmul={shared.opts.sdnq_use_quantized_matmul} group_size={shared.opts.sdnq_quantize_weights_group_size} quant_conv={shared.opts.sdnq_quantize_conv_layers} matmul_conv={shared.opts.sdnq_use_quantized_matmul_conv} quantize_with_gpu={shared.opts.sdnq_quantize_with_gpu} dequantize_fp32={shared.opts.sdnq_dequantize_fp32} modules={shared.opts.sdnq_quantize_weights}") + log.debug(f"Quantization: type=SDNQ modules={shared.opts.sdnq_quantize_weights} dtype={shared.opts.sdnq_quantize_weights_mode} dtype_te={shared.opts.sdnq_quantize_weights_mode_te} matmul={shared.opts.sdnq_use_quantized_matmul} group_size={shared.opts.sdnq_quantize_weights_group_size} quant_conv={shared.opts.sdnq_quantize_conv_layers} matmul_conv={shared.opts.sdnq_use_quantized_matmul_conv} quantize_with_gpu={shared.opts.sdnq_quantize_with_gpu} dequantize_fp32={shared.opts.sdnq_dequantize_fp32}") global quant_last_model_name, quant_last_model_device # pylint: disable=global-statement sd_model = sd_models.apply_function_to_model(sd_model, sdnq_quantize_model, shared.opts.sdnq_quantize_weights, op="sdnq") diff --git a/modules/model_te.py b/modules/model_te.py index 6472721ee..27ad10357 100644 --- a/modules/model_te.py +++ b/modules/model_te.py @@ -4,7 +4,6 @@ import torch import transformers from safetensors.torch import load_file from modules import shared, devices, files_cache, errors, model_quant -from installer import install te_dict = {} @@ -72,27 +71,32 @@ def load_t5(name=None, cache_dir=None): elif 'int8' in name.lower(): from modules.model_quant import create_sdnq_config quantization_config = create_sdnq_config(kwargs=None, allow_sdnq=True, module='any', weights_dtype='int8') - t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) + if quantization_config is not None: + t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) elif 'uint4' in name.lower(): from modules.model_quant import create_sdnq_config quantization_config = create_sdnq_config(kwargs=None, allow_sdnq=True, module='any', weights_dtype='uint4') - t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) + if quantization_config is not None: + t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) elif 'qint4' in name.lower(): model_quant.load_quanto('Load model: type=T5') quantization_config = transformers.QuantoConfig(weights='int4') - t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) + if quantization_config is not None: + t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) elif 'qint8' in name.lower(): model_quant.load_quanto('Load model: type=T5') quantization_config = transformers.QuantoConfig(weights='int8') - t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) + if quantization_config is not None: + t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) elif '/' in name: shared.log.debug(f'Load model: type=T5 repo={name}') quant_config = model_quant.create_config(module='TE') - t5 = transformers.T5EncoderModel.from_pretrained(name, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_config) + if quantization_config is not None: + t5 = transformers.T5EncoderModel.from_pretrained(name, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_config) else: t5 = None diff --git a/modules/sd_models.py b/modules/sd_models.py index 88644b17e..f6a7e53db 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -660,7 +660,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No from modules import modelstats modelstats.analyze() - shared.log.info(f"Load {op}: time={timer.summary()} native={get_native(sd_model)} memory={memory_stats()}") + shared.log.info(f"Load {op}: family={shared.sd_model_type} time={timer.dct()} native={get_native(sd_model)} memory={memory_stats()}") class DiffusersTaskType(Enum): @@ -1086,7 +1086,8 @@ def clear_caches(): lora_common.loaded_networks.clear() lora_common.previously_loaded_networks.clear() lora_load.lora_cache.clear() - from modules import prompt_parser_diffusers, memstats + from modules import prompt_parser_diffusers, memstats, sd_offload + sd_offload.offload_hook_instance = None prompt_parser_diffusers.cache.clear() memstats.reset_stats() diff --git a/modules/sd_offload.py b/modules/sd_offload.py index 57057d233..e36db4332 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -4,6 +4,7 @@ import time import inspect import torch import accelerate.hooks +import accelerate.utils.modeling from installer import log from modules import shared, devices, errors, model_quant from modules.timer import process as process_timer @@ -15,6 +16,16 @@ offload_warn = ['sc', 'sd3', 'f1', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'c offload_post = ['h1'] offload_hook_instance = None balanced_offload_exclude = ['CogView4Pipeline'] +accelerate_dtype_byte_size = None + + +def dtype_byte_size(dtype: torch.dtype): + try: + if dtype in [torch.float8_e4m3fn, torch.float8_e4m3fnuz, torch.float8_e5m2, torch.float8_e5m2fnuz]: + dtype = accelerate.utils.modeling.CustomDtype.FP8 + except Exception: # catch since older torch many not have defined dtypes + pass + return accelerate_dtype_byte_size(dtype) def get_signature(cls): @@ -58,6 +69,7 @@ def set_accelerate(sd_model): def set_diffuser_offload(sd_model, op:str='model', quiet:bool=False): + global accelerate_dtype_byte_size # pylint: disable=global-statement t0 = time.time() if not shared.native: shared.log.warning('Attempting to use offload with backend=original') @@ -67,6 +79,9 @@ def set_diffuser_offload(sd_model, op:str='model', quiet:bool=False): return if not (hasattr(sd_model, "has_accelerate") and sd_model.has_accelerate): sd_model.has_accelerate = False + if accelerate_dtype_byte_size is None: + accelerate_dtype_byte_size = accelerate.utils.modeling.dtype_byte_size + accelerate.utils.modeling.dtype_byte_size = dtype_byte_size if shared.opts.diffusers_offload_mode == "none": if shared.sd_model_type in offload_warn or 'video' in shared.sd_model_type: shared.log.warning(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} type={shared.sd_model.__class__.__name__} large model') @@ -163,14 +178,18 @@ class OffloadHook(accelerate.hooks.ModelHook): max_memory = { device_index: self.gpu, "cpu": self.cpu } device_map = getattr(module, "balanced_offload_device_map", None) if device_map is None or max_memory != getattr(module, "balanced_offload_max_memory", None): + # try: device_map = accelerate.infer_auto_device_map(module, max_memory=max_memory) + # except Exception as e: + # shared.log.error(f'Offload: type=balanced module={module.__class__.__name__} {e}') offload_dir = getattr(module, "offload_dir", os.path.join(shared.opts.accelerate_offload_path, module.__class__.__name__)) if devices.backend == "directml": keys = device_map.keys() for v in keys: if isinstance(device_map[v], int): device_map[v] = f"{devices.device.type}:{device_map[v]}" # int implies CUDA or XPU device, but it will break DirectML backend so we add type - module = accelerate.dispatch_model(module, device_map=device_map, offload_dir=offload_dir) + if device_map is not None: + module = accelerate.dispatch_model(module, device_map=device_map, offload_dir=offload_dir) module._hf_hook.execution_device = torch.device(devices.device) # pylint: disable=protected-access module.balanced_offload_device_map = device_map module.balanced_offload_max_memory = max_memory diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index 7837c2b14..08fd834f6 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -52,34 +52,36 @@ def warn_once(msg, variant=None): def get_model(model_type = 'decoder', variant = None): global prev_cls, prev_type, prev_model # pylint: disable=global-statement from modules import shared - cls = shared.sd_model_type - if cls in {'ldm', 'pixartalpha'}: - cls = 'sd' - elif cls in {'h1', 'lumina2'}: - cls = 'f1' - elif cls in {'pixartsigma', 'omnigen'}: - cls = 'sdxl' - elif cls not in supported: - warn_once(f'cls={shared.sd_model.__class__.__name__} type={cls} unsuppported', variant=variant) + model_cls = shared.sd_model_type + if model_cls is None or model_cls == 'none': + return None + elif model_cls in {'ldm', 'pixartalpha'}: + model_cls = 'sd' + elif model_cls in {'h1', 'lumina2'}: + model_cls = 'f1' + elif model_cls in {'pixartsigma', 'omnigen'}: + model_cls = 'sdxl' + elif model_cls not in supported: + warn_once(f'cls={shared.sd_model.__class__.__name__} type={model_cls} unsuppported', variant=variant) variant = variant or shared.opts.taesd_variant folder = os.path.join(paths.models_path, "TAESD") os.makedirs(folder, exist_ok=True) if variant.startswith('TAE'): cfg = TAESD_MODELS[variant] - if (cls == prev_cls) and (model_type == prev_type) and (variant == prev_model) and (cfg['model'] is not None): + if (model_cls == prev_cls) and (model_type == prev_type) and (variant == prev_model) and (cfg['model'] is not None): return cfg['model'] - fn = os.path.join(folder, cfg['fn'] + cls + '_' + model_type + '.pth') + fn = os.path.join(folder, cfg['fn'] + model_type + '_' + model_cls + '.pth') if not os.path.exists(fn): uri = cfg['uri'] if not uri.endswith('.pth'): - uri += '/tae' + cls + '_' + model_type + '.pth' + uri += '/tae' + model_cls + '_' + model_type + '.pth' try: shared.log.info(f'Decode: type="taesd" variant="{variant}": uri="{uri}" fn="{fn}" download') torch.hub.download_url_to_file(uri, fn) except Exception as e: warn_once(f'download uri={uri} {e}', variant=variant) if os.path.exists(fn): - prev_cls = cls + prev_cls = model_cls prev_type = model_type prev_model = variant shared.log.debug(f'Decode: type="taesd" variant="{variant}" fn="{fn}" load') @@ -97,14 +99,14 @@ def get_model(model_type = 'decoder', variant = None): TAESD_MODELS[variant]['model'] = TAESD(decoder_path=fn if model_type=='decoder' else None, encoder_path=fn if model_type=='encoder' else None) return TAESD_MODELS[variant]['model'] elif variant.startswith('Hybrid'): - cfg = CQYAN_MODELS[variant].get(cls, None) - if (cls == prev_cls) and (model_type == prev_type) and (variant == prev_model) and (cfg['model'] is not None): + cfg = CQYAN_MODELS[variant].get(model_cls, None) + if (model_cls == prev_cls) and (model_type == prev_type) and (variant == prev_model) and (cfg['model'] is not None): return cfg['model'] if cfg is None: - warn_once(f'cls={shared.sd_model.__class__.__name__} type={cls} unsuppported', variant=variant) + warn_once(f'cls={shared.sd_model.__class__.__name__} type={model_cls} unsuppported', variant=variant) return None repo = cfg['repo'] - prev_cls = cls + prev_cls = model_cls prev_type = model_type prev_model = variant shared.log.debug(f'Decode: type="taesd" variant="{variant}" id="{repo}" load') @@ -116,10 +118,12 @@ def get_model(model_type = 'decoder', variant = None): from modules.taesd.hybrid_small import AutoencoderSmall vae = AutoencoderSmall.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir, torch_dtype=dtype) vae = vae.to(devices.device, dtype=dtype) - CQYAN_MODELS[variant][cls]['model'] = vae + CQYAN_MODELS[variant][model_cls]['model'] = vae return vae + elif variant is None: + warn_once(f'cls={shared.sd_model.__class__.__name__} type={model_cls} variant is none', variant=variant) else: - warn_once(f'cls={shared.sd_model.__class__.__name__} type={cls} unsuppported', variant=variant) + warn_once(f'cls={shared.sd_model.__class__.__name__} type={model_cls} unsuppported', variant=variant) return None diff --git a/modules/shared.py b/modules/shared.py index 2231e0484..276143e42 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -66,6 +66,7 @@ dir_timestamps = {} dir_cache = {} max_workers = 8 default_hfcache_dir = os.environ.get("SD_HFCACHEDIR", None) or os.path.join(os.path.expanduser('~'), '.cache', 'huggingface', 'hub') +sdnq_quant_modes = ["int8", "float8_e4m3fn", "int7", "int6", "int5", "uint4", "uint3", "uint2", "float8_e5m2", "float8_e4m3fnuz", "float8_e5m2fnuz", "uint8", "uint7", "uint6", "uint5", "int4", "int3", "int2", "uint1"] class Backend(Enum): @@ -518,8 +519,8 @@ options_templates.update(options_section(("quantization", "Quantization Settings "sdnq_quantize_sep": OptionInfo("

SDNQ: SD.Next Quantization

", "", gr.HTML), "sdnq_quantize_weights": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), "sdnq_quantize_mode": OptionInfo("pre", "Quantization mode", gr.Dropdown, {"choices": ["pre", "post"], "visible": native}), - "sdnq_quantize_weights_mode": OptionInfo("int8", "Quantization type", gr.Dropdown, {"choices": ["int8", "float8_e4m3fn", "int7", "int6", "int5", "uint4", "uint3", "uint2", "float8_e5m2", "float8_e4m3fnuz", "float8_e5m2fnuz", "uint8", "uint7", "uint6", "uint5", "int4", "int3", "int2", "uint1"], "visible": native}), - "sdnq_quantize_weights_mode_te": OptionInfo("default", "Quantization type for Text Encoders", gr.Dropdown, {"choices": ["default", "int8", "float8_e4m3fn", "int7", "int6", "int5", "uint4", "uint3", "uint2", "float8_e5m2", "float8_e4m3fnuz", "float8_e5m2fnuz", "uint8", "uint7", "uint6", "uint5", "int4", "int3", "int2", "uint1"], "visible": native}), + "sdnq_quantize_weights_mode": OptionInfo("int8", "Quantization type", gr.Dropdown, {"choices": sdnq_quant_modes, "visible": native}), + "sdnq_quantize_weights_mode_te": OptionInfo("default", "Quantization type for Text Encoders", gr.Dropdown, {"choices": ['default'] + sdnq_quant_modes, "visible": native}), "sdnq_quantize_weights_group_size": OptionInfo(0, "Group size", gr.Slider, {"minimum": -1, "maximum": 4096, "step": 1, "visible": native}), "sdnq_quantize_conv_layers": OptionInfo(False, "Quantize the convolutional layers", gr.Checkbox, {"visible": native}), "sdnq_dequantize_compile": OptionInfo(devices.has_triton(), "Dequantize using torch.compile", gr.Checkbox, {"visible": native}), diff --git a/modules/timer.py b/modules/timer.py index 69107e605..59c6a1de3 100644 --- a/modules/timer.py +++ b/modules/timer.py @@ -44,8 +44,7 @@ class Timer: def summary(self, min_time=default_min_time, total=True): if self.profile: min_time = -1 - if self.total <= 0: - self.total = sum(self.records.values()) + self.total = sum(self.records.values()) res = f"total={self.total:.2f} " if total else '' additions = [x for x in self.records.items() if x[1] >= min_time] additions = sorted(additions, key=lambda x: x[1], reverse=True) @@ -60,6 +59,8 @@ class Timer: def dct(self, min_time=default_min_time): if self.profile: res = {k: round(v, 4) for k, v in self.records.items()} + self.total = sum(self.records.values()) + self.records['total'] = self.total res = {k: round(v, 2) for k, v in self.records.items() if v >= min_time} res = {k: v for k, v in sorted(res.items(), key=lambda x: x[1], reverse=True)} # noqa: C416 # pylint: disable=unnecessary-comprehension return res diff --git a/scripts/xyz_grid_classes.py b/scripts/xyz_grid_classes.py index 0d3d6dd97..289059afb 100644 --- a/scripts/xyz_grid_classes.py +++ b/scripts/xyz_grid_classes.py @@ -1,4 +1,4 @@ -from scripts.xyz_grid_shared import apply_field, apply_task_arg, apply_task_args, apply_setting, apply_prompt_primary, apply_prompt_refine, apply_prompt_detailer, apply_prompt_all, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_dict, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_lora_strength, apply_te, apply_styles, apply_upscaler, apply_context, apply_detailer, apply_override, apply_processing, apply_options, apply_seed, format_value_add_label, format_bool, format_value, format_value_join_list, do_nothing, format_nothing, str_permutations # pylint: disable=no-name-in-module, unused-import +from scripts.xyz_grid_shared import apply_field, apply_task_arg, apply_task_args, apply_setting, apply_prompt_primary, apply_prompt_refine, apply_prompt_detailer, apply_prompt_all, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_dict, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_lora_strength, apply_te, apply_styles, apply_upscaler, apply_context, apply_detailer, apply_override, apply_processing, apply_options, apply_seed, apply_sdnq_quant, apply_sdnq_quant_te, format_value_add_label, format_bool, format_value, format_value_join_list, do_nothing, format_nothing, str_permutations # pylint: disable=no-name-in-module, unused-import from modules import shared, shared_items, sd_samplers, ipadapter, sd_models, sd_vae, sd_unet @@ -58,6 +58,7 @@ class SharedSettingsStackHelper(object): extra_networks_default_multiplier = None disable_apply_metadata = None disable_apply_params = None + sdnq_quant_mode = None def __enter__(self): # Save overridden settings so they can be restored later @@ -89,6 +90,8 @@ class SharedSettingsStackHelper(object): self.teacache_thresh = shared.opts.teacache_thresh self.disable_apply_metadata = shared.opts.disable_apply_metadata self.disable_apply_params = shared.opts.disable_apply_params + self.sdnq_quant_mode = shared.opts.sdnq_quantize_weights_mode + shared.opts.data["disable_apply_metadata"] = [] shared.opts.data["disable_apply_params"] = '' @@ -135,6 +138,9 @@ class SharedSettingsStackHelper(object): if self.sd_unet != shared.opts.sd_unet: shared.opts.data["sd_unet"] = self.sd_unet sd_unet.load_unet(shared.sd_model) + if self.sdnq_quant_mode != shared.opts.sdnq_quantize_weights_mode: + shared.opts.data["sdnq_quantize_weights_mode"] = self.sdnq_quant_mode + sd_models.reload_model_weights(op='model') axis_options = [ @@ -193,6 +199,8 @@ axis_options = [ AxisOption("[Postprocess] Context", str, apply_context, choices=lambda: ["Add with forward", "Remove with forward", "Add with backward", "Remove with backward"]), AxisOption("[Postprocess] Detailer", str, apply_detailer, fmt=format_value_add_label), AxisOption("[Postprocess] Detailer strength", str, apply_field("detailer_strength")), + AxisOption("[Quant] SDNQ quant mode", str, apply_sdnq_quant, cost=0.9, fmt=format_value_add_label, choices=lambda: ['none'] + sorted(shared.sdnq_quant_modes)), + AxisOption("[Quant] SDNQ quant mode TE", str, apply_sdnq_quant_te, cost=0.9, fmt=format_value_add_label, choices=lambda: ['none'] + sorted(shared.sdnq_quant_modes)), AxisOption("[HDR] Mode", int, apply_field("hdr_mode")), AxisOption("[HDR] Brightness", float, apply_field("hdr_brightness")), AxisOption("[HDR] Color", float, apply_field("hdr_color")), diff --git a/scripts/xyz_grid_shared.py b/scripts/xyz_grid_shared.py index 2d594bac7..265af6862 100644 --- a/scripts/xyz_grid_shared.py +++ b/scripts/xyz_grid_shared.py @@ -147,6 +147,18 @@ def confirm_samplers(p, xs): shared.log.warning(f"XYZ grid: unknown sampler: {x}") +def apply_sdnq_quant(p, x, xs): + shared.opts.sdnq_quantize_weights_mode = x + sd_models.unload_model_weights(op='model') # reload will happen on-demand + shared.log.debug(f'XYZ grid apply sdnq quant: mode="{x}"') + + +def apply_sdnq_quant_te(p, x, xs): + shared.opts.sdnq_quantize_weights_mode_te = x + sd_models.unload_model_weights(op='model') # reload will happen on-demand + shared.log.debug(f'XYZ grid apply sdnq quant te: mode="{x}"') + + def apply_checkpoint(p, x, xs): if x == shared.opts.sd_model_checkpoint: return From 3a1d98c472041e5274d1d19931388f3a26f05911 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 15:33:20 -0400 Subject: [PATCH 30/85] add joycaption beta support Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4259c5bd..d4761d4ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Update for 2025-06-25 - **Changes** - - Add [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support + - Add [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* - Use Diffusers version of *OmniGen* From f8977d2f01b2c34e0d18f75f1e728d7970019307 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 15:54:43 -0400 Subject: [PATCH 31/85] add /sdapi/v1/controlnets api endpoint Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + modules/api/api.py | 1 + modules/api/endpoints.py | 4 ++++ modules/control/units/controlnet.py | 17 +++++++++++++++++ 4 files changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4761d4ac..a1ed3e66a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - **API** - Add `/sdapi/v1/lora?lora=` endpoint that returns full lora info and metadata + - Add `/sdapi/v1/controlnets?model_type=` endpoints that returns list of available controlnets for specific model type - **Fixes** - IPEX with DPM2++ FlowMatch samplers diff --git a/modules/api/api.py b/modules/api/api.py index 06b236d02..ad26a384a 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -78,6 +78,7 @@ class Api: self.add_api_route("/sdapi/v1/samplers", endpoints.get_samplers, methods=["GET"], response_model=List[models.ItemSampler]) self.add_api_route("/sdapi/v1/upscalers", endpoints.get_upscalers, methods=["GET"], response_model=List[models.ItemUpscaler]) self.add_api_route("/sdapi/v1/sd-models", endpoints.get_sd_models, methods=["GET"], response_model=List[models.ItemModel]) + self.add_api_route("/sdapi/v1/controlnets", endpoints.get_controlnets, methods=["GET"], response_model=List[str]) self.add_api_route("/sdapi/v1/hypernetworks", endpoints.get_hypernetworks, methods=["GET"], response_model=List[models.ItemHypernetwork]) self.add_api_route("/sdapi/v1/face-restorers", endpoints.get_detailers, methods=["GET"], response_model=List[models.ItemDetailer]) self.add_api_route("/sdapi/v1/prompt-styles", endpoints.get_prompt_styles, methods=["GET"], response_model=List[models.ItemStyle]) diff --git a/modules/api/endpoints.py b/modules/api/endpoints.py index 9dc94f92e..87181ba4f 100644 --- a/modules/api/endpoints.py +++ b/modules/api/endpoints.py @@ -23,6 +23,10 @@ def get_sd_models(): checkpoints.append({"title": v.title, "model_name": v.name, "filename": v.filename, "type": v.type, "hash": v.shorthash, "sha256": v.sha256, "config": sd_models_config.find_checkpoint_config_near_filename(v)}) return checkpoints +def get_controlnets(model_type: Optional[str] = None): + from modules.control.units.controlnet import api_list_models + return api_list_models() + def get_hypernetworks(): return [{"name": name, "path": shared.hypernetworks[name]} for name in shared.hypernetworks] diff --git a/modules/control/units/controlnet.py b/modules/control/units/controlnet.py index d2e59348c..debe54c16 100644 --- a/modules/control/units/controlnet.py +++ b/modules/control/units/controlnet.py @@ -137,6 +137,23 @@ def find_models(): find_models() + +def api_list_models(model_type: str = None): + import modules.shared + model_type = model_type or modules.shared.sd_model_type + model_list = [] + if model_type == 'sd' or model_type == 'all': + model_list += list(predefined_sd15) + if model_type == 'sdxl' or model_type == 'all': + model_list += list(predefined_sdxl) + if model_type == 'f1' or model_type == 'all': + model_list += list(predefined_f1) + if model_type == 'sd3' or model_type == 'all': + model_list += list(predefined_sd3) + model_list += sorted(find_models()) + return model_list + + def list_models(refresh=False): import modules.shared global models # pylint: disable=global-statement From 81e55f04592e29583ad6ebab160060e9c7d66c5b Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 25 Jun 2025 22:58:40 +0300 Subject: [PATCH 32/85] Fix SDNQ pre-mode --- modules/model_quant.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/modules/model_quant.py b/modules/model_quant.py index 1eab2aa64..c8fd0f9b7 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -113,15 +113,18 @@ def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Mo diffusers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig transformers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig - if weights_dtype is None and module in {"TE", "LLM"}: - if shared.opts.sdnq_quantize_weights_mode_te == "none": - return None - elif shared.opts.sdnq_quantize_weights_mode_te == "same as model" or shared.opts.sdnq_quantize_weights_mode_te == "default": - weights_dtype = shared.opts.sdnq_quantize_weights_mode - else: - weights_dtype = shared.opts.sdnq_quantize_weights_mode_te if weights_dtype is None: - return None + if module in {"TE", "LLM"}: + if shared.opts.sdnq_quantize_weights_mode_te == "none": + return kwargs + elif shared.opts.sdnq_quantize_weights_mode_te in {"same as model", "default"}: + weights_dtype = shared.opts.sdnq_quantize_weights_mode + else: + weights_dtype = shared.opts.sdnq_quantize_weights_mode_te + elif shared.opts.sdnq_quantize_weights_mode == "none": + return kwargs + else: + weights_dtype = shared.opts.sdnq_quantize_weights_mode if shared.opts.device_map == "gpu": quantization_device = devices.device From e43d1d2ba7308df035db2d5c7b64a8d2d2388bc4 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 25 Jun 2025 23:25:49 +0300 Subject: [PATCH 33/85] SDNQ use strings as target_dtype --- modules/sdnq/common.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/modules/sdnq/common.py b/modules/sdnq/common.py index ccfdf1227..ab8b70c0d 100644 --- a/modules/sdnq/common.py +++ b/modules/sdnq/common.py @@ -2,33 +2,34 @@ import sys import torch -from accelerate.utils import CustomDtype from modules import devices torch_version = float(torch.__version__[:3]) dtype_dict = { "int8": {"min": -128, "max": 127, "num_bits": 8, "target_dtype": torch.int8, "torch_dtype": torch.int8, "storage_dtype": torch.int8, "is_unsigned": False, "is_integer": True}, - "int7": {"min": -64, "max": 63, "num_bits": 7, "target_dtype": torch.int8, "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, - "int6": {"min": -32, "max": 31, "num_bits": 6, "target_dtype": torch.int8, "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, - "int5": {"min": -16, "max": 15, "num_bits": 5, "target_dtype": CustomDtype.INT4, "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, - "int4": {"min": -8, "max": 7, "num_bits": 4, "target_dtype": CustomDtype.INT4, "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, - "int3": {"min": -4, "max": 3, "num_bits": 3, "target_dtype": CustomDtype.INT4, "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, - "int2": {"min": -2, "max": 1, "num_bits": 2, "target_dtype": CustomDtype.INT2, "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, + "int7": {"min": -64, "max": 63, "num_bits": 7, "target_dtype": "int7", "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, + "int6": {"min": -32, "max": 31, "num_bits": 6, "target_dtype": "int6", "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, + "int5": {"min": -16, "max": 15, "num_bits": 5, "target_dtype": "int5", "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, + "int4": {"min": -8, "max": 7, "num_bits": 4, "target_dtype": "int4", "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, + "int3": {"min": -4, "max": 3, "num_bits": 3, "target_dtype": "int3", "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, + "int2": {"min": -2, "max": 1, "num_bits": 2, "target_dtype": "int2", "torch_dtype": torch.int8, "storage_dtype": torch.uint8, "is_unsigned": False, "is_integer": True}, "uint8": {"min": 0, "max": 255, "num_bits": 8, "target_dtype": torch.uint8, "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, - "uint7": {"min": 0, "max": 127, "num_bits": 7, "target_dtype": torch.uint8, "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, - "uint6": {"min": 0, "max": 63, "num_bits": 6, "target_dtype": torch.uint8, "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, - "uint5": {"min": 0, "max": 31, "num_bits": 5, "target_dtype": CustomDtype.INT4, "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, - "uint4": {"min": 0, "max": 15, "num_bits": 4, "target_dtype": CustomDtype.INT4, "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, - "uint3": {"min": 0, "max": 7, "num_bits": 3, "target_dtype": CustomDtype.INT4, "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, - "uint2": {"min": 0, "max": 3, "num_bits": 2, "target_dtype": CustomDtype.INT2, "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, + "uint7": {"min": 0, "max": 127, "num_bits": 7, "target_dtype": "uint7", "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, + "uint6": {"min": 0, "max": 63, "num_bits": 6, "target_dtype": "uint6", "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, + "uint5": {"min": 0, "max": 31, "num_bits": 5, "target_dtype": "uint5", "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, + "uint4": {"min": 0, "max": 15, "num_bits": 4, "target_dtype": "uint4", "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, + "uint3": {"min": 0, "max": 7, "num_bits": 3, "target_dtype": "uint3", "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, + "uint2": {"min": 0, "max": 3, "num_bits": 2, "target_dtype": "uint2", "torch_dtype": torch.uint8, "storage_dtype": torch.uint8, "is_unsigned": True, "is_integer": True}, "uint1": {"min": 0, "max": 1, "num_bits": 1, "target_dtype": torch.bool, "torch_dtype": torch.bool, "storage_dtype": torch.bool, "is_unsigned": True, "is_integer": True}, "float8_e4m3fn": {"min": -448, "max": 448, "num_bits": 8, "target_dtype": torch.float8_e4m3fn, "torch_dtype": torch.float8_e4m3fn, "storage_dtype": torch.float8_e4m3fn, "is_unsigned": False, "is_integer": False}, "float8_e5m2": {"min": -57344, "max": 57344, "num_bits": 8, "target_dtype": torch.float8_e5m2, "torch_dtype": torch.float8_e5m2, "storage_dtype": torch.float8_e5m2, "is_unsigned": False, "is_integer": False}, - "float8_e4m3fnuz": {"min": -240, "max": 240, "num_bits": 8, "target_dtype": CustomDtype.FP8, "torch_dtype": torch.float8_e4m3fnuz, "storage_dtype": torch.float8_e4m3fnuz, "is_unsigned": False, "is_integer": False}, - "float8_e5m2fnuz": {"min": -57344, "max": 57344, "num_bits": 8, "target_dtype": CustomDtype.FP8, "torch_dtype": torch.float8_e5m2fnuz, "storage_dtype": torch.float8_e5m2fnuz, "is_unsigned": False, "is_integer": False}, } dtype_dict["bool"] = dtype_dict["uint1"] +if hasattr(torch, "float8_e4m3fnuz"): + dtype_dict["float8_e4m3fnuz"] = {"min": -240, "max": 240, "num_bits": 8, "target_dtype": "fp8", "torch_dtype": torch.float8_e4m3fnuz, "storage_dtype": torch.float8_e4m3fnuz, "is_unsigned": False, "is_integer": False} +if hasattr(torch, "float8_e5m2fnuz"): + dtype_dict["float8_e5m2fnuz"] = {"min": -57344, "max": 57344, "num_bits": 8, "target_dtype": "fp8", "torch_dtype": torch.float8_e5m2fnuz, "storage_dtype": torch.float8_e5m2fnuz, "is_unsigned": False, "is_integer": False} use_tensorwise_fp8_matmul = torch_version < 2.5 or devices.backend in {"cpu", "openvino"} or (devices.backend == "cuda" and sys.platform == "win32" and torch_version <= 2.7 and torch.cuda.get_device_capability(devices.device) == (8,9)) quantized_matmul_dtypes = ("int8", "int7", "int6", "int5", "int4", "int3", "int2", "float8_e4m3fn", "float8_e5m2") From c9f49720c55cd7bbe2fb620e33418a45e00892db Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 25 Jun 2025 23:37:41 +0300 Subject: [PATCH 34/85] Cleanup --- modules/devices.py | 2 +- modules/sd_hijack_accelerate.py | 4 ++-- modules/sd_offload.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 1c35f2683..8d15fd238 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -665,6 +665,6 @@ def normalize_device(dev): def same_device(d1, d2): - if d1.type != d2.type: + if torch.device(d1).type != torch.device(d2).type: return False return normalize_device(d1) == normalize_device(d2) diff --git a/modules/sd_hijack_accelerate.py b/modules/sd_hijack_accelerate.py index f8cf8983f..7f312a029 100644 --- a/modules/sd_hijack_accelerate.py +++ b/modules/sd_hijack_accelerate.py @@ -36,7 +36,7 @@ def hijack_set_module_tensor( # note: majority of time is spent on .to(old_value.dtype) if tensor_name in module._buffers: # pylint: disable=protected-access module._buffers[tensor_name] = value.to(device, old_value.dtype) # pylint: disable=protected-access - elif value is not None or not devices.same_device(torch.device(device), module._parameters[tensor_name].device): # pylint: disable=protected-access + elif value is not None or not devices.same_device(device, module._parameters[tensor_name].device): # pylint: disable=protected-access param_cls = type(module._parameters[tensor_name]) # pylint: disable=protected-access module._parameters[tensor_name] = param_cls(value, requires_grad=old_value.requires_grad).to(device, old_value.dtype) # pylint: disable=protected-access t1 = time.time() @@ -64,7 +64,7 @@ def hijack_set_module_tensor_simple( with devices.inference_context(): if tensor_name in module._buffers: # pylint: disable=protected-access module._buffers[tensor_name] = value.to(device) # pylint: disable=protected-access - elif value is not None or not devices.same_device(torch.device(device), module._parameters[tensor_name].device): # pylint: disable=protected-access + elif value is not None or not devices.same_device(device, module._parameters[tensor_name].device): # pylint: disable=protected-access param_cls = type(module._parameters[tensor_name]) # pylint: disable=protected-access module._parameters[tensor_name] = param_cls(value, requires_grad=old_value.requires_grad).to(device) # pylint: disable=protected-access t1 = time.time() diff --git a/modules/sd_offload.py b/modules/sd_offload.py index e36db4332..70777a30a 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -171,7 +171,7 @@ class OffloadHook(accelerate.hooks.ModelHook): return module def pre_forward(self, module, *args, **kwargs): - if devices.normalize_device(module.device) != devices.normalize_device(devices.device): + if not devices.same_device(module.device, devices.device): device_index = torch.device(devices.device).index if device_index is None: device_index = 0 From 42b3e08e658e5966e037c3faed38e6b117e64f3c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 18:43:27 -0400 Subject: [PATCH 35/85] Control add setting to run hires with or without control Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 ++ modules/api/endpoints.py | 2 +- modules/control/units/controlnet.py | 2 +- modules/processing_diffusers.py | 6 +++--- modules/shared.py | 9 +++++---- modules/ui_control.py | 24 +++++++++++++++++++++++- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ed3e66a..90a7c43d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Add [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* - Use Diffusers version of *OmniGen* + - Control move global settings to control elements -> control settings tab + - Control add setting to run hires with or without control - **SDNQ Quantization** - Add modules_to_not_convert support for post mode diff --git a/modules/api/endpoints.py b/modules/api/endpoints.py index 87181ba4f..561afafe0 100644 --- a/modules/api/endpoints.py +++ b/modules/api/endpoints.py @@ -25,7 +25,7 @@ def get_sd_models(): def get_controlnets(model_type: Optional[str] = None): from modules.control.units.controlnet import api_list_models - return api_list_models() + return api_list_models(model_type) def get_hypernetworks(): return [{"name": name, "path": shared.hypernetworks[name]} for name in shared.hypernetworks] diff --git a/modules/control/units/controlnet.py b/modules/control/units/controlnet.py index debe54c16..9233b47f8 100644 --- a/modules/control/units/controlnet.py +++ b/modules/control/units/controlnet.py @@ -152,7 +152,7 @@ def api_list_models(model_type: str = None): model_list += list(predefined_sd3) model_list += sorted(find_models()) return model_list - + def list_models(refresh=False): import modules.shared diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 03d0b7b78..c3eb66d77 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -170,7 +170,7 @@ def process_hires(p: processing.StableDiffusionProcessing, output): prev_job = shared.state.job # hires runs on original pipeline - if hasattr(shared.sd_model, 'restore_pipeline') and shared.sd_model.restore_pipeline is not None: + if hasattr(shared.sd_model, 'restore_pipeline') and (shared.sd_model.restore_pipeline is not None) and not shared.opts.control_hires: shared.sd_model.restore_pipeline() # upscale @@ -200,8 +200,8 @@ def process_hires(p: processing.StableDiffusionProcessing, output): if 'Upscale' in shared.sd_model.__class__.__name__ or 'Flux' in shared.sd_model.__class__.__name__ or 'Kandinsky' in shared.sd_model.__class__.__name__: output.images = processing_vae.vae_decode(latents=output.images, model=shared.sd_model, vae_type=p.vae_type, output_type='pil', width=p.width, height=p.height) if p.is_control and hasattr(p, 'task_args') and p.task_args.get('image', None) is not None: - if hasattr(shared.sd_model, "vae") and output.images is not None and len(output.images) > 0: - output.images = processing_vae.vae_decode(latents=output.images, model=shared.sd_model, vae_type=p.vae_type, output_type='pil', width=p.hr_upscale_to_x, height=p.hr_upscale_to_y) # controlnet cannnot deal with latent input + if hasattr(shared.sd_model, "vae") and output.images is not None and len(output.images) > 0: + output.images = processing_vae.vae_decode(latents=output.images, model=shared.sd_model, vae_type=p.vae_type, output_type='pil', width=p.hr_upscale_to_x, height=p.hr_upscale_to_y) # controlnet cannnot deal with latent input update_sampler(p, shared.sd_model, second_pass=True) orig_denoise = p.denoising_strength p.denoising_strength = strength diff --git a/modules/shared.py b/modules/shared.py index 276143e42..0f01696e9 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -895,10 +895,11 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { })) options_templates.update(options_section(('control', "Control Options"), { - "control_max_units": OptionInfo(4, "Maximum number of units", gr.Slider, {"minimum": 1, "maximum": 10, "step": 1}), - "control_tiles": OptionInfo("1x1, 1x2, 1x3, 1x4, 2x1, 2x1, 2x2, 2x3, 2x4, 3x1, 3x2, 3x3, 3x4, 4x1, 4x2, 4x3, 4x4", "Tiling options"), - "control_move_processor": OptionInfo(False, "Processor move to CPU after use"), - "control_unload_processor": OptionInfo(False, "Processor unload after use"), + "control_hires": OptionInfo(False, "Use control during hires", gr.Checkbox, {"visible": False}), + "control_max_units": OptionInfo(4, "Maximum number of units", gr.Slider, {"minimum": 1, "maximum": 10, "step": 1, "visible": False}), + "control_tiles": OptionInfo("1x1, 1x2, 1x3, 1x4, 2x1, 2x1, 2x2, 2x3, 2x4, 3x1, 3x2, 3x3, 3x4, 4x1, 4x2, 4x3, 4x4", "Tiling options", gr.Textbox, {"visible": False}), + "control_move_processor": OptionInfo(False, "Processor move to CPU after use", gr.Checkbox, {"visible": False}), + "control_unload_processor": OptionInfo(False, "Processor unload after use", gr.Checkbox, {"visible": False}), })) options_templates.update(options_section(('interrogate', "Interrogate"), { diff --git a/modules/ui_control.py b/modules/ui_control.py index 94ad27eee..4b53a76bf 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -467,9 +467,31 @@ def create_ui(_blocks: gr.Blocks=None): if i == 0: units[-1].enabled = True # enable first unit in group - with gr.Accordion('Processor settings', open=False, elem_classes=['control-settings']) as _tab_settings: + with gr.Accordion('Control settings', open=False, elem_classes=['control-settings']) as _tab_settings: with gr.Group(elem_classes=['processor-group']): settings = [] + with gr.Accordion('Global', open=True, elem_classes=['processor-settings']): + control_hires = gr.Checkbox(label="Use control during hires", value=shared.opts.control_hires, elem_id='control_hires') + def set_control_hires(value): + shared.opts.control_active = value + control_hires.change(fn=set_control_hires, inputs=[control_hires], outputs=[]) + control_max_units = gr.Slider(label="Maximum units", minimum=1, maximum=10, step=1, value=shared.opts.control_max_units, elem_id='control_max_units') + def set_control_max_units(value): + shared.opts.control_max_units = value + control_max_units.change(fn=set_control_max_units, inputs=[control_max_units], outputs=[]) + control_tiles = gr.Textbox(label="Tiling options", value=shared.opts.control_tiles, elem_id='control_tiles') + def set_control_tiles(value): + shared.opts.control_tiles = value + control_tiles.change(fn=set_control_tiles, inputs=[control_tiles], outputs=[]) + control_move_processor = gr.Checkbox(label="Move processor to CPU after use", value=shared.opts.control_move_processor, elem_id='control_move_processor') + def set_control_move_processor(value): + shared.opts.control_move_processor = value + control_move_processor.change(fn=set_control_move_processor, inputs=[control_move_processor], outputs=[]) + control_unload_processor = gr.Checkbox(label="Unload processor after use", value=shared.opts.control_unload_processor, elem_id='control_unload_processor') + def set_control_unload_processor(value): + shared.opts.control_unload_processor = value + control_unload_processor.change(fn=set_control_unload_processor, inputs=[control_unload_processor], outputs=[]) + with gr.Accordion('HED', open=True, elem_classes=['processor-settings']): settings.append(gr.Checkbox(label="Scribble", value=False)) with gr.Accordion('Midas depth', open=True, elem_classes=['processor-settings']): From b6a0332694e3bf8cc43ba1bd58da016c08b17cb5 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 18:45:40 -0400 Subject: [PATCH 36/85] update changelog Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a7c43d1..cd2156a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Fix delete file with gallery views - Add `SD_SAVE_DEBUG` env variable to report all params and metadata save operations as they happen - Fix TAESD model type detection + - Fix LoRA loader incorrectly reporting errors ## Update for 2025-06-16 From c25d15398fca39d5e085f12f3359a7c0b5c1fef5 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 26 Jun 2025 01:48:01 +0300 Subject: [PATCH 37/85] SDNQ pre-mode don't quant if weights_dtype == 'none' --- modules/model_quant.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/model_quant.py b/modules/model_quant.py index c8fd0f9b7..307707708 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -125,6 +125,8 @@ def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Mo return kwargs else: weights_dtype = shared.opts.sdnq_quantize_weights_mode + if weights_dtype is None or weights_dtype == 'none': + return kwargs if shared.opts.device_map == "gpu": quantization_device = devices.device From 943b45fdb6c743d838527f7d64746a786d277309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Thu, 26 Jun 2025 01:57:15 +0300 Subject: [PATCH 38/85] fix the previous merge conflict --- modules/sd_vae_remote.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/modules/sd_vae_remote.py b/modules/sd_vae_remote.py index c7d1ac5d5..43871ebdc 100644 --- a/modules/sd_vae_remote.py +++ b/modules/sd_vae_remote.py @@ -12,11 +12,7 @@ hf_decode_endpoints = { 'sd': 'https://q1bj3bpq6kzilnsu.us-east-1.aws.endpoints.huggingface.cloud', 'sdxl': 'https://x2dmsqunjd6k9prw.us-east-1.aws.endpoints.huggingface.cloud', 'f1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', -<<<<<<< feature/chroma-support 'chroma': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', - 'h1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', -======= ->>>>>>> dev 'hunyuanvideo': 'https://o7ywnmrahorts457.us-east-1.aws.endpoints.huggingface.cloud', } hf_decode_endpoints['pixartalpha'] = hf_decode_endpoints['sd'] @@ -91,11 +87,7 @@ def remote_decode(latents: torch.Tensor, width: int = 0, height: int = 0, model_ params["output_type"] = "pt" params["output_tensor_type"] = "binary" headers["Accept"] = "tensor/binary" -<<<<<<< feature/chroma-support - if (model_type in ['f1', 'h1', 'chroma']) and (width > 0) and (height > 0): -======= - if model_type in {'f1', 'h1', 'lumina2'} and (width > 0) and (height > 0): ->>>>>>> dev + if model_type in {'f1', 'h1', 'lumina2', 'chroma'} and (width > 0) and (height > 0): params['width'] = width params['height'] = height if shared.sd_model.vae is not None and shared.sd_model.vae.config is not None: From f040d67f647aedd2d947c8cf279edfdee84c0e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Thu, 26 Jun 2025 01:58:09 +0300 Subject: [PATCH 39/85] another merge conflict --- modules/sd_vae_taesd.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index 32191613b..3ef7e123d 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -52,29 +52,17 @@ def warn_once(msg, variant=None): def get_model(model_type = 'decoder', variant = None): global prev_cls, prev_type, prev_model # pylint: disable=global-statement from modules import shared -<<<<<<< feature/chroma-support - cls = shared.sd_model_type - if cls in {'ldm', 'pixartalpha'}: - cls = 'sd' - elif cls in {'h1', 'lumina2', 'chroma'}: - cls = 'f1' - elif cls == 'pixartsigma': - cls = 'sdxl' - elif cls not in supported: - warn_once(f'cls={shared.sd_model.__class__.__name__} type={cls} unsuppported', variant=variant) -======= model_cls = shared.sd_model_type if model_cls is None or model_cls == 'none': return None elif model_cls in {'ldm', 'pixartalpha'}: model_cls = 'sd' - elif model_cls in {'h1', 'lumina2'}: + elif model_cls in {'h1', 'lumina2', 'chroma'}: model_cls = 'f1' elif model_cls in {'pixartsigma', 'omnigen'}: model_cls = 'sdxl' elif model_cls not in supported: warn_once(f'cls={shared.sd_model.__class__.__name__} type={model_cls} unsuppported', variant=variant) ->>>>>>> dev variant = variant or shared.opts.taesd_variant folder = os.path.join(paths.models_path, "TAESD") os.makedirs(folder, exist_ok=True) From fb0d354c4ec258d6fb892d104cedbf921bcd6541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Thu, 26 Jun 2025 02:04:03 +0300 Subject: [PATCH 40/85] reuse f1 remote vae for chroma --- modules/sd_vae_remote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_vae_remote.py b/modules/sd_vae_remote.py index 43871ebdc..d0ccd6ac3 100644 --- a/modules/sd_vae_remote.py +++ b/modules/sd_vae_remote.py @@ -12,13 +12,13 @@ hf_decode_endpoints = { 'sd': 'https://q1bj3bpq6kzilnsu.us-east-1.aws.endpoints.huggingface.cloud', 'sdxl': 'https://x2dmsqunjd6k9prw.us-east-1.aws.endpoints.huggingface.cloud', 'f1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', - 'chroma': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', 'hunyuanvideo': 'https://o7ywnmrahorts457.us-east-1.aws.endpoints.huggingface.cloud', } hf_decode_endpoints['pixartalpha'] = hf_decode_endpoints['sd'] hf_decode_endpoints['pixartsigma'] = hf_decode_endpoints['sdxl'] hf_decode_endpoints['omnigen'] = hf_decode_endpoints['sdxl'] hf_decode_endpoints['h1'] = hf_decode_endpoints['f1'] +hf_decode_endpoints['chroma'] = hf_decode_endpoints['f1'] hf_decode_endpoints['lumina2'] = hf_decode_endpoints['f1'] hf_encode_endpoints = { From c254c8c1ec95849fe5e3b9550532f96325db2375 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 25 Jun 2025 19:14:44 -0400 Subject: [PATCH 41/85] fix hypertile for img2img and inpaint operations Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + modules/processing_diffusers.py | 4 ++-- modules/sd_hijack_hypertile.py | 33 +++++++++++++++++++++++---------- wiki | 2 +- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd2156a3e..23b8feb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Add `SD_SAVE_DEBUG` env variable to report all params and metadata save operations as they happen - Fix TAESD model type detection - Fix LoRA loader incorrectly reporting errors + - Fix hypertile for img2img and inpaint operations ## Update for 2025-06-16 diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index c3eb66d77..4b10b4835 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -200,8 +200,8 @@ def process_hires(p: processing.StableDiffusionProcessing, output): if 'Upscale' in shared.sd_model.__class__.__name__ or 'Flux' in shared.sd_model.__class__.__name__ or 'Kandinsky' in shared.sd_model.__class__.__name__: output.images = processing_vae.vae_decode(latents=output.images, model=shared.sd_model, vae_type=p.vae_type, output_type='pil', width=p.width, height=p.height) if p.is_control and hasattr(p, 'task_args') and p.task_args.get('image', None) is not None: - if hasattr(shared.sd_model, "vae") and output.images is not None and len(output.images) > 0: - output.images = processing_vae.vae_decode(latents=output.images, model=shared.sd_model, vae_type=p.vae_type, output_type='pil', width=p.hr_upscale_to_x, height=p.hr_upscale_to_y) # controlnet cannnot deal with latent input + if hasattr(shared.sd_model, "vae") and output.images is not None and len(output.images) > 0: + output.images = processing_vae.vae_decode(latents=output.images, model=shared.sd_model, vae_type=p.vae_type, output_type='pil', width=p.hr_upscale_to_x, height=p.hr_upscale_to_y) # controlnet cannnot deal with latent input update_sampler(p, shared.sd_model, second_pass=True) orig_denoise = p.denoising_strength p.denoising_strength = strength diff --git a/modules/sd_hijack_hypertile.py b/modules/sd_hijack_hypertile.py index c27bf8f39..ebe86948a 100644 --- a/modules/sd_hijack_hypertile.py +++ b/modules/sd_hijack_hypertile.py @@ -181,17 +181,19 @@ def context_hypertile_vae(p): if shared.opts.cross_attention_optimization == 'Sub-quadratic': shared.log.warning('Hypertile UNet is not compatible with Sub-quadratic cross-attention optimization') return nullcontext() - global height, width, max_h, max_w, error_reported # pylint: disable=global-statement + global max_h, max_w, error_reported # pylint: disable=global-statement error_reported = False error_reported = False - height, width = p.height, p.width + set_resolution(p) max_h, max_w = 0, 0 vae = getattr(p.sd_model, "vae", None) if shared.native else getattr(p.sd_model, "first_stage_model", None) + if height == 0 or width == 0: + log.warning('Hypertile VAE disabled: resolution unknown') + return nullcontext() if height % 8 != 0 or width % 8 != 0: log.warning(f'Hypertile VAE disabled: width={width} height={height} are not divisible by 8') return nullcontext() if vae is None: - # shared.log.warning('Hypertile VAE is enabled but no VAE model was found') return nullcontext() else: tile_size = shared.opts.hypertile_vae_tile if shared.opts.hypertile_vae_tile > 0 else max(128, 64 * min(p.width // 128, p.height // 128)) @@ -208,11 +210,14 @@ def context_hypertile_unet(p): if shared.opts.cross_attention_optimization == 'Sub-quadratic' and not shared.cmd_opts.experimental: shared.log.warning('Hypertile UNet is not compatible with Sub-quadratic cross-attention optimization') return nullcontext() - global height, width, max_h, max_w, error_reported # pylint: disable=global-statement + global max_h, max_w, error_reported # pylint: disable=global-statement error_reported = False - height, width = p.height, p.width + set_resolution(p) max_h, max_w = 0, 0 unet = getattr(p.sd_model, "unet", None) if shared.native else getattr(p.sd_model.model, "diffusion_model", None) + if height == 0 or width == 0: + log.warning('Hypertile VAE disabled: resolution unknown') + return nullcontext() if height % 8 != 0 or width % 8 != 0: log.warning(f'Hypertile UNet disabled: width={width} height={height} are not divisible by 8') return nullcontext() @@ -229,17 +234,25 @@ def context_hypertile_unet(p): def hypertile_set(p, hr=False): from modules import shared - global height, width, error_reported, reset_needed, skip_hypertile # pylint: disable=global-statement + global error_reported, reset_needed, skip_hypertile # pylint: disable=global-statement if not shared.opts.hypertile_unet_enabled: return error_reported = False + set_resolution(p, hr=hr) + skip_hypertile = shared.opts.hypertile_hires_only and not getattr(p, 'is_hr_pass', False) + reset_needed = True + + +def set_resolution(p, hr=False): + global height, width # pylint: disable=global-statement if hr: x = getattr(p, 'hr_upscale_to_x', 0) y = getattr(p, 'hr_upscale_to_y', 0) width = y if y > 0 else p.width height = x if x > 0 else p.height else: - width=p.width - height=p.height - skip_hypertile = shared.opts.hypertile_hires_only and not getattr(p, 'is_hr_pass', False) - reset_needed = True + width = p.width + height = p.height + if height == 0 or width == 0: + if hasattr(p, 'init_images') and isinstance(p.init_images, list) and len(p.init_images) > 0: + height, width = p.init_images[0].size diff --git a/wiki b/wiki index 5e97702f2..f2814574e 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 5e97702f219b879c035057204303ae649e1edcf7 +Subproject commit f2814574e02fd313348cc68c31573606617240cd From dc8fd006b2b7e026dc0cd9914e6e760bc154ca62 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 26 Jun 2025 02:47:10 +0300 Subject: [PATCH 42/85] Add modules_to_not_convert to pre-mode quants --- modules/model_quant.py | 73 +++++++++++++++++++--------------------- modules/sdnq/__init__.py | 5 ++- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/modules/model_quant.py b/modules/model_quant.py index 307707708..9f2bd6a0b 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -37,7 +37,7 @@ def get_quant(name): return 'none' -def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Model'): +def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Model', modules_to_not_convert: list = []): from modules import shared, devices if len(shared.opts.bnb_quantization) > 0 and allow_bnb: if 'Model' in shared.opts.bnb_quantization or (module is not None and module in shared.opts.bnb_quantization) or module == 'any': @@ -49,7 +49,8 @@ def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Mode load_in_4bit=shared.opts.bnb_quantization_type in ['nf4', 'fp4'], bnb_4bit_quant_storage=shared.opts.bnb_quantization_storage, bnb_4bit_quant_type=shared.opts.bnb_quantization_type, - bnb_4bit_compute_dtype=devices.dtype + bnb_4bit_compute_dtype=devices.dtype, + #modules_to_not_convert=modules_to_not_convert, # ignored by bnb ) log.debug(f'Quantization: module={module} type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') if kwargs is None: @@ -60,7 +61,7 @@ def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Mode return kwargs -def create_ao_config(kwargs = None, allow_ao: bool = True, module: str = 'Model'): +def create_ao_config(kwargs = None, allow_ao: bool = True, module: str = 'Model', modules_to_not_convert: list = []): from modules import shared if len(shared.opts.torchao_quantization) > 0 and (shared.opts.torchao_quantization_mode == 'pre') and allow_ao: if 'Model' in shared.opts.torchao_quantization or (module is not None and module in shared.opts.torchao_quantization) or module == 'any': @@ -68,9 +69,9 @@ def create_ao_config(kwargs = None, allow_ao: bool = True, module: str = 'Model' if torchao is None: return kwargs if module in {'TE', 'LLM'}: - ao_config = transformers.TorchAoConfig(quant_type=shared.opts.torchao_quantization_type) + ao_config = transformers.TorchAoConfig(quant_type=shared.opts.torchao_quantization_type, modules_to_not_convert=modules_to_not_convert) else: - ao_config = diffusers.TorchAoConfig(shared.opts.torchao_quantization_type) + ao_config = diffusers.TorchAoConfig(shared.opts.torchao_quantization_type, modules_to_not_convert=modules_to_not_convert) log.debug(f'Quantization: module={module} type=torchao dtype={shared.opts.torchao_quantization_type}') if kwargs is None: return ao_config @@ -80,7 +81,7 @@ def create_ao_config(kwargs = None, allow_ao: bool = True, module: str = 'Model' return kwargs -def create_quanto_config(kwargs = None, allow_quanto: bool = True, module: str = 'Model'): +def create_quanto_config(kwargs = None, allow_quanto: bool = True, module: str = 'Model', modules_to_not_convert: list = []): from modules import shared if len(shared.opts.quanto_quantization) > 0 and allow_quanto: if 'Model' in shared.opts.quanto_quantization or (module is not None and module in shared.opts.quanto_quantization) or module == 'any': @@ -88,10 +89,10 @@ def create_quanto_config(kwargs = None, allow_quanto: bool = True, module: str = if optimum_quanto is None: return kwargs if module in {'TE', 'LLM'}: - quanto_config = transformers.QuantoConfig(weights=shared.opts.quanto_quantization_type) + quanto_config = transformers.QuantoConfig(weights=shared.opts.quanto_quantization_type, modules_to_not_convert=modules_to_not_convert) quanto_config.weights_dtype = quanto_config.weights else: - quanto_config = diffusers.QuantoConfig(weights_dtype=shared.opts.quanto_quantization_type) + quanto_config = diffusers.QuantoConfig(weights_dtype=shared.opts.quanto_quantization_type, modules_to_not_convert=modules_to_not_convert) quanto_config.activations = None # patch so it works with transformers quanto_config.weights = quanto_config.weights_dtype log.debug(f'Quantization: module={module} type=quanto dtype={shared.opts.quanto_quantization_type}') @@ -103,7 +104,7 @@ def create_quanto_config(kwargs = None, allow_quanto: bool = True, module: str = return kwargs -def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Model', weights_dtype: str = None): +def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Model', weights_dtype: str = None, modules_to_not_convert: list = []): from modules import devices, shared if len(shared.opts.sdnq_quantize_weights) > 0 and (shared.opts.sdnq_quantize_mode == 'pre') and allow_sdnq: if 'Model' in shared.opts.sdnq_quantize_weights or (module is not None and module in shared.opts.sdnq_quantize_weights) or module == 'any': @@ -114,15 +115,8 @@ def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Mo transformers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig if weights_dtype is None: - if module in {"TE", "LLM"}: - if shared.opts.sdnq_quantize_weights_mode_te == "none": - return kwargs - elif shared.opts.sdnq_quantize_weights_mode_te in {"same as model", "default"}: - weights_dtype = shared.opts.sdnq_quantize_weights_mode - else: - weights_dtype = shared.opts.sdnq_quantize_weights_mode_te - elif shared.opts.sdnq_quantize_weights_mode == "none": - return kwargs + if module in {"TE", "LLM"} and shared.opts.sdnq_quantize_weights_mode_te not in {"same as model", "default"}: + weights_dtype = shared.opts.sdnq_quantize_weights_mode_te else: weights_dtype = shared.opts.sdnq_quantize_weights_mode if weights_dtype is None or weights_dtype == 'none': @@ -150,6 +144,7 @@ def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Mo dequantize_fp32=shared.opts.sdnq_dequantize_fp32, quantization_device=quantization_device, return_device=return_device, + modules_to_not_convert=modules_to_not_convert, ) log.debug(f'Quantization: module="{module}" type=sdnq dtype={weights_dtype} matmul={shared.opts.sdnq_use_quantized_matmul} group_size={shared.opts.sdnq_quantize_weights_group_size} quant_conv={shared.opts.sdnq_quantize_conv_layers} matmul_conv={shared.opts.sdnq_use_quantized_matmul_conv} dequantize_fp32={shared.opts.sdnq_dequantize_fp32} quantize_with_gpu={shared.opts.sdnq_quantize_with_gpu} quantization_device={quantization_device} return_device={return_device}') if kwargs is None: @@ -180,25 +175,25 @@ def check_nunchaku(module: str = ''): return True -def create_config(kwargs = None, allow: bool = True, module: str = 'Model'): +def create_config(kwargs = None, allow: bool = True, module: str = 'Model', modules_to_not_convert = []): if kwargs is None: kwargs = {} - kwargs = create_sdnq_config(kwargs, allow_sdnq=allow, module=module) + kwargs = create_sdnq_config(kwargs, allow_sdnq=allow, module=module, modules_to_not_convert=modules_to_not_convert) if kwargs is not None and 'quantization_config' in kwargs: if debug: log.trace(f'Quantization: type=sdnq config={kwargs.get("quantization_config", None)}') return kwargs - kwargs = create_bnb_config(kwargs, allow_bnb=allow, module=module) + kwargs = create_bnb_config(kwargs, allow_bnb=allow, module=module, modules_to_not_convert=modules_to_not_convert) if kwargs is not None and 'quantization_config' in kwargs: if debug: log.trace(f'Quantization: type=bnb config={kwargs.get("quantization_config", None)}') return kwargs - kwargs = create_quanto_config(kwargs, allow_quanto=allow, module=module) + kwargs = create_quanto_config(kwargs, allow_quanto=allow, module=module, modules_to_not_convert=modules_to_not_convert) if kwargs is not None and 'quantization_config' in kwargs: if debug: log.trace(f'Quantization: type=quanto config={kwargs.get("quantization_config", None)}') return kwargs - kwargs = create_ao_config(kwargs, allow_ao=allow, module=module) + kwargs = create_ao_config(kwargs, allow_ao=allow, module=module, modules_to_not_convert=modules_to_not_convert) if kwargs is not None and 'quantization_config' in kwargs: if debug: log.trace(f'Quantization: type=torchao config={kwargs.get("quantization_config", None)}') @@ -331,20 +326,16 @@ def apply_layerwise(sd_model, quiet:bool=False): log.error(f'Quantization: type=layerwise {e}') -def sdnq_quantize_model(model, op=None, sd_model=None, do_gc=True): +def sdnq_quantize_model(model, op=None, sd_model=None, do_gc: bool = True, weights_dtype: str = None, modules_to_not_convert: list = []): global quant_last_model_name, quant_last_model_device # pylint: disable=global-statement from modules import devices, shared from modules.sdnq import apply_sdnq_to_module - model.eval() - backup_embeddings = None - if hasattr(model, "get_input_embeddings"): - backup_embeddings = copy.deepcopy(model.get_input_embeddings()) - - if shared.opts.sdnq_quantize_weights_mode_te != "default" and op is not None and "text_encoder" in op: - weights_dtype = shared.opts.sdnq_quantize_weights_mode_te - else: - weights_dtype = shared.opts.sdnq_quantize_weights_mode + if weights_dtype is None: + if op is not None and ("text_encoder" in op or op in {"TE", "LLM"}) and shared.opts.sdnq_quantize_weights_mode_te not in {"same as model", "default"}: + weights_dtype = shared.opts.sdnq_quantize_weights_mode_te + else: + weights_dtype = shared.opts.sdnq_quantize_weights_mode if weights_dtype is None or weights_dtype == 'none': return model @@ -361,9 +352,15 @@ def sdnq_quantize_model(model, op=None, sd_model=None, do_gc=True): quantization_device = None return_device = None - modules_to_not_convert = getattr(model, "_keep_in_fp32_modules", []) - if modules_to_not_convert is None: - modules_to_not_convert = [] + if getattr(model, "_keep_in_fp32_modules", None) is not None: + modules_to_not_convert.extend(model._keep_in_fp32_modules) + if model.__class__.__name__ == "ChromaTransformer2DModel": + modules_to_not_convert.append("distilled_guidance_layer") + + model.eval() + backup_embeddings = None + if hasattr(model, "get_input_embeddings"): + backup_embeddings = copy.deepcopy(model.get_input_embeddings()) model = apply_sdnq_to_module( model, @@ -558,7 +555,7 @@ def torchao_quantization(sd_model): return sd_model -def get_dit_args(load_config:dict={}, module:str=None, device_map:bool=False, allow_quant:bool=True): +def get_dit_args(load_config:dict={}, module:str=None, device_map:bool=False, allow_quant:bool=True, modules_to_not_convert: list = []): from modules import shared, devices config = load_config.copy() if 'torch_dtype' not in config: @@ -581,7 +578,7 @@ def get_dit_args(load_config:dict={}, module:str=None, device_map:bool=False, al elif shared.opts.device_map == 'gpu': config['device_map'] = devices.device if allow_quant: - quant_args = create_config(module=module) + quant_args = create_config(module=module, modules_to_not_convert=modules_to_not_convert) else: quant_args = {} return config, quant_args diff --git a/modules/sdnq/__init__.py b/modules/sdnq/__init__.py index c76d1e9f9..b4e251601 100644 --- a/modules/sdnq/__init__.py +++ b/modules/sdnq/__init__.py @@ -441,5 +441,8 @@ class SDNQConfig(QuantizationConfigMixin): accepted_weights = ["int8", "int7", "int6", "int5", "int4", "int3", "int2", "uint8", "uint7", "uint6", "uint5", "uint4", "uint3", "uint2", "uint1", "bool", "float8_e4m3fn", "float8_e4m3fnuz", "float8_e5m2", "float8_e5m2fnuz"] if self.weights_dtype not in accepted_weights: raise ValueError(f"Only support weights in {accepted_weights} but found {self.weights_dtype}") - if not isinstance(self.modules_to_not_convert, list): + + if self.modules_to_not_convert is None: + self.modules_to_not_convert = [] + elif not isinstance(self.modules_to_not_convert, list): self.modules_to_not_convert = [self.modules_to_not_convert] From 0f6eb624c9787c2d4c8fe2365df21a354fc55c75 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 26 Jun 2025 03:10:26 +0300 Subject: [PATCH 43/85] Use llm_int8_skip_modules with bnb --- modules/model_quant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/model_quant.py b/modules/model_quant.py index 9f2bd6a0b..b46e25715 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -50,7 +50,7 @@ def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Mode bnb_4bit_quant_storage=shared.opts.bnb_quantization_storage, bnb_4bit_quant_type=shared.opts.bnb_quantization_type, bnb_4bit_compute_dtype=devices.dtype, - #modules_to_not_convert=modules_to_not_convert, # ignored by bnb + llm_int8_skip_modules=modules_to_not_convert, ) log.debug(f'Quantization: module={module} type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') if kwargs is None: From 7380c08f8ea101f5a356678b227bf4072569c102 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 06:50:50 -0400 Subject: [PATCH 44/85] lint fix Signed-off-by: Vladimir Mandic --- modules/lora/lora_load.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/lora/lora_load.py b/modules/lora/lora_load.py index dbeb57630..c5f6bc952 100644 --- a/modules/lora/lora_load.py +++ b/modules/lora/lora_load.py @@ -150,7 +150,6 @@ def load_safetensors(name, network_on_disk) -> Union[network.Network, None]: break if net_module is None: module_errors += 1 - if l.debug: shared.log.error(f'LoRA unhandled: name={name} key={key} weights={weights.w.keys()}') else: From 4de160608ed3aa09a3064aa0072e27105d58805d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 07:47:03 -0400 Subject: [PATCH 45/85] fix prompt parser with batch size Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + modules/processing_helpers.py | 9 +++++++++ modules/prompt_parser_diffusers.py | 13 +++++++------ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23b8feb2d..651847e83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - Fix TAESD model type detection - Fix LoRA loader incorrectly reporting errors - Fix hypertile for img2img and inpaint operations + - Fix prompt parser batch size ## Update for 2025-06-16 diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index 461e0e9ed..b41127e69 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -424,19 +424,28 @@ def resize_hires(p, latents): # input=latents output=pil if not latent_upscaler def fix_prompts(p, prompts, negative_prompts, prompts_2, negative_prompts_2): if hasattr(p, 'keep_prompts'): return prompts, negative_prompts, prompts_2, negative_prompts_2 + if type(prompts) is str: prompts = [prompts] if type(negative_prompts) is str: negative_prompts = [negative_prompts] + if hasattr(p, '[init_images]') and p.init_images is not None and len(p.init_images) > 1: while len(prompts) < len(p.init_images): prompts.append(prompts[-1]) while len(negative_prompts) < len(p.init_images): negative_prompts.append(negative_prompts[-1]) + + while len(prompts) < p.batch_size: + prompts.append(prompts[-1]) + while len(negative_prompts) < p.batch_size: + negative_prompts.append(negative_prompts[-1]) + while len(negative_prompts) < len(prompts): negative_prompts.append(negative_prompts[-1]) while len(prompts) < len(negative_prompts): prompts.append(prompts[-1]) + if type(prompts_2) is str: prompts_2 = [prompts_2] if type(prompts_2) is list: diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index ad80e3af0..731dc6608 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -53,7 +53,8 @@ class PromptEmbedder: self.negative_prompts = negative_prompts self.batchsize = len(self.prompts) self.attention = last_attention - self.allsame = self.compare_prompts() # collapses batched prompts to single prompt if possible + self.allsame = False # dont collapse prompts + # self.allsame = self.compare_prompts() # collapses batched prompts to single prompt if possible self.steps = steps self.clip_skip = clip_skip # All embeds are nested lists, outer list batch length, inner schedule length @@ -83,7 +84,7 @@ class PromptEmbedder: self.checkcache(p) debug(f"Prompt encode: time={(time.time() - t0):.3f}") - def checkcache(self, p): + def checkcache(self, p) -> bool: if shared.opts.sd_textencoder_cache_size == 0: return False if self.scheduled_prompt: @@ -176,13 +177,13 @@ class PromptEmbedder: else: prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip) if prompt_embed is not None: - self.prompt_embeds[batchidx].append(prompt_embed) + self.prompt_embeds[batchidx] = [prompt_embed] if negative_embed is not None: - self.negative_prompt_embeds[batchidx].append(negative_embed) + self.negative_prompt_embeds[batchidx] = [negative_embed] if positive_pooled is not None: - self.positive_pooleds[batchidx].append(positive_pooled) + self.positive_pooleds[batchidx] = [positive_pooled] if negative_pooled is not None: - self.negative_pooleds[batchidx].append(negative_pooled) + self.negative_pooleds[batchidx] = [negative_pooled] if debug_enabled: get_tokens(pipe, 'positive', positive_prompt) From 82aaff9d707ab14d898e98db8a2092a292b6e58c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 07:59:51 -0400 Subject: [PATCH 46/85] gallery trace file fetch Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 +- modules/api/gallery.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 651847e83..ec8698873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2025-06-25 +## Update for 2025-06-26 - **Changes** - Add [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) diff --git a/modules/api/gallery.py b/modules/api/gallery.py index e1add81af..6510cd673 100644 --- a/modules/api/gallery.py +++ b/modules/api/gallery.py @@ -6,7 +6,7 @@ from typing import List, Union from urllib.parse import quote, unquote from fastapi import FastAPI from fastapi.responses import JSONResponse -from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect +from starlette.websockets import WebSocket, WebSocketState from pydantic import BaseModel, Field # pylint: disable=no-name-in-module from PIL import Image from modules import shared, images, files_cache @@ -196,6 +196,6 @@ def register_api(app: FastAPI): # register api await manager.send(ws, '#END#') t1 = time.time() shared.log.debug(f'Gallery: type=ws folder="{folder}" files={numFiles} time={t1-t0:.3f}') - except WebSocketDisconnect: - debug('Browser WS unexpected disconnect') + except Exception as e: + debug(f'Browser WS error: {e}') manager.disconnect(ws) From 931650c43f992e795a6e0a35fca5fc45fe42e4c6 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 09:17:10 -0400 Subject: [PATCH 47/85] fix process batch Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 ++ modules/img2img.py | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8698873..96d8fc9a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ - Fix LoRA loader incorrectly reporting errors - Fix hypertile for img2img and inpaint operations - Fix prompt parser batch size + - Fix process batch with batch count + - Fix process batch double image save ## Update for 2025-06-16 diff --git a/modules/img2img.py b/modules/img2img.py index ca71ff0e7..888bf3626 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -32,21 +32,23 @@ def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args) inpaint_masks = [f for f in inpaint_masks if filetype.is_image(f)] is_inpaint_batch = len(inpaint_masks) > 0 shared.log.info(f'Process batch: mask folder="{input_dir}" images={len(inpaint_masks)}') - save_normally = output_dir == '' p.do_not_save_grid = True - p.do_not_save_samples = not save_normally + p.do_not_save_samples = True p.default_prompt = p.prompt + if p.n_iter > 1: + p.n_iter = 1 + shared.log.warning(f'Process batch: batch_count={p.n_iter} forced to 1') shared.state.job_count = len(image_files) * p.n_iter if shared.opts.batch_frame_mode: # SBM Frame mode is on, process each image in batch with same seed window_size = p.batch_size btcrept = 1 p.seed = [p.seed] * window_size # SBM MONKEYPATCH: Need to change processing to support a fixed seed value. p.subseed = [p.subseed] * window_size # SBM MONKEYPATCH - shared.log.info(f"Process batch: inputs={len(image_files)} parallel={window_size} outputs={p.n_iter} per input ") + shared.log.info(f"Process batch: inputs={len(image_files)} outputs={p.n_iter}x{len(image_files)} parallel={window_size}") else: # SBM Frame mode is off, standard operation of repeating same images with sequential seed. window_size = 1 btcrept = p.batch_size - shared.log.info(f"Process batch: inputs={len(image_files)} outputs={p.n_iter * p.batch_size} per input") + shared.log.info(f"Process batch: inputs={len(image_files)} outputs={p.n_iter*p.batch_size}x{len(image_files)}") for i in range(0, len(image_files), window_size): if shared.state.skipped: shared.state.skipped = False @@ -117,8 +119,7 @@ def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args) basename = '' if output_dir == '': output_dir = shared.opts.outdir_img2img_samples - if not save_normally: - os.makedirs(output_dir, exist_ok=True) + os.makedirs(output_dir, exist_ok=True) geninfo, items = images.read_info_from_image(image) for k, v in items.items(): image.info[k] = v From 2714d29993a06c3a542eadbf577a77a6cf5d77dc Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 09:30:15 -0400 Subject: [PATCH 48/85] fix unapply texture tiling Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + modules/processing_helpers.py | 3 ++- modules/sd_models.py | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96d8fc9a2..cccb4ede7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Fix prompt parser batch size - Fix process batch with batch count - Fix process batch double image save + - Fix unapply texture tiling ## Update for 2025-06-16 diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index b41127e69..013b32109 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -551,7 +551,8 @@ def set_latents(p): def apply_circular(enable: bool, model): if not hasattr(model, 'unet') or not hasattr(model, 'vae'): return - if getattr(model, 'texture_tiling', False) == enable: + current = getattr(model, 'texture_tiling', 0) + if isinstance(current, bool) and current == enable: return try: i = 0 diff --git a/modules/sd_models.py b/modules/sd_models.py index f6a7e53db..0530f5a5b 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -427,7 +427,6 @@ def load_diffuser_folder(model_type, pipeline, checkpoint_info, diffusers_load_c def load_diffuser_file(model_type, pipeline, checkpoint_info, diffusers_load_config, op='model'): sd_model = None - diffusers_load_config["local_files_only"] = diffusers_version < 28 # must be true for old diffusers, otherwise false but we override config for sd15/sdxl diffusers_load_config["extract_ema"] = shared.opts.diffusers_extract_ema if pipeline is None: shared.log.error(f'Load {op}: pipeline={shared.opts.diffusers_pipeline} not initialized') From 61e949c1ccac6a94afe2e2dedfeeebfde90209f2 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 26 Jun 2025 18:33:04 +0300 Subject: [PATCH 49/85] Update OpenVINO to 2025.2.0 --- CHANGELOG.md | 1 + installer.py | 4 ++-- modules/intel/openvino/__init__.py | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cccb4ede7..bdd09b0e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Use Diffusers version of *OmniGen* - Control move global settings to control elements -> control settings tab - Control add setting to run hires with or without control + - Update OpenVINO to 2025.2.0 - **SDNQ Quantization** - Add modules_to_not_convert support for post mode diff --git a/installer.py b/installer.py index bf4d0b408..4e4d391cb 100644 --- a/installer.py +++ b/installer.py @@ -752,8 +752,8 @@ def install_openvino(): else: torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.7.1+cpu torchvision==0.22.1+cpu --index-url https://download.pytorch.org/whl/cpu') - install(os.environ.get('OPENVINO_COMMAND', 'openvino==2025.1.0'), 'openvino') - install(os.environ.get('NNCF_COMMAND', 'nncf==2.16.0'), 'nncf') + install(os.environ.get('OPENVINO_COMMAND', 'openvino==2025.2.0'), 'openvino') + install(os.environ.get('NNCF_COMMAND', 'nncf==2.17.0'), 'nncf') os.environ.setdefault('PYTORCH_TRACING_MODE', 'TORCHFX') if os.environ.get("NEOReadDebugKeys", None) is None: os.environ.setdefault('NEOReadDebugKeys', '1') diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index bb5ee9c64..26ac004e4 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -24,11 +24,13 @@ from modules import shared, devices, sd_models # importing openvino.runtime forces DeprecationWarning to "always" # And Intel's own libs (NNCF) imports the deprecated module -# Reset the warnings back to ignore: +# Don't allow openvino to override warning filters: try: import warnings + filterwarnings = warnings.filterwarnings + warnings.filterwarnings = lambda *args, **kwargs: None import openvino.runtime # pylint: disable=unused-import - warnings.filterwarnings(action="ignore", category=DeprecationWarning) + warnings.filterwarnings = filterwarnings except Exception: pass From ada38d57b466152503b1a8dce8e8f77dc2a5cb4b Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 12:21:35 -0400 Subject: [PATCH 50/85] add nvidia-cosmos-predict-2 Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 8 +- html/reference.json | 15 ++- .../nvidia--Cosmos-Predict2-2B-Text2Image.jpg | Bin 0 -> 43322 bytes modules/loader.py | 8 +- modules/model_cosmos.py | 108 ++++++++++++++++++ modules/modeldata.py | 2 + modules/processing_args.py | 2 + modules/processing_vae.py | 2 +- modules/sd_detect.py | 4 + modules/sd_models.py | 3 + modules/sd_offload.py | 2 +- modules/sd_samplers_common.py | 2 +- modules/shared_items.py | 1 + 13 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 models/Reference/nvidia--Cosmos-Predict2-2B-Text2Image.jpg create mode 100644 modules/model_cosmos.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd09b0e8..0b4cc2a0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,14 @@ ## Update for 2025-06-26 +- **Models** + - [nVidia Cosmos-Predict2 T2I](https://research.nvidia.com/labs/dir/cosmos-predict2/) *2B and 14B* + - available via *networks -> models -> reference* + - *note*: this is a gated model, you need to [accept terms](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Text2Image) and set your [huggingface token](https://vladmandic.github.io/sdnext-docs/Gated/) + - [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) + - available via *caption -> vlm caption* + - **Changes** - - Add [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* - Use Diffusers version of *OmniGen* - Control move global settings to control elements -> control settings tab diff --git a/html/reference.json b/html/reference.json index fe89f47ac..07d0c97dc 100644 --- a/html/reference.json +++ b/html/reference.json @@ -236,7 +236,20 @@ "desc": "Sana is a text-to-image framework that can efficiently generate images up to 4096 × 4096 resolution. Sana can synthesize high-resolution, high-quality images with strong text-image alignment at a remarkably fast speed, deployable on laptop GPU.", "preview": "Efficient-Large-Model--Sana_1600M_1024px_diffusers.jpg", "skip": true - }, + }, + + "nVidia Cosmos-Predict2 T2I 2B": { + "path": "nvidia/Cosmos-Predict2-2B-Text2Image", + "desc": "Cosmos-Predict2: A family of highly performant pre-trained world foundation models purpose-built for generating physics-aware images, videos and world states for physical AI development.", + "preview": "nvidia--Cosmos-Predict2-2B-Text2Image.jpg", + "skip": true + }, + "nVidia Cosmos-Predict2 T2I 14B": { + "path": "nvidia/Cosmos-Predict2-14B-Text2Image", + "desc": "Cosmos-Predict2: A family of highly performant pre-trained world foundation models purpose-built for generating physics-aware images, videos and world states for physical AI development.", + "preview": "nvidia--Cosmos-Predict2-2B-Text2Image.jpg", + "skip": true + }, "VectorSpaceLab OmniGen v1": { "path": "Shitao/OmniGen-v1-diffusers", diff --git a/models/Reference/nvidia--Cosmos-Predict2-2B-Text2Image.jpg b/models/Reference/nvidia--Cosmos-Predict2-2B-Text2Image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..54b9af8c145fa9115083ec9ee073cc752f073238 GIT binary patch literal 43322 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≤HHG)>@vMp6lv4xd6f|JxWG|aG14lwC8%S9AHZ8bC@2lBgR-n0g}$WZs;)@ zuk}9f*h|Ea%p_j{>h^vOOA6F%1sXg32a9!eIYCi<(5T9=or?!WdaTcXoI=xcS(WOc zB##4_Q+AQzK_GAv`|1rY&uZZ9kUeKE{1@?$4_e-GraAs=mrRcn${M z_)6XSzp0!3ooC2^7s-Pz>14p3luyy3&$A3tQsL!)1C)L#Bq+k~nlPa_B+4Pnz$!ko|8u28$R2l031Jveb_!1$GdM=6x&YuPA{YX~A@pi1zbfEAr->!_%@a~}6!qz1^BuS7%k=j!!H+~2u_~j>U9zUBZ{UU`pwGZEvAxQV&_3B7bj3vxf9Og{nCgGtBuDeCJ6194Hrox&o z0fS;YYZ6jeUeQbq`}L&>^KqWQ);C6cxThobii1ZR*~_$%t*+W7mIIX$Y`t$X8i#QP z>szXfkj{m!Zup}5rJ~~0_(gKwB`1iAFdF4%MU7&fS@eR9&tQONwp5c`Uj{Z>dUGhNR=CfP>aDk>(n%z696UK4AT_b$+iYwp(CCKaO$PmiJe-Q+t8-ZL$uLwo~ z7@KCl}mxqjI19`LZFeOCb~U@sOnjC#z!=EdJ@OwJ`FA=A{ZQ z$JHvF_GVpH+(v=FvjmnJUE=60*%OohKDezl8nC$kdKDJcun-dx4B?r6m1sr78UahF z$>iBt`#5%Qnu*c9P!pwVN4(R_$iA&`JVlpb`ex`*6&96DJ7X6;`i{cf|Teb1W zt#G^zQC2AO22+8W-Cr)Ak|3mFo&-OR=W!FFbfNrr?F+?Ont zP{rq~&QX47`Q1R4dhiX%FfKt|?~Af0ZN?T=WBL-U+rA?S83tz=;qQp36Vz$XxLg;z z8sQoCu0Pp*m=>fGH}HHn>fwM}ZK{B^>S|BE7(ckGu!fPPtE5c|rfq>z+B4pp)1Y$| zDDlA>WIEaP-pO^Qv?M)&d0#e&O_o;DK+g?;`V5(ZQ_xdB>MGL%N()R)(?XEsqY-Es z=07?zEEjwz9R$z_;s91X>0yP-YQIa&q`Y%$>ksN{^in*lm~7y3Xm*DliR#zD>wY4a z6BTci&aWiX+=gCeliE|r&N}~IyvboD8oM0Di$Dp z_*um$0Psl!*{eexKQBm|6bg{OKwsf1+rc!O0k({Q44D8%jx0ci>@hu6QcT^eR9SKH4t0%xv0$|R#6oa{Zz zq?|K_LEI6hT7er!2?9vEHsECwHLB!gm@G+M2;!BzI@G+4TXBHtU?x2hwuBn74374>b=aijGB_ zcbWy3voV(MjRU|q_3bK~ch2^?+Z$_^#36Qqtb-KdAf#r?q{nY31HE>p=8=^b+Sm$~ znR~`?{3C<5m4k#Gt+xFF8-~(te9E$~Rjv6vJ)N26mNu@Uu`WXb`z>Kb7J?&Wsh3Z{ zZ9B_9w-YCs)BdK<+%qG%Mf`DcaZ@L4>MF0}I`Wb$pW zDaK8845f-g#&Z@DHv`DS_)o2bNJr_q&Xi-KzTSm6z7HzbqXuu3icQX*6v;ycZ^P#@ zM<|3ESY!wl*(AYoCDt~6Mh7p{d zhO)av$nTEsirDTd@5*Qmm!!v#?#oX?A3kN-tFRxrsA|v2K^bAQSpW?ZwFDPYWa!ua zFRW=9`jp{ZK(N8Y(Sh`zrWxkp2iBAdSij(OkTnmwtp`H3>}G~r?BAG%piTi>lj^C$ zk=!*b7)DMX9!(EYqH(7l(3wUgdqy!fjP&VV8Zjj^ZiS_c!hN}7`?)J`6PB{2{c3-` zSF7g)=m95FGps$AfM{4;FWF0w7j2$l>20S^V$^mlR|$B4b3;Wu!Y;ay^DQ8A)gKl0 zx>voLem+db&NFg9s;oTCQH7bdOWJa0^nUzpMcVu;E|o^brXy(bx2jcRL6knd!lzvy z^!H427@PXz&H^zHG%3Th==mm_HMoFXL5;24Q+hi3z~TmOpDP!a5dRx*yB0*eGCD9~ zy8}GcJQ68Wtc##yX=2kbdDK$(mn{K36hY=S5?tcQn0|ce#O*u&mH}oj^=Q;RoQbo0TqhxAVPn65`G>PgLM>jgq zkoU_V|0QZpYs7v)qvcqn$b2A+=6amR8eLoxg^4Rpy(_duBUY~4pJ2^r_ChA&x+6>d zn&o*kT=`ci1+@#|`~axiV26GVLQx%+d{Jvb$W)tqb5MXaH>7PzE6!4wm3d^anw@j*k zkLAs9SR#|UgyTU^{uDd!nM5~ZK~YQWHbc`k%8d?d$sCB+YZCLRcqRm|i(O{nkvtat7EU0JzI2{+$>zv|!`fB7 z6af8qOvrEPt+6M8qWZKY)%iXts;I39<<` zZsJ$J9L###_XO%5%k|~TydJjK7)x%w;2t6DTg;^utThTxYu*+BzEs#+ zjNpt=g>X58e0#Y+HT|Ro>|TMyY^_StiIu2nJM~yNX)g!n%<)zKeA!V8uop?^uW#O{ zzp||1={W6Mux62gzw0|LnD4a;vXy9m^oWRRoDNlZVSYi0q_JK~I{seqoMWtN!%;RP z?nYY}zyu*=KFcj`v9-awDq$0X>}?E_zA@5>7+}sQ<%`se8Z{p+0k(=0+(I{7FS1Zh zr0f%}+D7ucA#!r%QVetQ>SLW!9Zs$%q$8h313;sHB=pC5_q~=|9Ez#y8;+g-hP0Ks ztV#|Qm$Qq}J_fbFhTPs(IN%gg^+fqpY3fciXJGBqKI{g%*M)DRPH~ zHP@M>dx<5fF*2t*84q+xHiNd|(n?G+5*+fP1EmEB(B4{F0EbfmeS5$LW937gJ9WKr z`b3+bKLAYA5|H6>0L^NEq7W2WQIsq>fTv9}pe!jE`M)lXtjrL};3663!mwQQ%LV%1 zx4*sK0oKsL)%{C`z{k4@F>mN3k51)6eOO9_p|A>t8aDH1Fiqc}s5v*xhP4#%wEcm7 z!tL2$428X8JD+9Ht==v}t-a2A66DJ-IquZ*>{%ByDboBTkuke)=hgIs>o<(COQ^PQ zcXo3t;e}kk(rMi$+dN2&Xc4Bw#?1P$SUu>;zWP>IkxDxJeQh#p_bUuVbSd7#aLNw8 zrB;BKT5|1Ld>LcTOpV)c^jfNRaDa_=v+%|FE>L*9{2ksPFSJ1WXR@6KPeqoNel7$I zGWy+d5E)Afe%3~sk501D-&&P+>OKvuQH0tU#LG9$P(`nqf49CDbGWhG7x5##dg!AB!frJ3th37=-PSX6}PyV%Ts!AbS zYERhui=hEtRv(vYCiJdyaZfll)Um%Lyt&4nx~Dx{h?BYo zf1D~r!_;x~)7t1cQo^a)joZ>{TS=>os-Fs>^fz%N#~NX4Yn#+NMN>leyXMx-#turw zMs^LObEwJMvj)l<7fy^b;;_=JyVcQo_SFcCJK8QSpFb%{|LD-(XTfbo;EBC-Vj z%FZOG%#{6oNt8UVHNwS@R!vi?#6LZ@y1%*r$mz8DrV%XF#$-SH7Aj8(w-M>6rnigs z+7myAV}1BF_qMlP2(*{dNOKqkeg_8;`C?Zt+PJK8MyT9TEMeE$KHw$4fhWGxW9meE zP|wAEVNovEk(O$~hInyoc&CmlAWI|Dk;YTUFLo@_WHe!E=Op=N9mV=+fcd8~*Bu#L z)J4Zt6Gev-l?px%mqVSXnLw1mMDTp70NAt)08UXbUNitYx3n&Sol$^=4#@PMR79bD z`u}~<;|#D0d*jY}V@t>AUOD9U^lhM;lzRXN+X%=raX!vpPW7`$=!LLel)j)9Ustzr zw%`5Q3L<&OJu83*zyu4^L%kk1okG*dRPdflx5vP!A%t$XfFOa}EG-nOp|hWuCs@uY(vBlb5;XTy3oM;XnfjDVuIa9OK-R?S#75bB`;T=*RMXhb@vi{A;(eo=w%;p71ygY?x_gb zWm^8c>Ay-}F3Q@|->y{WEHr3HFHH7tg+-L%IWoZ>>vl!Mc~U~M;dkjK=E z#PeJSUH(OdEaP}>KLbU)gu;h;VgbK-tq5LDRX<$Paqk3K%1XWofwec^0GnAkRoV0} z7Ouz3Y{;Y?!d#mzx4D!zy4^3r(Qp*RQD(tKJ%~~{UeaHh6K#ZG_%$qZ*`<7m=I6QV zKdo5rzsvu)`n-H4J;r9gXgFcGO6;fWdCbd(yrK6S$5yq*_B&(c5Mrq|bXs7f?q^3> z=aCx620$h-bChyf$}$}Na`b?Imy(+Le(wUpO2E7x%|u3;6iIE`7KZ5-3{0zcZ9_fk zW#ouHjJ0WT2V3zAuZ(U;!4EAE5LBZhuE7X%==01TeEHt9mgQ3~CxSVhU1WB+upi)? zpSg4=5Rq*aee3qO2e%WST)O;jRs6YB0#dIW6~%MH`6@W$D015vUp#%468>WWb5HX@ zf$Cb@!cVJG`f^NKYIJ=Z_*eaBlWg2f1sE1TZJRxwsgcdpZYH9CjD`SwE%v0rtjN0C z$#v?ZRKlxYUr?{m^=Rbe4&41r0Tff*uJoc&&Ju#~140cWp!iS*8&VZ;`&;^=%>&c& z@b0Z1ecgv-eG+Mc*9phtcjEN^MCd?zKpk;mMzYv^EFOfynPWjZEX2JyvAxE@cowb6 z5noASSvf-3ia8njkSH+Rs|?jms%PVvcILJX1bt-IwX^j^8aFygL9ae})UvXvKkED$ zncUs*HpI-w%}eNuxb{}m=y$jzksf%O3A(^AxfxA%=3BoB@wcL8P!g-5=1UcD zsOQ`4=H~2lL6h~Cq0&0Xa{_-Yvqc~T!(vNIkII9xS^;B}#EP;9V=R38k3Vyxlr+5c z4ARJ*&>h8#XqvQQ+o$?5FyI;a#B5f1eOQ#hgJQ1z+~;nz`E;&>=H`ZIDQS46?%w?N8G!=AM-8J$y7UXzuxW7-h~$+B;| zp!rAl!g+VLVloz2^DaEO;e3kT@>1Sf=KYVJvv~(W`l#&vWmk1yjd+4bHJxd318ppO z4d)BQO;z7C^2Jv5d^JP^x`xrmToobt3T7*MYM8)mNeeEEC{uatB)ud>`_xph&RFI; z1E;VgCwy_}x-v)esftTn`B|wm)u4=OOjx8M6DpB0MJXINKs|&*Zij&3v1pq(+Ka3i zkWsp`7>w+!DU2IGO$3nh{lyiqYytert&gC7V&?hBbJh(CC{K`#wLj^=>l;w11a+CX z2AyS=PLOSlzTlil`U}?pH|C&jTd-j72Xq}zu4)q@y1HIPPNxCW^yUb6lB83!#bK1)V4)A6*`0z)u zf?zMnxuI?|L`wEe(S~qH?ut?+$T4Kqv_RGtM(ADj5l4`B17uxt{+&B)Jgx^$D_?h^&Ji@`@NpGz zl)QFHKACr=_vj||BK%_&$KxDS%Sl!*@zpl^dK_j89VoYD?_p3$dovy@3-#3Bo|4r| zUFAPB45Eu7>7XYjEZVXm3%mb*S1$;CnteM^N0C>672; zh@_x7TKz}H2i4LvmADnlAc^`6YS5<4StyDappX6jG6WgWth2l>vZR#Afn|5eANuk0 zkNGatc^u0bvDTL}m!C<0XD{7s0c(Gu`M!D1gAZ@}*vB$)S1_E@8?vXLB7*b%cdZw* zG8aI>DoL12y~0Q@=e&of*h;&XRpTJ47S~FLQX4cL6N&=N@?)&+|FVcvDBwmv&=KV)RHgh z(f8fMSGM&BK;_Qb6Qf3<{JwgZB>8C}JL*Tu&sy|;?WmN=1~8f9$swBg$?NY9v+EOg0(Lsl5n|I4Z~bE(wYhvL!b8tQ{Ey z&UNw!q-U}w+P>hAXB5HZ7A!FNwXy|!*U%ozTbX%FVQ%QJ8w4tnUHb(W_QjjRtuOIc zP3;Rv38LoLVwO(UvZpF(d6Rm5ztiln{Njq2B*iSdL%rw}#!`EcK4O=M^Y^l3$N8RT zxh6FcLQ_{>De+0rlOHVhAX~#Ec=84dYC2|gJmrGo<0L`b^t6LLnyV*@i7kqJxbFMM z>j6vTn{ZD{lAq)w@#MX*hWZ&nLcT;2J<-qVi_f`@2ERhOF4!Iv&6f+Op+uJ^NNP*R zQzFPo<&g-cuEMh1n5uiTICfWa=XaCbWqvQ_@PnEtnO}u=PhHUr#kSh@K^d9{8C}yS z6#2exsXnMN!{fBNPQOS#RVLMo{16Fds)|9tIXLm>ozX@wKWP;U(?y}JO2dKMBMESv z2a)crdFrn2=lSn_d>EBnkT}D!V43_D2E5MtQB9lDzBc$nLyma>x?R)dMq<;PhPMC2 zNYThShzlfcfZu=b%P7O*#Wm6|14#xi6q3L)m?sE+dOf2H4ydG_W9T~Pmm4kBhL!$6 zc89wAi#-J+wcWPSlfeMGyfl=sA{H`2rd_Mn`t=~yM0>uNu=WdgoD|rkSr|d7RrBB- zz?6PP(Ruk^yys-*M8ARZ;zh1vXkV!jBT%%(k~vF`>h1A-bpPjJxQ@_Yj2(D_aiBJ) zTrJVD3JmqqMbIcB9pd@>`$a3;p^EVh)WKaUeo5WUK#x9DcyYRdH;%1od1AB84 zNwUh>F41$LV7sYKtD6OgR^{_=5da4VA{un5d zrupZguO8?SX!Z}lf^h&!D$pgJk}}}>8u{AVx&v*c31Y+SmxG()m3iF4^*cb8f{`b^ z^o*pBo?T=94loyJp)5RkP>s^VBfxBS2=BwNxc7clyrRISa2Hy+%4Si7;E#8*+ZpN_uPf2k4Pr@E<%g zTfWo0$_Z#NRAR*#?I8J=*sH2rJF_@M4rH;P4DBVxWaB+v!!vE{qAyB62 zRY{r-I;7O`NzP}Q;(zsx>$c=m(My9%zEEAVH!Z3GTdplcDO&bb(R5)>P)JfQD%JZ^ zMgm#29Li_Ai|Dg6;|M194nj*iAn+LO_G$n}-m1hP5y-?BhKT z#JZs-L9P*2W!VcN7hZF!AF$`vkaWF)zR*7c#tG6jLba}Tas3$kF=cnBql|V|fCWzO zg*MYAK*WCqCxOXH)5NKaS$=36LAWZ3GbNjS%tZySnhU3$B=1|3M@)X>k(O%cH!6w% z5Dq#dZ&S(9sg4eKJuBtZFQg6^63wddJ^x)e|LE(Vs|z~l4q+{v!OWs?)bj~J(7{T; zWj{%BWJN!1!=kMt+JtO}xccwIuL%z#mex%7CfY)(l9CVtsLDsFW&Lsn+{T~l7)Po= zboRZ0a85ZKAscmOyu*TTh>7?`us?AxZ`-vvG$$ngs;=JFSxAVOm-TLgSI;&1*hQA$ z7XxY#mst+-pq(Nt&UnKH?10014TGOpjS%M8iQ7c=@dJLI=3v5WbzZPDlaW^RZ@wa4 zcKy$m{5BfFBKuXq_(zLjRMB&sVk>gUucU{ zt8>hmU2lK8S`(74`M1FDI9tP!?e0zRnxz%>f8L0;DK#c$7|oK9jBw+hY15bu&dSEf zsK&AK&_l*i{c$c$Kf0Yb#UqRD(m>mvB?V-FlnNs6F_hd`dY$Dlay-}uFE%vV)Gpdt zvM`dVdyw6AY&mc)sfX##9;Nm+ zjN*oo>H?MnH|r!$;CFK0F4)P$FV_i;ec7WX$(Sm{&9^GI)x~+)Qsbtp6O4Be6%>wB zR0RpFka-|^IBtatunUm{<;`!XgxyEatrcP*q0a225*}O@;G5o;O<=Zkf8WXg1iVCl z+Z#JdtMU^8%1P*@@%(7pB=-TF5|ic3jr%ntCBJ88eP|;!K{m2pJe5-FD?jFYPsNX* zTPrcXX_bNv8NoAY(onAJ7}SdjZ3P+0A<=~K(vwz+iP^yH66u-|98-S}U02CbW{Uf6 zN`u&j175@5Bc#5h$DM?t8(wR#Y;KgS7A*rKClHk<%eHe9?A0;H9oJ4=C;wPg8=i`} zR>)Kg!(D|TZ(HhC0%x~^N+{|lNw@Y~WG%_k4Dg*=0Ej_<@J}=5W<`4Gu!xyBgF;fp zl&?kbc<`c|EampOmoN9bx{27CPAfjKIoxXZllb|8xp}Qg=lZ67W3AOO*Z#fOF_W!x zYl)KA^sgjUuom~)!qXV?c~}g8foa~+59T=XL&W7WkmB?6=QsEsMkfZ7)~1( z-F6l`5qEwI=SEv#sHoxViZS2rGcr<>70phq z_u6VRhX`#xeY;TDJMDXZoHNm6*=lP!%Lv`rO9R}BG-#@pTScNAM-A{l#zkz<*J+ea ziPIB25A_|fF7Z#Yh8L-PE28>wc`kDOJfPgkD?@3dgF}d+c>JEu^~(~=Rg3qD9* zMxB1pRy;(sigSHvX>a^$02t;PL^L&ja8ti$GEIWxE z%iwQK*Rb};t9|D2Sm#K^0$a`C(18q+n`l^cY+lY149M2rJtwSJEppZ5vzVAZ_n1&q zYM(i$4Byz4a3!FR-HrR3{G_30=HaqMyGZ8Tc>Yqz2gg_E=h}ts5VGXhM-C|ZvZ>?rP`fZ#o*rg#h~*}5wjKBex&X)YG+D22G*gxV4ecgK|@)O(9>nJm@zzpW5E9f)fYJ5aSu3mH+YS=BJX%veIkdv%w) zW*y2_+n_T71|bRLL zZt-^dS(ZTLj-~m>pFWzf$SDosIDnPW0410YBKDc;D7qsE{eo)chYcEP$kPjxyZ7)L zV|RR;#s_y*csQ!dXQkGd1~0m-<42*&?K+##)9XK!Z!^xIISQh5oCl_?!EOh}Lsvvp zVFn9$@^M3r&N+(a+ClO7RGf3c*xsZqEMQ}W6)OzlYz;%D?T&(y=j%GzZLOj;?fKVp zWmHzT^;=w=2LuL{4}q3aGE;J)El)NZ!`xmN}>jgWt9C>bn4m>g@uwT@0 z&Wj%#c@OT$Q+@GzE$|8p|E!I3#=~J04vCd4j4F*k9e8jM`!WT2!|7-*?naHNZ5!I^ z5pg>pOfyjO;ZCtA?i5zr@zRpZl{(SV>JBMS&vMQ?7P38?1u)jW`~#Gdm)d=NDN)i* zob+Om(xYsa8~Eay97~j>lC{Bw7wIPBT_%A#s|Mo+#YZIBq06hPP_!iDTMBCav{?@x z?!eK=kzv!{pa@yV#ADrEu>&Bt=EN+}^G;-=GDg!ymo3C&!u2yZ-tUhXvQ~m_x1@G| zz6G-*z{53+dDH!t?ia8tosWUataPH|Q~dxy7z0_p6UqLs7Pbu%4uqW%4_a{NDeqi| z(!RCzdQOsVz;qbClsvh3r+PVVnqzgvK&A;kP%fCyeM-X6XB&vEr@cPnP%7R9`H*~F zzBK<(bN4em`qM`2Rcoh#NI$m!4eChfgWBBHyT+hA(*%<|Q+nd0PQ0q^>^bd{ShEHe z3zQ$H3_Gig@G}>tQ_mYPmC0BKB$~U_=w0>5R#`G(d%}3{ks1%c(42GlE>nOWitD=J z+>9m5{f*d%8u+_pfyLH|wXZ^`V-UtH!;*O*uRP13MO132s3$>hWrE@T1Kn+bbiVCu zcCO~WVL=DnA#_l}`|AU>s;%7TQK4A2br_S+1P}UzQv{XDI!mwoAxoLVjjaGKu`Oom zit9HjC>jv$#;X`Sgf;`wj2>+XoxZZUuH2x;eM3{U-^M8){X6iZwtD@*U05xo+;BZ* zZaGQWWol^_y1SqI!IjzSx{{V6Hs{6EWhjJaiA206S#F8F5?#{MeUs`6HS&+u+{GA# z(IEwuOgP?l|JBH@Ovg^dFoxaFAvm|3f=9$dK0vk{m&aXm823{==>0cmcEtP44Ljvo zYyAn?1y3(lN5Hvi%f<8Hvxckev0Yj%$okxg;JTWu@hn;6f>m`AFALNuun0$E7HlUn zrxB`*wwrTyB8u9{Pn!)?6CDLD=KN0W`yTCrqwo2dGEb5zEL=5F^7nwEgqrwg$L10@ z1?Dff6uQJHpY#jOKbXPQvRr8dJ5rNeMa!nzq5Vi6>4*lMcjgbzu@E4o>^P$mlQo}H z*ZIYj6;;~6B%Et1`&1E$v=ttI67OJ_Ll7!E4X8TLjERx@eVoX)t{>e(v=aF`Y#fLJ(uYyNg-sDwo^? zX-ttQI%5$N$3>QDgV!V}qAr;JTPxv@$n9uqRlNQ_f9NXekL- z`4gaOEn-LitvUS*{GNA-!MV0ES!O{sDNl`}W1w9el&y~7rhT=hZ_YPr_L#GsL5dnf zy3Q&3T3o3~R>$Wi^ggUbph5@<4qyoe%BeFs<|YSD;lzu#3Q0)&MLhpC!# zOUg+xMiI-4Lg{a;uqx^l#e9T9YJgf02E_Qe#;LM+!hw9^-g`*ba|$J1>0nunCikKr zgn)olcdT+CwEDHb;2$6_;yEzjq~Y=(AeP&~Bd#aqw_xPEniIupKiRhoYIj_cqz@!e zzy@pu`{Rz(fD*cut$dPbN@*jT!JiU}z3Q0m?xYtFhH+)sUB5M< zDB&>99PJjLamn$x1l^U^fGFkLDs=6pXlMnh{1hf$I?|Q8&{QZUI5knxB@CmrCTLCp zG($;!^e{H`a2=0M1P)R@7_Uw7@F&DH zzs#phaZ+zkCqk@)svYGiN3%EDU2m)xeWSz^Y`KbDbd!YcUB*52`H$oPm+mlCXSg=W zKLBcl`g?O6EA6obKtOYxS-6sqB6KsDtLk7Sc{60aTq|&dyt05*tvcTfbU_5WC(zAY z3>>hD-b0=ta#wtHfa+ifq1G}5l2Fz>%W@!&Hh<#sabz3s{(8cP9HO1Vo({iQp&HMu zIEO-AQgs1!0ijwFMh#L4^PJsP6YDTxvz+(mlt|{B{a#`lZ zidkM!616TUUnX=}ZP`uXD_=RLjJX2CI6W+FsVyL+!q>b}NX?)@0WsR#AzAeoYEP^z z4B-$zVZ}x2n(fZ+xpm~)=Qd*kha<&E_(Q*i1}ewVzYrl%j)QTKu0w-%0*OU6@98a1 z%VK%8R63>%?T?NyQx8P1%wMle6SXCIUcbLZovb#iIu#~=GXRlJ?3No7>4%)f2AS;zq@=HcVl5Q(dSsT}i06JdKo*CZxo8dQZ6# zJAL5faa%IV*&MmKeSBq_Y@WUFOSq4YvS50Dt@QuDa2zyG`^&3-4IBFpAf&wUJ*{+&xVj`nJe`0P-qPDjW1d(6?3Wq7jqc1Y za3#05e0a9IKp?zgUaAa3(chfW4`O9@*ri5yb`dTVLn&(8|Jk_Sg z1|Khd2I;fAkkD%k6kno!grQ$bo*sq=>Pb&I|5(uidUE`0APDpI@Sd&RPjdMb@At^FOv^|~d>(1Uj&x%{|EhGuB5$^( zzkBai7v@EDTpX=`ri$6Ej|=Go zSk_`mvMnL~?CslV=O2w|k(+ij^!-0du+SpCVHT^a-ybg8*?wh*>N~f+LuG85>gP+!G?aOa81c3Vx=oXhYXqojsq1;?sm$Rj-yjNqGosnSd;|(UbFIusHn4u4XS2 z>)J)V^ZBA%d!~{hcFH%%>&FNBP!?rh6UV0Ln#^{(51Hm!Ht8=n671*S(0*NOemxF2 zd?2>6x|_@4=S?8NDgD)@DJZQk8(P*AAn_kglk-!%z`?-Sj+&lg~YtL+aL;Ze$8t$xzx2QeLfRC^ub4A-2DZ4W!2pG)0yt2X=FXm zZ(uuADh>Qh)#4J;2-McRNpY(a6nTc%`-7|ff5NeAc)~H+9P+6tZo-PP0*XK-2c-ZM zP#~g#i$ww~*jk(ILfX`S%Xw7~(4K!x)vpdXkHlJgw?&sfp`d%7x2-Z*TUo^lg^dAK z1F!&9J$^wwy}1XbYbi>`)ZwnCoweAN7oMWI`#ZLQloQ2DnkrP9*y?O;R_gc5kY*Tw zcOTwk(0w@1yEF)FpAjtk1QNS_Jhb zzSC^A2p&7zc&3bxm^y+nR5c6iSjZK*Y%k$Jk1p|dgluf$miI>U5-9n2T;yZ9UgPOq zWcwtA(oAh&RQ@$XO`bd|Yr9slyqek1F)>{Fj>q!FBFMp{6o7pw6bva%04Ow+$z-T` zrl(qBHEdsN3T2;BOp`9#=DKgKI>AY}{A)8~u-A@!KWIPMAIhZG(>fiu(wo?uYEjZ4 z-=`tYDt#Zz8hQq9MPk{qM269OnzCLGT2~#4zh#fq)q^Yoi-x6BYaLiK!a6M%9;GI= zil>s{4@IY-tjbMo7o|Q)atH#Rg0m~NyhTY>1di1KhGONTlOEKYeE}}zOR2@N`Qj`1 zRk3q`U;%9Us6F^($YmqKLUQ$8Td{GKX! zO1T5QQxYu2D`0a^ND9cIxK{5_%@Mz&ND?tDu2=^7V#f#E)R09KcB(^V$;LeeN}Yz` zbE?yIk2>InVyX^QS2Yxm8;3)KRH}C&(OnNzu)XsW=3r}%({-P+T5p?zy;$%uSImrE zIVO54FmcC9^Z2}Lb*RiC(}F_?IBDExKkkp~Nz{rojXA!DQ8tV%wOFrhup+M|CmZxuT ze{U?U@+6AQ>@!Ctx{sYchppNH0963tMmX>5QLN}Kyzs!FUD*^~!E2h*>!Qt3e4ttQ zNYj>Y^Xputjmi5|lgs|LPUrYXpseLl^frYR)a&BZFXtI^4Y>a31>5mF59e8NT;;|a ztz_y**V)IhtlFIRq%`GbhafQvwUm1M)jOfO*_S-v(5gu=bmGq)*DURAbsc4=GGE*)v1aodEP9ikwbY`TxyIzygvL8>?$r#r{wjQ^IoWBVgR<2_p6x}D zxtSwF9(dxR{7pLsql{6Grkjdl9kww{#Q;1qDd1Bm89&;H^{6RW2~E>E$2AE1)B&q& zsoq*zPi=25$ZjKHD&S>@vFZ&!NVc@Ql6dar@>yan6O;1fbqCiyzZx%KO6O0bNfv|P ztE=xd8sg_DyGSKRVO+hgm3GLJh`-R>hBD-HgX#65%E(-oWY+K{_L$ye?VD202II73 zo^kJ66SNk3c4bkrMjNm?)N?eiV?`UgH)9)2)MmD}ngqc6KnIWD$KG$me=4tfQUKy4 z+2`|&bWY!$Psh1m1=3|FAjri=0X$PHQz2;u04Ow20@G*#b4fr26aci82wErsMI``D zZ85jHiedZ?8ls_%J8F8gdA+lZfzB6js5}{Q6~>VY$ue(P_K;he$CslwbJFN?DoKUU z&FNHi=KBS~mMM@BaxlV|AbVDIHkX1cWQ?9vS0$nAgIu0lb^$IU8Q}2a)}FBsc3QKi zlT*X6&w9>lN6D$?kz9~2om(C2HLai0j%FN?FG;Bhz>g=BS*8{pI@3~ObmG>9);qfP zs#h2B+1oVNM`EDl`qt^Db2zkdJ}Zr6*XAIR@~&~~im!93$9p`o%NP2_&(e(nT+Z@6 z9M#2^(4JAhIy4>GP(uBsHK3-+ySSr3yD0fGf6q$OSgafVTe{E&V2W?~=szmgTU!?Y z0GG|bQ9v1Lmjk1%Z6=#$-8Cr3)KDWfIj>OsraI_sG_58lnXm7bWD$~!5y#E%%`#gc zw(!jH%ek2V&-bdq*K8B5z(0*oDSXedvwNkj#gs95;1wL^u^Yt}3DL`aDCG`k*w(eu z5(q*x;N#F%BY48me(Y|43S5YM(mFAxTlbld(z$l=tYhv##*RcB(mJ7IW;(N<#<=J4 zq^It|ihQU!q;_9rvR~k1^)_cX~Ey4QN6{qW0m+v5wO`%3c zDX5`2NgcQPGW54W_zLrB^~?VNG1t!E98%>$c{9>H=$E1**O#`Zc>e%;cB05V?&qep z+(WCzE6dX2d1O^-oPvF6=oWp=uMph}&0<2Wxe-DxI(z>BI?K<^kGKVZWU#SZArheI&6TDqk=G z_gL~PinG)uw`_(us^*d>*~Oim-Xl9CxaAnw`DnsY{ZlAc8Z4in!Ib z=5nEZj|W8tHTnwm1L3a;*<5*sE#Xc=ODSdiv(0PmVrTVvT=O_#dxzO?6U_er-H8jI z(!Iw+)$|LMclZpC`gHnz%504JGDw?pJurFVx+~o` zTNh^IPrO;;OzhhDYaC(*qKbUCGxo+!#9?9su^nq|?V^xj zkWlHqfU7R3`Lkb9ZLZzK>@(&QUqW1IDYg8lqLEm9(YlYD@vWmL6=r29#!l&(PPe zN7l74YTJ}(pg~0d6tn=^)X-zHpHUY2gnOfzN-OTh!|PEwa;veKbati?@AS+ErRD=1-F;sOL*~;Awod>4QN_T4aS`lK-rIA0R1?k zZ`^FwlDyJ69Z6%3*-VoQgo(~ee21q49sM&=Yg$bAdc5)>MNEZKeTVqwrW1>e+b(qE zrDU#R-d(-z)6VJ?zA&SrfH8quzwnGt9BT74imWmiMsP>gsa2-5OrK{r(8I0N(Cz!k z&-l^pq8`R(6?B6`g%mfCa7IHmaZB4oe!@pNT@&cgbtV>3&#}nXz1N52w3*?SP_iMx1XZfA zdog<{u7*WjZ8AZeNT=|s=Z|uECD^4_4V0tM)8>zHa!avZru!_z{#lwimvH5Mjg1?@ z+Qz?a_KO&!5E4{LBjY4>;8#)LiD$La)FZrSdvFiEOP;nLvu#}8(mXX4jjG1>LN~w; zW8AxsuNAWfsdfi56ZILTaByZ$q2SFgQBU-NaJU^-QZZc!)g~*3SlAy`s^%w@;cR{% z=+Is)`ewCsv!^SvNZTKVI#+EJut6`G9gVU+F}YI)i-nn-x?IYbd$8yG#vZ?st%Ir# z_R-K5W*nMiy{;4f{U7nIhSbpYwT_m7smtBzSA|cNbr3)G#Qy;CRnWetAN={nMuG1h zQ{p`m^6~so72_WYq6zTw@ z_)^d?qJR+68URWt0dqj01r(xyHMBE{CI0|+o1&aaFZ;DoCT4u!>*W6cc+yS&zEAg! zCN;3iDGZw2a!;VA!~(E{+Hm@Y{*iMNoKsT2! z@ei$OX`0gi0K#Kyacp*pr7FrhsRtwa{uMS|OS`s*HK=GBrjIT!EhLX0#=NQgYu5EG zW+@e;We%Oe8O%)X=jT$RBzND6KmBoAEEYuM z1|4u}B)X$WPnS-IoOq|gw!S9Pt~Gmgk}XCczyf~wT>PY0TVr)^bkA=X+RE*{53Xs# zZrU?h!97|$8ZQoNl7Wj|x&HfNy=q%aRAvhV%;25}H6Gff?Vx)KXc&@{JgOz zw_5LI)RGyPLuA3D$SU~GYentfRA$`>_c+M>7I^b-Hss@hjMmNmp?hb#<8BE) z^%Xt5dYR62{=|OrnebJuyiF{xB%uyBBlr9&uAz85rIaMG%HR(y5*0fSsjRuGz1cp~ zODLmC&sc^y{DGF8n_{s40G4P6CnmGBl)ti`*6P+PbSuBjmQ~!C$m1TocB+OQJJ^WD z$etUP@}_C74reNN^&3IqtF3)f)=Muw??=<_h+!Y@!9rWprC-%;!HH@XynVzC~_Znnnsf>SgN?J~Z#Bi`lxY=%nP9${5xGG)Tz5Y8kJa3roQE5?Yn@%I!(kuT z?rubeFPCqWc&&(*8-fKeJ%q@SiVz^l()Ap77r1CzvtB-4^>YiDOa}LG8Kyk%Sw43`zoRn?v#iGT`^4{$h zv&LiYpx}W~i#tmUd9ciX+b1HWsXfHGBm6(f6lCzk7gJ9q%G@(I+-{iv02;XRY4;3E zUrqt-y0ld%6=fqoWTfvU_%oB#GzZhrJgpfskF9k#6WQt3zH};=5fg?iGC{4O zh@aZm(9TpM{vXFfhWj%11pXD)pA*9zUSPM4P=6M|kIuFB@1$8jSAEysawgax?+-vl zZR&m?k5ab+Ad4apLxj&BhZVgSI*#17^*QS$NnN}8579T<+vCbEnLMvw%B$JxbHyGN zt=M(smHu_usZ?E#M_QZibkN_zrw$oE-clQL--f+ zqnv)s=Dc1aypG)w$tvXhqd4NUby)sb!mj}I0+XuH)g*BPOMmbFV!D5_yB<>m*A*Vw zmF%K(CrX>MDXxN3Zx<3z7$dU$$MmXISiO`^aOrnFf}h5=WYpn|A1XI<(=CM`)~nh+ z#QQjyf7#bx^Uw9I2h^?`B0Z6h_E#tKH2K*)kuzkmx`_+C3=nm0!lk*Hqs1Wu83u#!WwK4&(My>`{fIk$ks1Td}ME0Pv1nwoXSL zlzUirAG4ogq%Sp;yO%ij?^;plx2k~&1bfr=knS&MF2l|A`vYuQ6S1k{(KQ%=B0Q<- zk-?|!A}?nnS4Z)bwwR7cnUr=M(=F}wNVtw9j?oEH;%{pX%viY$- zGB+>+u3O1}Zr{DpnqoVb%^vW3r$3{3R(pL`E0a3N$W9avnHa9p9}+wZ`^_5SKV7N~ zX5ik9nkl8SC0ic<0B2pq=OuD;NPK5vpX+VzVn6LiPnN{5 zM5;lSK!4q$!FSlNscAMhDh1=BpHg|OEn`yC?l(LJRzK}hM>nWx=>^2mK_}f} z0|VBvwJlIXb(JS+^{H@oG*xuemNuT&LbnB)KF{50miojp045uUts3?%PA{pxJgrYn z0(KKRf2*B7`k$?4LLn0Q_EY(6dVqL8QA+I5B+V;fs9H#Y^E^kSi^DJ0wltfa@r<3N z*9-+_xWD`gvk)r(02T+t4-w;jJ3s2$M#$m%q&t{{V#-u+Js4@ibP}hT19ae6qoY zX5LrW9OMsb@2)%r;d_C)T_zX?NBX%6f#t?>KBpr&rOua7N^GP5010dtQ$&~7!h13I;BS;4L&iFK8pyf$ zWpJgopv##+kdas9)ec-)A@t3zGeqds}{A$|Okjp*PK!1Hm6@$Rhmw&wJSh|aNuekK6yj!N+ z4=!bh=c=ARLtb*8F4RE<-6bQRodK-gTOYF~XtB$zKNfmycKTiR=C?9ICNeg+AC-Ci z*TfA@?GP}D3Jd~^kL6IZ=`XkV7V+!sr}v$OhPicZc+gDJGz!EP#{_!U0+(9msNd-D z&nE8bhaa7EEEOcRW>Ut~meEH?s6lZqp>aB0MQ;PSD20K*`VY>kS^PTj9;%5XvRlBc zc@s^@{03`vMwP5&@pYF|M@{h(Xz{wsc9W8K0G&atoj2g*C_`!*q?7*u(>PaP{LX0h z6t8|$Ibtfyc^I09k7l`y$F)jh1TX*}!n>%x9r$+11~j3=sJEi=&IUtz)W$z{P#Rm z^U3u^az%7xFhg(k!?Qk5{={*prG^zQfydxxCi&>r9C*QJ8-Ue8=>!I%yF(Vyi`%b0ey`@oab^ z+S!}k&INJLe1ki3)cG3|sExagdiLX^BxC)R22DFCRi%*b;GEYZZ{iI?00EoKUq>0Jai2z7bo(6&cyhs8FB>`acP6P@_^vkM z=G>|N@*IlEs-~(`&4{$C7;sYBAboc8QQGSEH(1#^gZ zU5qlPcha+WC{6o%3XxSCjihrzR|}0$^0p(5nEEKqU$$n8EpI7PjAI<)v{cjFmpcSn zCZ{sxEhO#N0=KNK?yli&x9^`uHL9Z<7|OJfjUI!jMEST_;!(-m!#|a8T zWF!z2bf@fMx(nMk)aOOJMl&nrdte-%Yo&)mmhpGVKI~+C!jq-h744T+W59@y`^0W~ zWc03yd^HFgr@Cf6&S>^+Y%gsCItieJ`GGgc-BgYRbP#Ded@2+T$JCm&2^qSvW^{Mx zsubFwT<5K7$hv)?3h^KvdR1AI%@@#gI+eQs0}P%qRTo#%<6t9?r*O3Hy0Tm!L~Vx1 z2LiFizi6sNL5@9WyhY0{#1D2!rE+IR%tMEi6&<2h)8OTN_khaqKBD8F*}GxT~=@9ZV5p2YVl_76JLB*gE&8U=8Cx zcz$(WM2}yE}2n&^8xiW&)g&dzH6=B z>Rj41RMRz|RJKTBVhxp7C9sim7A*3YLa?tZHX+dPQX=(^sHN3JBEnK z6BXksM^b8|Hupl56m(43eJTd!OD$u)#NkRv@3?!?a)(q3GwxrLNkH3->K3v5(~Pks zdz#749gTS>+|g|~Al#BGA8KSbAYc~YjwySupk8aKURx;_CmAQ!w7k`LH&nPsu+>Q; z253~|G)wr3wEG>&&0zUa(5}o`v}+rJOqP(pvmoZS_8=L^sk>VhWz@yD(QGY_VX=ZS z{>lMbGu>K1r_GfVdzuua!$i!#>_5!nRyiulGec23*J0Q(nq{;VDuC8Nc=cj7p zuDoF*llE(1x#(QeN}||KRQjEimy*N_qcF+rYtC*ptwQ7Ty+Rz1ckQTO+xj zQvepYkOBVFSDVQ;PF_KBOm;&2HQHeKLmPV%xukIQ?p@Pckq`0F0is+uLc@ zc7yj&C_mmKCW{N0BUAw>p?dewa{{Z<5 zKe~z;N!-t*q)}a$=093-`^W1*#8dZ2p`djiT0mZZxM?5m8m@+GavDlSo|_6rB0|oi zf!?_m@ePDS2^^B+)X}LHI=iORn%&amtdZj-dgi#vd{j!Z!DxyI##rRgI}~**oi=~@ z2ieI9&sJ~gS-Iyl6=8EuldU0%nr#3n=8Vt4FWE+4K9d1^aG z*+wdTLB&g^`Z4=O)gHlbsoa)TYmKYwd9N?G*RSoc1iRPK`K8M>)O_%L4|TEDZPGKo zc4Se@o|&&7wefF7xQK!3r;4s=u7vVaeNSD#@eQxp_S&#_&v9ORYp>bdU?hMaLs05Z zv2mpi=Vkr3F$_ec?(7CJSWw(6fKil>YL96nPiF>j}QjJF&pUQVvg6sdAGoMk`{- zVmT(ER$lznS(Tx3CR0&H;~Xt9T^4O5+i-S{yi_)>#$QUMa%E#_3EiCHqd-krn+Nn}V>Hj*$aF|R|pKx(;| z`7T~JhX-zW@0!Mz>&jLc$u#-dN|Z}d>oadE9FBWdFrAr+=~&97ueqY94#vpVZ#NPj zJbG3~pSPi@_OtFkWe&!&Y7?@sc^K!OwU6exJX2Mqw_pf+_|VhX`oicmTaoDkfI@FT=`Yr@`rJzEnpi&v5 zVfV!)hu*40NZ(fbpITu50G5ARWEQtnS)d1&sLA4;z~*)1olTGX0-vo$TKjf-sHl6I oM`JFX<+TMB&r?Hi$LmM{brcSwftMfdH4pb2=~O}>q@+>**-2R>NdN!< literal 0 HcmV?d00001 diff --git a/modules/loader.py b/modules/loader.py index 2574c90b4..cb554c38e 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -35,9 +35,13 @@ warnings.filterwarnings(action="ignore", category=DeprecationWarning) warnings.filterwarnings(action="ignore", category=FutureWarning) warnings.filterwarnings(action="ignore", category=UserWarning, module="torchvision") try: + import torch._logging # pylint: disable=ungrouped-imports + torch._logging._internal.DEFAULT_LOG_LEVEL = logging.ERROR # pylint: disable=protected-access torch._logging.set_logs(all=logging.ERROR, bytecode=False, aot_graphs=False, aot_joint_graph=False, ddp_graphs=False, graph=False, graph_code=False, graph_breaks=False, graph_sizes=False, guards=False, recompiles=False, recompiles_verbose=False, trace_source=False, trace_call=False, trace_bytecode=False, output_code=False, kernel_code=False, schedule=False, perf_hints=False, post_grad_graphs=False, onnx_diagnostics=False, fusion=False, overlap=False, export=None, modules=None, cudagraphs=False, sym_node=False, compiled_autograd_verbose=False) # pylint: disable=protected-access -except Exception: - pass + torch._dynamo.config.verbose = False # pylint: disable=protected-access + torch._dynamo.config.suppress_errors = True # pylint: disable=protected-access +except Exception as e: + errors.log.warning(f'Torch logging: {e}') if ".dev" in torch.__version__ or "+git" in torch.__version__: torch.__long_version__ = torch.__version__ torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) diff --git a/modules/model_cosmos.py b/modules/model_cosmos.py new file mode 100644 index 000000000..c482d033b --- /dev/null +++ b/modules/model_cosmos.py @@ -0,0 +1,108 @@ +import os +import transformers +import diffusers +from huggingface_hub import auth_check +from modules import shared, devices, sd_models, model_quant, modelloader, sd_hijack_te + + +def load_transformer(repo_id, diffusers_load_config={}): + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer', device_map=True) + fn = None + + if shared.opts.sd_unet is not None and shared.opts.sd_unet != 'Default': + from modules import sd_unet + if shared.opts.sd_unet not in list(sd_unet.unet_dict): + shared.log.error(f'Load module: type=Transformer not found: {shared.opts.sd_unet}') + return None + fn = sd_unet.unet_dict[shared.opts.sd_unet] if os.path.exists(sd_unet.unet_dict[shared.opts.sd_unet]) else None + + if fn is not None and 'gguf' in fn.lower(): + shared.log.error('Load model: type=Cosmos format="gguf" unsupported') + transformer = None + elif fn is not None and 'safetensors' in fn.lower(): + shared.log.debug(f'Load model: type=Cosmos transformer="{repo_id}" quant="{model_quant.get_quant(repo_id)}" args={load_args}') + transformer = diffusers.CosmosTransformer3DModel.from_single_file(fn, cache_dir=shared.opts.hfcache_dir, **load_args) + else: + shared.log.debug(f'Load model: type=Cosmos transformer="{repo_id}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') + transformer = diffusers.CosmosTransformer3DModel.from_pretrained( + repo_id, + subfolder="transformer", + cache_dir=shared.opts.hfcache_dir, + **load_args, + **quant_args, + ) + if shared.opts.diffusers_offload_mode != 'none' and transformer is not None: + sd_models.move_model(transformer, devices.cpu) + return transformer + + +def load_text_encoder(repo_id, diffusers_load_config={}): + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='TE', device_map=True) + shared.log.debug(f'Load model: type=Cosmos te="{repo_id}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') + text_encoder = transformers.T5EncoderModel.from_pretrained( + repo_id, + subfolder="text_encoder", + cache_dir=shared.opts.hfcache_dir, + **load_args, + **quant_args, + ) + if shared.opts.diffusers_offload_mode != 'none' and text_encoder is not None: + sd_models.move_model(text_encoder, devices.cpu) + + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='LLM', device_map=True) + llama_repo = shared.opts.model_h1_llama_repo if shared.opts.model_h1_llama_repo != 'Default' else 'meta-llama/Meta-Llama-3.1-8B-Instruct' + shared.log.debug(f'Load model: type=HiDream te4="{llama_repo}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') + + return text_encoder + + +def load_cosmos_t2i(checkpoint_info, diffusers_load_config={}): + repo_id = sd_models.path_to_repo(checkpoint_info.name) + login = modelloader.hf_login() + try: + auth_check(repo_id) + except Exception as e: + shared.log.error(f'Load model: repo="{repo_id}" login={login} {e}') + return False + + transformer = load_transformer(repo_id, diffusers_load_config) + text_encoder = load_text_encoder(repo_id, diffusers_load_config) + safety_checker = Fake_safety_checker() + + load_args, _quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model') + shared.log.debug(f'Load model: type=Cosmos model="{checkpoint_info.name}" repo="{repo_id}" offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype} args={load_args}') + + cls = diffusers.Cosmos2TextToImagePipeline + pipe = cls.from_pretrained( + repo_id, + transformer=transformer, + text_encoder=text_encoder, + safety_checker=safety_checker, + cache_dir=shared.opts.diffusers_dir, + **load_args, + ) + + sd_hijack_te.init_hijack(pipe) + del text_encoder + del transformer + + devices.torch_gc() + return pipe + + +class Fake_safety_checker: + def __init__(self): + from diffusers.utils import import_utils + import_utils._cosmos_guardrail_available = True # pylint: disable=protected-access + + def __call__(self, *args, **kwargs): # pylint: disable=unused-argument + return + + def to(self, _device): + pass + + def check_text_safety(self, _prompt): + return True + + def check_video_safety(self, vid): + return vid diff --git a/modules/modeldata.py b/modules/modeldata.py index 688cff23d..1de6a74a4 100644 --- a/modules/modeldata.py +++ b/modules/modeldata.py @@ -43,6 +43,8 @@ def get_model_type(pipe): model_type = 'sana' elif "HiDream" in name: model_type = 'h1' + elif "Cosmos2TextToImage" in name: + model_type = 'cosmos' # video models elif "CogVideo" in name: model_type = 'cogvideo' diff --git a/modules/processing_args.py b/modules/processing_args.py index d9ad9869f..20e8becec 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -298,6 +298,8 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t args['control_strength'] = p.denoising_strength args['width'] = p.width args['height'] = p.height + if 'Cosmos2TextToImagePipeline': + kwargs['output_type'] = 'np' # cosmos uses wan-vae which is weird # set callbacks if 'prior_callback_steps' in possible: # Wuerstchen / Cascade args['prior_callback_steps'] = 1 diff --git a/modules/processing_vae.py b/modules/processing_vae.py index 36d5d0dda..6feaec0cd 100644 --- a/modules/processing_vae.py +++ b/modules/processing_vae.py @@ -130,7 +130,7 @@ def full_vae_decode(latents, model): # normalize latents latents_mean = model.vae.config.get("latents_mean", None) latents_std = model.vae.config.get("latents_std", None) - scaling_factor = model.vae.config.get("scaling_factor", None) + scaling_factor = model.vae.config.get("scaling_factor", 1.0) shift_factor = model.vae.config.get("shift_factor", None) if latents_mean and latents_std: latents_mean = (torch.tensor(latents_mean).view(1, -1, 1, 1).to(latents.device, latents.dtype)) diff --git a/modules/sd_detect.py b/modules/sd_detect.py index 3c2c1be72..fb8c9d6b5 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -98,6 +98,8 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): warn(f'Model detected as FLUX UNET model, but attempting to load a base model: {op}={f} size={size} MB') if 'flex.2' in f.lower(): guess = 'FLEX' + if 'cosmos-predict2' in f.lower(): + guess = 'Cosmos' # guess for diffusers index = os.path.join(f, 'model_index.json') if os.path.exists(index) and os.path.isfile(index): @@ -113,6 +115,7 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): guess = 'Stable Diffusion 3' if callable(pipeline) and 'Lumina2' in pipeline.__name__: guess = 'Lumina 2' + # switch for specific variant if guess == 'Stable Diffusion' and 'inpaint' in f.lower(): guess = 'Stable Diffusion Inpaint' @@ -122,6 +125,7 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): guess = 'Stable Diffusion XL Inpaint' elif guess == 'Stable Diffusion XL' and 'instruct' in f.lower(): guess = 'Stable Diffusion XL Instruct' + # get actual pipeline pipeline = shared_items.get_pipelines().get(guess, None) if pipeline is None else pipeline if debug_load is not None: diff --git a/modules/sd_models.py b/modules/sd_models.py index 0530f5a5b..1d85ed47b 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -350,6 +350,9 @@ def load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op=' elif model_type in ['HiDream']: from modules.model_hidream import load_hidream sd_model = load_hidream(checkpoint_info, diffusers_load_config) + elif model_type in ['Cosmos']: + from modules.model_cosmos import load_cosmos_t2i + sd_model = load_cosmos_t2i(checkpoint_info, diffusers_load_config) except Exception as e: shared.log.error(f'Load {op}: path="{checkpoint_info.path}" {e}') if debug_load: diff --git a/modules/sd_offload.py b/modules/sd_offload.py index 70777a30a..0db3a2ba0 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -12,7 +12,7 @@ from modules.timer import process as process_timer debug = os.environ.get('SD_MOVE_DEBUG', None) is not None debug_move = log.trace if debug else lambda *args, **kwargs: None -offload_warn = ['sc', 'sd3', 'f1', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'cogview4'] +offload_warn = ['sc', 'sd3', 'f1', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'cogview4', 'cosmos'] offload_post = ['h1'] offload_hook_instance = None balanced_offload_exclude = ['CogView4Pipeline'] diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 52fd6313e..7f3e802ec 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -9,7 +9,7 @@ from modules import shared, devices, processing, images, sd_vae_approx, sd_vae_t SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options']) approximation_indexes = { "Simple": 0, "Approximate": 1, "TAESD": 2, "Full VAE": 3 } -flow_models = ['f1', 'sd3', 'lumina', 'auraflow', 'sana', 'lumina2', 'cogview4', 'h1'] +flow_models = ['f1', 'sd3', 'lumina', 'auraflow', 'sana', 'lumina2', 'cogview4', 'h1', 'cosmos'] warned = False queue_lock = threading.Lock() diff --git a/modules/shared_items.py b/modules/shared_items.py index baf247a35..967f17b30 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -42,6 +42,7 @@ pipelines = { 'Amused': getattr(diffusers, 'AmusedPipeline', None), 'HiDream': getattr(diffusers, 'HiDreamImagePipeline', None), 'OmniGenPipeline': getattr(diffusers, 'OmniGenPipeline', None), + 'Cosmos': getattr(diffusers, 'Cosmos2TextToImagePipeline', None), # dynamically imported and redefined later 'Meissonic': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser From 0e4d712f275063795c8ee789186943fd76d7e192 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 26 Jun 2025 21:25:50 +0300 Subject: [PATCH 51/85] Chroma fixes --- modules/intel/ipex/diffusers.py | 9 +++++---- modules/model_chroma.py | 8 ++++---- modules/model_chroma_nf4.py | 2 +- modules/model_quant.py | 2 +- modules/processing_vae.py | 4 ++-- modules/sd_detect.py | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/intel/ipex/diffusers.py b/modules/intel/ipex/diffusers.py index d3487fefd..1f0391295 100644 --- a/modules/intel/ipex/diffusers.py +++ b/modules/intel/ipex/diffusers.py @@ -26,7 +26,7 @@ class FluxPosEmbed(torch.nn.Module): n_axes = ids.shape[-1] cos_out = [] sin_out = [] - pos = ids.float() + pos = ids.to(dtype=torch.float32) for i in range(n_axes): cos, sin = diffusers.models.embeddings.get_1d_rotary_pos_embed( self.axes_dim[i], @@ -99,12 +99,12 @@ def apply_rotary_emb(x, freqs_cis, use_real: bool = True, use_real_unbind_dim: i else: raise ValueError(f"`use_real_unbind_dim={use_real_unbind_dim}` but should be -1 or -2.") - out = (x.float() * cos + x_rotated.float() * sin).to(x.dtype) + out = (x.to(dtype=torch.float32) * cos + x_rotated.to(dtype=torch.float32) * sin).to(x.dtype) return out else: # used for lumina # force cpu with Alchemist - x_rotated = torch.view_as_complex(x.to("cpu").float().reshape(*x.shape[:-1], -1, 2)) + x_rotated = torch.view_as_complex(x.to("cpu").to(dtype=torch.float32).reshape(*x.shape[:-1], -1, 2)) freqs_cis = freqs_cis.to("cpu").unsqueeze(2) x_out = torch.view_as_real(x_rotated * freqs_cis).flatten(3) return x_out.type_as(x).to(x.device) @@ -122,5 +122,6 @@ def ipex_diffusers(device_supports_fp64=False): diffusers.models.embeddings.apply_rotary_emb = apply_rotary_emb diffusers.models.transformers.transformer_flux.FluxPosEmbed = FluxPosEmbed diffusers.models.transformers.transformer_lumina2.apply_rotary_emb = apply_rotary_emb - diffusers.models.controlnets.controlnet_flux.FluxPosEmbed = FluxPosEmbed diffusers.models.transformers.transformer_hidream_image.rope = hidream_rope + diffusers.models.transformers.transformer_chroma.FluxPosEmbed = FluxPosEmbed + diffusers.models.controlnets.controlnet_flux.FluxPosEmbed = FluxPosEmbed diff --git a/modules/model_chroma.py b/modules/model_chroma.py index b722837fc..f694917f4 100644 --- a/modules/model_chroma.py +++ b/modules/model_chroma.py @@ -115,10 +115,10 @@ def load_quants(kwargs, pretrained_model_name_or_path, cache_dir, allow_quant): if 'transformer' not in kwargs and model_quant.check_nunchaku('Transformer'): raise NotImplementedError('Nunchaku does not support Chroma Model yet. See https://github.com/mit-han-lab/nunchaku/issues/167') elif 'transformer' not in kwargs and model_quant.check_quant('Transformer'): - quant_args = model_quant.create_config(allow=allow_quant, module='Transformer') + quant_args = model_quant.create_config(allow=allow_quant, module='Transformer', modules_to_not_convert=["distilled_guidance_layer"]) if quant_args: if os.path.isfile(pretrained_model_name_or_path): - kwargs['transformer'] = diffusers.ChromaTransformer2DModel.from_single_file(pretrained_model_name_or_path, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) + kwargs['transformer'] = diffusers.ChromaTransformer2DModel.from_single_file(pretrained_model_name_or_path, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) pass else: kwargs['transformer'] = diffusers.ChromaTransformer2DModel.from_pretrained(pretrained_model_name_or_path, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) @@ -179,7 +179,7 @@ def load_transformer(file_path): # triggered by opts.sd_unet change transformer, _text_encoder = load_chroma_nf4(file_path, prequantized=False) if transformer is not None: return transformer - quant_args = model_quant.create_config(module='Transformer') + quant_args = model_quant.create_config(module='Transformer', modules_to_not_convert=["distilled_guidance_layer"]) if quant_args: shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=torchao dtype={devices.dtype}') transformer = diffusers.ChromaTransformer2DModel.from_single_file(file_path, **diffusers_load_config, **quant_args) @@ -318,7 +318,7 @@ def load_chroma(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ allow_quant = 'gguf' not in (sd_unet.loaded_unet or '') and (prequantized is None or prequantized == 'none') if (fn is None) or (not os.path.exists(fn) or os.path.isdir(fn)): kwargs = load_quants(kwargs, repo_id or fn, cache_dir=shared.opts.diffusers_dir, allow_quant=allow_quant) - # kwargs = model_quant.create_config(kwargs, allow_quant) + # kwargs = model_quant.create_config(kwargs, allow_quant, modules_to_not_convert=["distilled_guidance_layer"]) if fn.endswith('.safetensors') and os.path.isfile(fn): pipe = diffusers.ChromaPipeline.from_single_file(fn, cache_dir=shared.opts.diffusers_dir, **kwargs, **diffusers_load_config) else: diff --git a/modules/model_chroma_nf4.py b/modules/model_chroma_nf4.py index c72d624ab..fb02ebe05 100644 --- a/modules/model_chroma_nf4.py +++ b/modules/model_chroma_nf4.py @@ -1,5 +1,5 @@ """ -Copied from: https://github.com/huggingface/diffusers/issues/9165 +Copied from: https://github.com/huggingface/diffusers/issues/9165 + adjusted for Chroma by skipping distilled guidance layer """ diff --git a/modules/model_quant.py b/modules/model_quant.py index 8b4dcb8ad..55cdd8c3c 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -433,7 +433,7 @@ def optimum_quanto_model(model, op=None, sd_model=None, weights=None, activation from modules import devices, shared quanto = load_quanto('Quantize model: type=Optimum Quanto') global quant_last_model_name, quant_last_model_device # pylint: disable=global-statement - if sd_model is not None and "Flux" in sd_model.__class__.__name__ or "Chroma" in sd_model.__class__.__name__: # LayerNorm is not supported + if sd_model is not None and ("Flux" in sd_model.__class__.__name__ or "Chroma" in sd_model.__class__.__name__): # LayerNorm is not supported exclude_list = ["transformer_blocks.*.norm1.norm", "transformer_blocks.*.norm2", "transformer_blocks.*.norm1_context.norm", "transformer_blocks.*.norm2_context", "single_transformer_blocks.*.norm.norm", "norm_out.norm"] if "Chroma" in sd_model.__class__.__name__: # we ignore the distilled guidance layer because it degrades quality too much diff --git a/modules/processing_vae.py b/modules/processing_vae.py index 6feaec0cd..b1af54d69 100644 --- a/modules/processing_vae.py +++ b/modules/processing_vae.py @@ -298,9 +298,9 @@ def vae_decode(latents, model, output_type='np', vae_type='Full', width=None, he latent_num_frames = (frames - 1) // model.vae_temporal_compression_ratio + 1 latents = model._unpack_latents(latents.unsqueeze(0), latent_num_frames, height // 32, width // 32, model.transformer_spatial_patch_size, model.transformer_temporal_patch_size) # pylint: disable=protected-access latents = model._denormalize_latents(latents, model.vae.latents_mean, model.vae.latents_std, model.vae.config.scaling_factor) # pylint: disable=protected-access - if hasattr(model, '_unpack_latents') and hasattr(model, "vae_scale_factor") and width is not None and height is not None: # FLUX + if hasattr(model, '_unpack_latents') and hasattr(model, "vae_scale_factor") and width is not None and height is not None and latents.ndim == 3: # FLUX latents = model._unpack_latents(latents, height, width, model.vae_scale_factor) # pylint: disable=protected-access - if len(latents.shape) == 3: # lost a batch dim in hires + if latents.ndim == 3: # lost a batch dim in hires latents = latents.unsqueeze(0) if latents.shape[-1] <= 4: # not a latent, likely an image decoded = latents.float().cpu().numpy() diff --git a/modules/sd_detect.py b/modules/sd_detect.py index 414348b16..8f10bb31d 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -94,7 +94,7 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): guess = 'HiDream' if 'chroma' in f.lower(): guess = 'Chroma' - if 'flux' in f.lower() or 'flex.1' in f.lower() or 'lodestones' in f.lower(): + if 'flux' in f.lower() or 'flex.1' in f.lower(): guess = 'FLUX' if size > 11000 and size < 16000: warn(f'Model detected as FLUX UNET model, but attempting to load a base model: {op}={f} size={size} MB') From 46387d0c011fe83de66448a6935e94b4d8b21ec1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 14:33:31 -0400 Subject: [PATCH 52/85] cleanup chroma Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 7 +- html/reference.json | 8 + installer.py | 2 +- models/Reference/lodestones--Chroma.jpg | Bin 43017 -> 55512 bytes modules/model_chroma.py | 46 +----- modules/model_chroma_nf4.py | 202 ------------------------ 6 files changed, 18 insertions(+), 247 deletions(-) delete mode 100644 modules/model_chroma_nf4.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b4cc2a0b..1915b626e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,14 @@ ## Update for 2025-06-26 - **Models** - - [nVidia Cosmos-Predict2 T2I](https://research.nvidia.com/labs/dir/cosmos-predict2/) *2B and 14B* + - [nVidia Cosmos-Predict2 T2I](https://research.nvidia.com/labs/dir/cosmos-predict2/) *2B and 14B* + - new foundational model from Nvidia in two variants: small 2B and large 14B - available via *networks -> models -> reference* - *note*: this is a gated model, you need to [accept terms](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Text2Image) and set your [huggingface token](https://vladmandic.github.io/sdnext-docs/Gated/) + - [Chroma](https://huggingface.co/lodestones/Chroma) + - Chroma is a 8.9B parameter model based on *FLUX.1-schnell* and fully Apache 2.0 licensed + - available via *networks -> models -> reference* + - *note*: model is still in training so future updates will trigger re-download - [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) - available via *caption -> vlm caption* diff --git a/html/reference.json b/html/reference.json index 07d0c97dc..22fa4daba 100644 --- a/html/reference.json +++ b/html/reference.json @@ -180,6 +180,14 @@ "extras": "sampler: Default, cfg_scale: 3.5" }, + "lodestones Chroma": { + "path": "lodestones/Chroma", + "preview": "lodestones--Chroma.jpg", + "desc": "Chroma is a 8.9B parameter model based on FLUX.1-schnell. It’s fully Apache 2.0 licensed, ensuring that anyone can use, modify, and build on top of it—no corporate gatekeeping. The model is still training right now, and I’d love to hear your thoughts! Your input and feedback are really appreciated.", + "skip": true, + "extras": "sampler: Default, cfg_scale: 3.5" + }, + "Ostris Flex.2 Preview": { "path": "ostris/Flex.2-preview", "preview": "ostris--Flex.2-preview.jpg", diff --git a/installer.py b/installer.py index 8e06680fc..9d8016343 100644 --- a/installer.py +++ b/installer.py @@ -546,7 +546,7 @@ def check_diffusers(): t_start = time.time() if args.skip_all or args.skip_git or args.experimental: return - sha = '7fc53b5d66867f9e59398f5a14dd3dd9fbc700dd' # diffusers commit hash + sha = '00f95b9755718aabb65456e791b8408526ae6e76' # diffusers commit hash pkg = pkg_resources.working_set.by_key.get('diffusers', None) minor = int(pkg.version.split('.')[1] if pkg is not None else 0) cur = opts.get('diffusers_version', '') if minor > 0 else '' diff --git a/models/Reference/lodestones--Chroma.jpg b/models/Reference/lodestones--Chroma.jpg index 6c0c5d2f470fd71b6e8554b16ea6e1700768400a..78e6e33f7fc34fb8d8f83c914911c8856682fd1b 100644 GIT binary patch delta 55224 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 B7%Kn( delta 42630 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 Kc|8RnKmXaJp5e*> diff --git a/modules/model_chroma.py b/modules/model_chroma.py index f694917f4..acd60b02a 100644 --- a/modules/model_chroma.py +++ b/modules/model_chroma.py @@ -119,7 +119,6 @@ def load_quants(kwargs, pretrained_model_name_or_path, cache_dir, allow_quant): if quant_args: if os.path.isfile(pretrained_model_name_or_path): kwargs['transformer'] = diffusers.ChromaTransformer2DModel.from_single_file(pretrained_model_name_or_path, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) - pass else: kwargs['transformer'] = diffusers.ChromaTransformer2DModel.from_pretrained(pretrained_model_name_or_path, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) if 'text_encoder' not in kwargs and model_quant.check_nunchaku('TE'): @@ -166,19 +165,7 @@ def load_transformer(file_path): # triggered by opts.sd_unet change _transformer, _text_encoder = load_chroma_bnb(file_path, diffusers_load_config) if _transformer is not None: transformer = _transformer - elif 'nf4' in quant: # TODO chroma: loader for civitai nf4 models - from modules.model_chroma_nf4 import load_chroma_nf4 - _transformer, _text_encoder = load_chroma_nf4(file_path, prequantized=True) - if _transformer is not None: - transformer = _transformer else: - quant_args = model_quant.create_bnb_config({}) - if quant_args: - shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=bnb dtype={devices.dtype}') - from modules.model_chroma_nf4 import load_chroma_nf4 - transformer, _text_encoder = load_chroma_nf4(file_path, prequantized=False) - if transformer is not None: - return transformer quant_args = model_quant.create_config(module='Transformer', modules_to_not_convert=["distilled_guidance_layer"]) if quant_args: shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=torchao dtype={devices.dtype}') @@ -188,7 +175,7 @@ def load_transformer(file_path): # triggered by opts.sd_unet change shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=none dtype={devices.dtype}') # TODO chroma transformer from-single-file with quant # shared.log.warning('Load module: type=UNet/Transformer does not support load-time quantization') - transformer = diffusers.ChromaTransformer2DModel.from_single_file(file_path, **diffusers_load_config) + # transformer = diffusers.ChromaTransformer2DModel.from_single_file(file_path, **diffusers_load_config) if transformer is None: shared.log.error('Failed to load UNet model') shared.opts.sd_unet = 'Default' @@ -263,31 +250,6 @@ def load_chroma(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ if debug: errors.display(e, 'Chroma VAE:') - # load quantized components if any - if prequantized == 'nf4': - try: - from modules.model_chroma_nf4 import load_chroma_nf4 - _transformer, _text_encoder = load_chroma_nf4(checkpoint_info) - if _transformer is not None: - transformer = _transformer - if _text_encoder is not None: - text_encoder = _text_encoder - except Exception as e: - shared.log.error(f"Load model: type=Chroma failed to load NF4 components: {e}") - if debug: - errors.display(e, 'Chroma NF4:') - if prequantized == 'qint8' or prequantized == 'qint4': - try: - _transformer, _text_encoder = load_chroma_quanto(checkpoint_info) - if _transformer is not None: - transformer = _transformer - if _text_encoder is not None: - text_encoder = _text_encoder - except Exception as e: - shared.log.error(f"Load model: type=Chroma failed to load Quanto components: {e}") - if debug: - errors.display(e, 'Chroma Quanto:') - # initialize pipeline with pre-loaded components kwargs = {} if transformer is not None: @@ -299,10 +261,8 @@ def load_chroma(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ if vae is not None: kwargs['vae'] = vae - # Todo: atm only ChromaPipeline is implemented in diffusers. - # Need to add ChromaFillPipeline, ChromaControlPipeline, ChromaImg2ImgPipeline etc when available. - # Chroma will support inpainting *after* its training has finished: - # https://huggingface.co/lodestones/Chroma/discussions/28#6826dd2ed86f53ff983add5c + # TODO add ChromaFillPipeline, ChromaControlPipeline, ChromaImg2ImgPipeline etc when available + # TODO Chroma will support inpainting *after* its training has finished: https://huggingface.co/lodestones/Chroma/discussions/28#6826dd2ed86f53ff983add5c cls = diffusers.ChromaPipeline shared.log.debug(f'Load model: type=Chroma cls={cls.__name__} preloaded={list(kwargs)} revision={diffusers_load_config.get("revision", None)}') for c in kwargs: diff --git a/modules/model_chroma_nf4.py b/modules/model_chroma_nf4.py deleted file mode 100644 index fb02ebe05..000000000 --- a/modules/model_chroma_nf4.py +++ /dev/null @@ -1,202 +0,0 @@ -""" -Copied from: https://github.com/huggingface/diffusers/issues/9165 -+ adjusted for Chroma by skipping distilled guidance layer -""" - -import os -import torch -import torch.nn as nn -from transformers.quantizers.quantizers_utils import get_module_from_name -from huggingface_hub import hf_hub_download -from accelerate import init_empty_weights -from accelerate.utils import set_module_tensor_to_device -from diffusers.loaders.single_file_utils import convert_chroma_transformer_checkpoint_to_diffusers -import safetensors.torch -from modules import shared, devices, model_quant - - -debug = os.environ.get('SD_LOAD_DEBUG', None) is not None - - -def _replace_with_bnb_linear( - model, - method="nf4", - has_been_replaced=False, -): - """ - Private method that wraps the recursion for module replacement. - Returns the converted model and a boolean that indicates if the conversion has been successfull or not. - """ - bnb = model_quant.load_bnb('Load model: type=Chroma') - for name, module in model.named_children(): - # we ignore the distilled guidance layer because it degrades quality too much - # see: https://github.com/huggingface/diffusers/pull/11698#issuecomment-2969717180 for more details - if "distilled_guidance_layer" in name: - continue - if isinstance(module, nn.Linear): - with init_empty_weights(): - in_features = module.in_features - out_features = module.out_features - - if method == "llm_int8": - model._modules[name] = bnb.nn.Linear8bitLt( # pylint: disable=protected-access - in_features, - out_features, - module.bias is not None, - has_fp16_weights=False, - threshold=6.0, - ) - has_been_replaced = True - else: - model._modules[name] = bnb.nn.Linear4bit( # pylint: disable=protected-access - in_features, - out_features, - module.bias is not None, - compute_dtype=devices.dtype, - compress_statistics=False, - quant_type="nf4", - ) - has_been_replaced = True - # Store the module class in case we need to transpose the weight later - model._modules[name].source_cls = type(module) # pylint: disable=protected-access - # Force requires grad to False to avoid unexpected errors - model._modules[name].requires_grad_(False) # pylint: disable=protected-access - - if len(list(module.children())) > 0: - _, has_been_replaced = _replace_with_bnb_linear( - module, - has_been_replaced=has_been_replaced, - ) - # Remove the last key for recursion - return model, has_been_replaced - - -def check_quantized_param( - model, - param_name: str, -) -> bool: - bnb = model_quant.load_bnb('Load model: type=Chroma') - module, tensor_name = get_module_from_name(model, param_name) - if isinstance(module._parameters.get(tensor_name, None), bnb.nn.Params4bit): # pylint: disable=protected-access - # Add here check for loaded components' dtypes once serialization is implemented - return True - elif isinstance(module, bnb.nn.Linear4bit) and tensor_name == "bias": - # bias could be loaded by regular set_module_tensor_to_device() from accelerate, - # but it would wrongly use uninitialized weight there. - return True - else: - return False - - -def create_quantized_param( - model, - param_value: "torch.Tensor", - param_name: str, - target_device: "torch.device", - state_dict=None, - unexpected_keys=None, - pre_quantized=False -): - bnb = model_quant.load_bnb('Load model: type=Chroma') - module, tensor_name = get_module_from_name(model, param_name) - - if tensor_name not in module._parameters: # pylint: disable=protected-access - raise ValueError(f"{module} does not have a parameter or a buffer named {tensor_name}.") - - old_value = getattr(module, tensor_name) - - if tensor_name == "bias": - if param_value is None: - new_value = old_value.to(target_device) - else: - new_value = param_value.to(target_device) - new_value = torch.nn.Parameter(new_value, requires_grad=old_value.requires_grad) - module._parameters[tensor_name] = new_value # pylint: disable=protected-access - return - - if not isinstance(module._parameters[tensor_name], bnb.nn.Params4bit): # pylint: disable=protected-access - raise ValueError("this function only loads `Linear4bit components`") - if ( - old_value.device == torch.device("meta") - and target_device not in ["meta", torch.device("meta")] - and param_value is None - ): - raise ValueError(f"{tensor_name} is on the meta device, we need a `value` to put in on {target_device}.") - - if pre_quantized: - if (param_name + ".quant_state.bitsandbytes__fp4" not in state_dict) and (param_name + ".quant_state.bitsandbytes__nf4" not in state_dict): - raise ValueError(f"Supplied state dict for {param_name} does not contain `bitsandbytes__*` and possibly other `quantized_stats` components.") - quantized_stats = {} - for k, v in state_dict.items(): - # `startswith` to counter for edge cases where `param_name` - # substring can be present in multiple places in the `state_dict` - if param_name + "." in k and k.startswith(param_name): - quantized_stats[k] = v - if unexpected_keys is not None and k in unexpected_keys: - unexpected_keys.remove(k) - new_value = bnb.nn.Params4bit.from_prequantized( - data=param_value, - quantized_stats=quantized_stats, - requires_grad=False, - device=target_device, - ) - else: - new_value = param_value.to("cpu") - kwargs = old_value.__dict__ - new_value = bnb.nn.Params4bit(new_value, requires_grad=False, **kwargs).to(target_device) - module._parameters[tensor_name] = new_value # pylint: disable=protected-access - - -def load_chroma_nf4(checkpoint_info, prequantized: bool = True): - transformer = None - text_encoder = None - if isinstance(checkpoint_info, str): - repo_path = checkpoint_info - else: - repo_path = checkpoint_info.path - if os.path.exists(repo_path) and os.path.isfile(repo_path): - ckpt_path = repo_path - elif os.path.exists(repo_path) and os.path.isdir(repo_path) and os.path.exists(os.path.join(repo_path, "diffusion_pytorch_model.safetensors")): - ckpt_path = os.path.join(repo_path, "diffusion_pytorch_model.safetensors") - else: - ckpt_path = hf_hub_download(repo_path, filename="diffusion_pytorch_model.safetensors", cache_dir=shared.opts.diffusers_dir) - original_state_dict = safetensors.torch.load_file(ckpt_path) - - try: - converted_state_dict = convert_chroma_transformer_checkpoint_to_diffusers(original_state_dict) - except Exception as e: - shared.log.error(f"Load model: type=Chroma Failed to convert UNET: {e}") - if debug: - from modules import errors - errors.display(e, 'Chroma convert:') - converted_state_dict = original_state_dict - - with init_empty_weights(): - from diffusers import ChromaTransformer2DModel - config = ChromaTransformer2DModel.load_config(os.path.join('configs', 'chroma'), subfolder="transformer") - transformer = ChromaTransformer2DModel.from_config(config).to(devices.dtype) - expected_state_dict_keys = list(transformer.state_dict().keys()) - - _replace_with_bnb_linear(transformer, "nf4") - - try: - for param_name, param in converted_state_dict.items(): - if param_name not in expected_state_dict_keys: - continue - is_param_float8_e4m3fn = hasattr(torch, "float8_e4m3fn") and param.dtype == torch.float8_e4m3fn - if torch.is_floating_point(param) and not is_param_float8_e4m3fn: - param = param.to(devices.dtype) - if not check_quantized_param(transformer, param_name): - set_module_tensor_to_device(transformer, param_name, device=0, value=param) - else: - create_quantized_param(transformer, param, param_name, target_device=0, state_dict=original_state_dict, pre_quantized=prequantized) - except Exception as e: - transformer, text_encoder = None - shared.log.error(f"Load model: type=Chroma failed to load UNET: {e}") - if debug: - from modules import errors - errors.display(e, 'Chroma:') - - del original_state_dict - devices.torch_gc(force=True) - return transformer, text_encoder From d6a8d2b1736fe94d1d0bdfdc7a2e4a536d4b2c03 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 15:00:27 -0400 Subject: [PATCH 53/85] cleanup Signed-off-by: Vladimir Mandic --- modules/processing_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing_args.py b/modules/processing_args.py index 42a8f6af0..bb09cc0a4 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -301,7 +301,7 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t args['control_strength'] = p.denoising_strength args['width'] = p.width args['height'] = p.height - if 'Cosmos2TextToImagePipeline': + if 'Cosmos2' in model.__class__.__name__: kwargs['output_type'] = 'np' # cosmos uses wan-vae which is weird # set callbacks if 'prior_callback_steps' in possible: # Wuerstchen / Cascade From 1147f9ec5609f3e303bad0fca56c42fc9f1b90dc Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 26 Jun 2025 17:09:53 -0400 Subject: [PATCH 54/85] add flux.1-kontext-dev Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 11 +++++++-- html/reference.json | 22 ++++-------------- .../black-forest-labs--FLUX.1-Kontext-dev.jpg | Bin 0 -> 49447 bytes modules/model_flux.py | 7 ++++++ modules/processing_args.py | 18 ++++++++------ modules/processing_helpers.py | 4 +--- modules/ui_models.py | 9 ++++++- 7 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 models/Reference/black-forest-labs--FLUX.1-Kontext-dev.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 1915b626e..794c0c9f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,18 @@ ## Update for 2025-06-26 - **Models** + - [Models Wiki page](https://vladmandic.github.io/sdnext-docs/Models/) is updated will all new models - [nVidia Cosmos-Predict2 T2I](https://research.nvidia.com/labs/dir/cosmos-predict2/) *2B and 14B* - - new foundational model from Nvidia in two variants: small 2B and large 14B + - Cosmos-Predict2 T2I is a new foundational model from Nvidia in two variants: small 2B and large 14B - available via *networks -> models -> reference* + - *note*: 14B variant is a very large model at 36GB - *note*: this is a gated model, you need to [accept terms](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Text2Image) and set your [huggingface token](https://vladmandic.github.io/sdnext-docs/Gated/) - - [Chroma](https://huggingface.co/lodestones/Chroma) + - [Black Forest Labs FLUX.1 Kontext I2I](https://bfl.ai/announcements/flux-1-kontext-dev) *Dev* variant + - FLUX.1-Kontext is a 12B model billion parameter capable of editing images based on text instructions + - requirements are similar to regular FLUX.1 although 2x slower + - available via *networks -> models -> reference* + - *note*: this is a gated model, you need to [accept terms](https://huggingface.co/black-forest-labs/FLUX.1-Kontext-dev) and set your [huggingface token](https://vladmandic.github.io/sdnext-docs/Gated/) + - [lodestones Chroma](https://huggingface.co/lodestones/Chroma) - Chroma is a 8.9B parameter model based on *FLUX.1-schnell* and fully Apache 2.0 licensed - available via *networks -> models -> reference* - *note*: model is still in training so future updates will trigger re-download diff --git a/html/reference.json b/html/reference.json index 22fa4daba..599ad1c5b 100644 --- a/html/reference.json +++ b/html/reference.json @@ -158,24 +158,10 @@ "skip": true, "extras": "sampler: Default, cfg_scale: 3.5" }, - "Black Forest Labs FLUX.1 Dev qint8": { - "path": "Disty0/FLUX.1-dev-qint8", - "preview": "black-forest-labs--FLUX.1-dev.jpg", - "desc": "FLUX.1 models are based on a hybrid architecture of multimodal and parallel diffusion transformer blocks, scaled to 12B parameters and builing on flow matching", - "skip": true, - "extras": "sampler: Default, cfg_scale: 3.5" - }, - "Black Forest Labs FLUX.1 Dev qint4": { - "path": "Disty0/FLUX.1-dev-qint4", - "preview": "black-forest-labs--FLUX.1-dev.jpg", - "desc": "FLUX.1 models are based on a hybrid architecture of multimodal and parallel diffusion transformer blocks, scaled to 12B parameters and builing on flow matching", - "skip": true, - "extras": "sampler: Default, cfg_scale: 3.5" - }, - "Black Forest Labs FLUX.1 Dev nf4": { - "path": "sayakpaul/flux.1-dev-nf4", - "preview": "black-forest-labs--FLUX.1-dev.jpg", - "desc": "FLUX.1 models are based on a hybrid architecture of multimodal and parallel diffusion transformer blocks, scaled to 12B parameters and builing on flow matching", + "Black Forest Labs FLUX.1 Kontext Dev": { + "path": "black-forest-labs/FLUX.1-Kontext-dev", + "preview": "black-forest-labs--FLUX.1-Kontext-dev.jpg", + "desc": "FLUX.1 Kontext [dev] is a 12 billion parameter rectified flow transformer capable of editing images based on text instructions.", "skip": true, "extras": "sampler: Default, cfg_scale: 3.5" }, diff --git a/models/Reference/black-forest-labs--FLUX.1-Kontext-dev.jpg b/models/Reference/black-forest-labs--FLUX.1-Kontext-dev.jpg new file mode 100644 index 0000000000000000000000000000000000000000..04521fdd2af71149bf7bc02fb9293bc99360109f GIT binary patch literal 49447 zcmbTdbzB@x*ETq~LvWV_cXx;2?(RA`3@*U}3GNcyHMk6J!QExh;10nd$a3HB^L*dR zKf7D=tLmQWQ`L100j{d84&>m83`E;6$K5G1Pc=b1M?Fh5e^AG6(a*36)i0@ zhZrw2n=l(KEuSL4u%wi%oGc@cik7mprkISZ)IXg-p`xK-Vqkv6!ulx1Ldzoc|2e(& z05B1u-^ULFg$01dgo43@dg}*}zmFd7eO~^Vn*SsyXc$;HcmzZwWR!P-rVjvUC>R)M zSQt1sSlD;zfcN77SWGyqkL=>`*lK17pImV`f)nx)DJAN9an)xosW{EuLXeR0@CgWs zsA*{F=oz@Ud3gEw1tdR9Nz2H}$!ln8Y3u0f>04M@S=-nG?Lh7xo?hNQzM)^k!oPiw zh)hgMPDxEm|B+EpSX5k6T2@|B-_Y393~p&{>-*I|FgP?kGCDgqzp%KpyaM^Xy|cTw ze{gto{O9WW=JxLX;qmDoTu|>g|C{~?*#8X|<~uHESXdZXgnw{BL3_Pt7))5WkL>VR z;%W$HuGpVAf)Q~f67uVMktjLUFLBM?W{~lyxPDXr`3Ks+$o}5}3;F+r?0*6K-?$(E zR2ZoDiwA=V5Ct?slVYuVz8p!r9>QtP?PIMD*rChC2cOD8w_2UAmHl003dj+(^{@p1 zTBTIXT>=LaJrYfDCu~D0$6;TQ8B9P@)nR^})n7&_Ap{k4l%#+W(SuGqAWt{McVJ?^ z@Sd$5;P(N8Cgm)TdLf(q+bMJEjNzw-SR~4f(Oi8Vydy_zkoybZR_IkybTod&q@NdS z^SG*bh3;x6V0IWmGBzZNCU((;+OV;IUq|B_%P%k@4~vJZiilWua94e&hLlla6gQT6 z*xSjLnEI-ayr(<9l=~yeeC->+MsRhcS+;wze?W%uS>eIvQ7cA8inHbikg@2y0+U~t zdI0-{CMCw{-J(FWl9OhyC_~=M;JL`RxX~(cjJvKRDvNtw#(U5LN#O0F@8MwM0!|IH z|AdI_G=eu_woG739C>^x5FdR`z>p3LU+_}6?gfq3y_c~tYZ1BU>2oYRzNhz-#J#v@-L#_VB*H08(C~3r#@#Jo~;Gh z%wtDJwIAA^ChQYM`KQk0c18AfwHD2X`~cSw|Cq#ZvTsh)$dO`TRF{YZ(B(l#0=o0r zjNFEEa%^t6$&x*fn~P8rDEP{A`v&9mTFJiTgSugTKEuRGmEA1y$^|e6jtP2E{+W8v z!Pkt@p8U)s<~xG^&C9d@-1Jl2T2iETL!CioBCu@M&}zN~ zY}9(|blYxYkC#?3K=lUj)gcs~0zH=-?=&?E@V13aDf{f3mPI5T+eZaR{?$L=$Znni z)w&tR_V@(~z__}f5VBT_Y?gKPxL_jvEvzNG^;2xI{~_t~nNZQz9V2Z6B zM8K&3YL#KgvykprbgrZ#5Bq2lQ7&fsRvT=P>n{t~K>fO1mWCp(&kot>IzqUvt50fO z(LBqOt>4akwKW*bwakXhepy^j$Q6vWvkl8Z^m)*h4g&?qqU|+78EbLyb=EVet)X@= zZnpC-U6f71vHEcA;4S6%tNKgcY2VGOEXJcw)?W~`&vM!?BRuj3IGMX`{{t*VL>!YH zsYQQz1EiPfI33PF7KN(Cn`KO-Kcza~UfEP%Kc7q+kQCCDKuG~`RR1jyS`IS`ZQlTB zH)Abl6Tg147xWVoN3p3*?dUy$J> zNimhZlvJ+ML!>lqWX$-QEHA^@HQ#C2dlBI! zM~ZO0yg8X%jBKp)B==B02)xLh_W(;FR&!)?#!qd}=>hjdc}Eu1I1;D+x)DGbY-~0h zKsRY9Vc_>h*O@3_0yOB?iX zFqa^P9T(x1$P~t99!|w_LiLp+ICgo8pWyaq*^j|ow?C616T7j*<=m`^e$uc!P%!&s zx#QFD=LtI#GMa6T%q0W3MPL)_gt6?=0XKL?R#l++f|E^cjDb>=HaHQOT{|d&>rzPW zm9V)O#3jNOI1ILW1DJ%q0mOX&um8%N6ofn@r^Wws-}$QasZmN+BN{k_b3+2q(di!K zE2d9^SIyJs@oor&7D4p*RsIU)t4DQ`F9pUxb5qpFdI4EPSSJFsMtJAZGV1)6D^{*$ zJlmZJ7^{B7M7qb00ioZ?^sqLf@*y!OX!hEk0Z$E`b4DGXmSujKn%0W;<$Z8H$(Zu< zjwz}i*dMd!x*+kYp_lD);L(j$nH+~Ybd59xuGv9WWmCaA_-so4k)PF4;3+UD*bbLT z4i>X*3VYpZnVQ84z_9N(u(nJ*M#=CaHzYGbxpiTl7{ zX7?l_-sR~puN{K%u_RHqg+KYIORLHWuI@j@!`otVHy4*HkXC1x9;-+EPW^o+yILej zfFnZ{Fx&YufVC!Ha%@ZV(Su9WYV*!0y0wpPeLa}}pzKp{%HGl~y|x(mjDEgwokL1s zM+GX}_2?=`c8a^WO26;PhsRUY#*yR5(aXmVwrmvN)33}N;~6?VlKH2}s_bIJG5!Zg z#Y_6y@DM0wMJC#Ux;aOsn&CFyh=pbzR^sYZ>5dq2KQM*o<&Mc0HJs`RN{lcUA`mM0 z88UG;4);OUQS~_?{3DXNxDMZAEW>$>1xu?{HHuz}O{Z)F%WOqadmV)rfJ``$NRoo+ zwK{@{*Y`(L;v@$(YH%n+H9=+Yc=n|?AN|)!w)&;Z2T$7aE&?c zJNIf943#NU;Kc{Pi(T=WlHZPCdh#AoK)>&ntGvSJvCz($X>lyujSmUg%|ew8Uj8|y zj|(PPeBE75vpd@Me(FT(xtll7a1ztt3(-&EG5JO&Q+MEiG8bs{Uvu0uqm`w4Lyk_% z8@%keSlKa0Y92f^%`?oO(gH`iTl7cPhI`{v%+|EO%pZ9RbJ=L)eA+@qt}^qus}wuC zA40pW!dB6ARmKW!jK~Nt5jO^+0$WvEZ*VE08dLdTs_+RXO}J8Z9LowABsNdt$yEMo zHE?|}DjCx9xW_SR(cNz%P-k4e6?EX@<ALG?+A|Gm8}sd(R?q;L{x0hM(fC#2L=}kcJ+%Zw9z&A^ z;^Zw!c!3V zySj!_d*bKv_FBwFdhbub8SyvCa0H6^inwyOzDmEJ$NGigYQk-@qTj8Xl@Cypq8i}y zrsec^CVQ@aXnOIDJXZka2gvqecXuLnNMoIzyMBP~wg$in2Na|-_d&N+gSNX8Cd95; zM67E}pfuYEAF@I_@$lVX8Ud%%&F?!`g=&pg+8U)VBT$YFPu8iEniKnNfC z@I2ef8^GcCUY&hh>{0C%hVc1XH6^vAy}d9fH!~t|)A``@p+!py(uj=JR5dzdrvs>S zP)+f_F8~}>goah=dVl&lJ%Hs^!7ydn8vQ42=g^Vb-vi$n0uV2BZR2)b7 zQO?B1E|0qH=knPHN`(^isLL9z{p(0#dHJ1?rci?Q?q;a-n->x~R()8hl@ju=okct? z8PG+$#Gh2*hPP%9>gOzGx50BwKR)`9(-~RD!Loq{LDA7qTKtUzjy$~e8AcrY#Mx;| zWo**1yy_MBLYSe0bkGLvCziEyPTX+aEtso}QSL64_INx<_57$Z6dvXMY1*@PcYkzO zjlU9fT<4M*94^#?G@CP&+G-(|_CEU{HNOr6tkn5fhM`-Jhbjx#7 z&4Vi4mZrKi4U@AL#kwLdFJ4T+>sDPye_z}lvUvcNF+pIh!3ge6!NENkTEY^uy(YCp zn(B&83`V30^(pcVU}17}b#w+SS7O5I#Z4uNX4%_%19)h}2Ufvwb~Lrj9(*`vb=@6d zo&z91&p2pOd2QL8X#a4W6?_yDwPtZd%ThC3>sY2UC`XCyYJ>v+v5I<7QMQ<0|0sHY zR`$@y{#%L9*NF8s%C5{wQYhsAq=?}ELfp;x)vqSON!bG(ytXy>irbL@Ik`Y_OfXX~ zo!(jm9jfEef0~EZT|UM@ZnRD4Gv^phd|BL3Sts=t+HzGDqiL(H(`A`G!X&Kj8vE7p z?SLX_gj(*gA$USg>LIqcOr|($U3onNsW=RF8ZSf!?dwK`7+TL@fQ)Y>!7PiXXJAUoda2SY&r!90dMtTXT)d{?{LU#2P-=x_k!-S6%XCMTl7AA6b(^B2{9*GgDDmeJ7mFTV5zX2f4mfQm)){>1w zA>Hoipt#Y7TYEo{6?)g{c{8@ONUyXsCc1BpgvDRLp zk}dQp`%XN%3T;Ytph+#by;DhK`%m=+lM%*B)#P6yP@krts6oc|9{F?{x|}30VhcafWcAgpO(6%i5#9yu| zwLx(jl^FcgqZxE9lx37c*IIfPr75sKttf+oC#;-?e4RG_Oxx;RmE5c`Y~(sQI)r37 z3*}ZhD>E~z@Zzc|YsX0pBJtspL|z~%?dje!YJ)y(8p@!xsxLFoJu`HqGW~+L=HuU! zQcx_m)Y4A=Xd@_h6q{mAKt#oG(Y+SwpATYDuO}<^yx}gp{CHdtg?{kkp_$I{*1)d} z9!s*dhk*E7;AoBaV}9c3z}5w0=%ApE6uA#y)^gYg)mb@fxZ}n#Jqrp>qBRTg-0aUI zECnk^Yg~lHvFjvAeR9fVx`sbkXvYwp+b^Gn_pzqkS@EDr&_u_``eark3AI+i%cKs#ACV zL53?E$x>*phlZov#Zm@5?igYx1cICD@0E;XT5{JT!x};D=|RHBjNkD*pvGM15O7uy zOUu^bS8;q0vVRH$08LcBSHtiMUr5PeQ6=`o>VH>XcP!oz%L8s~eE+$69d>UntOS1WbaL+gsw zLc6rjWt6qCMy_Y_29V%B9I|v39xf}PC9KKu&{15tA_!Ub4KO83Kqzd4D_5 zznlrhJ-aWmx=lKM3=c4){3Bh&YSs+CF4YR6(mvGzaNg1)1Z$ZtCe(axGfnB5FOWN{ z)+8`QyrkasFLeu|12lE8HC4?Mad&X&ip}+1<6jMw?}9cYQ%x?0I#tfEU(F zD6Z^?o&p&0e&-~v127-wZ%KJZtispNPxlly1;?6;qs~)&|3qUz<6A6gYZIW({g*Joi0Wu=czT)52f71+^_8<7-!LV~)X+b#D1ki2xFvXqS&!b_|)aKk3NzmiY zFBs)KvgXII^=GkAyFT>}rG0EHJo2}KkpiLZ21GGFw#P+&2RKCt6DNkK6xIaJ#Bnh) z=w)(aZcQ2kN+zXj6hQ~w5QCcoat#aq7Vs4WjGO zN8`D5rme+1ZocA$WN;EopTwm+jA$gx79}`ckZnxsN)^lU!nGy-Hk2a%pfZ-*-Se(q zNzH!>)gy|7nDHbU$}#b3kL}CO5;U9UA0Tu15DZlrgil^5&kt4x;{Npewz3-!!)R0^ zo#5K%-|}>Sn^7)3y0fygUA;7toSfgiVzwGfrzr&+T})H-zaaizD)Jf#6~})u+#I zX7$p>zrFhAZ3W2K-9U4&`QU)y;+U)FG%p95hJlXX!Aa$>zn=v)+E9pckupBV8QQ7} zMG>WF%*0~gT!b~BcduR|eRcD(E6^08@7*PexFNzqQ`Tz2C%0~D?U;v=js0W_*lnyfn8K#o+_>2q{%Z_T-E_YsyS+S-^aA7{? zc-#Dl{;t3`RKgw_%U_i9ghn$+^X}q!c}QR$eRK+@rmB`vOURmri!vx1il&%5<900* zhn`}K{)|1A4Bz*ZW%;VvJOL6e4x8xgcR1ru9H(Q(&z#(2j)j6tqg@QbvduHWh-+|U z$D6ZU>mHzY&UyoMNOP(g_Bc6u%2>AvcYsjxa!iv)TvSGfV4p)_p>kS|A_1-2DyO%x zto7AQX!AKXMw<@k?Cq<>br0I^+O8Xv24iJw^_wdD>zRJ$P-&hP49XG*x7$J79`sS( zp{i-)k)z%tA$YpRoIDaF9eM6MrN@i{w#5tXV^%o_+Vcf1YJb0ZTrC>@Ax=>hhew^=Pn19p!gtB;N=pYfT$iZm?h;av`=*`U*O)Tbu$~{xZM&m6EOr^rQjGMP z`5$VMLB5kfnt*nDvh-ra6AhfiYdTHdAJ}YAO@bfAk0s>vVNP9wuuVK&g z9Ilp0axlFIr2tap?0+R!$2_?AT3a*|6?}viHYyY4p;~GIP>Gt@8L`ZBQdFM$p?YJ^ z*5p{9&2psGLcuP7ONFN4io06EUuCVEGDPe9mP+V;lOIJxQH7rtjt`1a?No^q8#S~x z1v9IpMiJ7Pp4$XFt2)ha{{x}9CiR6eQF1lps}{K$Mg6dv69 z`HEA{HI2Qn;13bsQgsNf?&4iH8GMQcC4XnuIhXWM`NyNC-|&l8f$m~wq?%gTIUPye zkZ7M7W1Em#Tk#bz$)vD#KDViyog5K*2{z!sw2u5#qPbi>=7)t|4KnwhR1%m;RI^e| zHb_=6YRcf(0RTqfWr|&W10$%YOhXJ@D16@l35Kr=eb0wW2@1b$ygjp`Lt~pJs>@z& zGKYn%R21*uAG`UT@6qXggp`4!(Ij7016;Mt=hY3GzM_U3h9h(rI~3#Bv2e(r)()d{ z2)GxHOIvH1%4cCp&C!foXnA!vV>U#H0}_UWB1sY%6Lp%tfnjKa_3-f%C!hx?B(r{| zAIg|BkbaKRyq)h98bj%?dIKC8#Wnmp9-fKw26y76+kqhqEh749NvM22Y5MrC)*wCOm*7TYqSF|s zBl9|F=t!Z$CzH8jpGNFwi*WJ>{F@qyIIZRFG>}N-TF`&qldQIxfnX*to`vf)+S!7+ z;Qf%N;wha z$JQ<$#HVPl!PwBw)~9?iqH1DX0onb4!0Q;64|7lH9g8Sdw$_#k26MaoEN&5Cwu{+D zDAuU&`YV^tM*fkHaZNi?E`ED)FP!w4xAnW&HQwU~%UpRcv4YiFqGH#w{<{@${CT{U zA!@MP$fp!uKRjxyXtYy&*Pbbm?me%yHX0=ifvPTs+B?rSgHxX7cO>r%*d%t*2i< zqOJS31JKcG8cQY9g;~uQT|C#{-p6CkLGZoE_d)Hjif{}yBic`b3j zW7^3=-ifY5nu6J-z`pdQB^A-px?{ZoJzx#V%(6HFq$Gp1uIL4iJ+w>wW!lTst7;Qt zGks%7J;MWRaAd_fyev3N1*x;VA;BIh25_u6Urun(MwCzn*?MD%yg3_ii4P zkvwH2ZyFieZrU%NKUdMJllj}0vBQr*$@aj|`o0zfaW?slMO*~d-|CuOSE;0fo7GZ4 zDZ|M8gXo|s@eA#bn*mgemsi2Nvl+c&4`aOD3o+-=)X zi-h9Ru6)FkD@G94S1DBzb<(1vuxf*&DDuk#cPJOOnuDF<^b~32))S(VD40^Wb zGt@52zW$+ICV6+h{gJn@|NX*Z2}r-d3=$b$z#mA;MLycRz3F~pF7Hv?3jEF1@dn8C z?&dUnF-qVSFmT3QpYT9cf;E*rE($$%HC(2aWss$emr;|7Klg<%uGRrw@eVK!kNEIa3 z3|3-0$-6*m7mN)^wf9twKA@+zty92i?b5J92EB?w3MW2I5v-i+l`53s{Eb=P83SU{ z;1p|69wi~k#^R#Y99#5(Jp+)cQhW5LHXO1IxgyZYr|#a_S8eGMfu0qn;y>m&e9bYH-60;@=9+ zRny^)VR=Qv@8|VO1i(Qy#mpV*860<3_w)hd4UKi3+8VB9D=Vv#%o7mj{wj`Ug2Zmg zo=ON!R-EugHZ!D=R>gaKBxbaDYJ-2T)`tr!&Ci*%kta?2scK>)*ZrqssV2O4k=T$m z;_e4sHxB-1+QsK~lx(t9712Ko5ypakjRBiSEXdCX&2ByiNoQhsLfq(on{vz?y@XR5 z*;bis?u{)AL3|?5pwj@mu!b1YvvZmZ9*x&H4;&e09Q%(SCZ=fZEwMMN0EUZAD8kZD z!XH_vIu{Dsb*Kg!CGhuMs-jJU!8Lz0jX&14MB1Kzj6|YXaKa+)2i5wFx}cxS%D}(| z2rB_pn-nmyF3jpzg)fJk{1X@U~RsI+LFVeqZ>3dLN9 zPxmf9kLHW=>#hYC8=HQ{u8!Ou?jla?XH%rr+{KhNdO$p9&y?>EDFwG3ei==k4Ui+> zF~GP9b*``Qz~dKbcZ`Jw22$o+zw$fnQv76CjpD|EV^g!(YLz8Jhbdz8&F=cx>Wq5W zYi@`UOxYr585~`kg~P8tR1IeVgtVzdc_EAoq26t`WFudCH3(9EFIqf2jIAAEa+8(s zh+6g#xy_~Gh?2HapS27^5SHsLD%3M_>uyc1P&@DXvwv-w$VJ~U?jJ8;vsjp*NRPhj zdFoyEW#RmX|7C!5W+`yZ4Tp}xrhLELaPnjD9v>M%T{gUaBb0^UeD#+Pcd>o_Pxq-czlGQzJ2&t##^*}4dn7WE8I>f&q zXna((MfXxT5E(EDj%AEPFI*KK|2aaK!A<5b~No>vyYNEli4GPfKv?WwcGgad2d3}#o8r4orVU+-|x=Ao|cw>}i z-WmmNB0F9S64}!(c*#p9c5t1C9DnR}=Q}XJsPA$`{RU8Mj?PY~po37I4GLT@f~!su z8$$XFv}5;Ywc=%%^1h#0o0srVU5CLY9K!)zbBs&Uuz!JEQoqMEOQj4Y6I@55hWLX0ydx!QVCBURPp&?$lA>U9UXy!QhtoVsM;-B zDYC;ZGN=Lv!i=88q%z7^=Qo*rj+XP1;@E}_PEA(YHo7ASd#0rb?<_}UrAfnbXNP_w zq_<$-KI$ht=X!nODCZ&4^Go@Xc815rx76*v4TUIsy1BjNN}m!%u4r*W4%352RZ5Y` zJ|*$MhIG_(2G!ioJ%ec0mz&*%c7ASQV?Tn%UnSIg?I@|8VPjmOV^<>#GU|vs%OEiv z)@l}$&%o{`o7{9PTcvuLEATqiEhR;qVW_Gd-n}!Bi8}C$1Xltc>WoCJ0VrW`-(p&o zv*MxV4e+Tt``rnw@CGP=ByEGABQsoXT2)@~7F5?#eMoUF!~Svb!r?$2<|1$Cp4~15 zOUl>Li%Gk=xT-v;5KrKNNu1Qyakajgi|;r5khNbeUQ5_G#=EcPpvtf%De^+^VKQg) zhE1BvH}MUq>fEc2X0rJ!sRtQpJ|?ilmgCGy`gAt3+U~4&TTWP2(1^DLiCmYzWCE8H z@UozUFt=f^UF%xtE-=|Fc&fPW%kzff$V0DvRUKY|-J(OFBt-+*8F)(%$*EcTYxzOx zK$$BJqL6KQJw;B;dx+v!r%-fs%R)$h*8TbbEF$c?YCRU;nGNE;5iI#Dxv@TZ{qE!f z)~9AqDsa{v2Z*UG)}G$1jtSal^Q;=q?lmo#>+NCT$>|}EITDDTS!4iaFLxyfzcPDp z72M6Bjmqzaexx=8t+LQ}uOvYxfFRS^<0>X6D)2QP|0gU*k4cq^X;?h?^~NfFq--^_ zxliC0iZ{TRI3r_jIfTAB6_VXtUcNR_3cQC=XSmW89vTm_y!~6`=9{Go*RB;3h+$t* zFFcr2w!Lv@cy1-NhVGa01?sM(!wV9vWCe1D{{h?!DA2tAnq(xMb*hR474VI09*P7H z``E=;4BYH%opbewCCA{DausQU>IaIf^uPDjy7Y6DpX@ug!k(O+ROk0X4MPeiKna_I z-Qj|oJ=vxc;`gE%Gyp29tv1v$Gi44g!qlD%E>f?7pCqy;}tZd~#St}O6Idr7ISkIN?Cr6+@%WhN}#Nk~HQBy{0 z^j^-0<+NiOZJD!7ytVcmEqj#7e>eH-+oj2Y6gvdCrBsSqddt1vWKnB|HU;BIPih-$ zKc*zZX)MuJDeOtnE2YOjcvwcLqzvz67JGrlMf#?TfQ0T4gKEBGIFLX?XR`9}%s1w> zN%=9k*hQsPwm^Y}-!7F&!3&idC^4I1Llo}|2uYIE*B{vI%RbwQT317g0YKf#^r!Gt zmS0>BqZ4DMm-cS6e3r4=NvW$>+Xap?+A;Kl>NA2Qs2M^PCKjM9)Fthcga}PWKa0pt zatY$Z*#j)DyOC>xOJ5y1S8F*z%`?ISZn@8C{8>da0?qRm8{jWfd&bgJWI8Sc(t>L>orMLHv?)+()`(A+e@) zqsPYBlpMgbgr!l?bBu?3^^nClb)+m6`*?J$Kqc}Gn)XJYfwiNgdEP6Bi_C2HfW_C^ zHP)?VS;rk%&Bd_~i)MIE6XfYUoYC0YV9xR)y(I(S6heBRgkn~r&q$T#V;`W!B0BmY z<5)GOS?HU(&RS2LJbO{(Ze9HI(7SG#>R<(xoFh!{)?WkUv5+hbzj`O9*`OiKIu#@yPYTvFjqam~Z0LPU!}~o3z(5w*dc|EC z|3bdTb7VQI`}B`6s)D1*U+ue1(=YfwZ(!2x>SThpR{Yi5ALFI@^66d zs8=qiI17*?O{5+TLDZ;bnU7C0Eh-+&%N z9nVTJ&!TP>Vx3`)jKV7Zaw;lxGvRK%`FlpxPwsy@g&X(b&TPjvLmO2P($S7{az7#@ zBu!?Bm8qdknr&3hy}X#_VF2Sk?e6N@T?%h@oUSN15 z_Bc`J5U4@gg zT-K-+)gxcc*8+b0`2f-R+sy-Y96(o#>zK@w(F_UCiC@z#pllx= z@?I7jy`cz$4IT2Wb1i!?KUJchigdPWOESeKmNsNa>QxOQ>l^@x1I_OfVL@$Ye7is-mR7_#(6I*u-)TI0&>)&^ZFSE^M| zK|}RDC863pD9fGjuJVIj7iPrz8vsl#^QdS|%#ZUC5WNFS)}5D!+^lmt0}04kj~y{> z6me!`go=5%NSF)=lOfr_kJ#~k*7L8zzKDK_iJgn~QjrH732|9sK7MrM&80yc#n{&LShi`E8@$)0ZU}`=`aSLKyT)<7q&I}wI+Tb=zrJnz> zx);etIOvzOdX1x2v4aABp-jV+QudS!{B_T)gz9-uZnH)Xi`j#)Sg0#GnbhFYKP*Ky&CVeMe!K-! z56o;u|C*TtFG26Q5{;WQ@cVQ)Td)hS{4Y-hi$jFPyh!Ur-JU_p$4pZ1BjYdhnj5c2 z$6H4DwyPW1npYW8GEP25yE_PTx3~#A@XocLmm$>2*i({kfMeVVnIEGwo`NS=()h_v z;`aX4vlM&NDf|=a4M95-BWr7Q!Y2zcXXU?DQbgT5w%k;xp036CNic&iG_ASlP;PI^ z&S^h<kQA$WZzu=(c(|7GxC2P z-wVTlD43R^iXTldB;H-K^rli}xMl9e<}0&Bmq5CXhgd7{befW_bgQ=lt~I>F8o1j4 z(4TM(z4jZ`-5hJbenhO+sZlSz8_a(Lh$aPYYRUhQdhm#P0|*E&C7!Kurk-Wwi;>2b zZkIV;lg4AqysQ4|~zHb>uub^XzLUwA*j#)gAx7#H>_O8UqOqC%yWB_Y4@;KnjqqA(=Z((e%N8EZ!W)NA1Pm?{&XkN55Tj z;t!|&D`#*8{HwEUvZDHGZT^V$uB-7pSAzH`X3k8^-T;=r9)En>eYfg_R;4;AE&f=3J8#HOmq7Vhxx{ti`JVxkN5s}l=~nRNLTMm62`tYw)D|`a?QNfi^7X~ zVCM@%zKBH>VQ4HFgWSfx2X6ksf88Xe@ydL-))ROt?8IH{YW!C*j0q?{>GZl;=YLx? zCOqgpnCzX=I^>n|(Z!FYxT)6Tqq#3%tG96HSP_L0!@B!A8~W0~kSyurKhQMnL^X<9 zY(g5Zm=iXy!8+adzq|Xo5{g}Oc?0|Sf4%{x8SY=7c^`CUX`+I!y`CxChF+jAL@#~? zh6NDFaE*w#y=#Ylx^I9yql1?R=gXQx{|2$!|3_`XpHM{|5s!CSuYY8<{ztY5zf?Xm z*?;p5fOy{U{;@^n)Zfd^-zKu#QDJeJ-M>i}0}pY1-T+^SpBWD`M+O>cBsdDIh3Q&! zADbNo?lwgNie`5H9!mMr#%!!LtVz;b9($7tXv%wuOOc_swLhggD$}@c zi@k#Qc}T&y@Smu?G+Fc&+^8eo@D2a0!^KJ2j*aFL5V-G5arR;VHF3s6Wx*H}8*=QAZ)sF02p zA^(QrUbd)>?GGI7eY5|3lLPej)e7y$!{Ts8VO_u7n)fAYI7wy5^_#@S#c~D4GMBph zMzi-#Pl4S0i&gbxPknbDHL9sU0NbJ%D*IZIavn(o^|zC2_^vt$8_gdrcbktT;r=R^ z5qD6eAW!8yi1J?Lb)cAM60~@y3Hr63>2I(UCxk{nvNB@xEQ!=9(o z>L^Qh?d>#tS-uJD12>EexcHBYvTn=pRKqk~V+*-Ej231_s7FsnzN>OxV}O)wV!lz4 z50*)yNULm&kTqe)Pu$BE{8EgyM(>Ar5@k>XJWz(7sL{KXQtG>S(B!WH9n0HAmMdlz zgLV0Pq>pslDvqjRUPAZm6g2ON%P`Z^19mPH(;uta`J_m8j_nyhk&eyI>d~i#xPDI4 z_c0~9o;3_UW!7D~+eu@)k3O)T++jY~{)_?W6*yVArObHUm&jMc>MNS8R0+Apz=BVo zqFCIPA;NL`DXd>E@+(W$sZuxhU3JL+{9*lj@Hhi{1CVQA$e2AXxq0dEL7Kn1Yy2DA z_CEn`%B!!!Nk?+;VZ3BVh7AutZqiw|KBp5o$}PA)7Qu&9)1AoLqD8b-v76xF6}2V8 z!-5p4JoCIr3`prafnh_t^#?6wzZ~u0CT0>V;k7lHDNIR{A@Zvb?W?)+>i2k(#nxvBK)kUD=vthF#8_$uJr+8BhX zj$xOZ225C~8BuY07!!3gH@2njsK-TgDNR617gL;X>O@y~Dw`!~xpv5;@`P2yn*T_r z;JD}=)fY9$^%aoi+$L>hqG8cVjl1F$yT<+KC!Lhdl`bHI!0VSBvK{|V^qlw zomKdTm!d^mwxhkIW+(;QmXRby?p%0OM%A(@+6klSFv2tdFm=g@7be!Mkuo_-C6DRl z(LX71x(p?$OehV~u+jgV2Vg7GWQXk8uUODcw}-Km5?1Ih=Cq|W4Cot#e zZI?}yC$h2$Sthb8{-N$*XRMH&<$xv^+FIgPqKb@~KF+a;)Q&Y@HZ|}ngc^R9QeU7M z|2{KnsiTr1<~?GpnFl`+9OkEyR-U3Ss_`qM%` zy3)4UWMQ(cHw)<6D%Q&}DU&yaOOuTPfvoobRR*<*o}UkyEaot%r6`mDgTLm>&uY(y zYET#cn0FMEb#@JYx+(KmA$7126ZQ9bN*ZK@Yu&ix3*az~7p#SC9o+IB5w-<4Wk3j- zt3|y&(Y=%2v`djEDOQ9sbq9l?E;$(uO3%$Pv*ID6P}~~h&>M1wSuAsL>aK?MKX(`I z=Qp@^P*#8?Z-9N_2;GjN12amImkPpdFCF@;pw%h9DO?6HSM@|^+~?*~?yTWfwR?pF zvh4^t>(64ix=uaE(z6Uk(sT5^R7qZxf`ze&>!AcAYQcS5HqZ2n6U0Ta$Ix^P{KvlJ zi{B>bS1XD46l5%8y^JvK`hC##rb&D?Jbrw=$7~@O{nA?f%=~CHDWuXLeau+4|KDI# zN$~^YI;iH?`wL$e;0CEU?RO^4EErmop81JleAl9l?>7-^CVFj7zp}~|Ets&M+4W}E zhUdsL_w_goTB-jJs=l%<3N36G1w{ctkWQt$I|U?$lx~pj?p6@#?xADo8FCngkdp4M z0qO3P^vr&*z29@rm-PdlYptj5SiyH!8`H&mKP|i_D=KU+Zi(2~`xf)#bRU{1@=mbx z7^%i2a)h~!fA!PoSr`Nk!ux2gZ=*g4c;%^YLT=Ngn#bsVeJhq~obB%-k0I0&3swM& zZhIui_7wT36;Wo5NtE$T?w3*!83k)3c3#;1MS+Cv>dVB5_+kW|{i^Ct;ua1v87wD! z?JQd18jmQrWWbOBGtF*Kiqyn1P^{#Y3jzV`p$Yt(Uj@wHd+^R@;t{ZWB}9pbx1U3r z2{s7Jvvs3*fbLdb1Bbdcb~~nDb1u%`Hi?aRjaGeenF28tjCNc*JQ1K8asV|ez8xs= z(a_~@DX94+DvG~SYjG8-NN4T*)rv}<6@2#>rL0<9H{Q3M4ab<+dAY~;0Fi4`lu3cV z7HzRJTY3BpPtHkn_!$DY?U~0ndo66LAj0SAYOGQF{G5n{=Hbz*CyC1)Cn*ntm4=24~rxr@$Z3vUU?3IkLtG2o~9>9QOQ(JEP+5Y|nU2FHz<%R@ z)V3AZEj+aF{_hseMn4b1MhCkKgV;VO-I0?$4g*F=%7kxTh^|-Z%fWaAB)c91?Mvb) zwT!dKiK_xdndfxoBfpX3hN`4#@PIm~KN7ck%-dzw}ZLg|E91jen^K9=#nwMzR+ zUdlTo$V)(iv7WumluGBCQ&y6g5t(44F~TJ$D=#u3zqq9={XZQv6CVyTy zgj1P}7s%N8{HNX0tNnIds{2l&%~Sma08||7OA@EbT3P9IS+zY0?Twe7V8{YYQ;&OM z26GG5nVOHd-TkZ%#)hP`ZUY?KXZnTJYDSkupI27jX0Tx8abdWKr<%z9NoKpR&aM5W z`DOEp4`8D11o0;QQA8Q*jIUP4kgvAbbnEFkQ=Mgh8x|<6g%o64oVWa5$)u&d0M^?( z43SuG@FKv4Xw{Y6?DX_*MaR{|C5SS`w59$LNBk;F`eWk>>wCWbyt>?=lqG@-{IXSK z#kZ8C0F73ny{$|eJyF)Z+Dn7C#`2Z@Of`y}+f z=Bat%0M7YikznCP&6zw7jj>o+5E1oN>wcy4^V1N{rTAKcPeMO@3ZDKj2f`t$6$?wv zwR2Yu8zT#dnz))y{+EwSdAZ#ZkM4SjgGbgL@zc-4FRJ#&vy;P=(|TuEy0|q;TM%%{ z5=}Ykke+o6*L}O(+*jYRmn+f;Dg2W~4NnT%oL!x2Zoj{YAisPfkCV_Zusg7Szf3wp z3E*m%9mU4kGCX;CdosRYwlT2nr{u;`8*VdH<|6kI=awqRY;W;WzvRo?+jqVA5c%$} za;WDOsX}rkKQ8yX{20oTUz<0valQY<4X053i(ub;1ZHTp0*az$r+A%yEP2M}y=c34>_Y|v(Z5#bQc83A z7X>3?4Rq$oo68Ke<6ga#3lu_XK!(pxc zxSL7;q2WH;`fkR5!K(~2KBtp5Sok28zxcR8h)#{S>WYW&Bl<$e(-wV6OkDvjfzXnH zMm*iR0DG`MTsCU@zlUTvsj{p;19QZDPkXPa&3!9y3W!}Yw4mH50MQl$zbwU-9j&PH zsC^s>!`lk%_#6!8R$lRA6w*Ygo{Aa;Q~J}oPeAgsEElY}my|atvVG0xL-k-pbZjMz z*t@^CZ+5`0&F?;Iudp7)`UnHtXrt>?37SGti_pYF^Pb>UZBo*7pr4bn7FKr`%Hy6i zV{Bx2ka9qmaIp>DG`H+^b%=MmNyVi^j^?L}50_?KKuQ897}4xi{rtp?k@|4P7zcGs zaEH65lTxx&*()q!E{4Z(36Qlk*7S*3ti74}fQ^$~Iqp(&llhn6j%;n)%;A^ReqGcI zyY4``@N$MqBs>0VQ3Qx!$N3kW_lwvX`U?_#DmU~$GfdXDlv2T4MOEMZAzTk-u^8oY zMi3!0CjMg>Lt9tEPJvp>trnAoR}7Owocl>K(6t+&X!QL|ZEMP{6UemIf$H{C=kes+ zUYrT*9Yp-#U6Mb!=5%+HT`ZFsjTl{l|7TmY7n-YUkrbmi1b`i{W`JQ*{>8qW#G{>B zfgr@5fb|pEma5crR!njo=kJD!(1U3$6H)nf8PV6ALD>_e%H0zMDis=6O7xaLh6}~j zxnOe%Tv|JxF>-kES4nt*=f04pPNPec@*$=cnU#~^#L<2(8XPHGq&bTb!+mdaQeCaU z&;QSZaj1>uSu-6mgV?s-=2{ z(7uL|GjvzPW0LCt8KlTt>|I zOT?=YztmsaH;Kf@@8dp+W!h;oi>W1zd|9a#g$e2Jdpf_qRhZgq$&{nVbCVmNJq5pZ z7?X+8>z}b(Us0>%fO$m7=`2HG_!KUzQpu(|4;a(A4GR!pFPyJc6=QZC# zbYX!ldue9sI!H}WdAVRZ%H5EmIcKmr{H_6M`jYhU5RI%uXuiH#tu*^;g$<&d|MHW` zlQQb#HZ`QuXvQh+g_b0XRHoE5xmkzWOiKK<^c>GSdIhOLn&vk1b*F#YQnA#(2#&oh zIz6tZOm^$t()+>rbH>%xUfr>LZVwmyqnw~dt;LCWmR7$#vl8^nw_AV!n7U90r#RjlroXH9&QeAY{_J){izjx0A$xW&gC{=%?L~??>Q|;&*F+mjhWE2P ze)EfDhNI2UUs&e?C&W7hyhq!Vz65Fx>hk@u!)_QlN+aJoM;x2E=BIpz3Xd3kNM2SF z00ouGg!Fs0BP$$_CEeHCvJZZcwVz%4=%gYI!hjl2V;C`q;?-{0qR9-sWLpR^8+CMhiKyS z$1d=}&1_;B_mabyixfPYCyV|RUB-t55+RdeU#k2EThPY^S0v&xSj2xkWXk z4%Pk9K3`l7%w?NBnR0ihs=dCcYKe}{iai5(vM}{88Yal7$7~c?jDkq^lcgxCS&FTF z)IZX+Pgd#4)__}2f9b8dSyNW#axz=$q(UA2d=tvc^?s}NhZx0|c1?tC`!4BTqgQyY=okWKbOFA-Pb)4(p$!)Q#*-tD`R zMMIl=uWT?Sl$Vhdvux}E>ZKNka8)lV;&rZFoT@!M861txRmCw@ugH1M1V5L=In`gDWHduUGcZZ%YV^tnw&`< zza+a@?QWa={}yQYpI?Tx^B*X~1jFos!SBRFU;RaK$D3(K1~82F%|+97we)kCXw32f zzHyT(|G?n64lr)Tt<=$vkEx<&%Q{lFs^*G7raZW-+3ff4NXu+xoAI% zR)+(QhKWR6f$(d4_~8)m^+W?f931kT@Cj8)CF9aK}_~0x^bIeW}gGM#M<^$aRluqcuEEL zuc_mdra52ppK$#gJK2aDTNs?HSzQ?Q83JgBAB{6&6~9KIkOO-Nl)(t=esweN%6OXE z3wz?FA!U|)_~aZ#qQ~-Hi`GA7baT1W;#*?OZ|9~rwDt_g7%HD;2JZ^Pm-TIh_%?L! z>5mFoOoA&^Z_3*LqG;!xX#Kk2;aKaNE|Sq(|Ca}b60jNe5x@Y|ZAc$rzjt%(gPLnK zq$bLf4>^5wU(BQ}(WY10oV67%VqIib6=dc4ypTxIL07{hMi|1k{zDEfu7dwb0Iv^4 zGkxS*k+F~}$aT9vumxkUAiO~gq?(8X(^B9}52J}3%TF2i zk@Y~~EBfXX8RvIHAD-|@_`Kzlg8?mRI|^wCehNQQR$ejV5vt(^PaTpMx_A;O_CgA8 zc1J#VR1KGxvbdew zXR*=SHRqo#HL>-m(@&B&jY%|Y%=25vPc%R~m20!qa%tUs?IGn3qJ9V{@gJ!mF5S5@t#Gh$_y=sUxk{E4%z%cF>L9XN|TrCp)cRB z$Hv|4tVoHIip$%?oj`p9#3AXNxGp-e!_F07UtrzNQxH;m^0RDP`mVJlM|o$!F_7bo zSC1;yE!LHX=?zPQ>?``@)bWjQ+me+&sl2ld+#Rdm8B|KEb0RxKFz1XVKXcyX@lu(U z8(|%GiiM!MfC!u$6}sx%AT=W4v8r5`T_3qNy_eu zqGg&@RT$YJI0~uZo7*m4?W+dKsVR44>LavsnVFFmSfOHH1$#_y>yVayLZj{mQ{mLL zsq*l~V+Ry10m=d{JxY&^-s`>+yMB+>J_P#&@uhx5ty3}Z4RsYAKK{Qm{EJvI#eM!k z;mj9ujv`1nL>NV%rwMfuNN?NJ5Vx{TL)V=UJbQ!|C%~R)qiYWrqYT5bpjz39ueZ!@5RbPqf zO$4ztj(&|_slZ8LR}nF9deZv7IMUr*tNB932j7NH#by>kO8<;g+!K9XTlIRRSMxA_ z3msOi?=5*)Y%2xuL>-skna<4vS;VS*;Uo|VO65?0%#Mqb>*mLKCJ6~RAi`Q;&Ji{P z)>012cJYf&Go-5S75zgn2J)yFYn(scYVxix6Z^;GWL{E!U`7NrkRDT|FHR7 z7H1kxyw8#bB2lnY-1&=wvR86zHgy}^$?r*=byO39=m$qykeqZ1Js^#iI>Cre&QvW- zG^Pt|c%KAFp-4~$RQZVMrjhhB`igys8llV^@r@w;1G?Ry!JbCGNlRmM0i~8X&^0Az zgaLIZAu)B4lSK?lVkFPpx{(J`6CWup(5lnjsrHj{16<`J{x8LuF(0!gxLjWz=Qs^+ z^|lzy^08Y9&$1W(S^zh#ir}8?A z7V6!m&+&3LRkmlp-ORJ;^J*N@7**;KXMU?kK8OPye@3wvL6Z8+#m%dd7dAj)GT8jK~Wg?FxP2-{JXf3JqG6AOE#) z!l2_SNBz@OjDp9bH`@Iv0`)o7X>r0^$sP+ez8GUqgx97;|C;3GK&3v`x2sCNK6;h* zZZbyrNPSuzIxnvgh$#P(CBB>XIyT<`yi73Qv!JMwIAf9@FO9K`)-d zr(CS@;xnueA&R6~a$bmVo(z@#;RA(@hXi_On=9`^n)I>iCBgYTa5M#Oyn8jBtRwfa z(jK&g=`T3RSycEJMSr02f+JT}7UQMcG3N{4W2oIoSqTfQP-e4zC}0HXJK*^;OC^(S z=LIMrRreMlR!LjQq*r3KMSC~`R>6Fug2!&I!5~TfxbQh9|nes&Agp zbIJauJmZwKq0aOUC4!}ttHG=|vO`FuoPa?FFF6-VF@(ZpB67wvCnGZSOMZcd_((ao zpS#`(nOA!nW9(ac#7D(T&w9^;SHR|kI5eShBFBX1 z-0Krx5>FMMJYo2PAHEjvD>hB5r80zrXSgwU9`_NR2w}~Nq+^X374LgItPPIa*)u2t zx%^QGnltu%jBKpQ+PDTjZ%0HAlDlG(yN(fA!BpQkX3 zy$R4Qm$8xm_xj;U5~q1cPs9=~+)J55Y{h=W;!ou}wY1$<)>IGj?CH64S4CBVKK4}x zc~)-%Y6N4E-b6mwOzzxzz-BhM1efcd*(9uKxFTGk*L8yJp6KD=3q$FKVy@9(9aokd zltSPy%5SaeEz=r5QlZ~2u*7uiFlU-C>B!Pr>BiXDxXNq<@FtDoraL{V!zxdHx&!wa zA=Z=1**9F}gn2~*u7XElz$KQ%x#~5!hIE%rS@dhRpT0<(KTvzA=X@vT&bw&w=f>xA z&ba88Dwt26b??wwODTH9eZ7g8jj-q^O^uIkXZsT2@ui~w#)hNx{ir|?J=Dn@#dRgl z8&RArG_Zc2fujb@`b?Z1++rZPj7w(*{$K7rl7LURM%An@uR^20S$@WY=z<;=;>Aq1 z(84Wt;|0Fz|9aM6pV?Kz zH>TTvlEui)fVid{W0B<8l94}$Z){M z!Z=^=z1E`G{#F{y`;{2ZD5oxLmicBF6i|ml32mQqB*U-lY*|tdj|6d)Dl%={gr1M^ z^w2^gC4__wpFF-C=)**#!;5m!9_^GEb205|3L9bxj zoun0RD=yw}2))|4o6$0Dbrdx1=X1&Wo)VeuyS{-qdMts~t5cSoo_RocSK>b2mp)tk*qTc^74D$aoV6^Csy}$=y%JY- zeWJEk4AejUTFuTT;fJPkY_u&zo=3i*(<^9~GQRxet-68`357V6qWxOXp%F4INB2*yNo&%|j_fl*aP*b@aSy##i9$n}fahcGoQrUD6mLj?7M#pR9mWFx z21W?BFbbZ}p`KOTwfO2c3~|V!#N3&+xNQ+nOze;9$yuBI1S-laKL0h{nE#kh+>dev z3GkE|I-C-=={?DSvtqWm<6f3ke;i|V5n~yMa@haS8XrJgjfMeo^*W%O+4TYMbtx!x z`I7+yY|aT&tQdQ@Z>}pz9mFjg_JdQ?2v9SRqoK7ZV0*1Plv zIOzIjS*fmF5ro66O{Ujpn&V~<*F)5JeHgo4XqqZcGc9ZGd4JU}<>cocEH+b5Pjm|o z##GJsryBk+X_k?(OEM_XLAQ}$dPY`-Ed0XzMS8v)B=jZevsg;&t|G%- ziWSYc2l$P@9&AE)VeuJl1gF$BI90@HSZIqQjM!avEz?3Br+~n4e{wmVTeDMR;JRdO z2AkVB|hV+p)v35hUk78R5LL>U*+p- zX*AI#fcv;HuN5nf!#^_q7lr(55Q>h-1k!5JE&Z|`FFac^(5VtDn+1E?XpgX74=>5g za<%%8w(&v^$7@3Hv{$Vy5{Wr|xdNx?1Aih_1dGb?E7ffCoa`A?DwC_L7a#V1iW*5A zpY|G}m&7G>x2b_ zyIfsDTh*uD&t@Rp25HKl4gUoBS42$NKS6d}Jw^?U%L(JKX79eP4V2|}WS~IuzhzU* z>h0kbiI_2{K&YwNV%YfS(!o){)juH~byq_aY$?(!snz=>Ft5NNoh9bWI$6fG zD_%Pd!;ujt!R!6a>G?jcH~(oW=>Si@NvM-o&{H}FI8j8TM%;wDm{SpBw6WL-s!?@I zB`A3Izh=xYn!a+e@$=k6A%F*^>(dc`Vgy;JlGpb4aG_#Vdz?I%{7Zy{aQzq2%4iqA z%$LE!$6<6BPHu=ZSK@Sa!-V;hBTeQPYbNmHY~1YZ?ah4l z6V!~BWZ(dxWpD9w)1E>J>^0E$1485vV0e=}y*l$~_Yo-5DV9z!6Q%;T$kTx2=*az! z+ezX5ep++^Ht2twD9m`<6We^*%lGaU_g7WtUK7yaon&oGnJ>=4s0!j0`(1*DX?K+?&8|APuFm_1lazy?2R%~c*pZv0Fk5!r+j`B zQ-dOYz^|;M%)jE>IuuP8V=d_3G$+TMb{Nj)IXyV*VAjfIbOF_%%%rF|u63!TUV4EM zm+kM~@ zp@0BGe6~;ql>6DnR()>gG&@=7Iq@~3D3xh0CPeI@xakLr;+rfz5Hs=H{GAJk0GXJo z{=01*IWg4e7UX1sN>31#m)drq*md`b$Ig8jfSwZh@S^VZ6-_?aRxw>8oruyR{WDzA z$Kp2h_WkLS&)p0auFvfD=rt}r|Cl)ocgCrAD!H{#-&)jez==kf6ypMC#EeK~OeOYu zL1PZ(3i0QsBNMKBCXyc9v|`P$)B$}uR8ei{OkI}IM40*uTX%Mim%4qtOKDv@4DkvU zOXcx=9g6*GzIrMMZO?@yBPk9VvY8IGV-@rZ5QDj{?0uRi5v(!gWhPqQ@>A-H%-XVm z{1mDxnp^)+Gr$sMb0fEn1zm&!CX#cX|@5)@EymTJv;seNtL&P-o8kYGAKXVNJORWh!%EEKNy;zR0)k zUKJMe&pwk~+LGUO|2){_FTsDX(b+q>G=F0quRRV)*D$L4gk$l%*|v=&RGUdn|39Vz z+HCOu6al?6U!waN?mxP-JFdlf%g?$0q?8By@XlHieMrnk38K#Q7RO?aKTMlOuEP8=W41Z5PvNMv zJ5(Tt+Z!Q#Gp7b>?y}$r3o13Wr9ld|RfK$ORJp|RzJ!8n>O%IKg-{_1WooIvQ@i!b zSN0c1!?#L82`B&xCK&7U9fms%qRf=3zbK`080U36QTan`qm>v-2~;jJ*KEDPV5OYc z4`j!Rfpl%HtG;Cx6xuE29&9YKQ#e^@4$ldFEm4yDXn_O6%aY`vlZgD9aZLU9dKr#v zPXq(@$qR-wIn1*DX19!zWH{s3;CIc`ypKl;gt2TRz1st5TNyWCF+36Fr4ld4_O2lq z!I-*Sk;;pK%c~!~Ruoe%*I*-~RFB*17?c*dpLS&}V3h#%hSWG3O(8h?j0|a6&pg#( z)NX0*_cRbQi{yU|s@g|9mZQ(iF9Te46~xb$RjOI>*bJ((K8U4QmDDLW-p6ZiH#1Jb zT1Qz`KD2uhvStaV*4?#VmN{gq7IXJj8NL7E z)hBx!zF{Jvo>j^~Lgo6S8cT*xU4%#c@=wL|JVsnpDFUEtIK(#4v4l8%CMy~#9U~Vp%zU)=+L$l)tInJ>{Mc_j(b7eQWFey zyb_1{dk|MvLi9-)?-n~Q>U{%sUP1%7P;&hfR5&Z+hv(SotLx>quWZe`H_)Tn z*mDQDIcIjp^}n2pJG z-z4G>B1m2y`eA9yR*j}j5NqDn!cK2A7E=nVEf`_;Z7P)a@;{BV*0@^pgcj)C3IHRX z+O{ z02Esp-D{7t9AZEv0Cl_ZS_F-P(H0#t_~SFp%wv$!m;-IMES3JfBH4FQZk9vmH1)QC zTUX#Vl;qgy%27(s;@rVu!xA&w$@wFbZ1GCfB!n=!)Z@3ct@mO|(*;+z>sFA}Z zp~2iA0IIWB``f>2vl_YYKquon9-m%=$@1Cc>4kSrI?nC+U&1OM`4+Iz8% z*pFClJhL@9j4Ma;E5woK3oJSOFn5aK&-zmNA{3M6b@3Nbr37_LsPLiw-@Z#=20j9t z%yYOR&{_bDWmq~sc~Bf!^Q<3bcG}Bg8Zlf5}`zRl58E9L23eH5dR`I;~NOGduvlR3om1hrp-b6kJdw#2);_~aOA zlNJ9(k?$Sny7`%!Ws8AWk<~@r%IQAPPRiXK8JIFY5U#{*XCL$-UdyKHrmls0C(?xF zYje{}zj%6Y3ozQn^)t$roT!#9y7t9IR61TnZrF&q8+jIU8>SNBY^80OW!FB1H0&JH zw~%__IcZvozU+`tkpL+>+Y;2i?Ywy#h}euOyAj$xDFFNX==b}6NsQ0F)8U<6EM7zi zc&TPM&Vzojx~B4FmrQvH@2P#Cbn{Px;!#7{RSAYx5QYeZowqmMt(Vund6ejeyT2M~ zl2_CIT6qtYsMc9NQ^Taus~4^NmUIYuL7((8deiE|H?g14;y9mOZ)5J(w2hA~u?B2g z2`DEAh9gfPL2p>oFUNReV!x;XnNoKZ%i4Zhn@PlQ=?jI2mE0K?BN=DUt)-(y1NI@r zo%T|A!R+2k;*Ft-{^Gz%o@8BpOX&0%L!ui^tcpC1SRhNT=6cSOKpeHB9!>r%gr+44 z)8^}7?D-RVR5d=P6Udv;>V9+?P%uJglUdeyTM8etry(kOTr_eB4RRgF(!LDBV}K*6 z;QuXtkwu6YVASDR=>jn$7!lH!tCYqenN%WFWqyG^nQ`6I9^Su96m3tYzELqf4Vti^Az08sB z`95>vCV7$zdU^4N8Rd_dITqsE42+Oj1Sg*2h0xWJ;cW4Y_< zhh4Ify=0(uk%Z{(OSAm`O`8-J#fd_mr=dG(m^@WZ4&2ozhPnYVBuKXFcbaw6S#2xZ zBIg?p@?h(L$%^w^UJ@noeA_KJ65uDH^S1MOLS!R%jM$%~&0bBbCy)6wWq2w!{>$%U6@+T zR!^DI&Xchx;e(s+=p34)T}_!XH{4zRuAd&9nR1Sw8^rk#?Qn>nK1 zb_U8Sh$F7UQP=$pf_DQYweC@#g zP_FoVLYcTjd`3Y&eH3afP&DXpMF0>#2q;N=phhU2ovMYe=}qfQWU2cZ{%$q;Lj}^h zc$iQ6==as#a6or?-~FzY?h4cATepJL=Jmkyl9?^F3O%L1Ep0TU^`DLrdJ%U;l1<*z z+okwhB#G>g#D+oeX_9U5#noSwIDtRfb5{Dq6Mw|l=;Q=k6RNQJDSI2!f9acZYQYB> zP&b`V3lDi2+9!`Zk&~w^CU%^l>7SXpk0cDJv&Ggp*=9_bkB^1_2|FpfzPC{un^n8FP2HZQ9pQ+C?W zn^oeBD3WV**-v$ObzRT?F@oso{e(qhIm=2p9&k(K`_xy| zmK4u!D0TrJZ1hh&e#hr*%t#8w$=i?VuhNptl#2&=qrwVmO zGICm^uAetYo4n1D-S#JgMsBIW>0-WcRw(1$rfp+~bHRRcNh}9RkjCv;xVI)|J0zc1 z*|D^cP5BwFv>5GFuYGP<>S_{mao1&g0~2y|^ji7$+eul%2TqO3s@U7d)t-9TZc=9FszbDsv*&>sW zX8Ad|)h1SmCwf7-EryCH^20RSR@ikK(z^DK#G_v&*!Yd?l^1!jv|XP5RZWHFhLzvE z5^Z+ir8T6~_Z=$SU_MIwyE$J==Ikl%p{Bi{XM# zpnD=~=r79McHZ!;yBBYW2=5Q)lbPBcYj;Kb2i1VO-90OU;*7F2IcNPw`Gar3Egz4q z5qF_Ub>;;FXzMG(Z7Zz{;wuIMb1+gI&fMRS#C>(H9G;%-B=k@r=95GqXKJXM*-};% zN+b}Z>GpdOE9OJf!2{d;OhlE;N|7TOyX>jzIFh7T?w2U5o?QFdJ@lK=?*+%ZTP`HD zM-9D$WiB7aNhnXywS_^9+>^t`>`wHTX=>ul!qBrZR;z`20}_RpCv=32_3SID=P z4vRwzNN_4!um|m7SXp*krmICFAMTcOdMVEnWzBA^H-E<4ni4u4RDv>zk~GaD&BNy1F72HI2A5e^qa;+8UpuL#7` z&0T#NIbTq7zaBCx*%JfUDT|Qa5Mh!ZII=j#d9L11K`HRAk2G$i-8^o?>~@Hzw_}C8 ztYYKRL3@iDA~N`sEoromhk<^xT0hb&X`{DgM@TPUgiC;>x6%$L{N?x4SBd;`8qgo9 zRPk_oEm=3tX=fv+oDCQbvvH@8TIN^=(!fD2yWg6YOFl_j4s76>Yus-t&8J5fcoRzr z{%h$E_Tsuc&i1?z{q!EeXou#&depw*q6ZdW^OUDZ5G6Xz<7zNi!m638;(R|kofK5{ z^=QiRo5bN?l&i|TY@?!papAl!U`b2tY`WR>Lres(YS( zSL+nV3f+8u1@_yf_2|ALnlxt*Yq8Emc8akvmQ9ZQGK;Bdv~R^)CVzOZj(fezJ{z`Lwv!?8?~w6Gq=!jQlx%G<@;491W3k1nw5E}y)9kj zm_)N7qDvn5hW{(CC!5oWI-OCY^dj%xpSxL4LZoh!3T1d#Z0`%Q*UHn+bl`(e=mt#n zwuPFNPC7U_f$%=!S6OE_vSjk+mei@cdhzaLz8R>`Y^ z@3|7%P|;ohYTr3!mh5rG(=%C^DGHng1=@3WE6=EP0{JbBzt-$rl?Bu-0~yqXi*=@* zjz}=*fpww1d*0&WnWEbDwy^J1m&Q2v(hs(7oesSfn$DW8wYm!f;wSZtCdV%&W?{#z z5&>=#ONp13e^E$vR29>`pf$C2*RzCO_>)Icx#E@s4+n%hs?$e*e8|5Y<6MFIThc1U zd$N`e6f-g<>W}8UUAW>WYfA3*x!$0b7f7-fc^d4>(ZzgB!l4YwBLHuf(i37Ll30|R zGxzV_Ej$7x+U&QV?H=;n&Bo|E_I42B$xW-b`qhd%MJ$DEPcBK@575Ms@H7%nd-XA# zRgoR>KhEfBa+h_ecSm{0%gV9l{j$y+_%;MPp;Z3mhY@*s-;nMRRhio^SfqTcuWXYz zJui@1O0Oyz=Rcz;G~xKXd?;ocz6s@5m42s(1im`N2$OY$TBwfaw5Yy!m26?KnEyvb zM^=Zc_Pemc-;YxNQyrv3pRfQV(l`eyRio+nmlhn?UxNV1LG@>aoj0eCopXV~AW-^# zKrb9#DV4|bBCOFsWv%dx`1-|H?6K01t(=-kS%`%qU$A7(6`_EG-(mCo z&HrXi(#XFkk&bhZZq8d$2&FAi>O$29!Y6XG{L}0uc2LY;l(z(95tBWWB15_fz1-Zk zA95sL2_p;5zJ0m*;aP$kmvYi)4gBImJ~x5JRjdou*zOWikVrvGfzm7d=iJKDVyk!r z|Jb3 zrM=JKY11`zbw56*=~7Ka3$AWeFehq$N#k(sRryU6JWn{1;M*$hWRfxMfezXYK9J}5 zXhVzKtHfk*_!pZ)7Pn1QhofG{deSrjEVKU>&}*+#{(?(SN z2z~!A6%M`g2`=6s{m%hHx~_Y#$>15NsD7TON|jy{3Tc?g-6XxJJdUfdc67!|A=fa? zac}T^Uc)<`-SOR{hZ`WNpLWRS#RZN>6JxA~0nYM_j+m8_rq+e$dg zYx+T*Q^xnqsM%VXhKjFEZ(2xo{0l%6*;g}3z28$`HoQ&KzQA5v;HZQpA>YZlawQzMJC=hM)p03S<_^7^>Euk7^;~YX6LycXysR2^ zorEf9Ht=_tt$Y^jEQY-lkuww54uTdb&#KmGv0f8GZ`an-tp_*AppT~*bg>0{p%)a{IyqS_ zWiyhPAL-Tj#I1}C`@j@Fc77Ckv?oREi*IkjN`8#Y%SV3d`qn|vZ)eO3OIn+E{ycOZ z&lLB@=e&EaoTXt@s8>1W?8Uke7k7x}eqHi)=XxbO?Hv64Q;i)V)S)SIML%o)%h6_<=EL-wrNmLRV=oh=@#};Gij-}e^ zxJ>YFImn&A7-)+r4i!wDAQZgLe(TGZ=t{0WdOqM%M!thNz;`4$wWXq}Q7)WhK$b?@ zJ>t5hgdg(`F3Y86t8J_|*t;K|=KB)U|Jj~`6OKHv2M{0&$q!yPKvIScvu-sw#*jTz z@IO6MSkp>ht_>{YjU+&nb17!BSn1XK7w>mKN=ZpIMLs#jMEje9e4~{@-AtCtb7DM( zqiHZc^=9F%pLp0f3fp*k8xB_#2Lf*tg)<balif4Kp(v%DqlhU|g zFt?Wn0hY1(ZyARjvo0fkd2iah%@wnG31a&J6YyY}cyYoIPWdxse#64kvMW_Qb%?gu zscot*-6GiKM1^^l(^9 zvKK(GD5=vw2UQSDq0yT>dzNZ(h_VzvTWWj5u%Z_qSC@Wm{z57W6UaRlCb+|wH1*cC zCHbGQu#UM65PxX7&>S%1?BjI{p`0U;@&{Ijt-LNt`q&RjNfO($vOwg2*#Oq+e(kMl z$tG1CYKWtNlQp9-?O(sELs+C+$%}?5$yVW@^69heuE#JL`z4 z<^^((h;I=S_jqW`5BjH;RsNR_k>8uQI6r}GLjQNjLzZz{7M_yz?WAuAPdXHKTSm+{ z{E&=3bq!+mcOM~W6rK$`2#)GAlzS!IZ)-LzkCAo z+bX@<5uLiIebol%LSe1xhRoVb{X^Y~29yxs1?to09rkJrbHsK(|BuGn_SD0b`oBX7%r}rHzc5hbN}O zFoXDsBkYo-{<(w&wD(N!@mAUnSS;!qrB?4T<`TWQcPtj84W)$*Mi^Z&@sbMeJF<5q zcJO=ano_WNqM&4W%cGTDix2x#on;D>^fNvKA`$2-wH`|(1p@)k1K;RS`$y-1fCNI!jx6>A<7>qa)0g zw?2-daz5eaD%h319o^7~!)fyuR{qwck4iTe6s07PH07%(9r7dEeT5&bc#l-}&?UPu17mRT-I;SrHkrVuel{z~^+A`r!XO zvsWYt!pxnc`kf(e=Z^rJ(A!BuNq@>lp>b$6wvQ-RH#$3rFj&&a(QZb;m#9o0V?3T)@^XL zF}4Z<4|qeKNDsS+)*U;Y5ymvbk{`^pro?cbtEr&L&ArIFFeMz2U$uLnGRqq-upCJG z!;g4r+!l_dB}-k`@kY*#{cCn3VbirfV^QH*%VNNulHabY3WX#SO|;m-NH=r(lt#Gd z2O0|>rSIAg9C3<}W2_r?V^;6!vb~<7iRgR`q|!!E$*-hhfXgjaRcQ>8t^cI8QCElY zt&xYgr-4FNJ8>_@efPkhO{rs8pe z6Iw^UKwE0*`lCs#7Cd|O#n}+5)4b;`5-E5uL`X;P?`JTm6UIHMaOeDve!_GCd6%C8 z-5!~;*n}U`({JxaV^{b_r?4p1*!06wKGv(03=?p`lK)XjS`nDjOl2YH8KCa-KLC&< z#jEr;Z0*rw&1-r$XQE4}d@rK52UHw$MSyG&z|NFTcd&-|!TCp^M%u7#YE_B0js%w< zju8zHV>pJ7f_{E{0Ag^)qRLeXK{5_}p(2l7Di0ozuL_ULU7kK5IM)pEt)o2eXDe$- z#R#TG=znLK!7d5SRgzOZ$m+QYQhF~*0?7+A7InP#43t(#QPSz~U=W+Io@2o{bn`Z- zWt_P=$808+#=nDxO^%U)xd8`js$f=DxNHQ5M z*Lai@-$MOgYf1Vv8Z}>fZ;Hoy<8c>xWd_apaH!C0-smLWQcrORPKti)PSVR~x7 z=M+|s!L0lax-QK5zlY9sl4J%5%K7)h*HSA;&guurJy~Id1|RtUaPVstqSb+r&!6&D ztB3e0ne+D*EU`)T0LuEmK^X}3UWmY-%HO7+RHmTp4A0|6Qv5#wWGgQJT&4Mv|4;M( zZuj;-Hvg}d$`Ai(@V^c~OG^K3>HgPQBsD}oCWJ{O1dBKXZk|7~vM?NV4m6Ov^j|;x zPq{t_3kb8A~;Pn~E=SsCr8{9CR_HCFt z){NMC!9At_#fay0vF7~-GI&8Yy4=sS@IS`+!{vxe4Y(lo_0uJp$Z(wJf9y^h@tSUR z2wow97MJb2zW2V`q{Xbzz{Imi65%PJU_G!Y zDdiG^lRT?bs?nz3f|f6=(61+{T>>S)^ycQ-=`xu;gChWMRaPXgkGRdF!M=CNK~sxs z3M?+eTR2obwKKL=5Wcy;Rcd`kr;Jj3kzm+F9k>Vt(z zaD=$f#}2STDa>eXSb9d(wA{OuDzhzjwEm-%L=j+wPiI1o^7 zUp;T{=`v_K8#{)E#-Kp(fRA9q%45oc(5bWzx%$esSir^b-x5;(4+)9Tg zP-c7tgzzDJ({k31W%td*;9qZz7Y}JC+A7;?IWm8Gx?Q~%6cdEMCpDNda7N_m5~bqw z4ZqL%`_b-Q!duhBOS*0uP2;(&#xtMP3>_Sbn}Jd_u~XyUQshH^Kox8$F2o2|OiM){ zPye!de;izYUIfzVfE4UyHA7aZSZ(j`+=Y%c2Xn4iqW>Pff2|xorbl&OV6|pT`CUBw z#dz~@KDEx-jFK1g)tMm4lmM4=O=eIyt#0PtYu-6r47449Q)^#UDvPU{L{!@RH z$FTX)-mwfLg$`y&Uf#2J46YOVzmW1^g&#wX19G7JCpiXtIx&OdK_AK9e^vuy0`y{w zO9-Xi*kfdoS(1$OOvO?G*1O^VPcP{GkY2Ts!Xh`=!LPDTIRJU@Lm4ABKkdm@XEc+( z!TyH=a{>{z&pVgZuK!^m&Uv%);gXFx)<^APDH!f{3JH5_w*Fj}y($Am7XjYaKLFtl z{zAuNafP1#{*;&lMs`a*i;&*ji)hDBR=@u>4~_&;ujY8~S8Wz!>1KO!cKl{9i;BVA z^`1@t`+;V&Q4WU4Uj7;Q)J@3ROE8GDrpgO`UTmEPo~Ymd)AGXzBetMt^P}fBP^!Dd zE)OO8iZ%PhV6Zao@rPd*ETe@=B){=Dq-r`iDP=7eQgayJhLg|AV}m1cOwE^Z5$D^n zd?DJ{f|@Sn@sD@aDp0W&geBL8kHW4OPY-eL@4zD{H_-1bULJLITs4VsXSoM#jAUX} zz^;eu00!U+7=U?T0OHiHs!IJlCb4E$MF}3jeU9K&!Ji8V+{g03 z{IK$XHV|Og0akzcJNp}SKW}URZ;S+8Q@uS4wGh)RQ2jRaFNpuu-v&E=&6H_9;qJU- zF&d)oVzAbA^DExAmECS2yLhPK?=0)h#fo_Gp20ZaLg;G0$#%boJHLSrk4Q#7Vv4dy zOOcxX%Q zTqiQ9mIk}h6`>L@eMKYsSFe#PmbxTn>D0ca(K6+ga=gYNIjJIxHSOf`mxK$QXmwhe z0JlGCt#zWm^mxDE(#`owl*Y%a(+j4<4kFXnL-XI0#1Np+r4iF!Rp=mhngwX+R<^||A{?b(6UizkJfCmho2F4Z zt=(wzG}V+9wWdG+iRBPb3r+_Un!am`cfI=9BxxI)Xo!6GepYcmR*|-~>#p1wSj}rm zW?Mvdw4ud;lsO<wxz?r)ttW{9u1yox7>3?^QG%7_MxaMh4;)9pl6liz;@#A@IQ_P4{<@ysm#?U zaFQW-I=_>OUQhjT%aQ7hus0L5{EHmI2iOZz{(ciWnEWlYJ67bybfd z7!XXtoW+Xv;A=cic^aBL#Q!Bvxrp5i{x241>h-qt>a&vOJ}zKK41GMVs_ zt-H!4(Y<9ZGr7X*ReE+aOfL3*gE>)zy7%)z_WN(NEitR6gq6zn$!l5a4cS!2wvK1j zC>c%6*(6)H?`Ergx}|`oE)WR@jLE9RBo-MfbUe&$YCIj~8$|8jmpLiyh|0oY0Wvh< z2kjme-$;CZZN{Vx7Hy@^Tmi2B2$U_B2&lp4(sOurRNC`Fo=p@%ZrjM({5ql~ZgMp+ zeT1hCJI2|M@p?vPC@zI;#f4gzyEaSfka|h+9bFsmPLvJOGX8ROSLML2ST79~U3mxo zLPO^^9<$G;xI2FuG)bq|^wQav^EV5Pw;1?5hgo)g*!rzOoKdEQtGb_i`f-(S%M}g? zG~5_WGUdLSsfOpE(rp7pJR-qDC#`=$kSRPS4~BhuZ>zrMm=2Ym7?Zm;5*HA)OuyZc z(Z~O6FYc0sGQ0hI5rXr?kxcYmm(W-6v7$!Z0H1KCuBDLIKEIad7baokVY{&nY;ibw zD099w<7Or#8Alb0w+C&!^d}YmguRx@b1ZsJXF~;s(_D}_|CvE^ad;oSP(Ou}DGozC z_LdG#7Rfw&o9j=dUn#g+J7cz{*@dREB!~%nt>U~+GWTphXiik@WX_WjWHl|shiW1E zWmNcG_Y0@fb+pXL_X`mH?<$CB3^mxpuM|zVq0`6v4SKEnjJ69P5%#Ot3w`h@cia)b zSNN10nsdy<1sgFstH8$1WLWY25z({X`Wo`lkteUdBeH!rfG%`PdEm*G^<8}uSo>cO3JF zyn0zgSL*5d%-2*AogQmY-f5dDHlID$cDwF=s+IsJ8Sw$C-s;<%w;f})6NVXW z+zlCYFXF8qp;b*&D5*~XMSb_AFYm0v`IsHZMe(v+-7@qMt@e<9oED*j%F5* zTA0B<5kUT@(2V^5DMqW$QIL*ERBI}PqpA?*!e@2K^pm|X8PR4JCj8Dr=Z$Q^$$g< zo27TG%yC&AhhDs@RG+;GR$l5&&C-5yr_WYt+TK_^m@Pl7Jm*LdlNp^UZaj(ooekn7 z*|6ddT2p&oEP^<3FzEAN4mb|gT7I0yLC@cg#*pq{(6EFq@1wGcdliS@sIG>g=4xvV>V1Vhy3%>y$4>pB!59%LP}R= z|6}UPGV)=Du;atY&|pr%@Mc!8x-4%_AXUQ?4U#4Rl-@z|ji4g(O~-yK;2HF=~t_v(qwkrdCbw*I=i-XXKoKGo@m; zDmv#nx2db%-k;?4jmOM!!0l*Iyhse@S)yDZCx%Mh?@k3d5RJ6uVl z`arpsbCJ9RRhLxmj{gsOXosPj)MO7~X)?Ax~9wRiRMX%g7nXyd^|Oer{>Mow&S6FH?GU{J4NgO-Vvt zqpsbDg~=}1fjlQOyxr*KMKdj9Hvi0lNcHqNQ_G`7q||D9&7)lTeQmm${no#F_@!8A zpce);nK7R2xJ#C!B4xG_s(dr|L4`M#u3rJ6l7k~5D5_?+XgxWSxtFG!IJBNap7TI% z-lDN%+uSyRm(x4)4KbQ#E)ZO2AQ@*lZlP*JiGDuxtSRm0vU}Ol4h7X3_d{<4P3@Nb ziFAK!Xc%WPJ)Fz>lyg_D5CNQB*0`nKW z^e9T-EtZof1C9(cJ^4Cb)6-sO818CZHAl~^YC~X!Np2oBIbWPkwqgo5V_t&zM)+15 z+7NYfV{ldn$Q!zE) z@1IM@E}S+N;B6=cSP*D>xwl-HgJFMO&okF6`M%O8nDpmfXYSr|NDkvC`LxZa)osULGI*bTadZJVTnhU4OGN?M2#q^j+3+i7MVQ#M5Cv z+*!*5_=vKq>0Bx%adlP3BUh#qx{gmM^I|A|khgy69<+WQNm7w7a8k)1+X&@brI(WZ zh-R$AI&6&T75D*PGBwH_U^i8t@FV7~=55z=sj0}t!ZGuq9U+p*3O1Tq40OuEXzURp zq(|k}oq>4%6nCWC#0e!78C&72_cI{-0bntYu5W2~ty%pG!UEx9t1Y>gTuOuc{*rB> zB~y3c!L9CzmobBBCu%Q2^|SgKZ@@`p2#!fc^28qqWXJ%$>@a?P?{d9*{MaPhh?m$} zc=!|Pq5!^3bM@b?Ho82NUpGd$4bnI$wCW73uOF+mn~hvqY!G;mK#+-XqCVD7{6qVE zB%gZh*=?HrhnXAUtFb~*%LQW~04mIMEl&hziZ(|0{H*LaEJg;tyrkrc3CpcC43FA5 zRJXnZA^-ADq{@IR*OS39FnI`g)`n}uk9M8OYhT6p=#*U}R74!YBP_O3g9JyPk~w;) z%g1lHa*{;L;p%r<;{H47OkJY&ij;m?K`xs9A^10)$uI|^44lPtV0^KXy6&BI2ELQMAmwS_@lW9Wg4|!eN!;~_ z(wUXwXEgBTnyuUlIBr3mM=^6~0+bdv-pvMm`lqaQ?+JCp>xE=a zmPPQ01+0cT**O~>rov10Cnbs+VDq%xri~{!^i!l~))nrVPIz<~~pDFNpFu+~>JQJ#-tnMg*wl7Eg6wb=4^i%h0Mcxs)oe08$?`9ny2q z+-&Es@b!Mzde5KW42vp5Fs9^*t_WoVbEV69V=_>tcg1hf z^<5%$TWT_N@x6`dn&VIEV0!+o*{JF8;0WYhsfB#w{TTq*&Ge>eCZCRUw{v3xY3{FV zbMd^bT3(j1Nc7_iOW-7?FDT$f`VKUXEnu0Wpr2y5W&pTeCVZO;`LFk^h5fRwF_(nHzx;UXloHTPWEl7KwyVozGxK_b-UFZo}-X>!ls? zpCjtRKI7`~QJmK08jVq=T*{}7SU(vet%3BP4rj@YlJ;RITTEl;3zf6KAVh$gdGajJ z$|P=FteqVuvYyQ>HBY*??4zsDqC(gRs2`I`hceO5I8zQ$`TH(6RC^ZgFt=mYAiD;&gZ2 z=V%?4KCZMYR$vXb@yf%54ZT%K2U45z`wX1==}f59aRhs& zgnUo~-81*XB_isN^+8hN+8-pmJ!>qalIkyiC|sEpm67R?`E~d17h|!}nvxPmlKW@P zBU~=>d}rBTuCKFRr94Zj{9EDn83_XLvDKQH8k;?|3i z%T(9*+e5uJzbt>gT4|!cqvMQa|DDCBy96PPt$~!l7kvjmr_!bio4BUWxN%Jp&v*bb z7T+qswb;pcuRjw5;6j#7HQ)6Vsh56hj+JNa#=@d`2d)h5zVd-kGs`9Et;O;rNTH+g z5E!LzcLMGz>+5K?*_oQJ437day=Moq4A@LND}JRjb#-W$g8xac@tMrb6C@RKx(04u}k z{WJGm=O*;?EdN(9pbop>?@ChkQ8M=g`6EDS;mPkG>`~|WgS5LJ#vvxzsRX8Fb=BJiPw)ZP%0+h_znLV?rZgeSQ#tj@y&nQf}Q*p?dc+@)a%8O_nt+To2cuEEIRq zcDY}Iw2ijscP*S+tc=J>;>6+57-A*^58oe23rJGrx=dNen#Mndx3zQo6u{rIXQLNg ze`A-{JO9qKkK(>bE0M8BBS1e;Svkqnid$Rvpz-cXc!R*ekO+f27xb8Zr#Bmzu(TUZ zUQCzUtXG2441SuiwVEah8+j8#1e>pF#I|*FZe3`&8YWXfWD8>+JT0V#7`-rsk*CEH zo4b)H>RjLGyiTxJ*Q|$z(L1aqzKAy{)^A8a5AI`8dzb9t`eMbK$QOm`y~gY&n+u4L z9Nbk4@)0KM?>YaTg652~(sDX!p06Ylo~a1D{aAfnrENW_k-iuBd65I{^uC7bT$)DI zhkH9!YgSXWxkj7ltK0R{SaJDBZjx-F5C;X!HM9LQ^Wu`Ki=1(VWmg~K{ROigsW6a$ z2w~9EboDXI;WN?v=*}0JoIBI5(q3;x>yE6Qik(pq@fS*w?qhEDSPvBi>2V(cY4QXI z)nVy#X{+DnbJl#>N78oO_3hGl?l1?$?HXz)5?Cq@raB-RM!N_gPKJ^37sRaJTT1>X zO9k|gVx9mt4%wt3Mrr~lc91Pdo$-a>9Vrs+%q&HvvfhXeHH@ER_O8sAn8N;h$pQIx z`=X8S6Aueb zGkY8%mG$;pe8}6ZACqKm#O%5B2thsu-&}i)Lb3ULp;zWUcJ$o^omFakn_AuW_nW;OE zDanZ9j*gZ4K;yp1faW1R3z8T9Y|)wbBbY zrd;L&N2@I*wH#*zLfz9xgwc&4SK?xEchF1e68c_AM7zvznonE6ABh)nEbk)9xpt6I zoyRz>g9ms9JQ<2&MV-mF_lR6J9~W)-T<@3|7k=*y(Yp}ZF(`#BpZGiid(Ecz8M zIB(&f-qkvOGOb8Dubs^xy_ly+&4?smrFmPl2lf8unY$(D{59Z^?|d!tg)g}y7U7lV zS}@o-YnUjr-L94TJBbo43OZw|bjZcPY8o4T)LYLWpC9seZaHSb3XwS!XB~Mn!I=N2 zV=Rw1d4m~-DYCU4R;jaL0XXpBp3ZEXD&A;rp}4KtKQSH=po}N+_#MeaR1a)j;%wZ3 zDiZ*!oSw91sI~I?wif_%cQn(27EzbLemK4C#@9^SN9^y4rRC~EyTVq;Hy=B{CfKk$ zeZrfBh`}JSg){3YG^Qy@#@uq8iXru@hEFrrb5k=>V7>3AAaPx8m$zmt3?6>AkTAgc z4}-95u=o%jH~dk$EKTt2*EdhSL+tO(IJm*VeYyIbb z+^^D-VVtZcBStCzMn%bb?MRj6e5;d`y7No@s=pT|dtrsP(qnvTNdc2Ui*0!l`LEjw zAslNQY)yRcl_B`^G%fV_ETMt2(C!F%nbYMuv`*cy$rjTi^B8%0jKnp0fN4;w@`CQmd#3jrm724Bhq0~>y-+B$1o1zz z`scjd?KhXLsAKRQQI>MLs%Z>z&x+(A$Cz#9y@Jw@dc_{+h7hG*=g>PjxS-bKVblCp zI8f@RSI71xx-Fh0EBVs9lh5*A_*3I|NIJ-UsW|v&$y4h*jRHRm2{so5iZ#$^;GUZD z;E9C_@VQrXV%ta;>Mx0t+iq=B?L9AC@*JOAJxYi!>ZJb~ASW487lQgT=;vG_k5NnG z$h_{QlCr0=bW$Dh!fuVjmjNYEMx=1frwozE?IheaF6%|WFp3?CgNuvV=u}X1E-{Xd zxe*d1QpPjEv~BOVg&zkkbcFO8BYi~@hbG~7^ml+q@n_OZ2G5@9P;|)PZ)-8qQgGA0 zy#%aVPZ=2e5<<;>XZ};l;C>s)#Iaz<{Xw&(tdy@u!3pWoH@S0x(9}Xa!M$eD@G*;f z+gq3V2ERC|!pn3KYiQ_YLIlz#R5{8$rNUsQpgrcq=8=4+YG*`3?^Df>1MXvl?Uq{U zwVYj4YxYCgm`UI1AAY9G*;1NMYP?btA=U!M(6?1oEyt2(Dn2quX;mbj>xIjjTm|!* z1^ndNX5VKW%^ZJr;gX}uQ;5&Cw^W)u&CK+++iXiNV9#uP?jcvYhz$X>W*?jIF@j3v z8g#$wY&oJgl@nFi!Ha}94QLwTvkMk`Dq?~?bg|6Q^J0BA-=VX5UTz)sK9-cnZfx*f zNr8!F6nF=(|7%%W3tqo-1BMVw_~%RE0a}fC4bia{awD+LbU9YB1kWtk&35*3vpM0( zq=_X0F@b;9|8GB)fj9tqW8KL;ic~i}`ehc~BN+`nmp$|I z)3=sflv*M)Hmk+RFqGZjXuoMei(`Rb1@NTa=(J>J<%Y|PJU-Et#jQ88EMnyuaHwLI zeT`2jhOeTVDveT|t_;^ww!(35iF1 z15EHe!JF70;Kw}3U?}5?zR0INUTAXv$Wq&4m5ocwa)VY~sUItR60h7+p&QlGCz#5X z9Aj?*ig@|a4cBH^m-BNBdcSbQ?k;Spv=CHj3YB({;CU75ZoBD)`1>3k>61UrsWIj5 z`N(2$y$Qw3%=e(HlG|b5<#PK0(-;J?V<$L2of65kcpj%plzF8{Ai4pS(yHM2FJKv~ z@O9Ne_+Jp-9?vB2zpeE(NaLgGU#5cEy{i32YBG59trYWF8yMhLZt+Yasm~>(oN;*s z?{Xb@#{#|sE10ydrD);+^I0waOT!vZ=O-)#~}L?qVhQgje}`Z|U1BvI}|=_|?ei z$L6SAkdq(wgJCaJLtaAbS2eqp1IPl9rAng8N?SBG#t7STD6?Ots~>X3%)Y$i5CYu1 z$)30w4C`nO z`#4SyCTLK7-P(WLwAIxyggU!4w)BC%f^^Rktot)B_ z3fAS~KNK)XAv2*9;tJtAgvZK}#C-K1j%g_x;3`F;p}^3^?K2Mki|cUP*u;2T!~4otHr zWIvr%b7wrdHf%v078>u#tGGSs%YGSw>#Psp*HVKmfP&zPK0sy45~dUWx)SN-Ljz2V z`Q-VkW53+?ZaqhI0**`*fx+(O%smnJg{HAGp$LvYH&hw=o`gF2?qIx>PRZD=AX(Kn z0o(~T$IhWyUvjQj)f!;C3ApAGLDuu*huAQ{m!RsY#HTn~BS<-%VVy zotSCVO`w$`^Q;ZeIp-cHKHWS}(Rl?*`K0L0c#&caFl&;lJ?Vnd+!3%8xG%J~)_J69 zV~6h=hQSF0_;40oT`Qm?+M0O(L0qW%43LCho4X-6V|P8yaYmPyBbU(k%rDvF_%T~2 zcKx&N6AU<}41y4?iZ1NUiwt)FS7hf^yS9+W+7~ z#9i6Gowe~O?zmQ~1}-Msw+ppBdz;2Ah91sh%&3wX3{M>s$h%E76QB}YdaJWK(TO4e zF9RwGmggQz7qtg^EEW71CaTdMc7fbudtCflYP4T*EYt#&-Tj!cQ**525b5HHF1bn; zoj5)5=fFUQ`}0opVsuH-ouz1>w2MIQH{-l+dmY)w2$8N%rh$8aa!T!H;38{dvR7e0?8tgTu&RkFS{^ zp+C_dCoukXTuZ;MaDY0dazfDL!}FB9N}|HPQ?S9VGcqaDgFL5d*K=g^a8v214!Uxy zb&WVEs@`f>T_h)F=xtpyjO-FZyH@*-syw^-tKI{08)3BW~+}t+1_1? z1|vL>x~Jd7>rTuOMVkLGn)oX47X)L&&)r#Jbphg8QM-0rTw-PD8!V@hweo_rE+rA-mda8}bEQskdy{`bkYKo?eowd($uPgs zN{d)!YL>zvLhN>@^r&3M4A1A^Jz(W~TZ;z5kLAN?lC|=tkUl3~QAYMIlydC=%i7K#*Qc|Un9nCZrwy~8? zMQyPl!TtJhK>a5a?NL20_X&qg0$EYo*+VgJ@a&=FTAiRE$6`<)9F z9$5^}DSk&k-WxJfisF~{=Zxm!2$-xBBJ!-uD5yPiP@T7tgBvrN-=8LYlMM%J+mN7} z_F&%~e7P$@b+q9fm$Q&}#LQ2Mqv?jB$9YK0(obpg;8QriO0|BuojVG(E7Tk^66(z7 zH_$f`0v(SyrRcvPCQbaHC-2H4*d%ML7%y^`*q{&sxTyq6pCQq5K)aU2qOQ0Cw8Z;0 zOKpjYt5Rl`?AP^>K~<%uXgeAW;j&L2XXz1Na1omYtR-WTZcBd+i(ZHpTuRWk4yjE+ z=SKJPmb*;CwRGa^k2;_vP03A?(vrdo^kqn$5^M_FGxuD7u|{ia_g_7O3*W&{)2^mE*iUR?AY-8xr4 z8bihfLJACQYV&?_XrbwXdN(&bxtAu%<&1O{`_GnhN|5%!W}Vq4H9(;_uhTu@rZ@K^ zCy*>#$5RJlN07|&9sTg;F9-o33@=E9>|u`f#t#H881$MIlh>}b2^fV7L?-0*$iO2C z3cK<0^t7XRWbS&Zp^BufrV^j5x94poup(Y6{17{A61+64!$x-0^=1B$Q83slf6g6- zfgU8SSkIjHYqg0Y2!8%ux>kwbO7~sc2_B20QKt6(MDH@eWql-=Cve?gEHM_8(0gO% z!qKmSzjheBC$H1D2Y3+_=@-706dU+Nj8>PFy?iJh33n}AN_AvqL=g+5Srrw?Z?(f1 z`@;z(VT{5bju-@xnp=;a8D`^CXFJnvc2T z1+}Ow>-}hbjx63wA56yQy*brlWhyCKMG;gJt7Pn}>e__f$Wwm`TPUNUH0K$S87I-5 zmK*wKxkYMWCxZJ zKtn~=0_*d?SE^Y&eGsw`z7J*M8mOTr9$di2X+oAYU#)c1R}CT~{Uc@MOa~!QZV;kC zO(c!-^+OJI7v}NI!v9FqrAd^ajLs#uY|cTWek?B&^D9|i(&aX_(k?RcfC&hB-flA7 zO2V+7*%5G9=?wh5U@B-OWijz}`f~YU zCtZFrpO!z$#RZ?S+@a#=`}#_=x$CaG#yN_Ghi*^QR+~k%{;=cN-Y^;PrjvR5dNy@f zQUvfy@w{1oegHhfj%uwX;|H%WDm1v`7WmbW;*l&>E=A@PybGf=)NYsHenvGM{;2-i zi0Us5#)%HiEzkFrR&KG}H+?mp{mb3M;EYYgq=H7e^<$>MGjo98#@lsGZqV@= zQva>6z^&%PmiSj9ED2au`E$|Qr7J)5;-95mTpwJe=wb#Y+ee&^qNDnM-Xe#CB~PADntdw^tZ_8 zFR3t&ym$Rb>CZ9Ow*qu_QrKmE@n@xO(?;BW5vAEEk^n{@*l){U@qo7glf-jUcW=j1cIDfWD z+i7X0XH0M$U0&`}%bK!7=6H)`a=uQHmT^ax?R9-4(?CImS$}SHSa2)OXHH_}6X|9e z*?*kJY0`rTsau`8Pn&rBQB*rExmzUGQ!fMM-ZWay!%lvu2nI{dE zy1?`m&>b1`lwy&zKi-XKZNK%D?OvA$cA+TZu7P!D739ecY0*g$y0FZOK2%G%5OE)K zU`m7R@y#KetaZ7T9V=s#kId(7)9Dhp+E|GAM@xTkX6`~hE@aN3`?M6O?>5@>Rr9F! zbEvii<$7A1wI=k(Reh*Lu-jovT^-)9>z`LHJ7qGuC>iQXf*XJ=-w=7>j+aDq1gYh# zE34~Fzr(8pH7sW=02Q~?)8GpoZ&>w|zxKz{dct`Y6%2vJCFIamnz!qt<|dRig#KFo EKczoK`~Uy| literal 0 HcmV?d00001 diff --git a/modules/model_flux.py b/modules/model_flux.py index b5f482f2b..636daff65 100644 --- a/modules/model_flux.py +++ b/modules/model_flux.py @@ -317,6 +317,13 @@ def load_flux(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ch cls = diffusers.FluxControlPipeline elif 'Depth' in repo_id: cls = diffusers.FluxControlPipeline + elif 'Kontext' in repo_id: + cls = diffusers.FluxKontextPipeline + from diffusers import pipelines + pipelines.auto_pipeline.AUTO_TEXT2IMAGE_PIPELINES_MAPPING["flux1kontext"] = diffusers.FluxKontextPipeline + pipelines.auto_pipeline.AUTO_IMAGE2IMAGE_PIPELINES_MAPPING["flux1kontext"] = diffusers.FluxKontextPipeline + pipelines.auto_pipeline.AUTO_INPAINT_PIPELINES_MAPPING["flux1kontext"] = diffusers.FluxKontextPipeline + else: cls = diffusers.FluxPipeline shared.log.debug(f'Load model: type=FLUX cls={cls.__name__} preloaded={list(kwargs)} revision={diffusers_load_config.get("revision", None)}') diff --git a/modules/processing_args.py b/modules/processing_args.py index bb09cc0a4..154f5e272 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -48,17 +48,21 @@ def task_specific_kwargs(p, model): 'image': p.init_images, 'strength': p.denoising_strength, } - if model.__class__.__name__ == 'FluxImg2ImgPipeline': # needs explicit width/height + if model.__class__.__name__ == 'FluxImg2ImgPipeline' or model.__class__.__name__ == 'FluxKontextPipeline': # needs explicit width/height if torch.is_tensor(p.init_images[0]): - p.width = p.init_images[0].shape[-1] * 16 - p.height = p.init_images[0].shape[-2] * 16 + p.width, p.height = p.init_images[0].shape[-1] * 16, p.init_images[0].shape[-2] * 16 else: - p.width = 8 * math.ceil(p.init_images[0].width / 8) - p.height = 8 * math.ceil(p.init_images[0].height / 8) + p.width, p.height = 8 * math.ceil(p.init_images[0].width / 8), 8 * math.ceil(p.init_images[0].height / 8) + if model.__class__.__name__ == 'FluxKontextPipeline': + aspect_ratio = p.width / p.height + vae_scale_factor = 16 + max_area = max(p.width, p.height)**2 + p.width, p.height = round((max_area * aspect_ratio) ** 0.5), round((max_area / aspect_ratio) ** 0.5) + p.width, p.height = p.width // vae_scale_factor * vae_scale_factor, p.height // vae_scale_factor * vae_scale_factor + task_args['max_area'] = max_area task_args['width'], task_args['height'] = p.width, p.height if model.__class__.__name__ == 'OmniGenPipeline': - p.width = 16 * math.ceil(p.init_images[0].width / 16) - p.height = 16 * math.ceil(p.init_images[0].height / 16) + p.width, p.height = 16 * math.ceil(p.init_images[0].width / 16), 16 * math.ceil(p.init_images[0].height / 16) task_args = { 'width': p.width, 'height': p.height, diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index 013b32109..7d536999a 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -466,9 +466,7 @@ def calculate_base_steps(p, use_denoise_start, use_refiner_start): cls = shared.sd_model.__class__.__name__ if cls in sd_models.i2i_pipes: steps = p.steps - elif 'Flex' in cls: - steps = p.steps - elif 'HiDreamImageEditingPipeline' in cls: + elif 'Flex' in cls or 'HiDreamImageEditingPipeline' in cls or 'Kontext' in cls: steps = p.steps elif use_denoise_start and (shared.sd_model_type == 'sdxl'): steps = p.steps // (1 - p.refiner_start) diff --git a/modules/ui_models.py b/modules/ui_models.py index 704399158..82ed79298 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -37,7 +37,14 @@ def create_ui(): model = modelstats.analyze() desc = f"Model: {model.name}
Type: {model.type}
Class: {model.cls}
Size: {model.size} bytes
Modified: {model.mtime}
" meta = model.meta - components = [(m.name, m.cls, m.device, m.dtype, m.params, m.modules, str(m.config)) for m in model.modules] + components = [] + for m in model.modules: + try: + component = (m.name, m.cls, str(m.device), str(m.dtype), m.params, m.modules, str(m.config)) + components.append(component) + except Exception: + component = (m.name, m.cls, str(m.device), str(m.dtype), m.params, m.modules, '') + components.append(component) return [desc, components, meta] with gr.Row(): From 518015d0d1e9d03c337b00c4f999e388926e91cd Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 08:30:25 -0400 Subject: [PATCH 55/85] fix lumina2 Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 +- TODO.md | 47 +++++++++++++++++++++++++++++------------ modules/model_lumina.py | 4 +--- modules/sd_detect.py | 2 +- wiki | 2 +- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 794c0c9f2..997a8f8ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2025-06-26 +## Update for 2025-06-27 - **Models** - [Models Wiki page](https://vladmandic.github.io/sdnext-docs/Models/) is updated will all new models diff --git a/TODO.md b/TODO.md index 7b4b0f1f6..b35b5ea0e 100644 --- a/TODO.md +++ b/TODO.md @@ -4,31 +4,52 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma ## Current -### Issues/Limitations +## Future Candidates +### Features + +- Model receipe: load/save +- Python 3.13 improved support - Control: API enhance scripts compatibility - Video: API support -## Future Candidates +### Enhancements - [IPAdapter negative guidance](https://github.com/huggingface/diffusers/discussions/7167) - [STG](https://github.com/huggingface/diffusers/blob/main/examples/community/README.md#spatiotemporal-skip-guidance) - [LBM](https://github.com/gojasper/LBM) - [SmoothCache](https://github.com/huggingface/diffusers/issues/11135) -- [Magi](https://github.com/SandAI-org/MAGI-1) -- [SkyReels-v2](https://github.com/huggingface/diffusers/pull/11518) -- [WanAI-2.1 VACE](https://huggingface.co/Wan-AI/Wan2.1-VACE-14B) -- [LTXVideo-0.9.7](https://github.com/huggingface/diffusers/pull/11516) -- [VisualClose](https://github.com/huggingface/diffusers/pull/11377) -- [SEVA](https://github.com/huggingface/diffusers/pull/11440) -- [CausVid-Plus](https://github.com/goatWu/CausVid-Plus/) -- [Index-AniSora](https://github.com/bilibili/Index-anisora) - [HiDream GGUF](https://github.com/huggingface/diffusers/pull/11550) -- [JoyCaption-Beta-One](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) - [Diffusers guiders](https://github.com/huggingface/diffusers/pull/11311) - [Nunchaku PulID](https://github.com/mit-han-lab/nunchaku/pull/274) -- [Dream0](https://huggingface.co/ByteDance/DreamO) - [Pydantic changes](https://github.com/Cschlaefli/automatic) +- [Dream0 guidance](https://huggingface.co/ByteDance/DreamO) + +### Models + +- [Moondream vlm](https://github.com/vikhyat/moondream) +- [AniSora t2v](https://github.com/bilibili/Index-anisora) +- [Ming t2i](https://github.com/inclusionAI/Ming) +- [Magi t2v](https://github.com/SandAI-org/MAGI-1) +- [SEVA t2v](https://github.com/huggingface/diffusers/pull/11440) +- [WanAI-2.1 VACE t2v](https://huggingface.co/Wan-AI/Wan2.1-VACE-14B) +- [Bagel t2i](https://github.com/bytedance-seed/bagel) +- [OmniGen2 t2i](https://huggingface.co/OmniGen2/OmniGen2) +- [Step1X i2i](https://github.com/stepfun-ai/Step1X-Edit) +- [LTXVideo t2v](https://github.com/Lightricks/LTX-Video?tab=readme-ov-file#diffusers-integration) +- [LTXVideo t2v](https://github.com/huggingface/diffusers/pull/11516) +- [SkyReels-v2 t2v](https://github.com/SkyworkAI/SkyReels-V2) +- [SkyReels-v2 t2v](https://github.com/huggingface/diffusers/pull/11518) +- [Cosmos2 i2v](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Video2World) +- [WAN2GP t2v](https://github.com/deepbeepmeep/Wan2GP) +- [SelfForcing t2v](https://github.com/guandeh17/Self-Forcing) +- [DiffusionForcing t2v](https://github.com/kwsong0113/diffusion-forcing-transformer) +- [LanDiff t2v](https://github.com/landiff/landiff) +- [HunyuanCustom t2v](https://github.com/Tencent-Hunyuan/HunyuanCustom) +- [WAN-CausVid t2v](https://huggingface.co/lightx2v/Wan2.1-T2V-14B-CausVid) +- [WAN-StepDistill t2v](https://huggingface.co/lightx2v/Wan2.1-T2V-14B-StepDistill-CfgDistill) +- [WAN-CausVid-Plus t2v](https://github.com/goatWu/CausVid-Plus/) +- [HunyuanAvatar t2v](https://huggingface.co/tencent/HunyuanVideo-Avatar) ## Code TODO @@ -44,10 +65,10 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - lora: add other quantization types - lora: add t5 key support for sd35/f1 - lora: maybe force imediate quantization +- lora: support pre-quantized flux - model load: force-reloading entire model as loading transformers only leads to massive memory usage - model loader: implement model in-memory caching - modernui: monkey-patch for missing tabs.select event -- modules/lora/lora_extract.py:185:9: W0511: TODO: lora: support pre-quantized flux - nunchaku: batch support - nunchaku: cache-dir for transformer and t5 loader - processing: remove duplicate mask params diff --git a/modules/model_lumina.py b/modules/model_lumina.py index d817fa48c..1ecbd4e4c 100644 --- a/modules/model_lumina.py +++ b/modules/model_lumina.py @@ -2,13 +2,12 @@ import os import transformers import diffusers from huggingface_hub import repo_exists -from modules import errors, shared, sd_unet, sd_hijack_te +from modules import errors, shared, sd_models, sd_unet, sd_hijack_te, devices, modelloader, model_quant debug = shared.log.trace if os.environ.get('SD_LOAD_DEBUG', None) is not None else lambda *args, **kwargs: None def load_lumina(_checkpoint_info, diffusers_load_config={}): - from modules import shared, devices, modelloader, model_quant modelloader.hf_login() load_config, _quant_config = model_quant.get_dit_args(diffusers_load_config, allow_quant=False) pipe = diffusers.LuminaText2ImgPipeline.from_pretrained( @@ -21,7 +20,6 @@ def load_lumina(_checkpoint_info, diffusers_load_config={}): def load_lumina2(checkpoint_info, diffusers_load_config={}): - from modules import shared, devices, sd_models, model_quant transformer, text_encoder, vae = None, None, None repo_id = sd_models.path_to_repo(checkpoint_info.name) if os.path.isdir(checkpoint_info.filename) and not repo_exists(repo_id): diff --git a/modules/sd_detect.py b/modules/sd_detect.py index 8f10bb31d..cb99f8d17 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -70,7 +70,7 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): if 'lumina-next' in f.lower(): guess = 'Lumina-Next' if 'lumina-image-2' in f.lower(): - guess = 'Lumina2' + guess = 'Lumina 2' if 'kolors' in f.lower(): guess = 'Kolors' if 'auraflow' in f.lower(): diff --git a/wiki b/wiki index f2814574e..706852830 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit f2814574e02fd313348cc68c31573606617240cd +Subproject commit 7068528301f5f1304f5e096291a9d014dce13c96 From f9480a1047f186b8562cdd1d2e0218ef48d27392 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 09:04:32 -0400 Subject: [PATCH 56/85] add --trace for trace-logging and suppress empty torch logging Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 ++ installer.py | 28 ++++++++++++++++++++++------ modules/loader.py | 1 + 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 997a8f8ae..849ed50af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - **Changes** - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* + - Add `--trace` command line param that enables trace logging - Use Diffusers version of *OmniGen* - Control move global settings to control elements -> control settings tab - Control add setting to run hires with or without control @@ -60,6 +61,7 @@ - Fix process batch with batch count - Fix process batch double image save - Fix unapply texture tiling + - Suppress torch empty logging ## Update for 2025-06-16 diff --git a/installer.py b/installer.py index 9d8016343..e86b89cd6 100644 --- a/installer.py +++ b/installer.py @@ -129,6 +129,13 @@ def setup_logging(): def get(self): return self.buffer + class LogFilter(logging.Filter): + def __init__(self): + super().__init__() + + def filter(self, record): + return len(record.getMessage()) > 2 + t_start = time.time() from functools import partial, partialmethod from logging.handlers import RotatingFileHandler @@ -147,7 +154,7 @@ def setup_logging(): logging.Logger.trace = partialmethod(logging.Logger.log, logging.TRACE) logging.trace = partial(logging.log, logging.TRACE) - level = logging.DEBUG if args.debug else logging.INFO + level = logging.DEBUG if (args.debug or args.trace) else logging.INFO log.setLevel(logging.DEBUG) # log to file is always at level debug for facility `sd` log.print = rprint global console # pylint: disable=global-statement @@ -173,22 +180,30 @@ def setup_logging(): while log.hasHandlers() and len(log.handlers) > 0: log.removeHandler(log.handlers[0]) + log_filter = LogFilter() # handlers rh = RichHandler(show_time=True, omit_repeated_times=False, show_level=True, show_path=False, markup=False, rich_tracebacks=True, log_time_format='%H:%M:%S-%f', level=level, console=console) + if args.trace: + rh.formatter = logging.Formatter('[%(module)s][%(pathname)s:%(lineno)d] %(message)s') + rh.addFilter(log_filter) rh.setLevel(level) log.addHandler(rh) fh = RotatingFileHandler(log_file, maxBytes=32*1024*1024, backupCount=9, encoding='utf-8', delay=True) # 10MB default for log rotation + if args.trace: + fh.formatter = logging.Formatter(f'%(asctime)s | {hostname} | %(name)s | %(levelname)s | %(module)s | | %(pathname)s:%(lineno)d | %(message)s') + else: + fh.formatter = logging.Formatter(f'%(asctime)s | {hostname} | %(name)s | %(levelname)s | %(module)s | %(message)s') + fh.addFilter(log_filter) + fh.setLevel(logging.DEBUG) + log.addHandler(fh) global log_rolled # pylint: disable=global-statement if not log_rolled and args.debug and not args.log: fh.doRollover() log_rolled = True - fh.formatter = logging.Formatter(f'%(asctime)s | {hostname} | %(name)s | %(levelname)s | %(module)s | %(message)s') - fh.setLevel(logging.DEBUG) - log.addHandler(fh) - rb = RingBuffer(100) # 100 entries default in log ring buffer + rb.addFilter(log_filter) rb.setLevel(level) log.addHandler(rb) log.buffer = rb.buffer @@ -1528,7 +1543,8 @@ def add_args(parser): group_log = parser.add_argument_group('Logging') group_log.add_argument("--log", type=str, default=os.environ.get("SD_LOG", None), help="Set log file, default: %(default)s") - group_log.add_argument('--debug', default=os.environ.get("SD_DEBUG",False), action='store_true', help="Run installer with debug logging, default: %(default)s") + group_log.add_argument('--debug', default=os.environ.get("SD_DEBUG",False), action='store_true', help="Run with debug logging, default: %(default)s") + group_log.add_argument("--trace", default=os.environ.get("SD_TRACE", False), action='store_true', help="Run with trace logging, default: %(default)s") group_log.add_argument("--profile", default=os.environ.get("SD_PROFILE", False), action='store_true', help="Run profiler, default: %(default)s") group_log.add_argument('--docs', default=os.environ.get("SD_DOCS", False), action='store_true', help="Mount API docs, default: %(default)s") group_log.add_argument("--api-log", default=os.environ.get("SD_APILOG", False), action='store_true', help="Log all API requests") diff --git a/modules/loader.py b/modules/loader.py index cb554c38e..674167d20 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -38,6 +38,7 @@ try: import torch._logging # pylint: disable=ungrouped-imports torch._logging._internal.DEFAULT_LOG_LEVEL = logging.ERROR # pylint: disable=protected-access torch._logging.set_logs(all=logging.ERROR, bytecode=False, aot_graphs=False, aot_joint_graph=False, ddp_graphs=False, graph=False, graph_code=False, graph_breaks=False, graph_sizes=False, guards=False, recompiles=False, recompiles_verbose=False, trace_source=False, trace_call=False, trace_bytecode=False, output_code=False, kernel_code=False, schedule=False, perf_hints=False, post_grad_graphs=False, onnx_diagnostics=False, fusion=False, overlap=False, export=None, modules=None, cudagraphs=False, sym_node=False, compiled_autograd_verbose=False) # pylint: disable=protected-access + import torch._dynamo torch._dynamo.config.verbose = False # pylint: disable=protected-access torch._dynamo.config.suppress_errors = True # pylint: disable=protected-access except Exception as e: From 78330142aee73db142fce40caec240d9d5519866 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 09:41:32 -0400 Subject: [PATCH 57/85] add moondream2, sdnq xyzgrid timing info Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 10 ++++------ TODO.md | 1 - modules/interrogate/vqa.py | 14 ++++++++++++-- scripts/prompt_matrix.py | 2 +- scripts/xyz_grid.py | 8 ++++++-- scripts/xyz_grid_draw.py | 10 +++++----- scripts/xyz_grid_on.py | 8 ++++++-- scripts/xyz_grid_shared.py | 6 ++++-- 8 files changed, 38 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 849ed50af..4334ac18b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,9 @@ - available via *networks -> models -> reference* - *note*: model is still in training so future updates will trigger re-download - [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) - - available via *caption -> vlm caption* - + - available via *caption -> vlm caption* + - [MoonDream 2](https://huggingface.co/vikhyatk/moondream2) support (updated) + - available via *caption -> vlm caption* - **Changes** - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* - Add `--trace` command line param that enables trace logging @@ -28,20 +29,17 @@ - Control move global settings to control elements -> control settings tab - Control add setting to run hires with or without control - Update OpenVINO to 2025.2.0 - - **SDNQ Quantization** - - Add modules_to_not_convert support for post mode + - Add `modules_to_not_convert` support for post mode - Fix Qwen 2.5 with int8 matmul - Fix Dora loading - Remove per layer GC - Improve offload compatibility - Add support for XYZ grid to test quantization modes *note*: you need to enable quantization and choose what it applies on, then xyz grid can change quantization mode - - **API** - Add `/sdapi/v1/lora?lora=` endpoint that returns full lora info and metadata - Add `/sdapi/v1/controlnets?model_type=` endpoints that returns list of available controlnets for specific model type - - **Fixes** - IPEX with DPM2++ FlowMatch samplers - Invalid attention processor with ControlNet diff --git a/TODO.md b/TODO.md index b35b5ea0e..8bc429532 100644 --- a/TODO.md +++ b/TODO.md @@ -27,7 +27,6 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma ### Models -- [Moondream vlm](https://github.com/vikhyat/moondream) - [AniSora t2v](https://github.com/bilibili/Index-anisora) - [Ming t2i](https://github.com/inclusionAI/Ming) - [Magi t2v](https://github.com/SandAI-org/MAGI-1) diff --git a/modules/interrogate/vqa.py b/modules/interrogate/vqa.py index 5d74a8b9c..f11065617 100644 --- a/modules/interrogate/vqa.py +++ b/modules/interrogate/vqa.py @@ -429,7 +429,7 @@ def moondream(question: str, image: Image.Image, repo: str = None): model = None model = transformers.AutoModelForCausalLM.from_pretrained( repo, - revision="2024-08-26", + revision="2025-06-21", trust_remote_code=True, cache_dir=shared.opts.hfcache_dir ) @@ -442,7 +442,17 @@ def moondream(question: str, image: Image.Image, repo: str = None): question = question.replace('<', '').replace('>', '').replace('_', ' ') encoded = model.encode_image(image) with devices.inference_context(): - response = model.answer_question(encoded, question, processor) + if question == 'CAPTION': + response = model.caption(image, length="short")['caption'] + elif question == 'DETAILED CAPTION': + response = model.caption(image, length="normal")['caption'] + elif question == 'MORE DETAILED CAPTION': + response = model.caption(image, length="long")['caption'] + else: + response = model.answer_question(encoded, question, processor)['answer'] + # model.detect(image, "face") + # model.point(image, "person") + # model.detect_gaze(image) return response diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index 5babf20e7..37cc2795c 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -21,7 +21,7 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell): for ix, x in enumerate(xs): state.job = f"{ix + iy * len(xs) + 1} out of {len(xs) * len(ys)}" - processed = cell(x, y) + processed, t = cell(x, y) if first_processed is None: first_processed = processed diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 240376b3c..c79ce6909 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -1,6 +1,7 @@ # xyz grid that shows as selectable script import os import csv +import time import random from collections import namedtuple from copy import copy @@ -303,7 +304,7 @@ class Script(scripts.Script): def cell(x, y, z, ix, iy, iz): if shared.state.interrupted: - return processing.Processed(p, [], p.seed, "") + return processing.Processed(p, [], p.seed, ""), 0 p.xyz = True pc = copy(p) pc.override_settings_restore_afterwards = False @@ -311,6 +312,8 @@ class Script(scripts.Script): x_opt.apply(pc, x, xs) y_opt.apply(pc, y, ys) z_opt.apply(pc, z, zs) + + t0 = time.time() try: processed = processing.process_images(pc) except Exception as e: @@ -341,7 +344,8 @@ class Script(scripts.Script): pc.extra_generation_params["Fixed Z Values"] = ", ".join([str(z) for z in zs]) grid_text = f'{len(zs)}x{len(xs)}x{len(ys)}' if len(zs) > 0 else f'{len(xs)}x{len(ys)}' grid_infotext[0] = processing.create_infotext(pc, pc.all_prompts, pc.all_seeds, pc.all_subseeds, grid=grid_text) - return processed + t1 = time.time() + return processed, t1-t0 with SharedSettingsStackHelper(): processed = draw_xyz_grid( diff --git a/scripts/xyz_grid_draw.py b/scripts/xyz_grid_draw.py index d654a2c7f..646a3bd52 100644 --- a/scripts/xyz_grid_draw.py +++ b/scripts/xyz_grid_draw.py @@ -22,9 +22,9 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend def index(ix, iy, iz): return ix + iy * len(xs) + iz * len(xs) * len(ys) - p0 = time.time() - processed: processing.Processed = cell(x, y, z, ix, iy, iz) - p1 = time.time() + res = cell(x, y, z, ix, iy, iz) + processed: processing.Processed = res[0] if isinstance(res, tuple) else res + elapsed = res[1] if isinstance(res, tuple) else 0 if processed_result is None: processed_result = copy(processed) if processed_result is None: @@ -48,13 +48,13 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend if len(z_labels[iz]) > 0: overlay_text += f'{z_labels[iz]}\n' if include_time: - overlay_text += f'Time: {p1 - p0:.2f}' + overlay_text += f'Time: {elapsed:.2f}' if len(overlay_text) > 0: processed_result.images[idx] = images.draw_overlay(processed_result.images[idx], overlay_text) processed_result.all_prompts[idx] = processed.prompt processed_result.all_seeds[idx] = processed.seed processed_result.infotexts[idx] = processed.infotexts[0] - processed_result.time[idx] = round(p1 - p0, 2) + processed_result.time[idx] = round(elapsed, 2) else: cell_mode = "P" cell_size = (processed_result.width, processed_result.height) diff --git a/scripts/xyz_grid_on.py b/scripts/xyz_grid_on.py index f40a1731b..db454b2b7 100644 --- a/scripts/xyz_grid_on.py +++ b/scripts/xyz_grid_on.py @@ -1,6 +1,7 @@ # xyz grid that shows up as alwayson script import os import csv +import time import random from collections import namedtuple from copy import copy @@ -322,7 +323,7 @@ class Script(scripts.Script): def cell(x, y, z, ix, iy, iz): if shared.state.interrupted: - return processing.Processed(p, [], p.seed, "") + return processing.Processed(p, [], p.seed, ""), 0 p.xyz = True pc = copy(p) pc.override_settings_restore_afterwards = False @@ -331,6 +332,8 @@ class Script(scripts.Script): y_opt.apply(pc, y, ys) z_opt.apply(pc, z, zs) + print('HERE') + t0 = time.time() try: processed = processing.process_images(pc) except Exception as e: @@ -362,7 +365,8 @@ class Script(scripts.Script): pc.extra_generation_params["Fixed Z Values"] = ", ".join([str(z) for z in zs]) info = processing.create_infotext(pc, pc.all_prompts, pc.all_seeds, pc.all_subseeds, grid=f'{len(zs)}x{len(xs)}x{len(ys)}') grid_infotext.insert(0, info) - return processed + t1 = time.time() + return processed, t1-t0 with SharedSettingsStackHelper(): processed: processing.Processed = draw_xyz_grid( diff --git a/scripts/xyz_grid_shared.py b/scripts/xyz_grid_shared.py index 265af6862..64f179075 100644 --- a/scripts/xyz_grid_shared.py +++ b/scripts/xyz_grid_shared.py @@ -149,13 +149,15 @@ def confirm_samplers(p, xs): def apply_sdnq_quant(p, x, xs): shared.opts.sdnq_quantize_weights_mode = x - sd_models.unload_model_weights(op='model') # reload will happen on-demand + sd_models.unload_model_weights(op='model') + sd_models.reload_model_weights() shared.log.debug(f'XYZ grid apply sdnq quant: mode="{x}"') def apply_sdnq_quant_te(p, x, xs): shared.opts.sdnq_quantize_weights_mode_te = x - sd_models.unload_model_weights(op='model') # reload will happen on-demand + sd_models.unload_model_weights(op='model') + sd_models.reload_model_weights() shared.log.debug(f'XYZ grid apply sdnq quant te: mode="{x}"') From 57fec767d1b2c6e9ece5a44b711aa3fc67035442 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 09:42:06 -0400 Subject: [PATCH 58/85] update changelog Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4334ac18b..0a62a6b45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - Improve offload compatibility - Add support for XYZ grid to test quantization modes *note*: you need to enable quantization and choose what it applies on, then xyz grid can change quantization mode + *note*: you can also enable 'add time info' to compare performance of different quantization modes - **API** - Add `/sdapi/v1/lora?lora=` endpoint that returns full lora info and metadata - Add `/sdapi/v1/controlnets?model_type=` endpoints that returns list of available controlnets for specific model type From 15cbffeb978c723d85f49f06d97809346d5b0c2f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 09:52:31 -0400 Subject: [PATCH 59/85] cleanup Signed-off-by: Vladimir Mandic --- modules/processing_helpers.py | 5 +++-- scripts/xyz_grid_on.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index 7d536999a..e31ebb028 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -549,7 +549,7 @@ def set_latents(p): def apply_circular(enable: bool, model): if not hasattr(model, 'unet') or not hasattr(model, 'vae'): return - current = getattr(model, 'texture_tiling', 0) + current = getattr(model, 'texture_tiling', None) if isinstance(current, bool) and current == enable: return try: @@ -561,7 +561,8 @@ def apply_circular(enable: bool, model): i += 1 layer.padding_mode = 'circular' if enable else 'zeros' model.texture_tiling = enable - shared.log.debug(f'Apply texture tiling: enabled={enable} layers={i} cls={model.__class__.__name__} ') + if current is not None or enable: + shared.log.debug(f'Apply texture tiling: enabled={enable} layers={i} cls={model.__class__.__name__} ') except Exception as e: debug(f"Diffusers tiling failed: {e}") diff --git a/scripts/xyz_grid_on.py b/scripts/xyz_grid_on.py index db454b2b7..5e070ce8b 100644 --- a/scripts/xyz_grid_on.py +++ b/scripts/xyz_grid_on.py @@ -332,7 +332,6 @@ class Script(scripts.Script): y_opt.apply(pc, y, ys) z_opt.apply(pc, z, zs) - print('HERE') t0 = time.time() try: processed = processing.process_images(pc) From 9bc74c225d019428e07b0a7c9dc5634052556f06 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 10:38:46 -0400 Subject: [PATCH 60/85] add ultrasharp-v2 upscaler Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 5 ++++ TODO.md | 30 ++++++++++++++---------- extensions-builtin/sd-extension-chainner | 2 +- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a62a6b45..1349a7d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,14 @@ - available via *networks -> models -> reference* - *note*: model is still in training so future updates will trigger re-download - [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) + - new version of highly popular captioning model - available via *caption -> vlm caption* - [MoonDream 2](https://huggingface.co/vikhyatk/moondream2) support (updated) + - really good 2B captioning model that can work on different levels of detail - available via *caption -> vlm caption* + - [UltraSharp v2](https://huggingface.co/Kim2091/UltraSharpV2) support + - one of the best upscalers (traditional, non-diffusion) available today! + - available via *process -> upscale -> chainner* - **Changes** - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* - Add `--trace` command line param that enables trace logging diff --git a/TODO.md b/TODO.md index 8bc429532..ef19a4b6e 100644 --- a/TODO.md +++ b/TODO.md @@ -24,22 +24,28 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - [Nunchaku PulID](https://github.com/mit-han-lab/nunchaku/pull/274) - [Pydantic changes](https://github.com/Cschlaefli/automatic) - [Dream0 guidance](https://huggingface.co/ByteDance/DreamO) +- [S3Diff diffusion upscaler](https://github.com/ArcticHare105/S3Diff) +- [SUPIR upscaler](https://github.com/Fanghua-Yu/SUPIR) ### Models -- [AniSora t2v](https://github.com/bilibili/Index-anisora) -- [Ming t2i](https://github.com/inclusionAI/Ming) -- [Magi t2v](https://github.com/SandAI-org/MAGI-1) -- [SEVA t2v](https://github.com/huggingface/diffusers/pull/11440) -- [WanAI-2.1 VACE t2v](https://huggingface.co/Wan-AI/Wan2.1-VACE-14B) -- [Bagel t2i](https://github.com/bytedance-seed/bagel) -- [OmniGen2 t2i](https://huggingface.co/OmniGen2/OmniGen2) +#### Ready +- [WanAI-2.1 VACE](https://huggingface.co/Wan-AI/Wan2.1-VACE-14B)(https://github.com/huggingface/diffusers/pull/11582) +- [LTXVideo-0.9.7](https://github.com/Lightricks/LTX-Video?tab=readme-ov-file#diffusers-integration)(https://github.com/huggingface/diffusers/pull/11516) +- [Cosmos-Predict2](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Video2World)(https://github.com/huggingface/diffusers/pull/11695) +#### Pending +- [Magi](https://github.com/SandAI-org/MAGI-1)(https://github.com/huggingface/diffusers/pull/11713) +- [SEVA](https://github.com/huggingface/diffusers/pull/11440) +- [SkyReels-v2](https://github.com/SkyworkAI/SkyReels-V2)(https://github.com/huggingface/diffusers/pull/11518) +#### External:Unified +- [Bagel](https://huggingface.co/ByteDance-Seed/BAGEL-7B-MoT)(https://github.com/bytedance-seed/bagel) +- [OmniGen2](https://huggingface.co/OmniGen2/OmniGen2) +- [Ming](https://github.com/inclusionAI/Ming) +#### External:Image2Image - [Step1X i2i](https://github.com/stepfun-ai/Step1X-Edit) -- [LTXVideo t2v](https://github.com/Lightricks/LTX-Video?tab=readme-ov-file#diffusers-integration) -- [LTXVideo t2v](https://github.com/huggingface/diffusers/pull/11516) -- [SkyReels-v2 t2v](https://github.com/SkyworkAI/SkyReels-V2) -- [SkyReels-v2 t2v](https://github.com/huggingface/diffusers/pull/11518) -- [Cosmos2 i2v](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Video2World) +- [SD3 UltraEdit i2i](https://github.com/HaozheZhao/UltraEdit) +#### External:Video +- [AniSora t2v](https://github.com/bilibili/Index-anisora) - [WAN2GP t2v](https://github.com/deepbeepmeep/Wan2GP) - [SelfForcing t2v](https://github.com/guandeh17/Self-Forcing) - [DiffusionForcing t2v](https://github.com/kwsong0113/diffusion-forcing-transformer) diff --git a/extensions-builtin/sd-extension-chainner b/extensions-builtin/sd-extension-chainner index a841a9dfe..c12e8cda4 160000 --- a/extensions-builtin/sd-extension-chainner +++ b/extensions-builtin/sd-extension-chainner @@ -1 +1 @@ -Subproject commit a841a9dfeab8178d0e0236e156a11973cca9462d +Subproject commit c12e8cda4e45a1d5f20659a30e329c7cc7e69339 From 1df2175387cf690789bcf00524bbc543381ce14c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 10:49:42 -0400 Subject: [PATCH 61/85] update todo Signed-off-by: Vladimir Mandic --- TODO.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index ef19a4b6e..4f5a06cbb 100644 --- a/TODO.md +++ b/TODO.md @@ -8,10 +8,9 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma ### Features -- Model receipe: load/save - Python 3.13 improved support -- Control: API enhance scripts compatibility - Video: API support +- LoRA: add OMI support for SD35/FLUX.1 ### Enhancements @@ -50,11 +49,11 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - [SelfForcing t2v](https://github.com/guandeh17/Self-Forcing) - [DiffusionForcing t2v](https://github.com/kwsong0113/diffusion-forcing-transformer) - [LanDiff t2v](https://github.com/landiff/landiff) -- [HunyuanCustom t2v](https://github.com/Tencent-Hunyuan/HunyuanCustom) +- [HunyuanCustom x2v](https://github.com/Tencent-Hunyuan/HunyuanCustom) - [WAN-CausVid t2v](https://huggingface.co/lightx2v/Wan2.1-T2V-14B-CausVid) - [WAN-StepDistill t2v](https://huggingface.co/lightx2v/Wan2.1-T2V-14B-StepDistill-CfgDistill) - [WAN-CausVid-Plus t2v](https://github.com/goatWu/CausVid-Plus/) -- [HunyuanAvatar t2v](https://huggingface.co/tencent/HunyuanVideo-Avatar) +- [HunyuanAvatar i2v](https://huggingface.co/tencent/HunyuanVideo-Avatar) ## Code TODO @@ -70,10 +69,10 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - lora: add other quantization types - lora: add t5 key support for sd35/f1 - lora: maybe force imediate quantization -- lora: support pre-quantized flux - model load: force-reloading entire model as loading transformers only leads to massive memory usage - model loader: implement model in-memory caching - modernui: monkey-patch for missing tabs.select event +- modules/lora/lora_extract.py:188:9: W0511: TODO: lora: support pre-quantized flux - nunchaku: batch support - nunchaku: cache-dir for transformer and t5 loader - processing: remove duplicate mask params From 70193c5c64f47a911d94f74f87ffa3fac1ce37f6 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 11:30:23 -0400 Subject: [PATCH 62/85] update todo Signed-off-by: Vladimir Mandic --- TODO.md | 59 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/TODO.md b/TODO.md index 4f5a06cbb..51d342ab8 100644 --- a/TODO.md +++ b/TODO.md @@ -6,54 +6,61 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma ## Future Candidates -### Features +### Complete Features -- Python 3.13 improved support +- Python==3.13 improved support - Video: API support -- LoRA: add OMI support for SD35/FLUX.1 +- LoRA: add OMI format support for SD35/FLUX.1 -### Enhancements +### Under Consideration - [IPAdapter negative guidance](https://github.com/huggingface/diffusers/discussions/7167) +- [IPAdapter composition](https://huggingface.co/ostris/ip-composition-adapter) - [STG](https://github.com/huggingface/diffusers/blob/main/examples/community/README.md#spatiotemporal-skip-guidance) - [LBM](https://github.com/gojasper/LBM) - [SmoothCache](https://github.com/huggingface/diffusers/issues/11135) +- [MagCache](https://github.com/lllyasviel/FramePack/pull/673/files) - [HiDream GGUF](https://github.com/huggingface/diffusers/pull/11550) - [Diffusers guiders](https://github.com/huggingface/diffusers/pull/11311) - [Nunchaku PulID](https://github.com/mit-han-lab/nunchaku/pull/274) - [Pydantic changes](https://github.com/Cschlaefli/automatic) - [Dream0 guidance](https://huggingface.co/ByteDance/DreamO) -- [S3Diff diffusion upscaler](https://github.com/ArcticHare105/S3Diff) -- [SUPIR upscaler](https://github.com/Fanghua-Yu/SUPIR) +- [S3Diff diffusion upscaler](https://github.com/ArcticHare105/S3Diff) +- [SUPIR upscaler](https://github.com/Fanghua-Yu/SUPIR) -### Models +### Monitoring -#### Ready +- [TensorRT](https://github.com/huggingface/diffusers/pull/11173) + +### New models + +#### Stable +- [Diffusers-0.34.0](https://github.com/huggingface/diffusers/releases/tag/v0.34.0) - [WanAI-2.1 VACE](https://huggingface.co/Wan-AI/Wan2.1-VACE-14B)(https://github.com/huggingface/diffusers/pull/11582) - [LTXVideo-0.9.7](https://github.com/Lightricks/LTX-Video?tab=readme-ov-file#diffusers-integration)(https://github.com/huggingface/diffusers/pull/11516) -- [Cosmos-Predict2](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Video2World)(https://github.com/huggingface/diffusers/pull/11695) +- [Cosmos-Predict2-Video](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Video2World)(https://github.com/huggingface/diffusers/pull/11695) #### Pending -- [Magi](https://github.com/SandAI-org/MAGI-1)(https://github.com/huggingface/diffusers/pull/11713) +- [Magi](https://github.com/SandAI-org/MAGI-1)(https://github.com/huggingface/diffusers/pull/11713) - [SEVA](https://github.com/huggingface/diffusers/pull/11440) - [SkyReels-v2](https://github.com/SkyworkAI/SkyReels-V2)(https://github.com/huggingface/diffusers/pull/11518) -#### External:Unified -- [Bagel](https://huggingface.co/ByteDance-Seed/BAGEL-7B-MoT)(https://github.com/bytedance-seed/bagel) -- [OmniGen2](https://huggingface.co/OmniGen2/OmniGen2) -- [Ming](https://github.com/inclusionAI/Ming) -#### External:Image2Image -- [Step1X i2i](https://github.com/stepfun-ai/Step1X-Edit) -- [SD3 UltraEdit i2i](https://github.com/HaozheZhao/UltraEdit) +#### External:Unified/MultiModal +- [Bagel](https://huggingface.co/ByteDance-Seed/BAGEL-7B-MoT)(https://github.com/bytedance-seed/bagel) +- [OmniGen2](https://huggingface.co/OmniGen2/OmniGen2) +- [Ming](https://github.com/inclusionAI/Ming) +- [Liquid](https://github.com/FoundationVision/Liquid) +#### External:Image2Image/Editing +- [Step1X](https://github.com/stepfun-ai/Step1X-Edit) +- [SD3 UltraEdit](https://github.com/HaozheZhao/UltraEdit) #### External:Video -- [AniSora t2v](https://github.com/bilibili/Index-anisora) -- [WAN2GP t2v](https://github.com/deepbeepmeep/Wan2GP) -- [SelfForcing t2v](https://github.com/guandeh17/Self-Forcing) -- [DiffusionForcing t2v](https://github.com/kwsong0113/diffusion-forcing-transformer) -- [LanDiff t2v](https://github.com/landiff/landiff) -- [HunyuanCustom x2v](https://github.com/Tencent-Hunyuan/HunyuanCustom) -- [WAN-CausVid t2v](https://huggingface.co/lightx2v/Wan2.1-T2V-14B-CausVid) -- [WAN-StepDistill t2v](https://huggingface.co/lightx2v/Wan2.1-T2V-14B-StepDistill-CfgDistill) +- [WAN2GP](https://github.com/deepbeepmeep/Wan2GP) +- [SelfForcing](https://github.com/guandeh17/Self-Forcing) +- [DiffusionForcing](https://github.com/kwsong0113/diffusion-forcing-transformer) +- [LanDiff](https://github.com/landiff/landiff) +- [HunyuanCustom](https://github.com/Tencent-Hunyuan/HunyuanCustom) +- [HunyuanAvatar](https://huggingface.co/tencent/HunyuanVideo-Avatar) +- [WAN-CausVid](https://huggingface.co/lightx2v/Wan2.1-T2V-14B-CausVid) - [WAN-CausVid-Plus t2v](https://github.com/goatWu/CausVid-Plus/) -- [HunyuanAvatar i2v](https://huggingface.co/tencent/HunyuanVideo-Avatar) +- [WAN-StepDistill](https://huggingface.co/lightx2v/Wan2.1-T2V-14B-StepDistill-CfgDistill) ## Code TODO From 3b8ced444c7e6add87e8b3fab371443b4ed73cdb Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 27 Jun 2025 18:54:15 +0300 Subject: [PATCH 63/85] Add auto quantization mode --- CHANGELOG.md | 1 + modules/model_quant.py | 10 +++++----- modules/sd_models.py | 29 +++++++++++++++++++++++++---- modules/shared.py | 4 ++-- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1349a7d77..61e90bf3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - Add support for XYZ grid to test quantization modes *note*: you need to enable quantization and choose what it applies on, then xyz grid can change quantization mode *note*: you can also enable 'add time info' to compare performance of different quantization modes + - Add `auto` quantization mode - **API** - Add `/sdapi/v1/lora?lora=` endpoint that returns full lora info and metadata - Add `/sdapi/v1/controlnets?model_type=` endpoints that returns list of available controlnets for specific model type diff --git a/modules/model_quant.py b/modules/model_quant.py index 55cdd8c3c..4b6236221 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -63,7 +63,7 @@ def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Mode def create_ao_config(kwargs = None, allow_ao: bool = True, module: str = 'Model', modules_to_not_convert: list = []): from modules import shared - if len(shared.opts.torchao_quantization) > 0 and (shared.opts.torchao_quantization_mode == 'pre') and allow_ao: + if len(shared.opts.torchao_quantization) > 0 and (shared.opts.torchao_quantization_mode in {'pre', 'auto'}) and allow_ao: if 'Model' in shared.opts.torchao_quantization or (module is not None and module in shared.opts.torchao_quantization) or module == 'any': torchao = load_torchao() if torchao is None: @@ -106,7 +106,7 @@ def create_quanto_config(kwargs = None, allow_quanto: bool = True, module: str = def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Model', weights_dtype: str = None, modules_to_not_convert: list = []): from modules import devices, shared - if len(shared.opts.sdnq_quantize_weights) > 0 and (shared.opts.sdnq_quantize_mode == 'pre') and allow_sdnq: + if len(shared.opts.sdnq_quantize_weights) > 0 and (shared.opts.sdnq_quantize_mode in {'pre', 'auto'}) and allow_sdnq: if 'Model' in shared.opts.sdnq_quantize_weights or (module is not None and module in shared.opts.sdnq_quantize_weights) or module == 'any': from modules.sdnq import SDNQQuantizer, SDNQConfig diffusers.quantizers.auto.AUTO_QUANTIZER_MAPPING["sdnq"] = SDNQQuantizer @@ -588,13 +588,13 @@ def get_dit_args(load_config:dict={}, module:str=None, device_map:bool=False, al return config, quant_args -def do_post_load_quant(sd_model): +def do_post_load_quant(sd_model, allow=True): from modules import shared - if shared.opts.sdnq_quantize_weights and shared.opts.sdnq_quantize_mode == 'post': + if shared.opts.sdnq_quantize_weights and (shared.opts.sdnq_quantize_mode == 'post' or (allow and shared.opts.sdnq_quantize_mode == 'auto')): sd_model = sdnq_quantize_weights(sd_model) if shared.opts.optimum_quanto_weights: sd_model = optimum_quanto_weights(sd_model) - if shared.opts.torchao_quantization and shared.opts.torchao_quantization_mode == 'post': + if shared.opts.torchao_quantization and (shared.opts.torchao_quantization_mode == 'post' or (allow and shared.opts.torchao_quantization_mode == 'auto')): sd_model = torchao_quantization(sd_model) if shared.opts.layerwise_quantization: apply_layerwise(sd_model) diff --git a/modules/sd_models.py b/modules/sd_models.py index e675be254..97e36b2e6 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -293,75 +293,95 @@ def load_diffuser_initial(diffusers_load_config, op='model'): def load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op='model'): sd_model = None + allow_post_quant = True unload_model_weights(op=op) shared.sd_model = None try: if model_type in ['Stable Cascade']: # forced pipeline from modules.model_stablecascade import load_cascade_combined sd_model = load_cascade_combined(checkpoint_info, diffusers_load_config) + allow_post_quant = True elif model_type in ['InstaFlow']: # forced pipeline pipeline = diffusers.utils.get_class_from_dynamic_module('instaflow_one_step', module_file='pipeline.py') shared_items.pipelines['InstaFlow'] = pipeline sd_model = pipeline.from_pretrained(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config) + allow_post_quant = True elif model_type in ['SegMoE']: # forced pipeline from modules.segmoe.segmoe_model import SegMoEPipeline sd_model = SegMoEPipeline(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config) sd_model = sd_model.pipe # segmoe pipe does its stuff in __init__ and __call__ is the original pipeline + allow_post_quant = True shared_items.pipelines['SegMoE'] = SegMoEPipeline elif model_type in ['PixArt Sigma']: # forced pipeline from modules.model_pixart import load_pixart sd_model = load_pixart(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['Sana']: # forced pipeline from modules.model_sana import load_sana sd_model = load_sana(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['Lumina-Next']: # forced pipeline from modules.model_lumina import load_lumina sd_model = load_lumina(checkpoint_info, diffusers_load_config) + allow_post_quant = True elif model_type in ['Kolors']: # forced pipeline from modules.model_kolors import load_kolors sd_model = load_kolors(checkpoint_info, diffusers_load_config) + allow_post_quant = True elif model_type in ['AuraFlow']: # forced pipeline from modules.model_auraflow import load_auraflow sd_model = load_auraflow(checkpoint_info, diffusers_load_config) + allow_post_quant = True elif model_type in ['FLUX']: from modules.model_flux import load_flux sd_model = load_flux(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['FLEX']: from modules.model_flex import load_flex sd_model = load_flex(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['Chroma']: from modules.model_chroma import load_chroma sd_model = load_chroma(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['Lumina 2']: from modules.model_lumina import load_lumina2 sd_model = load_lumina2(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['Stable Diffusion 3']: from modules.model_sd3 import load_sd3 sd_model = load_sd3(checkpoint_info, cache_dir=shared.opts.diffusers_dir, config=diffusers_load_config.get('config', None)) + allow_post_quant = False elif model_type in ['CogView 3']: # forced pipeline from modules.model_cogview import load_cogview3 sd_model = load_cogview3(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['CogView 4']: # forced pipeline from modules.model_cogview import load_cogview4 sd_model = load_cogview4(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['Meissonic']: # forced pipeline from modules.model_meissonic import load_meissonic sd_model = load_meissonic(checkpoint_info, diffusers_load_config) + allow_post_quant = True elif model_type in ['OmniGen']: # forced pipeline from modules.model_omnigen import load_omnigen sd_model = load_omnigen(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['HiDream']: from modules.model_hidream import load_hidream sd_model = load_hidream(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['Cosmos']: from modules.model_cosmos import load_cosmos_t2i sd_model = load_cosmos_t2i(checkpoint_info, diffusers_load_config) + allow_post_quant = False except Exception as e: shared.log.error(f'Load {op}: path="{checkpoint_info.path}" {e}') if debug_load: errors.display(e, 'Load') - return None - return sd_model + return None, True + return sd_model, allow_post_quant def load_diffuser_folder(model_type, pipeline, checkpoint_info, diffusers_load_config, op='model'): @@ -541,6 +561,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No return sd_model = None + allow_post_quant = True try: # initial load only if sd_model is None: @@ -572,7 +593,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No # load with custom loader if sd_model is None: - sd_model = load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op) + sd_model, allow_post_quant = load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op) if sd_model is not None and not sd_model: shared.log.error(f'Load {op}: type="{model_type}" pipeline="{pipeline}" not loaded') return @@ -625,7 +646,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No prompt_parser_diffusers.cache.clear() set_diffuser_options(sd_model, vae, op, offload=False) - sd_model = model_quant.do_post_load_quant(sd_model) # run this before move model so it can be compressed in CPU + sd_model = model_quant.do_post_load_quant(sd_model, allow=allow_post_quant) # run this before move model so it can be compressed in CPU timer.record("options") set_diffuser_offload(sd_model, op) diff --git a/modules/shared.py b/modules/shared.py index 0f01696e9..be66c1127 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -518,7 +518,7 @@ options_templates.update(options_section(('backends', "Backend Settings"), { options_templates.update(options_section(("quantization", "Quantization Settings"), { "sdnq_quantize_sep": OptionInfo("

SDNQ: SD.Next Quantization

", "", gr.HTML), "sdnq_quantize_weights": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), - "sdnq_quantize_mode": OptionInfo("pre", "Quantization mode", gr.Dropdown, {"choices": ["pre", "post"], "visible": native}), + "sdnq_quantize_mode": OptionInfo("auto", "Quantization mode", gr.Dropdown, {"choices": ["auto", "pre", "post"], "visible": native}), "sdnq_quantize_weights_mode": OptionInfo("int8", "Quantization type", gr.Dropdown, {"choices": sdnq_quant_modes, "visible": native}), "sdnq_quantize_weights_mode_te": OptionInfo("default", "Quantization type for Text Encoders", gr.Dropdown, {"choices": ['default'] + sdnq_quant_modes, "visible": native}), "sdnq_quantize_weights_group_size": OptionInfo(0, "Group size", gr.Slider, {"minimum": -1, "maximum": 4096, "step": 1, "visible": native}), @@ -547,7 +547,7 @@ options_templates.update(options_section(("quantization", "Quantization Settings "torchao_sep": OptionInfo("

TorchAO

", "", gr.HTML), "torchao_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM"], "visible": native}), - "torchao_quantization_mode": OptionInfo("pre", "Quantization mode", gr.Dropdown, {"choices": ["pre", "post"], "visible": native}), + "torchao_quantization_mode": OptionInfo("auto", "Quantization mode", gr.Dropdown, {"choices": ["auto", "pre", "post"], "visible": native}), "torchao_quantization_type": OptionInfo("int8_weight_only", "Quantization type", gr.Dropdown, {"choices": ["int4_weight_only", "int8_dynamic_activation_int4_weight", "int8_weight_only", "int8_dynamic_activation_int8_weight", "float8_weight_only", "float8_dynamic_activation_float8_weight", "float8_static_activation_float8_weight"], "visible": native}), "layerwise_quantization_sep": OptionInfo("

Layerwise Casting

", "", gr.HTML), From 71f7474de28ff58c93eb73c6a44aacdf20248c38 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 27 Jun 2025 21:05:14 +0300 Subject: [PATCH 64/85] Unify quant options --- modules/control/units/controlnet.py | 14 +- modules/infiniteyou/pipeline_infu_flux.py | 2 +- modules/model_chroma.py | 10 +- modules/model_cogview.py | 4 +- modules/model_cosmos.py | 4 +- modules/model_flex.py | 4 +- modules/model_flux.py | 10 +- modules/model_hidream.py | 6 +- modules/model_lumina.py | 2 +- modules/model_omnigen.py | 2 +- modules/model_pixart.py | 2 +- modules/model_quant.py | 212 +++++++++++----------- modules/model_sana.py | 6 +- modules/model_sd3.py | 2 +- modules/sd_models_utils.py | 5 +- modules/shared.py | 22 +-- modules/video_models/video_load.py | 2 +- 17 files changed, 155 insertions(+), 154 deletions(-) diff --git a/modules/control/units/controlnet.py b/modules/control/units/controlnet.py index 9233b47f8..5e9e372af 100644 --- a/modules/control/units/controlnet.py +++ b/modules/control/units/controlnet.py @@ -303,21 +303,29 @@ class ControlNet(): return if self.dtype is not None: self.model.to(self.dtype) - if "ControlNet" in opts.sdnq_quantize_weights: + if "Control" in opts.sdnq_quantize_weights: try: log.debug(f'Control {what} model SDNQ Compress: id="{model_id}"') from modules.model_quant import sdnq_quantize_model self.model = sdnq_quantize_model(self.model) except Exception as e: log.error(f'Control {what} model SDNQ Compression failed: id="{model_id}" {e}') - elif "ControlNet" in opts.optimum_quanto_weights: + elif "Control" in opts.optimum_quanto_weights: try: log.debug(f'Control {what} model Optimum Quanto: id="{model_id}"') - model_quant.load_quanto('Load model: type=ControlNet') + model_quant.load_quanto('Load model: type=Control') from modules.model_quant import optimum_quanto_model self.model = optimum_quanto_model(self.model) except Exception as e: log.error(f'Control {what} model Optimum Quanto: id="{model_id}" {e}') + elif "Control" in opts.torchao_quantization: + try: + log.debug(f'Control {what} model Torch AO: id="{model_id}"') + model_quant.load_torchao('Load model: type=Control') + from modules.model_quant import torchao_quantization + self.model = torchao_quantization(self.model) + except Exception as e: + log.error(f'Control {what} model Torch AO: id="{model_id}" {e}') if self.device is not None: self.model.to(self.device) t1 = time.time() diff --git a/modules/infiniteyou/pipeline_infu_flux.py b/modules/infiniteyou/pipeline_infu_flux.py index 2b4762d97..fc8b84647 100644 --- a/modules/infiniteyou/pipeline_infu_flux.py +++ b/modules/infiniteyou/pipeline_infu_flux.py @@ -151,7 +151,7 @@ class InfUFluxPipeline: local_path = snapshot_download(repo_id='ByteDance/InfiniteYou', cache_dir=shared.opts.hfcache_dir) infiniteyou_path = os.path.join(local_path, f'infu_flux_{infu_flux_version}', model_version) infusenet_path = os.path.join(infiniteyou_path, 'InfuseNetModel') - quant_args = model_quant.create_config(module='ControlNet') + quant_args = model_quant.create_config(module='Control') shared.log.debug(f'InfiniteYou: fn="{infusenet_path}" load infusenet') self.infusenet = FluxControlNetModel.from_pretrained( infusenet_path, diff --git a/modules/model_chroma.py b/modules/model_chroma.py index acd60b02a..9333ccc30 100644 --- a/modules/model_chroma.py +++ b/modules/model_chroma.py @@ -112,10 +112,10 @@ def load_chroma_bnb(checkpoint_info, diffusers_load_config): # pylint: disable=u def load_quants(kwargs, pretrained_model_name_or_path, cache_dir, allow_quant): try: - if 'transformer' not in kwargs and model_quant.check_nunchaku('Transformer'): + if 'transformer' not in kwargs and model_quant.check_nunchaku('Model'): raise NotImplementedError('Nunchaku does not support Chroma Model yet. See https://github.com/mit-han-lab/nunchaku/issues/167') - elif 'transformer' not in kwargs and model_quant.check_quant('Transformer'): - quant_args = model_quant.create_config(allow=allow_quant, module='Transformer', modules_to_not_convert=["distilled_guidance_layer"]) + elif 'transformer' not in kwargs and model_quant.check_quant('Model'): + quant_args = model_quant.create_config(allow=allow_quant, module='Model', modules_to_not_convert=["distilled_guidance_layer"]) if quant_args: if os.path.isfile(pretrained_model_name_or_path): kwargs['transformer'] = diffusers.ChromaTransformer2DModel.from_single_file(pretrained_model_name_or_path, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) @@ -166,7 +166,7 @@ def load_transformer(file_path): # triggered by opts.sd_unet change if _transformer is not None: transformer = _transformer else: - quant_args = model_quant.create_config(module='Transformer', modules_to_not_convert=["distilled_guidance_layer"]) + quant_args = model_quant.create_config(module='Model', modules_to_not_convert=["distilled_guidance_layer"]) if quant_args: shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=torchao dtype={devices.dtype}') transformer = diffusers.ChromaTransformer2DModel.from_single_file(file_path, **diffusers_load_config, **quant_args) @@ -284,7 +284,7 @@ def load_chroma(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ else: pipe = cls.from_pretrained(repo_id or fn, cache_dir=shared.opts.diffusers_dir, **kwargs, **diffusers_load_config) - if shared.opts.teacache_enabled and model_quant.check_nunchaku('Transformer'): + if shared.opts.teacache_enabled and model_quant.check_nunchaku('Model'): from nunchaku.caching.diffusers_adapters import apply_cache_on_pipe apply_cache_on_pipe(pipe, residual_diff_threshold=0.12) diff --git a/modules/model_cogview.py b/modules/model_cogview.py index 8b761d816..eaba437ed 100644 --- a/modules/model_cogview.py +++ b/modules/model_cogview.py @@ -7,7 +7,7 @@ def load_cogview3(checkpoint_info, diffusers_load_config={}): modelloader.hf_login() repo_id = sd_models.path_to_repo(checkpoint_info.name) - load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer') + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model') shared.log.debug(f'Load model: type=CogView3 transformer="{repo_id}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') transformer = diffusers.CogView3PlusTransformer2DModel.from_pretrained( repo_id, @@ -44,7 +44,7 @@ def load_cogview4(checkpoint_info, diffusers_load_config={}): modelloader.hf_login() repo_id = sd_models.path_to_repo(checkpoint_info.name) - load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer') + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model') shared.log.debug(f'Load model: type=CogView4 transformer="{repo_id}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') transformer = diffusers.CogView4Transformer2DModel.from_pretrained( repo_id, diff --git a/modules/model_cosmos.py b/modules/model_cosmos.py index c482d033b..6d7b17f33 100644 --- a/modules/model_cosmos.py +++ b/modules/model_cosmos.py @@ -6,7 +6,7 @@ from modules import shared, devices, sd_models, model_quant, modelloader, sd_hij def load_transformer(repo_id, diffusers_load_config={}): - load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer', device_map=True) + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model', device_map=True) fn = None if shared.opts.sd_unet is not None and shared.opts.sd_unet != 'Default': @@ -49,7 +49,7 @@ def load_text_encoder(repo_id, diffusers_load_config={}): if shared.opts.diffusers_offload_mode != 'none' and text_encoder is not None: sd_models.move_model(text_encoder, devices.cpu) - load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='LLM', device_map=True) + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='TE', device_map=True) llama_repo = shared.opts.model_h1_llama_repo if shared.opts.model_h1_llama_repo != 'Default' else 'meta-llama/Meta-Llama-3.1-8B-Instruct' shared.log.debug(f'Load model: type=HiDream te4="{llama_repo}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') diff --git a/modules/model_flex.py b/modules/model_flex.py index 953a22ec1..1ce5d6145 100644 --- a/modules/model_flex.py +++ b/modules/model_flex.py @@ -6,7 +6,7 @@ from modules import shared, devices, sd_models, model_quant, modelloader, sd_hij def load_transformer(repo_id, diffusers_load_config={}): - load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer', device_map=True) + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model', device_map=True) fn = None if shared.opts.sd_unet is not None and shared.opts.sd_unet != 'Default': @@ -24,7 +24,7 @@ def load_transformer(repo_id, diffusers_load_config={}): elif fn is not None and 'safetensors' in fn.lower(): shared.log.debug(f'Load model: type=FLEX transformer="{repo_id}" quant="{model_quant.get_quant(repo_id)}" args={load_args}') transformer = diffusers.FluxTransformer2DModel.from_single_file(fn, cache_dir=shared.opts.hfcache_dir, **load_args) - # elif model_quant.check_nunchaku('Transformer'): + # elif model_quant.check_nunchaku('Model'): # shared.log.error(f'Load model: type=HiDream transformer="{repo_id}" quant="Nunchaku" unsupported') # transformer = None else: diff --git a/modules/model_flux.py b/modules/model_flux.py index 636daff65..b164de963 100644 --- a/modules/model_flux.py +++ b/modules/model_flux.py @@ -110,7 +110,7 @@ def load_flux_bnb(checkpoint_info, diffusers_load_config): # pylint: disable=unu def load_quants(kwargs, repo_id, cache_dir, allow_quant): try: - if 'transformer' not in kwargs and model_quant.check_nunchaku('Transformer'): + if 'transformer' not in kwargs and model_quant.check_nunchaku('Model'): import nunchaku nunchaku_precision = nunchaku.utils.get_precision() nunchaku_repo = None @@ -128,8 +128,8 @@ def load_quants(kwargs, repo_id, cache_dir, allow_quant): kwargs['transformer'].quantization_method = 'SVDQuant' if shared.opts.nunchaku_attention: kwargs['transformer'].set_attention_impl("nunchaku-fp16") - elif 'transformer' not in kwargs and model_quant.check_quant('Transformer'): - quant_args = model_quant.create_config(allow=allow_quant, module='Transformer') + elif 'transformer' not in kwargs and model_quant.check_quant('Model'): + quant_args = model_quant.create_config(allow=allow_quant, module='Model') if quant_args: kwargs['transformer'] = diffusers.FluxTransformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) if 'text_encoder_2' not in kwargs and model_quant.check_nunchaku('TE'): @@ -186,7 +186,7 @@ def load_transformer(file_path): # triggered by opts.sd_unet change transformer, _text_encoder_2 = load_flux_nf4(file_path, prequantized=False) if transformer is not None: return transformer - quant_args = model_quant.create_config(module='Transformer') + quant_args = model_quant.create_config(module='Model') if quant_args: shared.log.info(f'Load module: type=UNet/Transformer file="{file_path}" offload={shared.opts.diffusers_offload_mode} quant=torchao dtype={devices.dtype}') transformer = diffusers.FluxTransformer2DModel.from_single_file(file_path, **diffusers_load_config, **quant_args) @@ -347,7 +347,7 @@ def load_flux(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ch else: pipe = cls.from_pretrained(repo_id, cache_dir=shared.opts.diffusers_dir, **kwargs, **diffusers_load_config) - if shared.opts.teacache_enabled and model_quant.check_nunchaku('Transformer'): + if shared.opts.teacache_enabled and model_quant.check_nunchaku('Model'): from nunchaku.caching.diffusers_adapters import apply_cache_on_pipe apply_cache_on_pipe(pipe, residual_diff_threshold=0.12) diff --git a/modules/model_hidream.py b/modules/model_hidream.py index f3c5c6483..ac7b335d1 100644 --- a/modules/model_hidream.py +++ b/modules/model_hidream.py @@ -6,7 +6,7 @@ from modules import shared, devices, sd_models, model_quant, modelloader, sd_hij def load_transformer(repo_id, diffusers_load_config={}): - load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer', device_map=True) + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model', device_map=True) fn = None if shared.opts.sd_unet is not None and shared.opts.sd_unet != 'Default': @@ -24,7 +24,7 @@ def load_transformer(repo_id, diffusers_load_config={}): elif fn is not None and 'safetensors' in fn.lower(): shared.log.debug(f'Load model: type=HiDream transformer="{repo_id}" quant="{model_quant.get_quant(repo_id)}" args={load_args}') transformer = diffusers.HiDreamImageTransformer2DModel.from_single_file(fn, cache_dir=shared.opts.hfcache_dir, **load_args) - # elif model_quant.check_nunchaku('Transformer'): + # elif model_quant.check_nunchaku('Model'): # shared.log.error(f'Load model: type=HiDream transformer="{repo_id}" quant="Nunchaku" unsupported') # transformer = None else: @@ -56,7 +56,7 @@ def load_text_encoders(repo_id, diffusers_load_config={}): if shared.opts.diffusers_offload_mode != 'none' and text_encoder_3 is not None: sd_models.move_model(text_encoder_3, devices.cpu) - load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='LLM', device_map=True) + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='TE', device_map=True) llama_repo = shared.opts.model_h1_llama_repo if shared.opts.model_h1_llama_repo != 'Default' else 'meta-llama/Meta-Llama-3.1-8B-Instruct' shared.log.debug(f'Load model: type=HiDream te4="{llama_repo}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') diff --git a/modules/model_lumina.py b/modules/model_lumina.py index 1ecbd4e4c..306881bbd 100644 --- a/modules/model_lumina.py +++ b/modules/model_lumina.py @@ -30,7 +30,7 @@ def load_lumina2(checkpoint_info, diffusers_load_config={}): shared.log.debug(f'Transformers cache: type=teacache patch=forward cls={diffusers.Lumina2Transformer2DModel.__name__}') diffusers.Lumina2Transformer2DModel.forward = teacache.teacache_lumina2_forward # patch must be done before transformer is loaded - load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, module='Transformer') + load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, module='Model') if shared.opts.sd_unet != 'Default': try: debug(f'Load model: type=Lumina2 unet="{shared.opts.sd_unet}"') diff --git a/modules/model_omnigen.py b/modules/model_omnigen.py index 0df4948a6..7de3a93fe 100644 --- a/modules/model_omnigen.py +++ b/modules/model_omnigen.py @@ -24,7 +24,7 @@ def load_omnigen(checkpoint_info, diffusers_load_config={}): # pylint: disable=u if debug: errors.display(e, 'OmniGen VAE:') - load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, module='Transformer') + load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, module='Model') transformer = diffusers.OmniGenTransformer2DModel.from_pretrained( repo_id, subfolder="transformer", diff --git a/modules/model_pixart.py b/modules/model_pixart.py index 6f6d6cf1c..7edcee7ac 100644 --- a/modules/model_pixart.py +++ b/modules/model_pixart.py @@ -15,7 +15,7 @@ def load_pixart(checkpoint_info, diffusers_load_config={}): if not file_exists(repo_id_pipe, "model_index.json"): repo_id_pipe = "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer') + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model') transformer = diffusers.PixArtTransformer2DModel.from_pretrained( repo_id, subfolder='transformer', diff --git a/modules/model_quant.py b/modules/model_quant.py index 4b6236221..cbacd7842 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -37,128 +37,122 @@ def get_quant(name): return 'none' -def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Model', modules_to_not_convert: list = []): +def create_bnb_config(kwargs = None, allow: bool = True, module: str = 'Model', modules_to_not_convert: list = []): from modules import shared, devices - if len(shared.opts.bnb_quantization) > 0 and allow_bnb: - if 'Model' in shared.opts.bnb_quantization or (module is not None and module in shared.opts.bnb_quantization) or module == 'any': - load_bnb() - if bnb is None: - return kwargs - bnb_config = diffusers.BitsAndBytesConfig( - load_in_8bit=shared.opts.bnb_quantization_type in ['fp8'], - load_in_4bit=shared.opts.bnb_quantization_type in ['nf4', 'fp4'], - bnb_4bit_quant_storage=shared.opts.bnb_quantization_storage, - bnb_4bit_quant_type=shared.opts.bnb_quantization_type, - bnb_4bit_compute_dtype=devices.dtype, - llm_int8_skip_modules=modules_to_not_convert, - ) - log.debug(f'Quantization: module={module} type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') - if kwargs is None: - return bnb_config - else: - kwargs['quantization_config'] = bnb_config - return kwargs + if allow and (module == 'any' or module in shared.opts.bnb_quantization): + load_bnb() + if bnb is None: + return kwargs + bnb_config = diffusers.BitsAndBytesConfig( + load_in_8bit=shared.opts.bnb_quantization_type in ['fp8'], + load_in_4bit=shared.opts.bnb_quantization_type in ['nf4', 'fp4'], + bnb_4bit_quant_storage=shared.opts.bnb_quantization_storage, + bnb_4bit_quant_type=shared.opts.bnb_quantization_type, + bnb_4bit_compute_dtype=devices.dtype, + llm_int8_skip_modules=modules_to_not_convert, + ) + log.debug(f'Quantization: module={module} type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') + if kwargs is None: + return bnb_config + else: + kwargs['quantization_config'] = bnb_config + return kwargs return kwargs -def create_ao_config(kwargs = None, allow_ao: bool = True, module: str = 'Model', modules_to_not_convert: list = []): +def create_ao_config(kwargs = None, allow: bool = True, module: str = 'Model', modules_to_not_convert: list = []): from modules import shared - if len(shared.opts.torchao_quantization) > 0 and (shared.opts.torchao_quantization_mode in {'pre', 'auto'}) and allow_ao: - if 'Model' in shared.opts.torchao_quantization or (module is not None and module in shared.opts.torchao_quantization) or module == 'any': - torchao = load_torchao() - if torchao is None: - return kwargs - if module in {'TE', 'LLM'}: - ao_config = transformers.TorchAoConfig(quant_type=shared.opts.torchao_quantization_type, modules_to_not_convert=modules_to_not_convert) - else: - ao_config = diffusers.TorchAoConfig(shared.opts.torchao_quantization_type, modules_to_not_convert=modules_to_not_convert) - log.debug(f'Quantization: module={module} type=torchao dtype={shared.opts.torchao_quantization_type}') - if kwargs is None: - return ao_config - else: - kwargs['quantization_config'] = ao_config - return kwargs + if allow and (shared.opts.torchao_quantization_mode in {'pre', 'auto'}) and (module == 'any' or module in shared.opts.torchao_quantization): + torchao = load_torchao() + if torchao is None: + return kwargs + if module in {'TE', 'LLM'}: + ao_config = transformers.TorchAoConfig(quant_type=shared.opts.torchao_quantization_type, modules_to_not_convert=modules_to_not_convert) + else: + ao_config = diffusers.TorchAoConfig(shared.opts.torchao_quantization_type, modules_to_not_convert=modules_to_not_convert) + log.debug(f'Quantization: module={module} type=torchao dtype={shared.opts.torchao_quantization_type}') + if kwargs is None: + return ao_config + else: + kwargs['quantization_config'] = ao_config + return kwargs return kwargs -def create_quanto_config(kwargs = None, allow_quanto: bool = True, module: str = 'Model', modules_to_not_convert: list = []): +def create_quanto_config(kwargs = None, allow: bool = True, module: str = 'Model', modules_to_not_convert: list = []): from modules import shared - if len(shared.opts.quanto_quantization) > 0 and allow_quanto: - if 'Model' in shared.opts.quanto_quantization or (module is not None and module in shared.opts.quanto_quantization) or module == 'any': - load_quanto(silent=True) - if optimum_quanto is None: - return kwargs - if module in {'TE', 'LLM'}: - quanto_config = transformers.QuantoConfig(weights=shared.opts.quanto_quantization_type, modules_to_not_convert=modules_to_not_convert) - quanto_config.weights_dtype = quanto_config.weights - else: - quanto_config = diffusers.QuantoConfig(weights_dtype=shared.opts.quanto_quantization_type, modules_to_not_convert=modules_to_not_convert) - quanto_config.activations = None # patch so it works with transformers - quanto_config.weights = quanto_config.weights_dtype - log.debug(f'Quantization: module={module} type=quanto dtype={shared.opts.quanto_quantization_type}') - if kwargs is None: - return quanto_config - else: - kwargs['quantization_config'] = quanto_config - return kwargs + if allow and (module == 'any' or module in shared.opts.quanto_quantization): + load_quanto(silent=True) + if optimum_quanto is None: + return kwargs + if module in {'TE', 'LLM'}: + quanto_config = transformers.QuantoConfig(weights=shared.opts.quanto_quantization_type, modules_to_not_convert=modules_to_not_convert) + quanto_config.weights_dtype = quanto_config.weights + else: + quanto_config = diffusers.QuantoConfig(weights_dtype=shared.opts.quanto_quantization_type, modules_to_not_convert=modules_to_not_convert) + quanto_config.activations = None # patch so it works with transformers + quanto_config.weights = quanto_config.weights_dtype + log.debug(f'Quantization: module={module} type=quanto dtype={shared.opts.quanto_quantization_type}') + if kwargs is None: + return quanto_config + else: + kwargs['quantization_config'] = quanto_config + return kwargs return kwargs -def create_sdnq_config(kwargs = None, allow_sdnq: bool = True, module: str = 'Model', weights_dtype: str = None, modules_to_not_convert: list = []): +def create_sdnq_config(kwargs = None, allow: bool = True, module: str = 'Model', weights_dtype: str = None, modules_to_not_convert: list = []): from modules import devices, shared - if len(shared.opts.sdnq_quantize_weights) > 0 and (shared.opts.sdnq_quantize_mode in {'pre', 'auto'}) and allow_sdnq: - if 'Model' in shared.opts.sdnq_quantize_weights or (module is not None and module in shared.opts.sdnq_quantize_weights) or module == 'any': - from modules.sdnq import SDNQQuantizer, SDNQConfig - diffusers.quantizers.auto.AUTO_QUANTIZER_MAPPING["sdnq"] = SDNQQuantizer - transformers.quantizers.auto.AUTO_QUANTIZER_MAPPING["sdnq"] = SDNQQuantizer - diffusers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig - transformers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig + if allow and (shared.opts.sdnq_quantize_mode in {'pre', 'auto'}) and (module == 'any' or module in shared.opts.sdnq_quantize_weights): + from modules.sdnq import SDNQQuantizer, SDNQConfig + diffusers.quantizers.auto.AUTO_QUANTIZER_MAPPING["sdnq"] = SDNQQuantizer + transformers.quantizers.auto.AUTO_QUANTIZER_MAPPING["sdnq"] = SDNQQuantizer + diffusers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig + transformers.quantizers.auto.AUTO_QUANTIZATION_CONFIG_MAPPING["sdnq"] = SDNQConfig - if weights_dtype is None: - if module in {"TE", "LLM"} and shared.opts.sdnq_quantize_weights_mode_te not in {"same as model", "default"}: - weights_dtype = shared.opts.sdnq_quantize_weights_mode_te - else: - weights_dtype = shared.opts.sdnq_quantize_weights_mode - if weights_dtype is None or weights_dtype == 'none': - return kwargs - - if shared.opts.device_map == "gpu": - quantization_device = devices.device - return_device = devices.device - elif shared.opts.diffusers_offload_mode in {"none", "model"}: - quantization_device = devices.device if shared.opts.sdnq_quantize_with_gpu else devices.cpu - return_device = devices.device - elif shared.opts.sdnq_quantize_with_gpu: - quantization_device = devices.device - return_device = devices.cpu + if weights_dtype is None: + if module in {"TE", "LLM"} and shared.opts.sdnq_quantize_weights_mode_te not in {"same as model", "default"}: + weights_dtype = shared.opts.sdnq_quantize_weights_mode_te else: - quantization_device = None - return_device = None + weights_dtype = shared.opts.sdnq_quantize_weights_mode + if weights_dtype is None or weights_dtype == 'none': + return kwargs - sdnq_config = SDNQConfig( - weights_dtype=weights_dtype, - group_size=shared.opts.sdnq_quantize_weights_group_size, - quant_conv=shared.opts.sdnq_quantize_conv_layers, - use_quantized_matmul=shared.opts.sdnq_use_quantized_matmul, - use_quantized_matmul_conv=shared.opts.sdnq_use_quantized_matmul_conv, - dequantize_fp32=shared.opts.sdnq_dequantize_fp32, - quantization_device=quantization_device, - return_device=return_device, - modules_to_not_convert=modules_to_not_convert, - ) - log.debug(f'Quantization: module="{module}" type=sdnq dtype={weights_dtype} matmul={shared.opts.sdnq_use_quantized_matmul} group_size={shared.opts.sdnq_quantize_weights_group_size} quant_conv={shared.opts.sdnq_quantize_conv_layers} matmul_conv={shared.opts.sdnq_use_quantized_matmul_conv} dequantize_fp32={shared.opts.sdnq_dequantize_fp32} quantize_with_gpu={shared.opts.sdnq_quantize_with_gpu} quantization_device={quantization_device} return_device={return_device}') - if kwargs is None: - return sdnq_config - else: - kwargs['quantization_config'] = sdnq_config - return kwargs + if shared.opts.device_map == "gpu": + quantization_device = devices.device + return_device = devices.device + elif shared.opts.diffusers_offload_mode in {"none", "model"}: + quantization_device = devices.device if shared.opts.sdnq_quantize_with_gpu else devices.cpu + return_device = devices.device + elif shared.opts.sdnq_quantize_with_gpu: + quantization_device = devices.device + return_device = devices.cpu + else: + quantization_device = None + return_device = None + + sdnq_config = SDNQConfig( + weights_dtype=weights_dtype, + group_size=shared.opts.sdnq_quantize_weights_group_size, + quant_conv=shared.opts.sdnq_quantize_conv_layers, + use_quantized_matmul=shared.opts.sdnq_use_quantized_matmul, + use_quantized_matmul_conv=shared.opts.sdnq_use_quantized_matmul_conv, + dequantize_fp32=shared.opts.sdnq_dequantize_fp32, + quantization_device=quantization_device, + return_device=return_device, + modules_to_not_convert=modules_to_not_convert, + ) + log.debug(f'Quantization: module="{module}" type=sdnq dtype={weights_dtype} matmul={shared.opts.sdnq_use_quantized_matmul} group_size={shared.opts.sdnq_quantize_weights_group_size} quant_conv={shared.opts.sdnq_quantize_conv_layers} matmul_conv={shared.opts.sdnq_use_quantized_matmul_conv} dequantize_fp32={shared.opts.sdnq_dequantize_fp32} quantize_with_gpu={shared.opts.sdnq_quantize_with_gpu} quantization_device={quantization_device} return_device={return_device}') + if kwargs is None: + return sdnq_config + else: + kwargs['quantization_config'] = sdnq_config + return kwargs return kwargs def check_quant(module: str = ''): from modules import shared - if 'Model' in shared.opts.bnb_quantization or 'Model' in shared.opts.torchao_quantization or 'Model' in shared.opts.quanto_quantization or 'Model' in shared.opts.sdnq_quantize_weights: - return True if module in shared.opts.bnb_quantization or module in shared.opts.torchao_quantization or module in shared.opts.quanto_quantization or module in shared.opts.sdnq_quantize_weights: return True return False @@ -166,7 +160,7 @@ def check_quant(module: str = ''): def check_nunchaku(module: str = ''): from modules import shared - if 'Model' not in shared.opts.nunchaku_quantization and module not in shared.opts.nunchaku_quantization: + if module not in shared.opts.nunchaku_quantization: return False from modules import mit_nunchaku mit_nunchaku.install_nunchaku() @@ -178,22 +172,22 @@ def check_nunchaku(module: str = ''): def create_config(kwargs = None, allow: bool = True, module: str = 'Model', modules_to_not_convert = []): if kwargs is None: kwargs = {} - kwargs = create_sdnq_config(kwargs, allow_sdnq=allow, module=module, modules_to_not_convert=modules_to_not_convert) + kwargs = create_sdnq_config(kwargs, allow=allow, module=module, modules_to_not_convert=modules_to_not_convert) if kwargs is not None and 'quantization_config' in kwargs: if debug: log.trace(f'Quantization: type=sdnq config={kwargs.get("quantization_config", None)}') return kwargs - kwargs = create_bnb_config(kwargs, allow_bnb=allow, module=module, modules_to_not_convert=modules_to_not_convert) + kwargs = create_bnb_config(kwargs, allow=allow, module=module, modules_to_not_convert=modules_to_not_convert) if kwargs is not None and 'quantization_config' in kwargs: if debug: log.trace(f'Quantization: type=bnb config={kwargs.get("quantization_config", None)}') return kwargs - kwargs = create_quanto_config(kwargs, allow_quanto=allow, module=module, modules_to_not_convert=modules_to_not_convert) + kwargs = create_quanto_config(kwargs, allow=allow, module=module, modules_to_not_convert=modules_to_not_convert) if kwargs is not None and 'quantization_config' in kwargs: if debug: log.trace(f'Quantization: type=quanto config={kwargs.get("quantization_config", None)}') return kwargs - kwargs = create_ao_config(kwargs, allow_ao=allow, module=module, modules_to_not_convert=modules_to_not_convert) + kwargs = create_ao_config(kwargs, allow=allow, module=module, modules_to_not_convert=modules_to_not_convert) if kwargs is not None and 'quantization_config' in kwargs: if debug: log.trace(f'Quantization: type=torchao config={kwargs.get("quantization_config", None)}') @@ -309,13 +303,13 @@ def apply_layerwise(sd_model, quiet:bool=False): m.enable_layerwise_casting(compute_dtype=devices.dtype, storage_dtype=storage_dtype, non_blocking=non_blocking) m.quantization_method = 'LayerWise' log.quiet(quiet, f'Quantization: type=layerwise module={module} cls={cls} storage={storage_dtype} compute={devices.dtype} blocking={not non_blocking}') - if module.startswith('transformer') and ('Model' in shared.opts.layerwise_quantization or 'Transformer' in shared.opts.layerwise_quantization): + if module.startswith('transformer') and ('Model' in shared.opts.layerwise_quantization): m = getattr(sd_model, module) if hasattr(m, 'enable_layerwise_casting'): m.enable_layerwise_casting(compute_dtype=devices.dtype, storage_dtype=storage_dtype, non_blocking=non_blocking) m.quantization_method = 'LayerWise' log.quiet(quiet, f'Quantization: type=layerwise module={module} cls={cls} storage={storage_dtype} compute={devices.dtype} blocking={not non_blocking}') - if module.startswith('text_encoder') and ('Model' in shared.opts.layerwise_quantization or 'TE' in shared.opts.layerwise_quantization) and ('clip' not in cls.lower()): + if module.startswith('text_encoder') and ('TE' in shared.opts.layerwise_quantization) and ('clip' not in cls.lower()): m = getattr(sd_model, module) if hasattr(m, 'enable_layerwise_casting'): m.enable_layerwise_casting(compute_dtype=devices.dtype, storage_dtype=storage_dtype, non_blocking=non_blocking) @@ -575,7 +569,7 @@ def get_dit_args(load_config:dict={}, module:str=None, device_map:bool=False, al # if 'variant' in config: # del config['variant'] if device_map: - if devices.backend == "ipex" and os.environ.get('UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS', '0') != '1' and module in {'TE', 'LLM'}: + if devices.backend == 'ipex' and os.environ.get('UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS', '0') != '1' and module in {'TE', 'LLM'}: config['device_map'] = 'cpu' # alchemist gpus hits the 4GB allocation limit with transformers, UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS emulates above 4GB allocations elif shared.opts.device_map == 'cpu': config['device_map'] = 'cpu' diff --git a/modules/model_sana.py b/modules/model_sana.py index d211321fd..4de29f0d8 100644 --- a/modules/model_sana.py +++ b/modules/model_sana.py @@ -7,14 +7,14 @@ from modules import shared, sd_models, devices, modelloader, model_quant def load_quants(kwargs, repo_id, cache_dir): kwargs_copy = kwargs.copy() - if model_quant.check_nunchaku('Transformer') and 'Sana_1600M' in repo_id: # only sana-1600m + if model_quant.check_nunchaku('Model') and 'Sana_1600M' in repo_id: # only sana-1600m import nunchaku nunchaku_precision = nunchaku.utils.get_precision() nunchaku_repo = f"mit-han-lab/svdq-{nunchaku_precision}-sana-1600m" shared.log.debug(f'Load module: quant=Nunchaku module=transformer repo="{nunchaku_repo}" precision={nunchaku_precision} attention={shared.opts.nunchaku_attention}') kwargs['transformer'] = nunchaku.NunchakuSanaTransformer2DModel.from_pretrained(nunchaku_repo, torch_dtype=devices.dtype) - elif model_quant.check_quant('Transformer'): - load_args, quant_args = model_quant.get_dit_args(kwargs_copy, module='Transformer') + elif model_quant.check_quant('Model'): + load_args, quant_args = model_quant.get_dit_args(kwargs_copy, module='Model') if quant_args: kwargs['transformer'] = diffusers.SanaTransformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=cache_dir, **load_args, **quant_args) if model_quant.check_quant('TE'): diff --git a/modules/model_sd3.py b/modules/model_sd3.py index ecfc533a1..8bfea1afa 100644 --- a/modules/model_sd3.py +++ b/modules/model_sd3.py @@ -57,7 +57,7 @@ def load_overrides(kwargs, cache_dir): def load_quants(kwargs, repo_id, cache_dir): - quant_args = model_quant.create_config(module='Transformer') + quant_args = model_quant.create_config(module='Model') if quant_args and 'quantization_config' in quant_args: kwargs['transformer'] = diffusers.SD3Transformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) quant_args = model_quant.create_config(module='TE') diff --git a/modules/sd_models_utils.py b/modules/sd_models_utils.py index 546297d25..70b0a2173 100644 --- a/modules/sd_models_utils.py +++ b/modules/sd_models_utils.py @@ -151,14 +151,13 @@ def patch_diffuser_config(sd_model, model_file): def apply_function_to_model(sd_model, function, options, op=None): - if "Model" in options or "Transformer" in options: - if hasattr(sd_model, 'transformer') and hasattr(sd_model.transformer, 'config'): - sd_model.transformer = function(sd_model.transformer, op="transformer", sd_model=sd_model) if "Model" in options: if hasattr(sd_model, 'model') and (hasattr(sd_model.model, 'config') or isinstance(sd_model.model, torch.nn.Module)): sd_model.model = function(sd_model.model, op="model", sd_model=sd_model) if hasattr(sd_model, 'unet') and hasattr(sd_model.unet, 'config'): sd_model.unet = function(sd_model.unet, op="unet", sd_model=sd_model) + if hasattr(sd_model, 'transformer') and hasattr(sd_model.transformer, 'config'): + sd_model.transformer = function(sd_model.transformer, op="transformer", sd_model=sd_model) if hasattr(sd_model, 'decoder_pipe') and hasattr(sd_model, 'decoder'): sd_model.decoder = None sd_model.decoder = sd_model.decoder_pipe.decoder = function(sd_model.decoder_pipe.decoder, op="decoder_pipe.decoder", sd_model=sd_model) diff --git a/modules/shared.py b/modules/shared.py index be66c1127..cc224dc00 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -502,7 +502,7 @@ options_templates.update(options_section(('backends', "Backend Settings"), { "olive_cache_optimized": OptionInfo(True, 'Olive cache optimized models'), "ipex_sep": OptionInfo("

IPEX

", "", gr.HTML, {"visible": devices.backend == "ipex"}), - "ipex_optimize": OptionInfo([], "IPEX Optimize", gr.CheckboxGroup, {"choices": ["Model", "VAE", "TE", "Upscaler"], "visible": devices.backend == "ipex"}), + "ipex_optimize": OptionInfo([], "IPEX Optimize", gr.CheckboxGroup, {"choices": ["Model", "TE", "VAE", "Upscaler"], "visible": devices.backend == "ipex"}), "openvino_sep": OptionInfo("

OpenVINO

", "", gr.HTML, {"visible": cmd_opts.use_openvino}), "openvino_devices": OptionInfo([], "OpenVINO devices to use", gr.CheckboxGroup, {"choices": get_openvino_device_list() if cmd_opts.use_openvino else [], "visible": cmd_opts.use_openvino}), # pylint: disable=E0606 @@ -517,7 +517,7 @@ options_templates.update(options_section(('backends', "Backend Settings"), { options_templates.update(options_section(("quantization", "Quantization Settings"), { "sdnq_quantize_sep": OptionInfo("

SDNQ: SD.Next Quantization

", "", gr.HTML), - "sdnq_quantize_weights": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), + "sdnq_quantize_weights": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "TE", "LLM", "Control", "VAE"], "visible": native}), "sdnq_quantize_mode": OptionInfo("auto", "Quantization mode", gr.Dropdown, {"choices": ["auto", "pre", "post"], "visible": native}), "sdnq_quantize_weights_mode": OptionInfo("int8", "Quantization type", gr.Dropdown, {"choices": sdnq_quant_modes, "visible": native}), "sdnq_quantize_weights_mode_te": OptionInfo("default", "Quantization type for Text Encoders", gr.Dropdown, {"choices": ['default'] + sdnq_quant_modes, "visible": native}), @@ -531,41 +531,41 @@ options_templates.update(options_section(("quantization", "Quantization Settings "sdnq_quantize_shuffle_weights": OptionInfo(False, "Shuffle weights in post mode", gr.Checkbox, {"visible": native}), "bnb_quantization_sep": OptionInfo("

BitsAndBytes

", "", gr.HTML), - "bnb_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), + "bnb_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "TE", "LLM", "VAE"], "visible": native}), "bnb_quantization_type": OptionInfo("nf4", "Quantization type", gr.Dropdown, {"choices": ["nf4", "fp8", "fp4"], "visible": native}), "bnb_quantization_storage": OptionInfo("uint8", "Backend storage", gr.Dropdown, {"choices": ["float16", "float32", "int8", "uint8", "float64", "bfloat16"], "visible": native}), "quanto_quantization_sep": OptionInfo("

Optimum Quanto

", "", gr.HTML), - "quanto_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), + "quanto_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "TE", "LLM"], "visible": native}), "quanto_quantization_type": OptionInfo("int8", "Quantization weights type", gr.Dropdown, {"choices": ["float8", "int8", "int4", "int2"], "visible": native}), "optimum_quanto_sep": OptionInfo("

Optimum Quanto: post-load

", "", gr.HTML), - "optimum_quanto_weights": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), + "optimum_quanto_weights": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "TE", "Control", "VAE"], "visible": native}), "optimum_quanto_weights_type": OptionInfo("qint8", "Quantization weights type", gr.Dropdown, {"choices": ["qint8", "qfloat8_e4m3fn", "qfloat8_e5m2", "qint4", "qint2"], "visible": native}), "optimum_quanto_activations_type": OptionInfo("none", "Quantization activations type ", gr.Dropdown, {"choices": ["none", "qint8", "qfloat8_e4m3fn", "qfloat8_e5m2"], "visible": native}), "optimum_quanto_shuffle_weights": OptionInfo(False, "Shuffle weights in post mode", gr.Checkbox, {"visible": native}), "torchao_sep": OptionInfo("

TorchAO

", "", gr.HTML), - "torchao_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM"], "visible": native}), + "torchao_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "TE", "LLM", "Control", "VAE"], "visible": native}), "torchao_quantization_mode": OptionInfo("auto", "Quantization mode", gr.Dropdown, {"choices": ["auto", "pre", "post"], "visible": native}), "torchao_quantization_type": OptionInfo("int8_weight_only", "Quantization type", gr.Dropdown, {"choices": ["int4_weight_only", "int8_dynamic_activation_int4_weight", "int8_weight_only", "int8_dynamic_activation_int8_weight", "float8_weight_only", "float8_dynamic_activation_float8_weight", "float8_static_activation_float8_weight"], "visible": native}), "layerwise_quantization_sep": OptionInfo("

Layerwise Casting

", "", gr.HTML), - "layerwise_quantization": OptionInfo([], "Layerwise casting enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "TE"], "visible": native}), + "layerwise_quantization": OptionInfo([], "Layerwise casting enabled", gr.CheckboxGroup, {"choices": ["Model", "TE"], "visible": native}), "layerwise_quantization_storage": OptionInfo("float8_e4m3fn", "Layerwise casting storage", gr.Dropdown, {"choices": ["float8_e4m3fn", "float8_e5m2"], "visible": native}), "layerwise_quantization_nonblocking": OptionInfo(False, "Layerwise non-blocking operations", gr.Checkbox, {"visible": native}), "nunchaku_sep": OptionInfo("

Nunchaku Engine

", "", gr.HTML), - "nunchaku_quantization": OptionInfo([], "SVDQuant enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), + "nunchaku_quantization": OptionInfo([], "SVDQuant enabled", gr.CheckboxGroup, {"choices": ["Model", "TE"], "visible": native}), "nunchaku_attention": OptionInfo(False, "Nunchaku attention", gr.Checkbox, {"visible": native}), "nunchaku_offload": OptionInfo(False, "Nunchaku offloading", gr.Checkbox, {"visible": native}), "nncf_compress_sep": OptionInfo("

NNCF: Neural Network Compression Framework

", "", gr.HTML, {"visible": cmd_opts.use_openvino}), - "nncf_compress_weights": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": cmd_opts.use_openvino}), + "nncf_compress_weights": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "TE", "VAE"], "visible": cmd_opts.use_openvino}), "nncf_compress_weights_mode": OptionInfo("INT8_SYM", "Quantization type", gr.Dropdown, {"choices": ["INT8", "INT4_ASYM", "INT8_SYM", "INT4_SYM", "NF4"], "visible": cmd_opts.use_openvino}), "nncf_compress_weights_raito": OptionInfo(0, "Compress ratio", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01, "visible": cmd_opts.use_openvino}), "nncf_compress_weights_group_size": OptionInfo(0, "Group size", gr.Slider, {"minimum": -1, "maximum": 4096, "step": 1, "visible": cmd_opts.use_openvino}), - "nncf_quantize": OptionInfo([], "Static Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "VAE", "TE"], "visible": cmd_opts.use_openvino}), + "nncf_quantize": OptionInfo([], "Static Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "TE", "VAE"], "visible": cmd_opts.use_openvino}), "nncf_quantize_mode": OptionInfo("INT8", "OpenVINO activations mode", gr.Dropdown, {"choices": ["INT8", "FP8_E4M3", "FP8_E5M2"], "visible": cmd_opts.use_openvino}), })) @@ -650,7 +650,7 @@ options_templates.update(options_section(('advanced', "Pipeline Modifiers"), { options_templates.update(options_section(('compile', "Model Compile"), { "cuda_compile_sep": OptionInfo("

Model Compile

", "", gr.HTML), - "cuda_compile": OptionInfo([] if not cmd_opts.use_openvino else ["Model", "VAE", "Upscaler"], "Compile Model", gr.CheckboxGroup, {"choices": ["Model", "VAE", "TE", "Upscaler"]}), + "cuda_compile": OptionInfo([] if not cmd_opts.use_openvino else ["Model", "VAE", "Upscaler"], "Compile Model", gr.CheckboxGroup, {"choices": ["Model", "TE", "VAE", "Upscaler"]}), "cuda_compile_backend": OptionInfo("none" if not cmd_opts.use_openvino else "openvino_fx", "Model compile backend", gr.Radio, {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'migraphx', 'ipex', 'onediff', 'stable-fast', 'deep-cache', 'olive-ai', 'openvino_fx']}), "cuda_compile_mode": OptionInfo("default", "Model compile mode", gr.Radio, {"choices": ['default', 'reduce-overhead', 'max-autotune', 'max-autotune-no-cudagraphs']}), "cuda_compile_fullgraph": OptionInfo(True if not cmd_opts.use_openvino else False, "Model compile fullgraph"), diff --git a/modules/video_models/video_load.py b/modules/video_models/video_load.py index d607b8fc3..2a959f0d3 100644 --- a/modules/video_models/video_load.py +++ b/modules/video_models/video_load.py @@ -38,7 +38,7 @@ def load_model(selected: models_def.Model): # transformer try: - quant_args = model_quant.create_config(module='Video') + quant_args = model_quant.create_config(module='Model') debug(f'Video load: module=transformer repo="{selected.dit or selected.repo}" folder="{selected.dit_folder}" cls={selected.dit_cls.__name__} quant={model_quant.get_quant_type(quant_args)}') transformer = selected.dit_cls.from_pretrained( pretrained_model_name_or_path=selected.dit or selected.repo, From 1d8551788adda95eb9d8df45d7682f33cb229815 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 17:02:25 -0400 Subject: [PATCH 65/85] update changelog and cleanup samplers Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 5 +-- modules/sd_samplers_diffusers.py | 54 +++++++++++++------------------- wiki | 2 +- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e90bf3a..ec1c40e63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,16 +34,17 @@ - Control move global settings to control elements -> control settings tab - Control add setting to run hires with or without control - Update OpenVINO to 2025.2.0 + - Simplified and unified quantization enabled for options - **SDNQ Quantization** + - Add `auto` quantization mode - Add `modules_to_not_convert` support for post mode + - Improve offload compatibility - Fix Qwen 2.5 with int8 matmul - Fix Dora loading - Remove per layer GC - - Improve offload compatibility - Add support for XYZ grid to test quantization modes *note*: you need to enable quantization and choose what it applies on, then xyz grid can change quantization mode *note*: you can also enable 'add time info' to compare performance of different quantization modes - - Add `auto` quantization mode - **API** - Add `/sdapi/v1/lora?lora=` endpoint that returns full lora info and metadata - Add `/sdapi/v1/controlnets?model_type=` endpoints that returns list of available controlnets for specific model type diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index f151ab151..8df2d0dbd 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -13,30 +13,30 @@ debug_log = shared.log.trace if debug else lambda *args, **kwargs: None try: from diffusers import ( CMStochasticIterativeScheduler, - UniPCMultistepScheduler, - DDIMScheduler, - EulerDiscreteScheduler, - EulerAncestralDiscreteScheduler, - EDMEulerScheduler, - FlowMatchEulerDiscreteScheduler, - DEISMultistepScheduler, - SASolverScheduler, - DPMSolverSinglestepScheduler, - DPMSolverMultistepScheduler, - DPMSolverMultistepInverseScheduler, - EDMDPMSolverMultistepScheduler, CosineDPMSolverMultistepScheduler, - DPMSolverSDEScheduler, - HeunDiscreteScheduler, - FlowMatchHeunDiscreteScheduler, - LCMScheduler, - FlowMatchLCMScheduler, - PNDMScheduler, - IPNDMScheduler, + DDIMScheduler, DDPMScheduler, - LMSDiscreteScheduler, - KDPM2DiscreteScheduler, + DEISMultistepScheduler, + DPMSolverMultistepInverseScheduler, + DPMSolverMultistepScheduler, + DPMSolverSDEScheduler, + DPMSolverSinglestepScheduler, + EDMDPMSolverMultistepScheduler, + EDMEulerScheduler, + EulerAncestralDiscreteScheduler, + EulerDiscreteScheduler, + FlowMatchEulerDiscreteScheduler, + FlowMatchHeunDiscreteScheduler, + FlowMatchLCMScheduler, + HeunDiscreteScheduler, + IPNDMScheduler, KDPM2AncestralDiscreteScheduler, + KDPM2DiscreteScheduler, + LCMScheduler, + LMSDiscreteScheduler, + PNDMScheduler, + SASolverScheduler, + UniPCMultistepScheduler, ) except Exception as e: shared.log.error(f'Sampler import: version={diffusers.__version__} error: {e}') @@ -52,10 +52,6 @@ try: from modules.schedulers.scheduler_ufogen import UFOGenScheduler # pylint: disable=ungrouped-imports from modules.schedulers.scheduler_unipc_flowmatch import FlowUniPCMultistepScheduler # pylint: disable=ungrouped-imports from modules.perflow import PeRFlowScheduler # pylint: disable=ungrouped-imports - # from modules.schedulers.scheduler_kohaku import KohakuLoNyuYogScheduler # pylint: disable=ungrouped-imports - # from modules.schedulers.scheduler_smea import SMEAScheduler # pylint: disable=ungrouped-imports - # from modules.schedulers.scheduler_dy import DYScheduler # pylint: disable=ungrouped-imports - # from modules.schedulers.scheduler_negative import EulerNegativeScheduler # pylint: disable=ungrouped-imports except Exception as e: shared.log.error(f'Sampler import: version={diffusers.__version__} error: {e}') if os.environ.get('SD_SAMPLER_DEBUG', None) is not None: @@ -74,10 +70,6 @@ config = { 'Euler SGM': { 'steps_offset': 0, 'interpolation_type': "linear", 'rescale_betas_zero_snr': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'trailing', 'use_beta_sigmas': False, 'use_exponential_sigmas': False, 'use_karras_sigmas': False, 'prediction_type': "sample" }, 'Euler EDM': { 'sigma_schedule': "karras" }, 'Euler FlowMatch': { 'timestep_spacing': "linspace", 'shift': 1, 'use_dynamic_shifting': False, 'use_karras_sigmas': False, 'use_exponential_sigmas': False, 'use_beta_sigmas': False }, - # 'Euler SMEA': {}, - # 'Euler DY': {}, - # 'Euler Negative': {}, - # 'Kohaku LoNyu': {}, 'DPM++': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'use_exponential_sigmas': False, 'use_flow_sigmas': False, 'use_beta_sigmas': False, 'use_lu_lambdas': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'linspace', 'solver_order': 1 }, 'DPM++ 2M': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'use_exponential_sigmas': False, 'use_flow_sigmas': False, 'use_beta_sigmas': False, 'use_lu_lambdas': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'linspace', 'solver_order': 2 }, @@ -135,9 +127,6 @@ samplers_data_diffusers = [ SamplerData('Euler SGM', lambda model: DiffusionSampler('Euler SGM', EulerDiscreteScheduler, model), [], {}), SamplerData('Euler EDM', lambda model: DiffusionSampler('Euler EDM', EDMEulerScheduler, model), [], {}), SamplerData('Euler FlowMatch', lambda model: DiffusionSampler('Euler FlowMatch', FlowMatchEulerDiscreteScheduler, model), [], {}), - # SamplerData('Euler SMEA', lambda model: DiffusionSampler('Euler SMEA', SMEAScheduler, model), [], {}), - # SamplerData('Euler DY', lambda model: DiffusionSampler('Euler DY', DYScheduler, model), [], {}), - # SamplerData('Euler Negative', lambda model: DiffusionSampler('Euler Negative', EulerNegativeScheduler, model), [], {}), SamplerData('DPM++', lambda model: DiffusionSampler('DPM++', DPMSolverMultistepScheduler, model), [], {}), SamplerData('DPM++ 2M', lambda model: DiffusionSampler('DPM++ 2M', DPMSolverMultistepScheduler, model), [], {}), @@ -185,7 +174,6 @@ samplers_data_diffusers = [ SamplerData('TDD', lambda model: DiffusionSampler('TDD', TDDScheduler, model), [], {}), SamplerData('PeRFlow', lambda model: DiffusionSampler('PeRFlow', PeRFlowScheduler, model), [], {}), SamplerData('UFOGen', lambda model: DiffusionSampler('UFOGen', UFOGenScheduler, model), [], {}), - # SamplerData('Kohaku LoNyu', lambda model: DiffusionSampler('Kohaku LoNyu', KohakuLoNyuYogScheduler, model), [], {}), SamplerData('Same as primary', None, [], {}), ] diff --git a/wiki b/wiki index 706852830..c1ae36ab4 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 7068528301f5f1304f5e096291a9d014dce13c96 +Subproject commit c1ae36ab4c2197487306c5c9fe4e328a038d1367 From 7447a79b468f42dc84ff78699935f05f172fcb3f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 17:58:18 -0400 Subject: [PATCH 66/85] update requirements Signed-off-by: Vladimir Mandic --- requirements.txt | 8 ++++---- scripts/prompt_enhance.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5d88bbf35..0eea8fbac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,11 +42,11 @@ torchsde==0.2.6 antlr4-python3-runtime==4.9.3 requests==2.32.3 tqdm==4.67.1 -accelerate==1.7.0 +accelerate==1.8.1 opencv-contrib-python-headless==4.9.0.80 einops==0.4.1 gradio==3.43.2 -huggingface_hub==0.33.0 +huggingface_hub==0.33.1 numexpr==2.10.2 numpy==1.26.4 pandas==2.3.0 @@ -54,10 +54,10 @@ numba==0.61.2 protobuf==4.25.3 pytorch_lightning==1.9.4 tokenizers==0.21.1 -transformers==4.52.4 +transformers==4.53.0 urllib3==1.26.19 Pillow==10.4.0 -timm==0.9.16 +timm==1.0.16 pydantic==1.10.21 pyparsing==3.1.4 typing-extensions==4.12.2 diff --git a/scripts/prompt_enhance.py b/scripts/prompt_enhance.py index e1b9c5502..26981f812 100644 --- a/scripts/prompt_enhance.py +++ b/scripts/prompt_enhance.py @@ -367,6 +367,7 @@ class Script(scripts.Script): clean_up_tokenization_spaces=True, ) except Exception as e: + outputs = None shared.log.error(f'Prompt enhance generate: {e}') errors.display(e, 'Prompt enhance') self.busy = False From 533aa04bbd3be2c83b98e5b87b89c3c841b779eb Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 18:16:32 -0400 Subject: [PATCH 67/85] improve taesd live preview downscaling Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + modules/sd_samplers_common.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec1c40e63..f34d5d22f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ - Fix process batch double image save - Fix unapply texture tiling - Suppress torch empty logging + - Improve TAESD live preview downscale handling ## Update for 2025-06-16 diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 8935b4626..2d6c2858c 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -52,9 +52,9 @@ def single_sample_to_image(sample, approximation=None): if len(sample.shape) == 4 and sample.shape[0]: # likely animatediff latent sample = sample.permute(1, 0, 2, 3)[0] if approximation == 2: # TAESD - if (len(sample.shape) == 3 or len(sample.shape) == 4) and shared.opts.live_preview_downscale and (sample.shape[-1] > 128 or sample.shape[-2] > 128): + if (len(sample.shape) == 3 or len(sample.shape) == 4) and shared.opts.live_preview_downscale and (sample.shape[-1]*sample.shape[-2] > 128*128): try: - scale = 128 / max(sample.shape[-1], sample.shape[-2]) + scale = (128 * 128) / (sample.shape[-1] * sample.shape[-2]) sample = torch.nn.functional.interpolate(sample.unsqueeze(0), scale_factor=[scale, scale], mode='bilinear', align_corners=False)[0] except Exception: pass From 01a960b19e653543e92f603caa54a1f985e7d4c7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 18:30:23 -0400 Subject: [PATCH 68/85] update changelog and todo Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + TODO.md | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f34d5d22f..0ed2c89c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Chroma is a 8.9B parameter model based on *FLUX.1-schnell* and fully Apache 2.0 licensed - available via *networks -> models -> reference* - *note*: model is still in training so future updates will trigger re-download + - large credits to @Trojaner for work on bringing Chroma support to SD.Next and all the optimizations around it! - [JoyCaption Beta](https://huggingface.co/fancyfeast/llama-joycaption-beta-one-hf-llava) support (in addition to existing JoyCaption Alpha) - new version of highly popular captioning model - available via *caption -> vlm caption* diff --git a/TODO.md b/TODO.md index 51d342ab8..c8d6183e9 100644 --- a/TODO.md +++ b/TODO.md @@ -6,6 +6,14 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma ## Future Candidates +- Refactor: Move `model_*` stuff into subfolder +- Refactor: sampler options +- Fix LoRA unload for HiRes +- Simplify Control tab +- Common repo for T5 and CLiP +- Upgrade: unblock `numpy`: see `gradio` +- Upgrade: unblock `pydantic`: see + ### Complete Features - Python==3.13 improved support @@ -23,7 +31,6 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - [HiDream GGUF](https://github.com/huggingface/diffusers/pull/11550) - [Diffusers guiders](https://github.com/huggingface/diffusers/pull/11311) - [Nunchaku PulID](https://github.com/mit-han-lab/nunchaku/pull/274) -- [Pydantic changes](https://github.com/Cschlaefli/automatic) - [Dream0 guidance](https://huggingface.co/ByteDance/DreamO) - [S3Diff diffusion upscaler](https://github.com/ArcticHare105/S3Diff) - [SUPIR upscaler](https://github.com/Fanghua-Yu/SUPIR) From bff75f0db3b3e71f2f642813f6f39be13c3e8347 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 27 Jun 2025 20:20:43 -0400 Subject: [PATCH 69/85] add omnigen2 Signed-off-by: Vladimir Mandic --- .pylintrc | 1 + .ruff.toml | 1 + CHANGELOG.md | 5 + TODO.md | 1 - html/reference.json | 7 + models/Reference/OmniGen2--OmniGen2.jpg | Bin 0 -> 51113 bytes modules/model_omnigen.py | 15 - modules/model_omnigen2.py | 49 + modules/modeldata.py | 2 + modules/omnigen2/__init__.py | 3 + modules/omnigen2/image_processor.py | 265 ++++ modules/omnigen2/import_utils.py | 46 + modules/omnigen2/models/__init__.py | 0 .../omnigen2/models/attention_processor.py | 357 +++++ modules/omnigen2/models/embeddings.py | 126 ++ .../omnigen2/models/transformers/__init__.py | 3 + .../models/transformers/block_lumina2.py | 217 +++ .../models/transformers/components.py | 4 + modules/omnigen2/models/transformers/repo.py | 129 ++ .../transformers/transformer_omnigen2.py | 617 ++++++++ modules/omnigen2/pipeline_omnigen2.py | 718 ++++++++++ modules/omnigen2/pipeline_utils.py | 62 + modules/omnigen2/triton_layer_norm.py | 1257 +++++++++++++++++ modules/processing_args.py | 6 +- modules/sd_detect.py | 3 + modules/sd_models.py | 5 + modules/sd_offload.py | 2 +- modules/sd_samplers_common.py | 2 +- modules/shared.py | 8 +- wiki | 2 +- 30 files changed, 3886 insertions(+), 27 deletions(-) create mode 100644 models/Reference/OmniGen2--OmniGen2.jpg create mode 100644 modules/model_omnigen2.py create mode 100644 modules/omnigen2/__init__.py create mode 100644 modules/omnigen2/image_processor.py create mode 100644 modules/omnigen2/import_utils.py create mode 100644 modules/omnigen2/models/__init__.py create mode 100644 modules/omnigen2/models/attention_processor.py create mode 100644 modules/omnigen2/models/embeddings.py create mode 100644 modules/omnigen2/models/transformers/__init__.py create mode 100644 modules/omnigen2/models/transformers/block_lumina2.py create mode 100644 modules/omnigen2/models/transformers/components.py create mode 100644 modules/omnigen2/models/transformers/repo.py create mode 100644 modules/omnigen2/models/transformers/transformer_omnigen2.py create mode 100644 modules/omnigen2/pipeline_omnigen2.py create mode 100644 modules/omnigen2/pipeline_utils.py create mode 100644 modules/omnigen2/triton_layer_norm.py diff --git a/.pylintrc b/.pylintrc index 2c9bbc0c2..5ecbbef48 100644 --- a/.pylintrc +++ b/.pylintrc @@ -27,6 +27,7 @@ ignore-paths=/usr/lib/.*$, modules/meissonic, modules/mod, modules/omnigen, + modules/omnigen2, modules/onnx_impl, modules/pag, modules/pixelsmith, diff --git a/.ruff.toml b/.ruff.toml index ab5e0601c..023678c33 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -20,6 +20,7 @@ exclude = [ "modules/meissonic", "modules/mod", "modules/omnigen", + "modules/omnigen2", "modules/hidream", "modules/pag", "modules/pixelsmith", diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed2c89c0..6c11b9be5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - **Models** - [Models Wiki page](https://vladmandic.github.io/sdnext-docs/Models/) is updated will all new models + *note* all new image models larger than 30GB, so [offloading](https://vladmandic.github.io/sdnext-docs/Offload/) and [quantization](https://vladmandic.github.io/sdnext-docs/Quantization/) are necessary! + - [OmniGen2](https://huggingface.co/OmniGen2/OmniGen2) + - OmniGen2 is a powerful unified multimodal model that supports t2i and i2i workflows and uses 4B transformer with Qwen-VL-2.5 4B VLM + - available via *networks -> models -> reference* - [nVidia Cosmos-Predict2 T2I](https://research.nvidia.com/labs/dir/cosmos-predict2/) *2B and 14B* - Cosmos-Predict2 T2I is a new foundational model from Nvidia in two variants: small 2B and large 14B - available via *networks -> models -> reference* @@ -11,6 +15,7 @@ - *note*: this is a gated model, you need to [accept terms](https://huggingface.co/nvidia/Cosmos-Predict2-2B-Text2Image) and set your [huggingface token](https://vladmandic.github.io/sdnext-docs/Gated/) - [Black Forest Labs FLUX.1 Kontext I2I](https://bfl.ai/announcements/flux-1-kontext-dev) *Dev* variant - FLUX.1-Kontext is a 12B model billion parameter capable of editing images based on text instructions + - model is primarily designed for image editing workflows, but also works for text-to-image workflows - requirements are similar to regular FLUX.1 although 2x slower - available via *networks -> models -> reference* - *note*: this is a gated model, you need to [accept terms](https://huggingface.co/black-forest-labs/FLUX.1-Kontext-dev) and set your [huggingface token](https://vladmandic.github.io/sdnext-docs/Gated/) diff --git a/TODO.md b/TODO.md index c8d6183e9..17963a7af 100644 --- a/TODO.md +++ b/TODO.md @@ -52,7 +52,6 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - [SkyReels-v2](https://github.com/SkyworkAI/SkyReels-V2)(https://github.com/huggingface/diffusers/pull/11518) #### External:Unified/MultiModal - [Bagel](https://huggingface.co/ByteDance-Seed/BAGEL-7B-MoT)(https://github.com/bytedance-seed/bagel) -- [OmniGen2](https://huggingface.co/OmniGen2/OmniGen2) - [Ming](https://github.com/inclusionAI/Ming) - [Liquid](https://github.com/FoundationVision/Liquid) #### External:Image2Image/Editing diff --git a/html/reference.json b/html/reference.json index 599ad1c5b..e90aad1d1 100644 --- a/html/reference.json +++ b/html/reference.json @@ -252,6 +252,13 @@ "skip": true }, + "VectorSpaceLab OmniGen v2": { + "path": "OmniGen2/OmniGen2", + "desc": "OmniGen2 is a powerful and efficient unified multimodal model. Unlike OmniGen v1, OmniGen2 features two distinct decoding pathways for text and image modalities, utilizing unshared parameters and a decoupled image tokenizer.", + "preview": "OmniGen2--OmniGen2.jpg", + "skip": true + }, + "AuraFlow 0.3": { "path": "fal/AuraFlow-v0.3", "desc": "AuraFlow v0.3 is the fully open-sourced flow-based text-to-image generation model. The model was trained with more compute compared to the previous version, AuraFlow-v0.2. Compared to AuraFlow-v0.2, the model is fine-tuned on more aesthetic datasets and now supports various aspect ratio, (now width and height up to 1536 pixels).", diff --git a/models/Reference/OmniGen2--OmniGen2.jpg b/models/Reference/OmniGen2--OmniGen2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d8fce4558842e4d53b78983701a4ec069f1391a9 GIT binary patch literal 51113 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 Tuple[int, int]: + r""" + Returns the height and width of the image, downscaled to the next integer multiple of `vae_scale_factor`. + + Args: + image (`Union[PIL.Image.Image, np.ndarray, torch.Tensor]`): + The image input, which can be a PIL image, NumPy array, or PyTorch tensor. If it is a NumPy array, it + should have shape `[batch, height, width]` or `[batch, height, width, channels]`. If it is a PyTorch + tensor, it should have shape `[batch, channels, height, width]`. + height (`Optional[int]`, *optional*, defaults to `None`): + The height of the preprocessed image. If `None`, the height of the `image` input will be used. + width (`Optional[int]`, *optional*, defaults to `None`): + The width of the preprocessed image. If `None`, the width of the `image` input will be used. + + Returns: + `Tuple[int, int]`: + A tuple containing the height and width, both resized to the nearest integer multiple of + `vae_scale_factor`. + """ + + if height is None: + if isinstance(image, PIL.Image.Image): + height = image.height + elif isinstance(image, torch.Tensor): + height = image.shape[2] + else: + height = image.shape[1] + + if width is None: + if isinstance(image, PIL.Image.Image): + width = image.width + elif isinstance(image, torch.Tensor): + width = image.shape[3] + else: + width = image.shape[2] + + if max_side_length is None: + max_side_length = self.max_side_length + + if max_pixels is None: + max_pixels = self.max_pixels + + ratio = 1.0 + if max_side_length is not None: + if height > width: + max_side_length_ratio = max_side_length / height + else: + max_side_length_ratio = max_side_length / width + + cur_pixels = height * width + max_pixels_ratio = (max_pixels / cur_pixels) ** 0.5 + ratio = min(max_pixels_ratio, max_side_length_ratio, 1.0) # do not upscale input image + + new_height, new_width = int(height * ratio) // self.config.vae_scale_factor * self.config.vae_scale_factor, int(width * ratio) // self.config.vae_scale_factor * self.config.vae_scale_factor + return new_height, new_width + + def preprocess( + self, + image: PipelineImageInput, + height: Optional[int] = None, + width: Optional[int] = None, + max_pixels: Optional[int] = None, + max_side_length: Optional[int] = None, + resize_mode: str = "default", # "default", "fill", "crop" + crops_coords: Optional[Tuple[int, int, int, int]] = None, + ) -> torch.Tensor: + """ + Preprocess the image input. + + Args: + image (`PipelineImageInput`): + The image input, accepted formats are PIL images, NumPy arrays, PyTorch tensors; Also accept list of + supported formats. + height (`int`, *optional*): + The height in preprocessed image. If `None`, will use the `get_default_height_width()` to get default + height. + width (`int`, *optional*): + The width in preprocessed. If `None`, will use get_default_height_width()` to get the default width. + resize_mode (`str`, *optional*, defaults to `default`): + The resize mode, can be one of `default` or `fill`. If `default`, will resize the image to fit within + the specified width and height, and it may not maintaining the original aspect ratio. If `fill`, will + resize the image to fit within the specified width and height, maintaining the aspect ratio, and then + center the image within the dimensions, filling empty with data from image. If `crop`, will resize the + image to fit within the specified width and height, maintaining the aspect ratio, and then center the + image within the dimensions, cropping the excess. Note that resize_mode `fill` and `crop` are only + supported for PIL image input. + crops_coords (`List[Tuple[int, int, int, int]]`, *optional*, defaults to `None`): + The crop coordinates for each image in the batch. If `None`, will not crop the image. + + Returns: + `torch.Tensor`: + The preprocessed image. + """ + supported_formats = (PIL.Image.Image, np.ndarray, torch.Tensor) + + # Expand the missing dimension for 3-dimensional pytorch tensor or numpy array that represents grayscale image + if self.config.do_convert_grayscale and isinstance(image, (torch.Tensor, np.ndarray)) and image.ndim == 3: + if isinstance(image, torch.Tensor): + # if image is a pytorch tensor could have 2 possible shapes: + # 1. batch x height x width: we should insert the channel dimension at position 1 + # 2. channel x height x width: we should insert batch dimension at position 0, + # however, since both channel and batch dimension has same size 1, it is same to insert at position 1 + # for simplicity, we insert a dimension of size 1 at position 1 for both cases + image = image.unsqueeze(1) + else: + # if it is a numpy array, it could have 2 possible shapes: + # 1. batch x height x width: insert channel dimension on last position + # 2. height x width x channel: insert batch dimension on first position + if image.shape[-1] == 1: + image = np.expand_dims(image, axis=0) + else: + image = np.expand_dims(image, axis=-1) + + if isinstance(image, list) and isinstance(image[0], np.ndarray) and image[0].ndim == 4: + warnings.warn( + "Passing `image` as a list of 4d np.ndarray is deprecated." + "Please concatenate the list along the batch dimension and pass it as a single 4d np.ndarray", + FutureWarning, + ) + image = np.concatenate(image, axis=0) + if isinstance(image, list) and isinstance(image[0], torch.Tensor) and image[0].ndim == 4: + warnings.warn( + "Passing `image` as a list of 4d torch.Tensor is deprecated." + "Please concatenate the list along the batch dimension and pass it as a single 4d torch.Tensor", + FutureWarning, + ) + image = torch.cat(image, axis=0) + + if not is_valid_image_imagelist(image): + raise ValueError( + f"Input is in incorrect format. Currently, we only support {', '.join(str(x) for x in supported_formats)}" + ) + if not isinstance(image, list): + image = [image] + + if isinstance(image[0], PIL.Image.Image): + if crops_coords is not None: + image = [i.crop(crops_coords) for i in image] + if self.config.do_resize: + height, width = self.get_new_height_width(image[0], height, width, max_pixels, max_side_length) + image = [self.resize(i, height, width, resize_mode=resize_mode) for i in image] + if self.config.do_convert_rgb: + image = [self.convert_to_rgb(i) for i in image] + elif self.config.do_convert_grayscale: + image = [self.convert_to_grayscale(i) for i in image] + image = self.pil_to_numpy(image) # to np + image = self.numpy_to_pt(image) # to pt + + elif isinstance(image[0], np.ndarray): + image = np.concatenate(image, axis=0) if image[0].ndim == 4 else np.stack(image, axis=0) + + image = self.numpy_to_pt(image) + + height, width = self.get_new_height_width(image, height, width, max_pixels, max_side_length) + if self.config.do_resize: + image = self.resize(image, height, width) + + elif isinstance(image[0], torch.Tensor): + image = torch.cat(image, axis=0) if image[0].ndim == 4 else torch.stack(image, axis=0) + + if self.config.do_convert_grayscale and image.ndim == 3: + image = image.unsqueeze(1) + + channel = image.shape[1] + # don't need any preprocess if the image is latents + if channel == self.config.vae_latent_channels: + return image + + height, width = self.get_new_height_width(image, height, width, max_pixels, max_side_length) + if self.config.do_resize: + image = self.resize(image, height, width) + + # expected range [0,1], normalize to [-1,1] + do_normalize = self.config.do_normalize + if do_normalize and image.min() < 0: + warnings.warn( + "Passing `image` as torch tensor with value range in [-1,1] is deprecated. The expected value range for image tensor is [0,1] " + f"when passing as pytorch tensor or numpy Array. You passed `image` with value range [{image.min()},{image.max()}]", + FutureWarning, + ) + do_normalize = False + if do_normalize: + image = self.normalize(image) + + if self.config.do_binarize: + image = self.binarize(image) + + return image diff --git a/modules/omnigen2/import_utils.py b/modules/omnigen2/import_utils.py new file mode 100644 index 000000000..148b88684 --- /dev/null +++ b/modules/omnigen2/import_utils.py @@ -0,0 +1,46 @@ +# Copyright 2024 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Import utilities: Utilities related to imports and our lazy inits. +""" + +import importlib.util +import sys + +# The package importlib_metadata is in a different place, depending on the python version. +if sys.version_info < (3, 8): + import importlib_metadata +else: + import importlib.metadata as importlib_metadata + +def _is_package_available(pkg_name: str): + pkg_exists = importlib.util.find_spec(pkg_name) is not None + pkg_version = "N/A" + + if pkg_exists: + try: + pkg_version = importlib_metadata.version(pkg_name) + except (ImportError, importlib_metadata.PackageNotFoundError): + pkg_exists = False + + return pkg_exists, pkg_version + +_triton_available, _triton_version = _is_package_available("triton") +_flash_attn_available, _flash_attn_version = _is_package_available("flash_attn") + +def is_triton_available(): + return _triton_available + +def is_flash_attn_available(): + return _flash_attn_available diff --git a/modules/omnigen2/models/__init__.py b/modules/omnigen2/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/omnigen2/models/attention_processor.py b/modules/omnigen2/models/attention_processor.py new file mode 100644 index 000000000..592d6b503 --- /dev/null +++ b/modules/omnigen2/models/attention_processor.py @@ -0,0 +1,357 @@ +""" +OmniGen2 Attention Processor Module + +Copyright 2025 BAAI, The OmniGen2 Team and The HuggingFace Team. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import warnings +import math +from typing import Optional, Tuple, Dict, Any + +import torch +import torch.nn.functional as F +from einops import repeat + +from ..import_utils import is_flash_attn_available + +if is_flash_attn_available(): + from flash_attn import flash_attn_varlen_func + from flash_attn.bert_padding import index_first_axis, pad_input, unpad_input +else: + warnings.warn("Cannot import flash_attn, install flash_attn to use Flash2Varlen attention for better performance") + + +from diffusers.models.attention_processor import Attention +from .embeddings import apply_rotary_emb + + +class OmniGen2AttnProcessorFlash2Varlen: + """ + Processor for implementing scaled dot-product attention with flash attention and variable length sequences. + + This processor implements: + - Flash attention with variable length sequences + - Rotary position embeddings (RoPE) + - Query-Key normalization + - Proportional attention scaling + + Args: + None + """ + + def __init__(self) -> None: + """Initialize the attention processor.""" + if not is_flash_attn_available(): + raise ImportError( + "OmniGen2AttnProcessorFlash2Varlen requires flash_attn. " + "Please install flash_attn." + ) + + def _upad_input( + self, + query_layer: torch.Tensor, + key_layer: torch.Tensor, + value_layer: torch.Tensor, + attention_mask: torch.Tensor, + query_length: int, + num_heads: int, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, Tuple[torch.Tensor, torch.Tensor], Tuple[int, int]]: + """ + Unpad the input tensors for flash attention. + + Args: + query_layer: Query tensor of shape (batch_size, seq_len, num_heads, head_dim) + key_layer: Key tensor of shape (batch_size, seq_len, num_kv_heads, head_dim) + value_layer: Value tensor of shape (batch_size, seq_len, num_kv_heads, head_dim) + attention_mask: Attention mask tensor of shape (batch_size, seq_len) + query_length: Length of the query sequence + num_heads: Number of attention heads + + Returns: + Tuple containing: + - Unpadded query tensor + - Unpadded key tensor + - Unpadded value tensor + - Query indices + - Tuple of cumulative sequence lengths for query and key + - Tuple of maximum sequence lengths for query and key + """ + def _get_unpad_data(attention_mask: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, int]: + """Helper function to get unpadding data from attention mask.""" + seqlens_in_batch = attention_mask.sum(dim=-1, dtype=torch.int32) + indices = torch.nonzero(attention_mask.flatten(), as_tuple=False).flatten() + max_seqlen_in_batch = seqlens_in_batch.max().item() + cu_seqlens = F.pad(torch.cumsum(seqlens_in_batch, dim=0, dtype=torch.int32), (1, 0)) + return indices, cu_seqlens, max_seqlen_in_batch + + indices_k, cu_seqlens_k, max_seqlen_in_batch_k = _get_unpad_data(attention_mask) + batch_size, kv_seq_len, num_key_value_heads, head_dim = key_layer.shape + + # Unpad key and value layers + key_layer = index_first_axis( + key_layer.reshape(batch_size * kv_seq_len, num_key_value_heads, head_dim), + indices_k, + ) + value_layer = index_first_axis( + value_layer.reshape(batch_size * kv_seq_len, num_key_value_heads, head_dim), + indices_k, + ) + + # Handle different query length cases + if query_length == kv_seq_len: + query_layer = index_first_axis( + query_layer.reshape(batch_size * kv_seq_len, num_heads, head_dim), + indices_k, + ) + cu_seqlens_q = cu_seqlens_k + max_seqlen_in_batch_q = max_seqlen_in_batch_k + indices_q = indices_k + elif query_length == 1: + max_seqlen_in_batch_q = 1 + cu_seqlens_q = torch.arange( + batch_size + 1, dtype=torch.int32, device=query_layer.device + ) + indices_q = cu_seqlens_q[:-1] + query_layer = query_layer.squeeze(1) + else: + attention_mask = attention_mask[:, -query_length:] + query_layer, indices_q, cu_seqlens_q, max_seqlen_in_batch_q = unpad_input(query_layer, attention_mask) + + return ( + query_layer, + key_layer, + value_layer, + indices_q, + (cu_seqlens_q, cu_seqlens_k), + (max_seqlen_in_batch_q, max_seqlen_in_batch_k), + ) + + def __call__( + self, + attn: Attention, + hidden_states: torch.Tensor, + encoder_hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + image_rotary_emb: Optional[torch.Tensor] = None, + base_sequence_length: Optional[int] = None, + ) -> torch.Tensor: + """ + Process attention computation with flash attention. + + Args: + attn: Attention module + hidden_states: Hidden states tensor of shape (batch_size, seq_len, hidden_dim) + encoder_hidden_states: Encoder hidden states tensor + attention_mask: Optional attention mask tensor + image_rotary_emb: Optional rotary embeddings for image tokens + base_sequence_length: Optional base sequence length for proportional attention + + Returns: + torch.Tensor: Processed hidden states after attention computation + """ + batch_size, sequence_length, _ = hidden_states.shape + + # Get Query-Key-Value Pair + query = attn.to_q(hidden_states) + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + query_dim = query.shape[-1] + inner_dim = key.shape[-1] + head_dim = query_dim // attn.heads + dtype = query.dtype + + # Get key-value heads + kv_heads = inner_dim // head_dim + + # Reshape tensors for attention computation + query = query.view(batch_size, -1, attn.heads, head_dim) + key = key.view(batch_size, -1, kv_heads, head_dim) + value = value.view(batch_size, -1, kv_heads, head_dim) + + # Apply Query-Key normalization + if attn.norm_q is not None: + query = attn.norm_q(query) + if attn.norm_k is not None: + key = attn.norm_k(key) + + # Apply Rotary Position Embeddings + if image_rotary_emb is not None: + query = apply_rotary_emb(query, image_rotary_emb, use_real=False) + key = apply_rotary_emb(key, image_rotary_emb, use_real=False) + + query, key = query.to(dtype), key.to(dtype) + + # Calculate attention scale + if base_sequence_length is not None: + softmax_scale = math.sqrt(math.log(sequence_length, base_sequence_length)) * attn.scale + else: + softmax_scale = attn.scale + + # Unpad input for flash attention + ( + query_states, + key_states, + value_states, + indices_q, + cu_seq_lens, + max_seq_lens, + ) = self._upad_input(query, key, value, attention_mask, sequence_length, attn.heads) + + cu_seqlens_q, cu_seqlens_k = cu_seq_lens + max_seqlen_in_batch_q, max_seqlen_in_batch_k = max_seq_lens + + # Handle different number of heads + if kv_heads < attn.heads: + key_states = repeat(key_states, "l h c -> l (h k) c", k=attn.heads // kv_heads) + value_states = repeat(value_states, "l h c -> l (h k) c", k=attn.heads // kv_heads) + + # Apply flash attention + attn_output_unpad = flash_attn_varlen_func( + query_states, + key_states, + value_states, + cu_seqlens_q=cu_seqlens_q, + cu_seqlens_k=cu_seqlens_k, + max_seqlen_q=max_seqlen_in_batch_q, + max_seqlen_k=max_seqlen_in_batch_k, + dropout_p=0.0, + causal=False, + softmax_scale=softmax_scale, + ) + + # Pad output and apply final transformations + hidden_states = pad_input(attn_output_unpad, indices_q, batch_size, sequence_length) + hidden_states = hidden_states.flatten(-2) + hidden_states = hidden_states.type_as(query) + + # Apply output projection + hidden_states = attn.to_out[0](hidden_states) + hidden_states = attn.to_out[1](hidden_states) + + return hidden_states + + +class OmniGen2AttnProcessor: + """ + Processor for implementing scaled dot-product attention with flash attention and variable length sequences. + + This processor is optimized for PyTorch 2.0 and implements: + - Flash attention with variable length sequences + - Rotary position embeddings (RoPE) + - Query-Key normalization + - Proportional attention scaling + + Args: + None + + Raises: + ImportError: If PyTorch version is less than 2.0 + """ + + def __init__(self) -> None: + """Initialize the attention processor.""" + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError( + "OmniGen2AttnProcessorFlash2Varlen requires PyTorch 2.0. " + "Please upgrade PyTorch to version 2.0 or later." + ) + + def __call__( + self, + attn: Attention, + hidden_states: torch.Tensor, + encoder_hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + image_rotary_emb: Optional[torch.Tensor] = None, + base_sequence_length: Optional[int] = None, + ) -> torch.Tensor: + """ + Process attention computation with flash attention. + + Args: + attn: Attention module + hidden_states: Hidden states tensor of shape (batch_size, seq_len, hidden_dim) + encoder_hidden_states: Encoder hidden states tensor + attention_mask: Optional attention mask tensor + image_rotary_emb: Optional rotary embeddings for image tokens + base_sequence_length: Optional base sequence length for proportional attention + + Returns: + torch.Tensor: Processed hidden states after attention computation + """ + batch_size, sequence_length, _ = hidden_states.shape + + # Get Query-Key-Value Pair + query = attn.to_q(hidden_states) + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + query_dim = query.shape[-1] + inner_dim = key.shape[-1] + head_dim = query_dim // attn.heads + dtype = query.dtype + + # Get key-value heads + kv_heads = inner_dim // head_dim + + # Reshape tensors for attention computation + query = query.view(batch_size, -1, attn.heads, head_dim) + key = key.view(batch_size, -1, kv_heads, head_dim) + value = value.view(batch_size, -1, kv_heads, head_dim) + + # Apply Query-Key normalization + if attn.norm_q is not None: + query = attn.norm_q(query) + if attn.norm_k is not None: + key = attn.norm_k(key) + + # Apply Rotary Position Embeddings + if image_rotary_emb is not None: + query = apply_rotary_emb(query, image_rotary_emb, use_real=False) + key = apply_rotary_emb(key, image_rotary_emb, use_real=False) + + query, key = query.to(dtype), key.to(dtype) + + # Calculate attention scale + if base_sequence_length is not None: + softmax_scale = math.sqrt(math.log(sequence_length, base_sequence_length)) * attn.scale + else: + softmax_scale = attn.scale + + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + if attention_mask is not None: + attention_mask = attention_mask.bool().view(batch_size, 1, 1, -1) + + query = query.transpose(1, 2) + key = key.transpose(1, 2) + value = value.transpose(1, 2) + + # explicitly repeat key and value to match query length, otherwise using enable_gqa=True results in MATH backend of sdpa in our test of pytorch2.6 + key = key.repeat_interleave(query.size(-3) // key.size(-3), -3) + value = value.repeat_interleave(query.size(-3) // value.size(-3), -3) + + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, scale=softmax_scale + ) + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + hidden_states = hidden_states.type_as(query) + + # Apply output projection + hidden_states = attn.to_out[0](hidden_states) + hidden_states = attn.to_out[1](hidden_states) + + return hidden_states diff --git a/modules/omnigen2/models/embeddings.py b/modules/omnigen2/models/embeddings.py new file mode 100644 index 000000000..047526f69 --- /dev/null +++ b/modules/omnigen2/models/embeddings.py @@ -0,0 +1,126 @@ +# Copyright 2024 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from typing import List, Optional, Tuple, Union + +import torch +from torch import nn + + +from diffusers.models.activations import get_activation + + +class TimestepEmbedding(nn.Module): + def __init__( + self, + in_channels: int, + time_embed_dim: int, + act_fn: str = "silu", + out_dim: int = None, + post_act_fn: Optional[str] = None, + cond_proj_dim=None, + sample_proj_bias=True, + ): + super().__init__() + + self.linear_1 = nn.Linear(in_channels, time_embed_dim, sample_proj_bias) + + if cond_proj_dim is not None: + self.cond_proj = nn.Linear(cond_proj_dim, in_channels, bias=False) + else: + self.cond_proj = None + + self.act = get_activation(act_fn) + + if out_dim is not None: + time_embed_dim_out = out_dim + else: + time_embed_dim_out = time_embed_dim + self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim_out, sample_proj_bias) + + if post_act_fn is None: + self.post_act = None + else: + self.post_act = get_activation(post_act_fn) + + self.initialize_weights() + + def initialize_weights(self): + nn.init.normal_(self.linear_1.weight, std=0.02) + nn.init.zeros_(self.linear_1.bias) + nn.init.normal_(self.linear_2.weight, std=0.02) + nn.init.zeros_(self.linear_2.bias) + + def forward(self, sample, condition=None): + if condition is not None: + sample = sample + self.cond_proj(condition) + sample = self.linear_1(sample) + + if self.act is not None: + sample = self.act(sample) + + sample = self.linear_2(sample) + + if self.post_act is not None: + sample = self.post_act(sample) + return sample + + +def apply_rotary_emb( + x: torch.Tensor, + freqs_cis: Union[torch.Tensor, Tuple[torch.Tensor]], + use_real: bool = True, + use_real_unbind_dim: int = -1, +) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Apply rotary embeddings to input tensors using the given frequency tensor. This function applies rotary embeddings + to the given query or key 'x' tensors using the provided frequency tensor 'freqs_cis'. The input tensors are + reshaped as complex numbers, and the frequency tensor is reshaped for broadcasting compatibility. The resulting + tensors contain rotary embeddings and are returned as real tensors. + + Args: + x (`torch.Tensor`): + Query or key tensor to apply rotary embeddings. [B, H, S, D] xk (torch.Tensor): Key tensor to apply + freqs_cis (`Tuple[torch.Tensor]`): Precomputed frequency tensor for complex exponentials. ([S, D], [S, D],) + + Returns: + Tuple[torch.Tensor, torch.Tensor]: Tuple of modified query tensor and key tensor with rotary embeddings. + """ + if use_real: + cos, sin = freqs_cis # [S, D] + cos = cos[None, None] + sin = sin[None, None] + cos, sin = cos.to(x.device), sin.to(x.device) + + if use_real_unbind_dim == -1: + # Used for flux, cogvideox, hunyuan-dit + x_real, x_imag = x.reshape(*x.shape[:-1], -1, 2).unbind(-1) # [B, S, H, D//2] + x_rotated = torch.stack([-x_imag, x_real], dim=-1).flatten(3) + elif use_real_unbind_dim == -2: + # Used for Stable Audio, OmniGen and CogView4 + x_real, x_imag = x.reshape(*x.shape[:-1], 2, -1).unbind(-2) # [B, S, H, D//2] + x_rotated = torch.cat([-x_imag, x_real], dim=-1) + else: + raise ValueError(f"`use_real_unbind_dim={use_real_unbind_dim}` but should be -1 or -2.") + + out = (x.float() * cos + x_rotated.float() * sin).to(x.dtype) + + return out + else: + # used for lumina + # x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2)) + x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], x.shape[-1] // 2, 2)) + freqs_cis = freqs_cis.unsqueeze(2) + x_out = torch.view_as_real(x_rotated * freqs_cis).flatten(3) + + return x_out.type_as(x) diff --git a/modules/omnigen2/models/transformers/__init__.py b/modules/omnigen2/models/transformers/__init__.py new file mode 100644 index 000000000..b2a23df90 --- /dev/null +++ b/modules/omnigen2/models/transformers/__init__.py @@ -0,0 +1,3 @@ +from .transformer_omnigen2 import OmniGen2Transformer2DModel + +__all__ = ["OmniGen2Transformer2DModel"] diff --git a/modules/omnigen2/models/transformers/block_lumina2.py b/modules/omnigen2/models/transformers/block_lumina2.py new file mode 100644 index 000000000..e6d36e0f1 --- /dev/null +++ b/modules/omnigen2/models/transformers/block_lumina2.py @@ -0,0 +1,217 @@ + +# Copyright 2024 Alpha-VLLM Authors and The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings +from typing import Optional, Tuple + +import torch +import torch.nn as nn + +from diffusers.models.embeddings import Timesteps +from ..embeddings import TimestepEmbedding +from ...import_utils import is_flash_attn_available, is_triton_available + +if is_triton_available(): + from ...triton_layer_norm import RMSNorm +else: + from torch.nn import RMSNorm + warnings.warn("Cannot import triton, install triton to use fused RMSNorm for better performance") + +if is_flash_attn_available(): + from flash_attn.ops.activations import swiglu +else: + from .components import swiglu + warnings.warn("Cannot import flash_attn, install flash_attn to use fused SwiGLU for better performance") + +# try: +# from flash_attn.ops.activations import swiglu as fused_swiglu +# FUSEDSWIGLU_AVALIBLE = True +# except ImportError: + +# FUSEDSWIGLU_AVALIBLE = False +# warnings.warn("Cannot import apex RMSNorm, switch to vanilla implementation") + +class LuminaRMSNormZero(nn.Module): + """ + Norm layer adaptive RMS normalization zero. + + Parameters: + embedding_dim (`int`): The size of each embedding vector. + """ + + def __init__( + self, + embedding_dim: int, + norm_eps: float, + norm_elementwise_affine: bool, + ): + super().__init__() + self.silu = nn.SiLU() + self.linear = nn.Linear( + min(embedding_dim, 1024), + 4 * embedding_dim, + bias=True, + ) + + self.norm = RMSNorm(embedding_dim, eps=norm_eps) + + def forward( + self, + x: torch.Tensor, + emb: Optional[torch.Tensor] = None, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + emb = self.linear(self.silu(emb)) + scale_msa, gate_msa, scale_mlp, gate_mlp = emb.chunk(4, dim=1) + x = self.norm(x) * (1 + scale_msa[:, None]) + return x, gate_msa, scale_mlp, gate_mlp + + +class LuminaLayerNormContinuous(nn.Module): + def __init__( + self, + embedding_dim: int, + conditioning_embedding_dim: int, + # NOTE: It is a bit weird that the norm layer can be configured to have scale and shift parameters + # because the output is immediately scaled and shifted by the projected conditioning embeddings. + # Note that AdaLayerNorm does not let the norm layer have scale and shift parameters. + # However, this is how it was implemented in the original code, and it's rather likely you should + # set `elementwise_affine` to False. + elementwise_affine=True, + eps=1e-5, + bias=True, + norm_type="layer_norm", + out_dim: Optional[int] = None, + ): + super().__init__() + + # AdaLN + self.silu = nn.SiLU() + self.linear_1 = nn.Linear(conditioning_embedding_dim, embedding_dim, bias=bias) + + if norm_type == "layer_norm": + self.norm = nn.LayerNorm(embedding_dim, eps, elementwise_affine, bias) + elif norm_type == "rms_norm": + self.norm = RMSNorm(embedding_dim, eps=eps, elementwise_affine=elementwise_affine) + else: + raise ValueError(f"unknown norm_type {norm_type}") + + self.linear_2 = None + if out_dim is not None: + self.linear_2 = nn.Linear(embedding_dim, out_dim, bias=bias) + + def forward( + self, + x: torch.Tensor, + conditioning_embedding: torch.Tensor, + ) -> torch.Tensor: + # convert back to the original dtype in case `conditioning_embedding`` is upcasted to float32 (needed for hunyuanDiT) + emb = self.linear_1(self.silu(conditioning_embedding).to(x.dtype)) + scale = emb + x = self.norm(x) * (1 + scale)[:, None, :] + + if self.linear_2 is not None: + x = self.linear_2(x) + + return x + + +class LuminaFeedForward(nn.Module): + r""" + A feed-forward layer. + + Parameters: + hidden_size (`int`): + The dimensionality of the hidden layers in the model. This parameter determines the width of the model's + hidden representations. + intermediate_size (`int`): The intermediate dimension of the feedforward layer. + multiple_of (`int`, *optional*): Value to ensure hidden dimension is a multiple + of this value. + ffn_dim_multiplier (float, *optional*): Custom multiplier for hidden + dimension. Defaults to None. + """ + + def __init__( + self, + dim: int, + inner_dim: int, + multiple_of: Optional[int] = 256, + ffn_dim_multiplier: Optional[float] = None, + ): + super().__init__() + self.swiglu = swiglu + + # custom hidden_size factor multiplier + if ffn_dim_multiplier is not None: + inner_dim = int(ffn_dim_multiplier * inner_dim) + inner_dim = multiple_of * ((inner_dim + multiple_of - 1) // multiple_of) + + self.linear_1 = nn.Linear( + dim, + inner_dim, + bias=False, + ) + self.linear_2 = nn.Linear( + inner_dim, + dim, + bias=False, + ) + self.linear_3 = nn.Linear( + dim, + inner_dim, + bias=False, + ) + + def forward(self, x): + h1, h2 = self.linear_1(x), self.linear_3(x) + return self.linear_2(self.swiglu(h1, h2)) + + +class Lumina2CombinedTimestepCaptionEmbedding(nn.Module): + def __init__( + self, + hidden_size: int = 4096, + text_feat_dim: int = 2048, + frequency_embedding_size: int = 256, + norm_eps: float = 1e-5, + timestep_scale: float = 1.0, + ) -> None: + super().__init__() + + self.time_proj = Timesteps( + num_channels=frequency_embedding_size, flip_sin_to_cos=True, downscale_freq_shift=0.0, scale=timestep_scale + ) + + self.timestep_embedder = TimestepEmbedding( + in_channels=frequency_embedding_size, time_embed_dim=min(hidden_size, 1024) + ) + + self.caption_embedder = nn.Sequential( + RMSNorm(text_feat_dim, eps=norm_eps), + nn.Linear(text_feat_dim, hidden_size, bias=True), + ) + + self._initialize_weights() + + def _initialize_weights(self): + nn.init.trunc_normal_(self.caption_embedder[1].weight, std=0.02) + nn.init.zeros_(self.caption_embedder[1].bias) + + def forward( + self, timestep: torch.Tensor, text_hidden_states: torch.Tensor, dtype: torch.dtype + ) -> Tuple[torch.Tensor, torch.Tensor]: + timestep_proj = self.time_proj(timestep).to(dtype=dtype) + time_embed = self.timestep_embedder(timestep_proj) + caption_embed = self.caption_embedder(text_hidden_states) + return time_embed, caption_embed diff --git a/modules/omnigen2/models/transformers/components.py b/modules/omnigen2/models/transformers/components.py new file mode 100644 index 000000000..05dd6f5f3 --- /dev/null +++ b/modules/omnigen2/models/transformers/components.py @@ -0,0 +1,4 @@ +import torch.nn.functional as F + +def swiglu(x, y): + return F.silu(x.float(), inplace=False).to(x.dtype) * y diff --git a/modules/omnigen2/models/transformers/repo.py b/modules/omnigen2/models/transformers/repo.py new file mode 100644 index 000000000..8f7c47566 --- /dev/null +++ b/modules/omnigen2/models/transformers/repo.py @@ -0,0 +1,129 @@ +from typing import List, Tuple + +import torch +import torch.nn as nn + +from einops import repeat +from diffusers.models.embeddings import get_1d_rotary_pos_embed + +class OmniGen2RotaryPosEmbed(nn.Module): + def __init__(self, theta: int, + axes_dim: Tuple[int, int, int], + axes_lens: Tuple[int, int, int] = (300, 512, 512), + patch_size: int = 2): + super().__init__() + self.theta = theta + self.axes_dim = axes_dim + self.axes_lens = axes_lens + self.patch_size = patch_size + + @staticmethod + def get_freqs_cis(axes_dim: Tuple[int, int, int], + axes_lens: Tuple[int, int, int], + theta: int) -> List[torch.Tensor]: + freqs_cis = [] + freqs_dtype = torch.float32 if torch.backends.mps.is_available() else torch.float64 + for i, (d, e) in enumerate(zip(axes_dim, axes_lens)): + emb = get_1d_rotary_pos_embed(d, e, theta=theta, freqs_dtype=freqs_dtype) + freqs_cis.append(emb) + return freqs_cis + + def _get_freqs_cis(self, freqs_cis, ids: torch.Tensor) -> torch.Tensor: + device = ids.device + if ids.device.type == "mps": + ids = ids.to("cpu") + + result = [] + for i in range(len(self.axes_dim)): + freqs = freqs_cis[i].to(ids.device) + index = ids[:, :, i : i + 1].repeat(1, 1, freqs.shape[-1]).to(torch.int64) + result.append(torch.gather(freqs.unsqueeze(0).repeat(index.shape[0], 1, 1), dim=1, index=index)) + return torch.cat(result, dim=-1).to(device) + + def forward( + self, + freqs_cis, + attention_mask, + l_effective_ref_img_len, + l_effective_img_len, + ref_img_sizes, + img_sizes, + device + ): + batch_size = len(attention_mask) + p = self.patch_size + + encoder_seq_len = attention_mask.shape[1] + l_effective_cap_len = attention_mask.sum(dim=1).tolist() + + seq_lengths = [cap_len + sum(ref_img_len) + img_len for cap_len, ref_img_len, img_len in zip(l_effective_cap_len, l_effective_ref_img_len, l_effective_img_len)] + + max_seq_len = max(seq_lengths) + max_ref_img_len = max([sum(ref_img_len) for ref_img_len in l_effective_ref_img_len]) + max_img_len = max(l_effective_img_len) + + # Create position IDs + position_ids = torch.zeros(batch_size, max_seq_len, 3, dtype=torch.int32, device=device) + + for i, (cap_seq_len, seq_len) in enumerate(zip(l_effective_cap_len, seq_lengths)): + # add text position ids + position_ids[i, :cap_seq_len] = repeat(torch.arange(cap_seq_len, dtype=torch.int32, device=device), "l -> l 3") + + pe_shift = cap_seq_len + pe_shift_len = cap_seq_len + + if ref_img_sizes[i] is not None: + for ref_img_size, ref_img_len in zip(ref_img_sizes[i], l_effective_ref_img_len[i]): + H, W = ref_img_size + ref_H_tokens, ref_W_tokens = H // p, W // p + assert ref_H_tokens * ref_W_tokens == ref_img_len + # add image position ids + + row_ids = repeat(torch.arange(ref_H_tokens, dtype=torch.int32, device=device), "h -> h w", w=ref_W_tokens).flatten() + col_ids = repeat(torch.arange(ref_W_tokens, dtype=torch.int32, device=device), "w -> h w", h=ref_H_tokens).flatten() + position_ids[i, pe_shift_len:pe_shift_len + ref_img_len, 0] = pe_shift + position_ids[i, pe_shift_len:pe_shift_len + ref_img_len, 1] = row_ids + position_ids[i, pe_shift_len:pe_shift_len + ref_img_len, 2] = col_ids + + pe_shift += max(ref_H_tokens, ref_W_tokens) + pe_shift_len += ref_img_len + + H, W = img_sizes[i] + H_tokens, W_tokens = H // p, W // p + assert H_tokens * W_tokens == l_effective_img_len[i] + + row_ids = repeat(torch.arange(H_tokens, dtype=torch.int32, device=device), "h -> h w", w=W_tokens).flatten() + col_ids = repeat(torch.arange(W_tokens, dtype=torch.int32, device=device), "w -> h w", h=H_tokens).flatten() + + assert pe_shift_len + l_effective_img_len[i] == seq_len + position_ids[i, pe_shift_len: seq_len, 0] = pe_shift + position_ids[i, pe_shift_len: seq_len, 1] = row_ids + position_ids[i, pe_shift_len: seq_len, 2] = col_ids + + # Get combined rotary embeddings + freqs_cis = self._get_freqs_cis(freqs_cis, position_ids) + + # create separate rotary embeddings for captions and images + cap_freqs_cis = torch.zeros( + batch_size, encoder_seq_len, freqs_cis.shape[-1], device=device, dtype=freqs_cis.dtype + ) + ref_img_freqs_cis = torch.zeros( + batch_size, max_ref_img_len, freqs_cis.shape[-1], device=device, dtype=freqs_cis.dtype + ) + img_freqs_cis = torch.zeros( + batch_size, max_img_len, freqs_cis.shape[-1], device=device, dtype=freqs_cis.dtype + ) + + for i, (cap_seq_len, ref_img_len, img_len, seq_len) in enumerate(zip(l_effective_cap_len, l_effective_ref_img_len, l_effective_img_len, seq_lengths)): + cap_freqs_cis[i, :cap_seq_len] = freqs_cis[i, :cap_seq_len] + ref_img_freqs_cis[i, :sum(ref_img_len)] = freqs_cis[i, cap_seq_len:cap_seq_len + sum(ref_img_len)] + img_freqs_cis[i, :img_len] = freqs_cis[i, cap_seq_len + sum(ref_img_len):cap_seq_len + sum(ref_img_len) + img_len] + + return ( + cap_freqs_cis, + ref_img_freqs_cis, + img_freqs_cis, + freqs_cis, + l_effective_cap_len, + seq_lengths, + ) diff --git a/modules/omnigen2/models/transformers/transformer_omnigen2.py b/modules/omnigen2/models/transformers/transformer_omnigen2.py new file mode 100644 index 000000000..fe324b0d2 --- /dev/null +++ b/modules/omnigen2/models/transformers/transformer_omnigen2.py @@ -0,0 +1,617 @@ +import warnings +import itertools +from typing import Any, Dict, List, Optional, Tuple, Union + +import torch +import torch.nn as nn + +from einops import rearrange + +from diffusers.configuration_utils import ConfigMixin, register_to_config +from diffusers.loaders import PeftAdapterMixin +from diffusers.loaders.single_file_model import FromOriginalModelMixin +from diffusers.utils import USE_PEFT_BACKEND, logging, scale_lora_layers, unscale_lora_layers +from diffusers.models.attention_processor import Attention +from diffusers.models.modeling_outputs import Transformer2DModelOutput +from diffusers.models.modeling_utils import ModelMixin + +from ..attention_processor import OmniGen2AttnProcessorFlash2Varlen, OmniGen2AttnProcessor +from .repo import OmniGen2RotaryPosEmbed +from .block_lumina2 import LuminaLayerNormContinuous, LuminaRMSNormZero, LuminaFeedForward, Lumina2CombinedTimestepCaptionEmbedding + +from ...import_utils import is_triton_available, is_flash_attn_available + +if is_triton_available(): + from ...triton_layer_norm import RMSNorm +else: + from torch.nn import RMSNorm + +logger = logging.get_logger(__name__) + + +class OmniGen2TransformerBlock(nn.Module): + """ + Transformer block for OmniGen2 model. + + This block implements a transformer layer with: + - Multi-head attention with flash attention + - Feed-forward network with SwiGLU activation + - RMS normalization + - Optional modulation for conditional generation + + Args: + dim: Dimension of the input and output tensors + num_attention_heads: Number of attention heads + num_kv_heads: Number of key-value heads + multiple_of: Multiple of which the hidden dimension should be + ffn_dim_multiplier: Multiplier for the feed-forward network dimension + norm_eps: Epsilon value for normalization layers + modulation: Whether to use modulation for conditional generation + use_fused_rms_norm: Whether to use fused RMS normalization + use_fused_swiglu: Whether to use fused SwiGLU activation + """ + + def __init__( + self, + dim: int, + num_attention_heads: int, + num_kv_heads: int, + multiple_of: int, + ffn_dim_multiplier: float, + norm_eps: float, + modulation: bool = True, + ) -> None: + """Initialize the transformer block.""" + super().__init__() + self.head_dim = dim // num_attention_heads + self.modulation = modulation + + try: + processor = OmniGen2AttnProcessorFlash2Varlen() + except ImportError: + processor = OmniGen2AttnProcessor() + + # Initialize attention layer + self.attn = Attention( + query_dim=dim, + cross_attention_dim=None, + dim_head=dim // num_attention_heads, + qk_norm="rms_norm", + heads=num_attention_heads, + kv_heads=num_kv_heads, + eps=1e-5, + bias=False, + out_bias=False, + processor=processor, + ) + + # Initialize feed-forward network + self.feed_forward = LuminaFeedForward( + dim=dim, + inner_dim=4 * dim, + multiple_of=multiple_of, + ffn_dim_multiplier=ffn_dim_multiplier + ) + + # Initialize normalization layers + if modulation: + self.norm1 = LuminaRMSNormZero( + embedding_dim=dim, + norm_eps=norm_eps, + norm_elementwise_affine=True + ) + else: + self.norm1 = RMSNorm(dim, eps=norm_eps) + + self.ffn_norm1 = RMSNorm(dim, eps=norm_eps) + self.norm2 = RMSNorm(dim, eps=norm_eps) + self.ffn_norm2 = RMSNorm(dim, eps=norm_eps) + + self.initialize_weights() + + def initialize_weights(self) -> None: + """ + Initialize the weights of the transformer block. + + Uses Xavier uniform initialization for linear layers and zero initialization for biases. + """ + nn.init.xavier_uniform_(self.attn.to_q.weight) + nn.init.xavier_uniform_(self.attn.to_k.weight) + nn.init.xavier_uniform_(self.attn.to_v.weight) + nn.init.xavier_uniform_(self.attn.to_out[0].weight) + + nn.init.xavier_uniform_(self.feed_forward.linear_1.weight) + nn.init.xavier_uniform_(self.feed_forward.linear_2.weight) + nn.init.xavier_uniform_(self.feed_forward.linear_3.weight) + + if self.modulation: + nn.init.zeros_(self.norm1.linear.weight) + nn.init.zeros_(self.norm1.linear.bias) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor, + image_rotary_emb: torch.Tensor, + temb: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + """ + Forward pass of the transformer block. + + Args: + hidden_states: Input hidden states tensor + attention_mask: Attention mask tensor + image_rotary_emb: Rotary embeddings for image tokens + temb: Optional timestep embedding tensor + + Returns: + torch.Tensor: Output hidden states after transformer block processing + """ + import time + if self.modulation: + if temb is None: + raise ValueError("temb must be provided when modulation is enabled") + + norm_hidden_states, gate_msa, scale_mlp, gate_mlp = self.norm1(hidden_states, temb) + attn_output = self.attn( + hidden_states=norm_hidden_states, + encoder_hidden_states=norm_hidden_states, + attention_mask=attention_mask, + image_rotary_emb=image_rotary_emb, + ) + hidden_states = hidden_states + gate_msa.unsqueeze(1).tanh() * self.norm2(attn_output) + mlp_output = self.feed_forward(self.ffn_norm1(hidden_states) * (1 + scale_mlp.unsqueeze(1))) + hidden_states = hidden_states + gate_mlp.unsqueeze(1).tanh() * self.ffn_norm2(mlp_output) + else: + norm_hidden_states = self.norm1(hidden_states) + attn_output = self.attn( + hidden_states=norm_hidden_states, + encoder_hidden_states=norm_hidden_states, + attention_mask=attention_mask, + image_rotary_emb=image_rotary_emb, + ) + hidden_states = hidden_states + self.norm2(attn_output) + mlp_output = self.feed_forward(self.ffn_norm1(hidden_states)) + hidden_states = hidden_states + self.ffn_norm2(mlp_output) + + return hidden_states + + +class OmniGen2Transformer2DModel(ModelMixin, ConfigMixin, PeftAdapterMixin, FromOriginalModelMixin): + """ + OmniGen2 Transformer 2D Model. + + A transformer-based diffusion model for image generation with: + - Patch-based image processing + - Rotary position embeddings + - Multi-head attention + - Conditional generation support + + Args: + patch_size: Size of image patches + in_channels: Number of input channels + out_channels: Number of output channels (defaults to in_channels) + hidden_size: Size of hidden layers + num_layers: Number of transformer layers + num_refiner_layers: Number of refiner layers + num_attention_heads: Number of attention heads + num_kv_heads: Number of key-value heads + multiple_of: Multiple of which the hidden dimension should be + ffn_dim_multiplier: Multiplier for feed-forward network dimension + norm_eps: Epsilon value for normalization layers + axes_dim_rope: Dimensions for rotary position embeddings + axes_lens: Lengths for rotary position embeddings + text_feat_dim: Dimension of text features + timestep_scale: Scale factor for timestep embeddings + use_fused_rms_norm: Whether to use fused RMS normalization + use_fused_swiglu: Whether to use fused SwiGLU activation + """ + + _supports_gradient_checkpointing = True + _no_split_modules = ["Omnigen2TransformerBlock"] + _skip_layerwise_casting_patterns = ["x_embedder", "norm"] + + @register_to_config + def __init__( + self, + patch_size: int = 2, + in_channels: int = 16, + out_channels: Optional[int] = None, + hidden_size: int = 2304, + num_layers: int = 26, + num_refiner_layers: int = 2, + num_attention_heads: int = 24, + num_kv_heads: int = 8, + multiple_of: int = 256, + ffn_dim_multiplier: Optional[float] = None, + norm_eps: float = 1e-5, + axes_dim_rope: Tuple[int, int, int] = (32, 32, 32), + axes_lens: Tuple[int, int, int] = (300, 512, 512), + text_feat_dim: int = 1024, + timestep_scale: float = 1.0 + ) -> None: + """Initialize the OmniGen2 transformer model.""" + super().__init__() + + # Validate configuration + if (hidden_size // num_attention_heads) != sum(axes_dim_rope): + raise ValueError( + f"hidden_size // num_attention_heads ({hidden_size // num_attention_heads}) " + f"must equal sum(axes_dim_rope) ({sum(axes_dim_rope)})" + ) + + self.out_channels = out_channels or in_channels + + # Initialize embeddings + self.rope_embedder = OmniGen2RotaryPosEmbed( + theta=10000, + axes_dim=axes_dim_rope, + axes_lens=axes_lens, + patch_size=patch_size, + ) + + self.x_embedder = nn.Linear( + in_features=patch_size * patch_size * in_channels, + out_features=hidden_size, + ) + + self.ref_image_patch_embedder = nn.Linear( + in_features=patch_size * patch_size * in_channels, + out_features=hidden_size, + ) + + self.time_caption_embed = Lumina2CombinedTimestepCaptionEmbedding( + hidden_size=hidden_size, + text_feat_dim=text_feat_dim, + norm_eps=norm_eps, + timestep_scale=timestep_scale + ) + + # Initialize transformer blocks + self.noise_refiner = nn.ModuleList([ + OmniGen2TransformerBlock( + hidden_size, + num_attention_heads, + num_kv_heads, + multiple_of, + ffn_dim_multiplier, + norm_eps, + modulation=True + ) + for _ in range(num_refiner_layers) + ]) + + self.ref_image_refiner = nn.ModuleList([ + OmniGen2TransformerBlock( + hidden_size, + num_attention_heads, + num_kv_heads, + multiple_of, + ffn_dim_multiplier, + norm_eps, + modulation=True + ) + for _ in range(num_refiner_layers) + ]) + + self.context_refiner = nn.ModuleList( + [ + OmniGen2TransformerBlock( + hidden_size, + num_attention_heads, + num_kv_heads, + multiple_of, + ffn_dim_multiplier, + norm_eps, + modulation=False + ) + for _ in range(num_refiner_layers) + ] + ) + + # 3. Transformer blocks + self.layers = nn.ModuleList( + [ + OmniGen2TransformerBlock( + hidden_size, + num_attention_heads, + num_kv_heads, + multiple_of, + ffn_dim_multiplier, + norm_eps, + modulation=True + ) + for _ in range(num_layers) + ] + ) + + # 4. Output norm & projection + self.norm_out = LuminaLayerNormContinuous( + embedding_dim=hidden_size, + conditioning_embedding_dim=min(hidden_size, 1024), + elementwise_affine=False, + eps=1e-6, + bias=True, + out_dim=patch_size * patch_size * self.out_channels + ) + + # Add learnable embeddings to distinguish different images + self.image_index_embedding = nn.Parameter(torch.randn(5, hidden_size)) # support max 5 ref images + + self.gradient_checkpointing = False + + self.initialize_weights() + + def initialize_weights(self) -> None: + """ + Initialize the weights of the model. + + Uses Xavier uniform initialization for linear layers. + """ + nn.init.xavier_uniform_(self.x_embedder.weight) + nn.init.constant_(self.x_embedder.bias, 0.0) + + nn.init.xavier_uniform_(self.ref_image_patch_embedder.weight) + nn.init.constant_(self.ref_image_patch_embedder.bias, 0.0) + + nn.init.zeros_(self.norm_out.linear_1.weight) + nn.init.zeros_(self.norm_out.linear_1.bias) + nn.init.zeros_(self.norm_out.linear_2.weight) + nn.init.zeros_(self.norm_out.linear_2.bias) + + nn.init.normal_(self.image_index_embedding, std=0.02) + + def img_patch_embed_and_refine( + self, + hidden_states, + ref_image_hidden_states, + padded_img_mask, + padded_ref_img_mask, + noise_rotary_emb, + ref_img_rotary_emb, + l_effective_ref_img_len, + l_effective_img_len, + temb + ): + batch_size = len(hidden_states) + max_combined_img_len = max([img_len + sum(ref_img_len) for img_len, ref_img_len in zip(l_effective_img_len, l_effective_ref_img_len)]) + + hidden_states = self.x_embedder(hidden_states) + ref_image_hidden_states = self.ref_image_patch_embedder(ref_image_hidden_states) + + for i in range(batch_size): + shift = 0 + for j, ref_img_len in enumerate(l_effective_ref_img_len[i]): + ref_image_hidden_states[i, shift:shift + ref_img_len, :] = ref_image_hidden_states[i, shift:shift + ref_img_len, :] + self.image_index_embedding[j] + shift += ref_img_len + + for layer in self.noise_refiner: + hidden_states = layer(hidden_states, padded_img_mask, noise_rotary_emb, temb) + + flat_l_effective_ref_img_len = list(itertools.chain(*l_effective_ref_img_len)) + num_ref_images = len(flat_l_effective_ref_img_len) + max_ref_img_len = max(flat_l_effective_ref_img_len) + + batch_ref_img_mask = ref_image_hidden_states.new_zeros(num_ref_images, max_ref_img_len, dtype=torch.bool) + batch_ref_image_hidden_states = ref_image_hidden_states.new_zeros(num_ref_images, max_ref_img_len, self.config.hidden_size) + batch_ref_img_rotary_emb = hidden_states.new_zeros(num_ref_images, max_ref_img_len, ref_img_rotary_emb.shape[-1], dtype=ref_img_rotary_emb.dtype) + batch_temb = temb.new_zeros(num_ref_images, *temb.shape[1:], dtype=temb.dtype) + + # sequence of ref imgs to batch + idx = 0 + for i in range(batch_size): + shift = 0 + for ref_img_len in l_effective_ref_img_len[i]: + batch_ref_img_mask[idx, :ref_img_len] = True + batch_ref_image_hidden_states[idx, :ref_img_len] = ref_image_hidden_states[i, shift:shift + ref_img_len] + batch_ref_img_rotary_emb[idx, :ref_img_len] = ref_img_rotary_emb[i, shift:shift + ref_img_len] + batch_temb[idx] = temb[i] + shift += ref_img_len + idx += 1 + + # refine ref imgs separately + for layer in self.ref_image_refiner: + batch_ref_image_hidden_states = layer(batch_ref_image_hidden_states, batch_ref_img_mask, batch_ref_img_rotary_emb, batch_temb) + + # batch of ref imgs to sequence + idx = 0 + for i in range(batch_size): + shift = 0 + for ref_img_len in l_effective_ref_img_len[i]: + ref_image_hidden_states[i, shift:shift + ref_img_len] = batch_ref_image_hidden_states[idx, :ref_img_len] + shift += ref_img_len + idx += 1 + + combined_img_hidden_states = hidden_states.new_zeros(batch_size, max_combined_img_len, self.config.hidden_size) + for i, (ref_img_len, img_len) in enumerate(zip(l_effective_ref_img_len, l_effective_img_len)): + combined_img_hidden_states[i, :sum(ref_img_len)] = ref_image_hidden_states[i, :sum(ref_img_len)] + combined_img_hidden_states[i, sum(ref_img_len):sum(ref_img_len) + img_len] = hidden_states[i, :img_len] + + return combined_img_hidden_states + + def flat_and_pad_to_seq(self, hidden_states, ref_image_hidden_states): + batch_size = len(hidden_states) + p = self.config.patch_size + device = hidden_states[0].device + + img_sizes = [(img.size(1), img.size(2)) for img in hidden_states] + l_effective_img_len = [(H // p) * (W // p) for (H, W) in img_sizes] + + if ref_image_hidden_states is not None: + ref_img_sizes = [[(img.size(1), img.size(2)) for img in imgs] if imgs is not None else None for imgs in ref_image_hidden_states] + l_effective_ref_img_len = [[(ref_img_size[0] // p) * (ref_img_size[1] // p) for ref_img_size in _ref_img_sizes] if _ref_img_sizes is not None else [0] for _ref_img_sizes in ref_img_sizes] + else: + ref_img_sizes = [None for _ in range(batch_size)] + l_effective_ref_img_len = [[0] for _ in range(batch_size)] + + max_ref_img_len = max([sum(ref_img_len) for ref_img_len in l_effective_ref_img_len]) + max_img_len = max(l_effective_img_len) + + # ref image patch embeddings + flat_ref_img_hidden_states = [] + for i in range(batch_size): + if ref_img_sizes[i] is not None: + imgs = [] + for ref_img in ref_image_hidden_states[i]: + C, H, W = ref_img.size() + ref_img = rearrange(ref_img, 'c (h p1) (w p2) -> (h w) (p1 p2 c)', p1=p, p2=p) + imgs.append(ref_img) + + img = torch.cat(imgs, dim=0) + flat_ref_img_hidden_states.append(img) + else: + flat_ref_img_hidden_states.append(None) + + # image patch embeddings + flat_hidden_states = [] + for i in range(batch_size): + img = hidden_states[i] + C, H, W = img.size() + + img = rearrange(img, 'c (h p1) (w p2) -> (h w) (p1 p2 c)', p1=p, p2=p) + flat_hidden_states.append(img) + + padded_ref_img_hidden_states = torch.zeros(batch_size, max_ref_img_len, flat_hidden_states[0].shape[-1], device=device, dtype=flat_hidden_states[0].dtype) + padded_ref_img_mask = torch.zeros(batch_size, max_ref_img_len, dtype=torch.bool, device=device) + for i in range(batch_size): + if ref_img_sizes[i] is not None: + padded_ref_img_hidden_states[i, :sum(l_effective_ref_img_len[i])] = flat_ref_img_hidden_states[i] + padded_ref_img_mask[i, :sum(l_effective_ref_img_len[i])] = True + + padded_hidden_states = torch.zeros(batch_size, max_img_len, flat_hidden_states[0].shape[-1], device=device, dtype=flat_hidden_states[0].dtype) + padded_img_mask = torch.zeros(batch_size, max_img_len, dtype=torch.bool, device=device) + for i in range(batch_size): + padded_hidden_states[i, :l_effective_img_len[i]] = flat_hidden_states[i] + padded_img_mask[i, :l_effective_img_len[i]] = True + + return ( + padded_hidden_states, + padded_ref_img_hidden_states, + padded_img_mask, + padded_ref_img_mask, + l_effective_ref_img_len, + l_effective_img_len, + ref_img_sizes, + img_sizes, + ) + + def forward( + self, + hidden_states: Union[torch.Tensor, List[torch.Tensor]], + timestep: torch.Tensor, + text_hidden_states: torch.Tensor, + freqs_cis: torch.Tensor, + text_attention_mask: torch.Tensor, + ref_image_hidden_states: Optional[List[List[torch.Tensor]]] = None, + attention_kwargs: Optional[Dict[str, Any]] = None, + return_dict: bool = False, + ) -> Union[torch.Tensor, Transformer2DModelOutput]: + if attention_kwargs is not None: + attention_kwargs = attention_kwargs.copy() + lora_scale = attention_kwargs.pop("scale", 1.0) + else: + lora_scale = 1.0 + + if USE_PEFT_BACKEND: + # weight the lora layers by setting `lora_scale` for each PEFT layer + scale_lora_layers(self, lora_scale) + else: + if attention_kwargs is not None and attention_kwargs.get("scale", None) is not None: + logger.warning( + "Passing `scale` via `attention_kwargs` when not using the PEFT backend is ineffective." + ) + + # 1. Condition, positional & patch embedding + batch_size = len(hidden_states) + is_hidden_states_tensor = isinstance(hidden_states, torch.Tensor) + + if is_hidden_states_tensor: + assert hidden_states.ndim == 4 + hidden_states = [_hidden_states for _hidden_states in hidden_states] + + device = hidden_states[0].device + + temb, text_hidden_states = self.time_caption_embed(timestep, text_hidden_states, hidden_states[0].dtype) + + ( + hidden_states, + ref_image_hidden_states, + img_mask, + ref_img_mask, + l_effective_ref_img_len, + l_effective_img_len, + ref_img_sizes, + img_sizes, + ) = self.flat_and_pad_to_seq(hidden_states, ref_image_hidden_states) + + ( + context_rotary_emb, + ref_img_rotary_emb, + noise_rotary_emb, + rotary_emb, + encoder_seq_lengths, + seq_lengths, + ) = self.rope_embedder( + freqs_cis, + text_attention_mask, + l_effective_ref_img_len, + l_effective_img_len, + ref_img_sizes, + img_sizes, + device, + ) + + # 2. Context refinement + for layer in self.context_refiner: + text_hidden_states = layer(text_hidden_states, text_attention_mask, context_rotary_emb) + + combined_img_hidden_states = self.img_patch_embed_and_refine( + hidden_states, + ref_image_hidden_states, + img_mask, + ref_img_mask, + noise_rotary_emb, + ref_img_rotary_emb, + l_effective_ref_img_len, + l_effective_img_len, + temb, + ) + + # 3. Joint Transformer blocks + max_seq_len = max(seq_lengths) + + attention_mask = hidden_states.new_zeros(batch_size, max_seq_len, dtype=torch.bool) + joint_hidden_states = hidden_states.new_zeros(batch_size, max_seq_len, self.config.hidden_size) + for i, (encoder_seq_len, seq_len) in enumerate(zip(encoder_seq_lengths, seq_lengths)): + attention_mask[i, :seq_len] = True + joint_hidden_states[i, :encoder_seq_len] = text_hidden_states[i, :encoder_seq_len] + joint_hidden_states[i, encoder_seq_len:seq_len] = combined_img_hidden_states[i, :seq_len - encoder_seq_len] + + hidden_states = joint_hidden_states + + for layer_idx, layer in enumerate(self.layers): + if torch.is_grad_enabled() and self.gradient_checkpointing: + hidden_states = self._gradient_checkpointing_func( + layer, hidden_states, attention_mask, rotary_emb, temb + ) + else: + hidden_states = layer(hidden_states, attention_mask, rotary_emb, temb) + + # 4. Output norm & projection + hidden_states = self.norm_out(hidden_states, temb) + + p = self.config.patch_size + output = [] + for i, (img_size, img_len, seq_len) in enumerate(zip(img_sizes, l_effective_img_len, seq_lengths)): + height, width = img_size + output.append(rearrange(hidden_states[i][seq_len - img_len:seq_len], '(h w) (p1 p2 c) -> c (h p1) (w p2)', h=height // p, w=width // p, p1=p, p2=p)) + if is_hidden_states_tensor: + output = torch.stack(output, dim=0) + + if USE_PEFT_BACKEND: + # remove `lora_scale` from each PEFT layer + unscale_lora_layers(self, lora_scale) + + if not return_dict: + return output + return Transformer2DModelOutput(sample=output) diff --git a/modules/omnigen2/pipeline_omnigen2.py b/modules/omnigen2/pipeline_omnigen2.py new file mode 100644 index 000000000..a7d6c2ca3 --- /dev/null +++ b/modules/omnigen2/pipeline_omnigen2.py @@ -0,0 +1,718 @@ +""" +OmniGen2 Diffusion Pipeline + +Copyright 2025 BAAI, The OmniGen2 Team and The HuggingFace Team. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Any, Dict, List, Optional, Tuple, Union +from dataclasses import dataclass +import inspect +import numpy as np +import torch +import torch.nn.functional as F +import PIL.Image + +from transformers import Qwen2_5_VLForConditionalGeneration +from diffusers.utils import BaseOutput +from diffusers.models.autoencoders import AutoencoderKL +from diffusers.schedulers import FlowMatchEulerDiscreteScheduler +from diffusers.utils import ( + is_torch_xla_available, + logging, +) +from diffusers.utils.torch_utils import randn_tensor +from diffusers.pipelines.pipeline_utils import DiffusionPipeline + +from .models.transformers import OmniGen2Transformer2DModel +from .models.transformers.repo import OmniGen2RotaryPosEmbed +from .image_processor import OmniGen2ImageProcessor + +if is_torch_xla_available(): + import torch_xla.core.xla_model as xm + XLA_AVAILABLE = True +else: + XLA_AVAILABLE = False + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + +@dataclass +class FMPipelineOutput(BaseOutput): + """ + Output class for OmniGen2 pipeline. + + Args: + images (Union[List[PIL.Image.Image], np.ndarray]): + List of denoised PIL images of length `batch_size` or numpy array of shape + `(batch_size, height, width, num_channels)`. Contains the generated images. + """ + images: Union[List[PIL.Image.Image], np.ndarray] + + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps +def retrieve_timesteps( + scheduler, + num_inference_steps: Optional[int] = None, + device: Optional[Union[str, torch.device]] = None, + timesteps: Optional[List[int]] = None, + **kwargs, +): + """ + Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles + custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. + + Args: + scheduler (`SchedulerMixin`): + The scheduler to get timesteps from. + num_inference_steps (`int`): + The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps` + must be `None`. + device (`str` or `torch.device`, *optional*): + The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. + timesteps (`List[int]`, *optional*): + Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, + `num_inference_steps` and `sigmas` must be `None`. + sigmas (`List[float]`, *optional*): + Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed, + `num_inference_steps` and `timesteps` must be `None`. + + Returns: + `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the + second element is the number of inference steps. + """ + if timesteps is not None: + accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) + if not accepts_timesteps: + raise ValueError( + f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" + f" timestep schedules. Please check whether you are using the correct scheduler." + ) + scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) + timesteps = scheduler.timesteps + num_inference_steps = len(timesteps) + else: + scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) + timesteps = scheduler.timesteps + return timesteps, num_inference_steps + + +class OmniGen2Pipeline(DiffusionPipeline): + """ + Pipeline for text-to-image generation using OmniGen2. + + This pipeline implements a text-to-image generation model that uses: + - Qwen2.5-VL for text encoding + - A custom transformer architecture for image generation + - VAE for image encoding/decoding + - FlowMatchEulerDiscreteScheduler for noise scheduling + + Args: + transformer (OmniGen2Transformer2DModel): The transformer model for image generation. + vae (AutoencoderKL): The VAE model for image encoding/decoding. + scheduler (FlowMatchEulerDiscreteScheduler): The scheduler for noise scheduling. + text_encoder (Qwen2_5_VLModel): The text encoder model. + tokenizer (Union[Qwen2Tokenizer, Qwen2TokenizerFast]): The tokenizer for text processing. + """ + + model_cpu_offload_seq = "mllm->transformer->vae" + + def __init__( + self, + transformer: OmniGen2Transformer2DModel, + vae: AutoencoderKL, + scheduler: FlowMatchEulerDiscreteScheduler, + mllm: Qwen2_5_VLForConditionalGeneration, + processor, + ) -> None: + """ + Initialize the OmniGen2 pipeline. + + Args: + transformer: The transformer model for image generation. + vae: The VAE model for image encoding/decoding. + scheduler: The scheduler for noise scheduling. + text_encoder: The text encoder model. + tokenizer: The tokenizer for text processing. + """ + super().__init__() + + self.register_modules( + transformer=transformer, + vae=vae, + scheduler=scheduler, + mllm=mllm, + processor=processor + ) + self.vae_scale_factor = ( + 2 ** (len(self.vae.config.block_out_channels) - 1) if hasattr(self, "vae") and self.vae is not None else 8 + ) + self.image_processor = OmniGen2ImageProcessor(vae_scale_factor=self.vae_scale_factor * 2, do_resize=True) + self.default_sample_size = 128 + + def prepare_latents( + self, + batch_size: int, + num_channels_latents: int, + height: int, + width: int, + dtype: torch.dtype, + device: torch.device, + generator: Optional[torch.Generator], + latents: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + """ + Prepare the initial latents for the diffusion process. + + Args: + batch_size: The number of images to generate. + num_channels_latents: The number of channels in the latent space. + height: The height of the generated image. + width: The width of the generated image. + dtype: The data type of the latents. + device: The device to place the latents on. + generator: The random number generator to use. + latents: Optional pre-computed latents to use instead of random initialization. + + Returns: + torch.FloatTensor: The prepared latents tensor. + """ + height = int(height) // self.vae_scale_factor + width = int(width) // self.vae_scale_factor + + shape = (batch_size, num_channels_latents, height, width) + + if latents is None: + latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) + else: + latents = latents.to(device) + return latents + + def encode_vae(self, img: torch.FloatTensor) -> torch.FloatTensor: + """ + Encode an image into the VAE latent space. + + Args: + img: The input image tensor to encode. + + Returns: + torch.FloatTensor: The encoded latent representation. + """ + z0 = self.vae.encode(img.to(dtype=self.vae.dtype)).latent_dist.sample() + if self.vae.config.shift_factor is not None: + z0 = z0 - self.vae.config.shift_factor + if self.vae.config.scaling_factor is not None: + z0 = z0 * self.vae.config.scaling_factor + z0 = z0.to(dtype=self.vae.dtype) + return z0 + + def prepare_image( + self, + images: Union[List[PIL.Image.Image], PIL.Image.Image], + batch_size: int, + num_images_per_prompt: int, + max_pixels: int, + max_side_length: int, + device: torch.device, + dtype: torch.dtype, + ) -> List[Optional[torch.FloatTensor]]: + """ + Prepare input images for processing by encoding them into the VAE latent space. + + Args: + images: Single image or list of images to process. + batch_size: The number of images to generate per prompt. + num_images_per_prompt: The number of images to generate for each prompt. + device: The device to place the encoded latents on. + dtype: The data type of the encoded latents. + + Returns: + List[Optional[torch.FloatTensor]]: List of encoded latent representations for each image. + """ + if batch_size == 1: + images = [images] + latents = [] + for i, img in enumerate(images): + if img is not None and len(img) > 0: + ref_latents = [] + for j, img_j in enumerate(img): + img_j = self.image_processor.preprocess(img_j, max_pixels=max_pixels, max_side_length=max_side_length) + ref_latents.append(self.encode_vae(img_j.to(device=device)).squeeze(0)) + else: + ref_latents = None + for _ in range(num_images_per_prompt): + latents.append(ref_latents) + + return latents + + def _get_qwen2_prompt_embeds( + self, + prompt: Union[str, List[str]], + device: Optional[torch.device] = None, + max_sequence_length: int = 256, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Get prompt embeddings from the Qwen2 text encoder. + + Args: + prompt: The prompt or list of prompts to encode. + device: The device to place the embeddings on. If None, uses the pipeline's device. + max_sequence_length: Maximum sequence length for tokenization. + + Returns: + Tuple[torch.Tensor, torch.Tensor]: A tuple containing: + - The prompt embeddings tensor + - The attention mask tensor + + Raises: + Warning: If the input text is truncated due to sequence length limitations. + """ + device = device or self._execution_device + prompt = [prompt] if isinstance(prompt, str) else prompt + # text_inputs = self.processor.tokenizer( + # prompt, + # padding="max_length", + # max_length=max_sequence_length, + # truncation=True, + # return_tensors="pt", + # ) + text_inputs = self.processor.tokenizer( + prompt, + padding="longest", + max_length=max_sequence_length, + truncation=True, + return_tensors="pt", + ) + + text_input_ids = text_inputs.input_ids.to(device) + untruncated_ids = self.processor.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids.to(device) + + if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids): + removed_text = self.processor.tokenizer.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1]) + logger.warning( + "The following part of your input was truncated because Gemma can only handle sequences up to" + f" {max_sequence_length} tokens: {removed_text}" + ) + + prompt_attention_mask = text_inputs.attention_mask.to(device) + prompt_embeds = self.mllm( + text_input_ids, + attention_mask=prompt_attention_mask, + output_hidden_states=True, + ).hidden_states[-1] + + if self.mllm is not None: + dtype = self.mllm.dtype + elif self.transformer is not None: + dtype = self.transformer.dtype + else: + dtype = None + + prompt_embeds = prompt_embeds.to(dtype=dtype, device=device) + + return prompt_embeds, prompt_attention_mask + + def _apply_chat_template(self, prompt: str): + prompt = [ + { + "role": "system", + "content": "You are a helpful assistant that generates high-quality images based on user instructions.", + }, + {"role": "user", "content": prompt}, + ] + prompt = self.processor.tokenizer.apply_chat_template(prompt, tokenize=False, add_generation_prompt=False) + return prompt + + def encode_prompt( + self, + prompt: Union[str, List[str]], + do_classifier_free_guidance: bool = True, + negative_prompt: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: int = 1, + device: Optional[torch.device] = None, + prompt_embeds: Optional[torch.Tensor] = None, + negative_prompt_embeds: Optional[torch.Tensor] = None, + prompt_attention_mask: Optional[torch.Tensor] = None, + negative_prompt_attention_mask: Optional[torch.Tensor] = None, + max_sequence_length: int = 256, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + r""" + Encodes the prompt into text encoder hidden states. + + Args: + prompt (`str` or `List[str]`, *optional*): + prompt to be encoded + negative_prompt (`str` or `List[str]`, *optional*): + The prompt not to guide the image generation. If not defined, one has to pass `negative_prompt_embeds` + instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is less than `1`). For + Lumina-T2I, this should be "". + do_classifier_free_guidance (`bool`, *optional*, defaults to `True`): + whether to use classifier free guidance or not + num_images_per_prompt (`int`, *optional*, defaults to 1): + number of images that should be generated per prompt + device: (`torch.device`, *optional*): + torch device to place the resulting embeddings on + prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated negative text embeddings. For Lumina-T2I, it's should be the embeddings of the "" string. + max_sequence_length (`int`, defaults to `256`): + Maximum sequence length to use for the prompt. + """ + device = device or self._execution_device + + prompt = [prompt] if isinstance(prompt, str) else prompt + prompt = [self._apply_chat_template(_prompt) for _prompt in prompt] + + if prompt is not None: + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + if prompt_embeds is None: + prompt_embeds, prompt_attention_mask = self._get_qwen2_prompt_embeds( + prompt=prompt, + device=device, + max_sequence_length=max_sequence_length + ) + + batch_size, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings and attention mask for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + prompt_attention_mask = prompt_attention_mask.repeat(num_images_per_prompt, 1) + prompt_attention_mask = prompt_attention_mask.view(batch_size * num_images_per_prompt, -1) + + # Get negative embeddings for classifier free guidance + if do_classifier_free_guidance and negative_prompt_embeds is None: + negative_prompt = negative_prompt if negative_prompt is not None else "" + + # Normalize str to list + negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt + negative_prompt = [self._apply_chat_template(_negative_prompt) for _negative_prompt in negative_prompt] + + if prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif isinstance(negative_prompt, str): + negative_prompt = [negative_prompt] + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + negative_prompt_embeds, negative_prompt_attention_mask = self._get_qwen2_prompt_embeds( + prompt=negative_prompt, + device=device, + max_sequence_length=max_sequence_length, + ) + + batch_size, seq_len, _ = negative_prompt_embeds.shape + # duplicate text embeddings and attention mask for each generation per prompt, using mps friendly method + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + negative_prompt_attention_mask = negative_prompt_attention_mask.repeat(num_images_per_prompt, 1) + negative_prompt_attention_mask = negative_prompt_attention_mask.view( + batch_size * num_images_per_prompt, -1 + ) + + return prompt_embeds, prompt_attention_mask, negative_prompt_embeds, negative_prompt_attention_mask + + @property + def num_timesteps(self): + return self._num_timesteps + + @property + def text_guidance_scale(self): + return self._text_guidance_scale + + @property + def image_guidance_scale(self): + return self._image_guidance_scale + + @property + def cfg_range(self): + return self._cfg_range + + @torch.no_grad() + def __call__( + self, + prompt: Optional[Union[str, List[str]]] = None, + negative_prompt: Optional[Union[str, List[str]]] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + prompt_attention_mask: Optional[torch.LongTensor] = None, + negative_prompt_attention_mask: Optional[torch.LongTensor] = None, + max_sequence_length: Optional[int] = None, + callback_on_step_end_tensor_inputs: Optional[List[str]] = None, + input_images: Optional[List[PIL.Image.Image]] = None, + num_images_per_prompt: int = 1, + height: Optional[int] = None, + width: Optional[int] = None, + max_pixels: int = 2048 * 2048, + max_input_image_side_length: int = 2048, + align_res: bool = True, + num_inference_steps: int = 28, + text_guidance_scale: float = 4.0, + image_guidance_scale: float = 1.0, + cfg_range: Tuple[float, float] = (0.0, 1.0), + attention_kwargs: Optional[Dict[str, Any]] = None, + timesteps: List[int] = None, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + verbose: bool = False, + step_func=None, + ): + + height = height or self.default_sample_size * self.vae_scale_factor + width = width or self.default_sample_size * self.vae_scale_factor + + self._text_guidance_scale = text_guidance_scale + self._image_guidance_scale = image_guidance_scale + self._cfg_range = cfg_range + self._attention_kwargs = attention_kwargs + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + + # 3. Encode input prompt + ( + prompt_embeds, + prompt_attention_mask, + negative_prompt_embeds, + negative_prompt_attention_mask, + ) = self.encode_prompt( + prompt, + self.text_guidance_scale > 1.0, + negative_prompt=negative_prompt, + num_images_per_prompt=num_images_per_prompt, + device=device, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + prompt_attention_mask=prompt_attention_mask, + negative_prompt_attention_mask=negative_prompt_attention_mask, + max_sequence_length=max_sequence_length, + ) + + dtype = self.vae.dtype + # 3. Prepare control image + ref_latents = self.prepare_image( + images=input_images, + batch_size=batch_size, + num_images_per_prompt=num_images_per_prompt, + max_pixels=max_pixels, + max_side_length=max_input_image_side_length, + device=device, + dtype=dtype, + ) + + if input_images is None: + input_images = [] + + if len(input_images) == 1 and align_res: + width, height = ref_latents[0][0].shape[-1] * self.vae_scale_factor, ref_latents[0][0].shape[-2] * self.vae_scale_factor + ori_width, ori_height = width, height + else: + ori_width, ori_height = width, height + + cur_pixels = height * width + ratio = (max_pixels / cur_pixels) ** 0.5 + ratio = min(ratio, 1.0) + + height, width = int(height * ratio) // 16 * 16, int(width * ratio) // 16 * 16 + + if len(input_images) == 0: + self._image_guidance_scale = 1 + + # 4. Prepare latents. + latent_channels = self.transformer.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + latent_channels, + height, + width, + prompt_embeds.dtype, + device, + generator, + latents, + ) + + freqs_cis = OmniGen2RotaryPosEmbed.get_freqs_cis( + self.transformer.config.axes_dim_rope, + self.transformer.config.axes_lens, + theta=10000, + ) + + image = self.processing( + latents=latents, + ref_latents=ref_latents, + prompt_embeds=prompt_embeds, + freqs_cis=freqs_cis, + negative_prompt_embeds=negative_prompt_embeds, + prompt_attention_mask=prompt_attention_mask, + negative_prompt_attention_mask=negative_prompt_attention_mask, + num_inference_steps=num_inference_steps, + timesteps=timesteps, + device=device, + dtype=dtype, + verbose=verbose, + step_func=step_func, + ) + + image = F.interpolate(image, size=(ori_height, ori_width), mode='bilinear') + + image = self.image_processor.postprocess(image, output_type=output_type) + + # Offload all models + self.maybe_free_model_hooks() + + if not return_dict: + return image + else: + return FMPipelineOutput(images=image) + + def processing( + self, + latents, + ref_latents, + prompt_embeds, + freqs_cis, + negative_prompt_embeds, + prompt_attention_mask, + negative_prompt_attention_mask, + num_inference_steps, + timesteps, + device, + dtype, + verbose, + step_func=None + ): + batch_size = latents.shape[0] + + timesteps, num_inference_steps = retrieve_timesteps( + self.scheduler, + num_inference_steps, + device, + timesteps, + num_tokens=latents.shape[-2] * latents.shape[-1] + ) + num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) + self._num_timesteps = len(timesteps) + + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + model_pred = self.predict( + t=t, + latents=latents, + prompt_embeds=prompt_embeds, + freqs_cis=freqs_cis, + prompt_attention_mask=prompt_attention_mask, + ref_image_hidden_states=ref_latents, + ) + text_guidance_scale = self.text_guidance_scale if self.cfg_range[0] <= i / len(timesteps) <= self.cfg_range[1] else 1.0 + image_guidance_scale = self.image_guidance_scale if self.cfg_range[0] <= i / len(timesteps) <= self.cfg_range[1] else 1.0 + + if text_guidance_scale > 1.0 and image_guidance_scale > 1.0: + model_pred_ref = self.predict( + t=t, + latents=latents, + prompt_embeds=negative_prompt_embeds, + freqs_cis=freqs_cis, + prompt_attention_mask=negative_prompt_attention_mask, + ref_image_hidden_states=ref_latents, + ) + + if image_guidance_scale != 1: + model_pred_uncond = self.predict( + t=t, + latents=latents, + prompt_embeds=negative_prompt_embeds, + freqs_cis=freqs_cis, + prompt_attention_mask=negative_prompt_attention_mask, + ref_image_hidden_states=None, + ) + else: + model_pred_uncond = torch.zeros_like(model_pred) + + model_pred = model_pred_uncond + image_guidance_scale * (model_pred_ref - model_pred_uncond) + \ + text_guidance_scale * (model_pred - model_pred_ref) + elif text_guidance_scale > 1.0: + model_pred_uncond = self.predict( + t=t, + latents=latents, + prompt_embeds=negative_prompt_embeds, + freqs_cis=freqs_cis, + prompt_attention_mask=negative_prompt_attention_mask, + ref_image_hidden_states=None, + ) + model_pred = model_pred_uncond + text_guidance_scale * (model_pred - model_pred_uncond) + + latents = self.scheduler.step(model_pred, t, latents, return_dict=False)[0] + + latents = latents.to(dtype=dtype) + + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + + if step_func is not None: + step_func(i, self._num_timesteps) + + latents = latents.to(dtype=dtype) + if self.vae.config.scaling_factor is not None: + latents = latents / self.vae.config.scaling_factor + if self.vae.config.shift_factor is not None: + latents = latents + self.vae.config.shift_factor + image = self.vae.decode(latents, return_dict=False)[0] + + return image + + def predict( + self, + t, + latents, + prompt_embeds, + freqs_cis, + prompt_attention_mask, + ref_image_hidden_states, + ): + # broadcast to batch dimension in a way that's compatible with ONNX/Core ML + timestep = t.expand(latents.shape[0]).to(latents.dtype) + + batch_size, num_channels_latents, height, width = latents.shape + + optional_kwargs = {} + if 'ref_image_hidden_states' in set(inspect.signature(self.transformer.forward).parameters.keys()): + optional_kwargs['ref_image_hidden_states'] = ref_image_hidden_states + + model_pred = self.transformer( + latents, + timestep, + prompt_embeds, + freqs_cis, + prompt_attention_mask, + **optional_kwargs + ) + return model_pred diff --git a/modules/omnigen2/pipeline_utils.py b/modules/omnigen2/pipeline_utils.py new file mode 100644 index 000000000..4efebc260 --- /dev/null +++ b/modules/omnigen2/pipeline_utils.py @@ -0,0 +1,62 @@ +import torch + + +def get_pipeline_embeds(pipeline, prompt, negative_prompt, device): + """ Get pipeline embeds for prompts bigger than the maxlength of the pipe + :param pipeline: + :param prompt: + :param negative_prompt: + :param device: + :return: + """ + max_length = pipeline.tokenizer.model_max_length + + # simple way to determine length of tokens + # count_prompt = len(prompt.split(" ")) + # count_negative_prompt = len(negative_prompt.split(" ")) + + # create the tensor based on which prompt is longer + # if count_prompt >= count_negative_prompt: + input_ids = pipeline.tokenizer(prompt, return_tensors="pt", truncation=False, padding='longest').input_ids.to(device) + # input_ids = pipeline.tokenizer(prompt, padding="max_length", + # max_length=pipeline.tokenizer.model_max_length, + # truncation=True, + # return_tensors="pt",).input_ids.to(device) + shape_max_length = input_ids.shape[-1] + + if negative_prompt is not None: + negative_ids = pipeline.tokenizer(negative_prompt, truncation=True, padding="max_length", + max_length=shape_max_length, return_tensors="pt").input_ids.to(device) + + # else: + # negative_ids = pipeline.tokenizer(negative_prompt, return_tensors="pt", truncation=False).input_ids.to(device) + # shape_max_length = negative_ids.shape[-1] + # input_ids = pipeline.tokenizer(prompt, return_tensors="pt", truncation=False, padding="max_length", + # max_length=shape_max_length).input_ids.to(device) + + concat_embeds = [] + neg_embeds = [] + for i in range(0, shape_max_length, max_length): + if hasattr(pipeline.text_encoder.config, "use_attention_mask") and pipeline.text_encoder.config.use_attention_mask: + attention_mask = input_ids[:, i: i + max_length].attention_mask.to(device) + else: + attention_mask = None + concat_embeds.append(pipeline.text_encoder(input_ids[:, i: i + max_length], + attention_mask=attention_mask)[0]) + + if negative_prompt is not None: + if hasattr(pipeline.text_encoder.config, "use_attention_mask") and pipeline.text_encoder.config.use_attention_mask: + attention_mask = negative_ids[:, i: i + max_length].attention_mask.to(device) + else: + attention_mask = None + neg_embeds.append(pipeline.text_encoder(negative_ids[:, i: i + max_length], + attention_mask=attention_mask)[0]) + + concat_embeds = torch.cat(concat_embeds, dim=1) + + if negative_prompt is not None: + neg_embeds = torch.cat(neg_embeds, dim=1) + else: + neg_embeds = None + + return concat_embeds, neg_embeds diff --git a/modules/omnigen2/triton_layer_norm.py b/modules/omnigen2/triton_layer_norm.py new file mode 100644 index 000000000..51a70b990 --- /dev/null +++ b/modules/omnigen2/triton_layer_norm.py @@ -0,0 +1,1257 @@ +# Copyright (c) 2024, Tri Dao. +# Implement dropout + residual + layer_norm / rms_norm. + +# Based on the Triton LayerNorm tutorial: https://triton-lang.org/main/getting-started/tutorials/05-layer-norm.html +# For the backward pass, we keep weight_grad and bias_grad in registers and accumulate. +# This is faster for dimensions up to 8k, but after that it's much slower due to register spilling. +# The models we train have hidden dim up to 8k anyway (e.g. Llama 70B), so this is fine. + +import math + +import torch +import torch.nn.functional as F + +import triton +import triton.language as tl + + +from typing import Callable + + +def custom_amp_decorator(dec: Callable, cuda_amp_deprecated: bool): + def decorator(*args, **kwargs): + if cuda_amp_deprecated: + kwargs["device_type"] = "cuda" + return dec(*args, **kwargs) + return decorator + + +if hasattr(torch.amp, "custom_fwd"): # type: ignore[attr-defined] + deprecated = True + from torch.amp import custom_fwd, custom_bwd # type: ignore[attr-defined] +else: + deprecated = False + from torch.cuda.amp import custom_fwd, custom_bwd + +custom_fwd = custom_amp_decorator(custom_fwd, deprecated) +custom_bwd = custom_amp_decorator(custom_bwd, deprecated) + + +def triton_autotune_configs(): + # Return configs with a valid warp count for the current device + configs=[] + # Maximum threads per block is architecture-dependent in theory, but in reality all are 1024 + max_threads_per_block=1024 + # Default to warp size 32 if not defined by device + warp_size=getattr(torch.cuda.get_device_properties(torch.cuda.current_device()), "warp_size", 32) + # Autotune for warp counts which are powers of 2 and do not exceed thread per block limit + warp_count=1 + while warp_count*warp_size <= max_threads_per_block: + configs.append(triton.Config({}, num_warps=warp_count)) + warp_count*=2 + return configs + +def layer_norm_ref( + x, + weight, + bias, + residual=None, + x1=None, + weight1=None, + bias1=None, + eps=1e-6, + dropout_p=0.0, + rowscale=None, + prenorm=False, + zero_centered_weight=False, + dropout_mask=None, + dropout_mask1=None, + upcast=False, +): + dtype = x.dtype + if upcast: + x = x.float() + weight = weight.float() + bias = bias.float() if bias is not None else None + residual = residual.float() if residual is not None else residual + x1 = x1.float() if x1 is not None else None + weight1 = weight1.float() if weight1 is not None else None + bias1 = bias1.float() if bias1 is not None else None + if zero_centered_weight: + weight = weight + 1.0 + if weight1 is not None: + weight1 = weight1 + 1.0 + if x1 is not None: + assert rowscale is None, "rowscale is not supported with parallel LayerNorm" + if rowscale is not None: + x = x * rowscale[..., None] + if dropout_p > 0.0: + if dropout_mask is not None: + x = x.masked_fill(~dropout_mask, 0.0) / (1.0 - dropout_p) + else: + x = F.dropout(x, p=dropout_p) + if x1 is not None: + if dropout_mask1 is not None: + x1 = x1.masked_fill(~dropout_mask1, 0.0) / (1.0 - dropout_p) + else: + x1 = F.dropout(x1, p=dropout_p) + if x1 is not None: + x = x + x1 + if residual is not None: + x = (x + residual).to(x.dtype) + out = F.layer_norm(x.to(weight.dtype), x.shape[-1:], weight=weight, bias=bias, eps=eps).to( + dtype + ) + if weight1 is None: + return out if not prenorm else (out, x) + else: + out1 = F.layer_norm( + x.to(weight1.dtype), x.shape[-1:], weight=weight1, bias=bias1, eps=eps + ).to(dtype) + return (out, out1) if not prenorm else (out, out1, x) + + +def rms_norm_ref( + x, + weight, + bias, + residual=None, + x1=None, + weight1=None, + bias1=None, + eps=1e-6, + dropout_p=0.0, + rowscale=None, + prenorm=False, + zero_centered_weight=False, + dropout_mask=None, + dropout_mask1=None, + upcast=False, +): + dtype = x.dtype + if upcast: + x = x.float() + weight = weight.float() + bias = bias.float() if bias is not None else None + residual = residual.float() if residual is not None else residual + x1 = x1.float() if x1 is not None else None + weight1 = weight1.float() if weight1 is not None else None + bias1 = bias1.float() if bias1 is not None else None + if zero_centered_weight: + weight = weight + 1.0 + if weight1 is not None: + weight1 = weight1 + 1.0 + if x1 is not None: + assert rowscale is None, "rowscale is not supported with parallel LayerNorm" + if rowscale is not None: + x = x * rowscale[..., None] + if dropout_p > 0.0: + if dropout_mask is not None: + x = x.masked_fill(~dropout_mask, 0.0) / (1.0 - dropout_p) + else: + x = F.dropout(x, p=dropout_p) + if x1 is not None: + if dropout_mask1 is not None: + x1 = x1.masked_fill(~dropout_mask1, 0.0) / (1.0 - dropout_p) + else: + x1 = F.dropout(x1, p=dropout_p) + if x1 is not None: + x = x + x1 + if residual is not None: + x = (x + residual).to(x.dtype) + rstd = 1 / torch.sqrt((x.square()).mean(dim=-1, keepdim=True) + eps) + out = ((x * rstd * weight) + bias if bias is not None else (x * rstd * weight)).to(dtype) + if weight1 is None: + return out if not prenorm else (out, x) + else: + out1 = ((x * rstd * weight1) + bias1 if bias1 is not None else (x * rstd * weight1)).to( + dtype + ) + return (out, out1) if not prenorm else (out, out1, x) + + +@triton.autotune( + configs=triton_autotune_configs(), + key=["N", "HAS_RESIDUAL", "STORE_RESIDUAL_OUT", "IS_RMS_NORM", "HAS_BIAS"], +) +# @triton.heuristics({"HAS_BIAS": lambda args: args["B"] is not None}) +# @triton.heuristics({"HAS_RESIDUAL": lambda args: args["RESIDUAL"] is not None}) +@triton.heuristics({"HAS_X1": lambda args: args["X1"] is not None}) +@triton.heuristics({"HAS_W1": lambda args: args["W1"] is not None}) +@triton.heuristics({"HAS_B1": lambda args: args["B1"] is not None}) +@triton.jit +def _layer_norm_fwd_1pass_kernel( + X, # pointer to the input + Y, # pointer to the output + W, # pointer to the weights + B, # pointer to the biases + RESIDUAL, # pointer to the residual + X1, + W1, + B1, + Y1, + RESIDUAL_OUT, # pointer to the residual + ROWSCALE, + SEEDS, # Dropout seeds for each row + DROPOUT_MASK, + Mean, # pointer to the mean + Rstd, # pointer to the 1/std + stride_x_row, # how much to increase the pointer when moving by 1 row + stride_y_row, + stride_res_row, + stride_res_out_row, + stride_x1_row, + stride_y1_row, + M, # number of rows in X + N, # number of columns in X + eps, # epsilon to avoid division by zero + dropout_p, # Dropout probability + zero_centered_weight, # If true, add 1.0 to the weight + IS_RMS_NORM: tl.constexpr, + BLOCK_N: tl.constexpr, + HAS_RESIDUAL: tl.constexpr, + STORE_RESIDUAL_OUT: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_DROPOUT: tl.constexpr, + STORE_DROPOUT_MASK: tl.constexpr, + HAS_ROWSCALE: tl.constexpr, + HAS_X1: tl.constexpr, + HAS_W1: tl.constexpr, + HAS_B1: tl.constexpr, +): + # Map the program id to the row of X and Y it should compute. + row = tl.program_id(0) + X += row * stride_x_row + Y += row * stride_y_row + if HAS_RESIDUAL: + RESIDUAL += row * stride_res_row + if STORE_RESIDUAL_OUT: + RESIDUAL_OUT += row * stride_res_out_row + if HAS_X1: + X1 += row * stride_x1_row + if HAS_W1: + Y1 += row * stride_y1_row + # Compute mean and variance + cols = tl.arange(0, BLOCK_N) + x = tl.load(X + cols, mask=cols < N, other=0.0).to(tl.float32) + if HAS_ROWSCALE: + rowscale = tl.load(ROWSCALE + row).to(tl.float32) + x *= rowscale + if HAS_DROPOUT: + # Compute dropout mask + # 7 rounds is good enough, and reduces register pressure + keep_mask = tl.rand(tl.load(SEEDS + row).to(tl.uint32), cols, n_rounds=7) > dropout_p + x = tl.where(keep_mask, x / (1.0 - dropout_p), 0.0) + if STORE_DROPOUT_MASK: + tl.store(DROPOUT_MASK + row * N + cols, keep_mask, mask=cols < N) + if HAS_X1: + x1 = tl.load(X1 + cols, mask=cols < N, other=0.0).to(tl.float32) + if HAS_ROWSCALE: + rowscale = tl.load(ROWSCALE + M + row).to(tl.float32) + x1 *= rowscale + if HAS_DROPOUT: + # Compute dropout mask + # 7 rounds is good enough, and reduces register pressure + keep_mask = ( + tl.rand(tl.load(SEEDS + M + row).to(tl.uint32), cols, n_rounds=7) > dropout_p + ) + x1 = tl.where(keep_mask, x1 / (1.0 - dropout_p), 0.0) + if STORE_DROPOUT_MASK: + tl.store(DROPOUT_MASK + (M + row) * N + cols, keep_mask, mask=cols < N) + x += x1 + if HAS_RESIDUAL: + residual = tl.load(RESIDUAL + cols, mask=cols < N, other=0.0).to(tl.float32) + x += residual + if STORE_RESIDUAL_OUT: + tl.store(RESIDUAL_OUT + cols, x, mask=cols < N) + if not IS_RMS_NORM: + mean = tl.sum(x, axis=0) / N + tl.store(Mean + row, mean) + xbar = tl.where(cols < N, x - mean, 0.0) + var = tl.sum(xbar * xbar, axis=0) / N + else: + xbar = tl.where(cols < N, x, 0.0) + var = tl.sum(xbar * xbar, axis=0) / N + rstd = 1 / tl.sqrt(var + eps) + tl.store(Rstd + row, rstd) + # Normalize and apply linear transformation + mask = cols < N + w = tl.load(W + cols, mask=mask).to(tl.float32) + if zero_centered_weight: + w += 1.0 + if HAS_BIAS: + b = tl.load(B + cols, mask=mask).to(tl.float32) + x_hat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd + y = x_hat * w + b if HAS_BIAS else x_hat * w + # Write output + tl.store(Y + cols, y, mask=mask) + if HAS_W1: + w1 = tl.load(W1 + cols, mask=mask).to(tl.float32) + if zero_centered_weight: + w1 += 1.0 + if HAS_B1: + b1 = tl.load(B1 + cols, mask=mask).to(tl.float32) + y1 = x_hat * w1 + b1 if HAS_B1 else x_hat * w1 + tl.store(Y1 + cols, y1, mask=mask) + + +def _layer_norm_fwd( + x, + weight, + bias, + eps, + residual=None, + x1=None, + weight1=None, + bias1=None, + dropout_p=0.0, + rowscale=None, + out_dtype=None, + residual_dtype=None, + zero_centered_weight=False, + is_rms_norm=False, + return_dropout_mask=False, + out=None, + residual_out=None +): + if residual is not None: + residual_dtype = residual.dtype + M, N = x.shape + assert x.stride(-1) == 1 + if residual is not None: + assert residual.stride(-1) == 1 + assert residual.shape == (M, N) + assert weight.shape == (N,) + assert weight.stride(-1) == 1 + if bias is not None: + assert bias.stride(-1) == 1 + assert bias.shape == (N,) + if x1 is not None: + assert x1.shape == x.shape + assert rowscale is None + assert x1.stride(-1) == 1 + if weight1 is not None: + assert weight1.shape == (N,) + assert weight1.stride(-1) == 1 + if bias1 is not None: + assert bias1.shape == (N,) + assert bias1.stride(-1) == 1 + if rowscale is not None: + assert rowscale.is_contiguous() + assert rowscale.shape == (M,) + # allocate output + if out is None: + out = torch.empty_like(x, dtype=x.dtype if out_dtype is None else out_dtype) + else: + assert out.shape == x.shape + assert out.stride(-1) == 1 + if weight1 is not None: + y1 = torch.empty_like(out) + assert y1.stride(-1) == 1 + else: + y1 = None + if ( + residual is not None + or (residual_dtype is not None and residual_dtype != x.dtype) + or dropout_p > 0.0 + or rowscale is not None + or x1 is not None + ): + if residual_out is None: + residual_out = torch.empty( + M, N, device=x.device, dtype=residual_dtype if residual_dtype is not None else x.dtype + ) + else: + assert residual_out.shape == x.shape + assert residual_out.stride(-1) == 1 + else: + residual_out = None + mean = torch.empty((M,), dtype=torch.float32, device=x.device) if not is_rms_norm else None + rstd = torch.empty((M,), dtype=torch.float32, device=x.device) + if dropout_p > 0.0: + seeds = torch.randint( + 2**32, (M if x1 is None else 2 * M,), device=x.device, dtype=torch.int64 + ) + else: + seeds = None + if return_dropout_mask and dropout_p > 0.0: + dropout_mask = torch.empty(M if x1 is None else 2 * M, N, device=x.device, dtype=torch.bool) + else: + dropout_mask = None + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(N)) + if N > BLOCK_N: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + with torch.cuda.device(x.device.index): + _layer_norm_fwd_1pass_kernel[(M,)]( + x, + out, + weight, + bias, + residual, + x1, + weight1, + bias1, + y1, + residual_out, + rowscale, + seeds, + dropout_mask, + mean, + rstd, + x.stride(0), + out.stride(0), + residual.stride(0) if residual is not None else 0, + residual_out.stride(0) if residual_out is not None else 0, + x1.stride(0) if x1 is not None else 0, + y1.stride(0) if y1 is not None else 0, + M, + N, + eps, + dropout_p, + zero_centered_weight, + is_rms_norm, + BLOCK_N, + residual is not None, + residual_out is not None, + bias is not None, + dropout_p > 0.0, + dropout_mask is not None, + rowscale is not None, + ) + # residual_out is None if residual is None and residual_dtype == input_dtype and dropout_p == 0.0 + if dropout_mask is not None and x1 is not None: + dropout_mask, dropout_mask1 = dropout_mask.tensor_split(2, dim=0) + else: + dropout_mask1 = None + return ( + out, + y1, + mean, + rstd, + residual_out if residual_out is not None else x, + seeds, + dropout_mask, + dropout_mask1, + ) + + +@triton.autotune( + configs=triton_autotune_configs(), + key=["N", "HAS_DRESIDUAL", "STORE_DRESIDUAL", "IS_RMS_NORM", "HAS_BIAS", "HAS_DROPOUT"], +) +# @triton.heuristics({"HAS_BIAS": lambda args: args["B"] is not None}) +# @triton.heuristics({"HAS_DRESIDUAL": lambda args: args["DRESIDUAL"] is not None}) +# @triton.heuristics({"STORE_DRESIDUAL": lambda args: args["DRESIDUAL_IN"] is not None}) +@triton.heuristics({"HAS_ROWSCALE": lambda args: args["ROWSCALE"] is not None}) +@triton.heuristics({"HAS_DY1": lambda args: args["DY1"] is not None}) +@triton.heuristics({"HAS_DX1": lambda args: args["DX1"] is not None}) +@triton.heuristics({"HAS_B1": lambda args: args["DB1"] is not None}) +@triton.heuristics({"RECOMPUTE_OUTPUT": lambda args: args["Y"] is not None}) +@triton.jit +def _layer_norm_bwd_kernel( + X, # pointer to the input + W, # pointer to the weights + B, # pointer to the biases + Y, # pointer to the output to be recomputed + DY, # pointer to the output gradient + DX, # pointer to the input gradient + DW, # pointer to the partial sum of weights gradient + DB, # pointer to the partial sum of biases gradient + DRESIDUAL, + W1, + DY1, + DX1, + DW1, + DB1, + DRESIDUAL_IN, + ROWSCALE, + SEEDS, + Mean, # pointer to the mean + Rstd, # pointer to the 1/std + stride_x_row, # how much to increase the pointer when moving by 1 row + stride_y_row, + stride_dy_row, + stride_dx_row, + stride_dres_row, + stride_dy1_row, + stride_dx1_row, + stride_dres_in_row, + M, # number of rows in X + N, # number of columns in X + eps, # epsilon to avoid division by zero + dropout_p, + zero_centered_weight, + rows_per_program, + IS_RMS_NORM: tl.constexpr, + BLOCK_N: tl.constexpr, + HAS_DRESIDUAL: tl.constexpr, + STORE_DRESIDUAL: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_DROPOUT: tl.constexpr, + HAS_ROWSCALE: tl.constexpr, + HAS_DY1: tl.constexpr, + HAS_DX1: tl.constexpr, + HAS_B1: tl.constexpr, + RECOMPUTE_OUTPUT: tl.constexpr, +): + # Map the program id to the elements of X, DX, and DY it should compute. + row_block_id = tl.program_id(0) + row_start = row_block_id * rows_per_program + # Do not early exit if row_start >= M, because we need to write DW and DB + cols = tl.arange(0, BLOCK_N) + mask = cols < N + X += row_start * stride_x_row + if HAS_DRESIDUAL: + DRESIDUAL += row_start * stride_dres_row + if STORE_DRESIDUAL: + DRESIDUAL_IN += row_start * stride_dres_in_row + DY += row_start * stride_dy_row + DX += row_start * stride_dx_row + if HAS_DY1: + DY1 += row_start * stride_dy1_row + if HAS_DX1: + DX1 += row_start * stride_dx1_row + if RECOMPUTE_OUTPUT: + Y += row_start * stride_y_row + w = tl.load(W + cols, mask=mask).to(tl.float32) + if zero_centered_weight: + w += 1.0 + if RECOMPUTE_OUTPUT and HAS_BIAS: + b = tl.load(B + cols, mask=mask, other=0.0).to(tl.float32) + if HAS_DY1: + w1 = tl.load(W1 + cols, mask=mask).to(tl.float32) + if zero_centered_weight: + w1 += 1.0 + dw = tl.zeros((BLOCK_N,), dtype=tl.float32) + if HAS_BIAS: + db = tl.zeros((BLOCK_N,), dtype=tl.float32) + if HAS_DY1: + dw1 = tl.zeros((BLOCK_N,), dtype=tl.float32) + if HAS_B1: + db1 = tl.zeros((BLOCK_N,), dtype=tl.float32) + row_end = min((row_block_id + 1) * rows_per_program, M) + for row in range(row_start, row_end): + # Load data to SRAM + x = tl.load(X + cols, mask=mask, other=0).to(tl.float32) + dy = tl.load(DY + cols, mask=mask, other=0).to(tl.float32) + if HAS_DY1: + dy1 = tl.load(DY1 + cols, mask=mask, other=0).to(tl.float32) + if not IS_RMS_NORM: + mean = tl.load(Mean + row) + rstd = tl.load(Rstd + row) + # Compute dx + xhat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd + xhat = tl.where(mask, xhat, 0.0) + if RECOMPUTE_OUTPUT: + y = xhat * w + b if HAS_BIAS else xhat * w + tl.store(Y + cols, y, mask=mask) + wdy = w * dy + dw += dy * xhat + if HAS_BIAS: + db += dy + if HAS_DY1: + wdy += w1 * dy1 + dw1 += dy1 * xhat + if HAS_B1: + db1 += dy1 + if not IS_RMS_NORM: + c1 = tl.sum(xhat * wdy, axis=0) / N + c2 = tl.sum(wdy, axis=0) / N + dx = (wdy - (xhat * c1 + c2)) * rstd + else: + c1 = tl.sum(xhat * wdy, axis=0) / N + dx = (wdy - xhat * c1) * rstd + if HAS_DRESIDUAL: + dres = tl.load(DRESIDUAL + cols, mask=mask, other=0).to(tl.float32) + dx += dres + # Write dx + if STORE_DRESIDUAL: + tl.store(DRESIDUAL_IN + cols, dx, mask=mask) + if HAS_DX1: + if HAS_DROPOUT: + keep_mask = ( + tl.rand(tl.load(SEEDS + M + row).to(tl.uint32), cols, n_rounds=7) > dropout_p + ) + dx1 = tl.where(keep_mask, dx / (1.0 - dropout_p), 0.0) + else: + dx1 = dx + tl.store(DX1 + cols, dx1, mask=mask) + if HAS_DROPOUT: + keep_mask = tl.rand(tl.load(SEEDS + row).to(tl.uint32), cols, n_rounds=7) > dropout_p + dx = tl.where(keep_mask, dx / (1.0 - dropout_p), 0.0) + if HAS_ROWSCALE: + rowscale = tl.load(ROWSCALE + row).to(tl.float32) + dx *= rowscale + tl.store(DX + cols, dx, mask=mask) + + X += stride_x_row + if HAS_DRESIDUAL: + DRESIDUAL += stride_dres_row + if STORE_DRESIDUAL: + DRESIDUAL_IN += stride_dres_in_row + if RECOMPUTE_OUTPUT: + Y += stride_y_row + DY += stride_dy_row + DX += stride_dx_row + if HAS_DY1: + DY1 += stride_dy1_row + if HAS_DX1: + DX1 += stride_dx1_row + tl.store(DW + row_block_id * N + cols, dw, mask=mask) + if HAS_BIAS: + tl.store(DB + row_block_id * N + cols, db, mask=mask) + if HAS_DY1: + tl.store(DW1 + row_block_id * N + cols, dw1, mask=mask) + if HAS_B1: + tl.store(DB1 + row_block_id * N + cols, db1, mask=mask) + + +def _layer_norm_bwd( + dy, + x, + weight, + bias, + eps, + mean, + rstd, + dresidual=None, + dy1=None, + weight1=None, + bias1=None, + seeds=None, + dropout_p=0.0, + rowscale=None, + has_residual=False, + has_x1=False, + zero_centered_weight=False, + is_rms_norm=False, + x_dtype=None, + recompute_output=False, +): + M, N = x.shape + assert x.stride(-1) == 1 + assert dy.stride(-1) == 1 + assert dy.shape == (M, N) + if dresidual is not None: + assert dresidual.stride(-1) == 1 + assert dresidual.shape == (M, N) + assert weight.shape == (N,) + assert weight.stride(-1) == 1 + if bias is not None: + assert bias.stride(-1) == 1 + assert bias.shape == (N,) + if dy1 is not None: + assert weight1 is not None + assert dy1.shape == dy.shape + assert dy1.stride(-1) == 1 + if weight1 is not None: + assert weight1.shape == (N,) + assert weight1.stride(-1) == 1 + if bias1 is not None: + assert bias1.shape == (N,) + assert bias1.stride(-1) == 1 + if seeds is not None: + assert seeds.is_contiguous() + assert seeds.shape == (M if not has_x1 else M * 2,) + if rowscale is not None: + assert rowscale.is_contiguous() + assert rowscale.shape == (M,) + # allocate output + dx = ( + torch.empty_like(x) + if x_dtype is None + else torch.empty(M, N, dtype=x_dtype, device=x.device) + ) + dresidual_in = ( + torch.empty_like(x) + if has_residual + and (dx.dtype != x.dtype or dropout_p > 0.0 or rowscale is not None or has_x1) + else None + ) + dx1 = torch.empty_like(dx) if (has_x1 and dropout_p > 0.0) else None + y = torch.empty(M, N, dtype=dy.dtype, device=dy.device) if recompute_output else None + if recompute_output: + assert weight1 is None, "recompute_output is not supported with parallel LayerNorm" + + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(N)) + if N > BLOCK_N: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + # Increasing the multiple (e.g. 8) will allow more thread blocks to be launched and hide the + # latency of the gmem reads/writes, but will increase the time of summing up dw / db. + sm_count = torch.cuda.get_device_properties(x.device).multi_processor_count * 8 + _dw = torch.empty((sm_count, N), dtype=torch.float32, device=weight.device) + _db = ( + torch.empty((sm_count, N), dtype=torch.float32, device=bias.device) + if bias is not None + else None + ) + _dw1 = torch.empty_like(_dw) if weight1 is not None else None + _db1 = torch.empty_like(_db) if bias1 is not None else None + rows_per_program = math.ceil(M / sm_count) + grid = (sm_count,) + with torch.cuda.device(x.device.index): + _layer_norm_bwd_kernel[grid]( + x, + weight, + bias, + y, + dy, + dx, + _dw, + _db, + dresidual, + weight1, + dy1, + dx1, + _dw1, + _db1, + dresidual_in, + rowscale, + seeds, + mean, + rstd, + x.stride(0), + 0 if not recompute_output else y.stride(0), + dy.stride(0), + dx.stride(0), + dresidual.stride(0) if dresidual is not None else 0, + dy1.stride(0) if dy1 is not None else 0, + dx1.stride(0) if dx1 is not None else 0, + dresidual_in.stride(0) if dresidual_in is not None else 0, + M, + N, + eps, + dropout_p, + zero_centered_weight, + rows_per_program, + is_rms_norm, + BLOCK_N, + dresidual is not None, + dresidual_in is not None, + bias is not None, + dropout_p > 0.0, + ) + dw = _dw.sum(0).to(weight.dtype) + db = _db.sum(0).to(bias.dtype) if bias is not None else None + dw1 = _dw1.sum(0).to(weight1.dtype) if weight1 is not None else None + db1 = _db1.sum(0).to(bias1.dtype) if bias1 is not None else None + # Don't need to compute dresidual_in separately in this case + if has_residual and dx.dtype == x.dtype and dropout_p == 0.0 and rowscale is None: + dresidual_in = dx + if has_x1 and dropout_p == 0.0: + dx1 = dx + return ( + (dx, dw, db, dresidual_in, dx1, dw1, db1) + if not recompute_output + else (dx, dw, db, dresidual_in, dx1, dw1, db1, y) + ) + + +class LayerNormFn(torch.autograd.Function): + @staticmethod + def forward( + ctx, + x, + weight, + bias, + residual=None, + x1=None, + weight1=None, + bias1=None, + eps=1e-6, + dropout_p=0.0, + rowscale=None, + prenorm=False, + residual_in_fp32=False, + zero_centered_weight=False, + is_rms_norm=False, + return_dropout_mask=False, + out=None, + residual_out=None + ): + x_shape_og = x.shape + # Check for zero sequence length + if x.numel() == 0: + ctx.zero_seq_length = True + # Only save minimal required tensors for backward + # ctx.save_for_backward(weight, bias, weight1, bias1) + ctx.x_shape_og = x_shape_og + ctx.weight_shape = weight.shape + ctx.weight_dtype = weight.dtype + ctx.weight_device = weight.device + + ctx.has_bias = bias is not None + ctx.bias_shape = bias.shape if bias is not None else None + ctx.bias_dtype = bias.dtype if bias is not None else None + ctx.bias_device = bias.device if bias is not None else None + + ctx.has_weight1 = weight1 is not None + ctx.weight1_shape = weight1.shape if weight1 is not None else None + ctx.weight1_dtype = weight1.dtype if weight1 is not None else None + ctx.weight1_device = weight1.device if weight1 is not None else None + + ctx.has_bias1 = bias1 is not None + ctx.bias1_shape = bias1.shape if bias1 is not None else None + ctx.bias1_dtype = bias1.dtype if bias1 is not None else None + ctx.bias1_device = bias1.device if bias1 is not None else None + + ctx.has_residual = residual is not None + ctx.has_x1 = x1 is not None + ctx.dropout_p = dropout_p + + # Handle output tensors with correct dtype + y = x # Preserve input tensor properties + y1 = torch.empty_like(x) if x1 is not None else None + + # Only create residual_out if prenorm is True + residual_out = torch.empty(x.shape, + dtype=torch.float32 if residual_in_fp32 else x.dtype, + device=x.device) if prenorm else None + + # Handle dropout masks + dropout_mask = None + dropout_mask1 = None + if return_dropout_mask: + dropout_mask = torch.empty_like(x, dtype=torch.uint8) + if x1 is not None: + dropout_mask1 = torch.empty_like(x, dtype=torch.uint8) + + # Return based on configuration + if not return_dropout_mask: + if weight1 is None: + return y if not prenorm else (y, residual_out) + else: + return (y, y1) if not prenorm else (y, y1, residual_out) + else: + if weight1 is None: + return ((y, dropout_mask, dropout_mask1) if not prenorm + else (y, residual_out, dropout_mask, dropout_mask1)) + else: + return ((y, y1, dropout_mask, dropout_mask1) if not prenorm + else (y, y1, residual_out, dropout_mask, dropout_mask1)) + + ctx.zero_seq_length = False + # reshape input data into 2D tensor + x = x.reshape(-1, x.shape[-1]) + if x.stride(-1) != 1: + x = x.contiguous() + if residual is not None: + assert residual.shape == x_shape_og + residual = residual.reshape(-1, residual.shape[-1]) + if residual.stride(-1) != 1: + residual = residual.contiguous() + if x1 is not None: + assert x1.shape == x_shape_og + assert rowscale is None, "rowscale is not supported with parallel LayerNorm" + x1 = x1.reshape(-1, x1.shape[-1]) + if x1.stride(-1) != 1: + x1 = x1.contiguous() + weight = weight.contiguous() + if bias is not None: + bias = bias.contiguous() + if weight1 is not None: + weight1 = weight1.contiguous() + if bias1 is not None: + bias1 = bias1.contiguous() + if rowscale is not None: + rowscale = rowscale.reshape(-1).contiguous() + residual_dtype = ( + residual.dtype + if residual is not None + else (torch.float32 if residual_in_fp32 else None) + ) + if out is not None: + out = out.reshape(-1, out.shape[-1]) + if residual_out is not None: + residual_out = residual_out.reshape(-1, residual_out.shape[-1]) + y, y1, mean, rstd, residual_out, seeds, dropout_mask, dropout_mask1 = _layer_norm_fwd( + x, + weight, + bias, + eps, + residual, + x1, + weight1, + bias1, + dropout_p=dropout_p, + rowscale=rowscale, + residual_dtype=residual_dtype, + zero_centered_weight=zero_centered_weight, + is_rms_norm=is_rms_norm, + return_dropout_mask=return_dropout_mask, + out=out, + residual_out=residual_out + ) + ctx.save_for_backward( + residual_out, weight, bias, weight1, bias1, rowscale, seeds, mean, rstd + ) + ctx.x_shape_og = x_shape_og + ctx.eps = eps + ctx.dropout_p = dropout_p + ctx.is_rms_norm = is_rms_norm + ctx.has_residual = residual is not None + ctx.has_x1 = x1 is not None + ctx.prenorm = prenorm + ctx.x_dtype = x.dtype + ctx.zero_centered_weight = zero_centered_weight + y = y.reshape(x_shape_og) + y1 = y1.reshape(x_shape_og) if y1 is not None else None + residual_out = residual_out.reshape(x_shape_og) if residual_out is not None else None + dropout_mask = dropout_mask.reshape(x_shape_og) if dropout_mask is not None else None + dropout_mask1 = dropout_mask1.reshape(x_shape_og) if dropout_mask1 is not None else None + if not return_dropout_mask: + if weight1 is None: + return y if not prenorm else (y, residual_out) + else: + return (y, y1) if not prenorm else (y, y1, residual_out) + else: + if weight1 is None: + return ( + (y, dropout_mask, dropout_mask1) + if not prenorm + else (y, residual_out, dropout_mask, dropout_mask1) + ) + else: + return ( + (y, y1, dropout_mask, dropout_mask1) + if not prenorm + else (y, y1, residual_out, dropout_mask, dropout_mask1) + ) + + @staticmethod + def backward(ctx, dy, *args): + if ctx.zero_seq_length: + return ( + torch.zeros(ctx.x_shape_og, dtype=dy.dtype, device=dy.device), + torch.zeros(ctx.weight_shape, dtype=ctx.weight_dtype, device=ctx.weight_device), + torch.zeros(ctx.bias_shape, dtype=ctx.bias_dtype, device=ctx.bias_device) if ctx.has_bias else None, + torch.zeros(ctx.x_shape_og, dtype=dy.dtype, device=dy.device) if ctx.has_residual else None, + torch.zeros(ctx.x_shape_og, dtype=dy.dtype, device=dy.device) if ctx.has_x1 and ctx.dropout_p > 0.0 else None, + torch.zeros(ctx.weight1_shape, dtype=ctx.weight1_dtype, device=ctx.weight1_device) if ctx.has_weight1 else None, + torch.zeros(ctx.bias1_shape, dtype=ctx.bias1_dtype, device=ctx.bias1_device) if ctx.has_bias1 else None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) + + x, weight, bias, weight1, bias1, rowscale, seeds, mean, rstd = ctx.saved_tensors + dy = dy.reshape(-1, dy.shape[-1]) + if dy.stride(-1) != 1: + dy = dy.contiguous() + assert dy.shape == x.shape + if weight1 is not None: + dy1, args = args[0], args[1:] + dy1 = dy1.reshape(-1, dy1.shape[-1]) + if dy1.stride(-1) != 1: + dy1 = dy1.contiguous() + assert dy1.shape == x.shape + else: + dy1 = None + if ctx.prenorm: + dresidual = args[0] + dresidual = dresidual.reshape(-1, dresidual.shape[-1]) + if dresidual.stride(-1) != 1: + dresidual = dresidual.contiguous() + assert dresidual.shape == x.shape + else: + dresidual = None + + dx, dw, db, dresidual_in, dx1, dw1, db1 = _layer_norm_bwd( + dy, + x, + weight, + bias, + ctx.eps, + mean, + rstd, + dresidual, + dy1, + weight1, + bias1, + seeds, + ctx.dropout_p, + rowscale, + ctx.has_residual, + ctx.has_x1, + ctx.zero_centered_weight, + ctx.is_rms_norm, + x_dtype=ctx.x_dtype, + ) + return ( + dx.reshape(ctx.x_shape_og), + dw, + db, + dresidual_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, + dx1.reshape(ctx.x_shape_og) if dx1 is not None else None, + dw1, + db1, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) + + +def layer_norm_fn( + x, + weight, + bias, + residual=None, + x1=None, + weight1=None, + bias1=None, + eps=1e-6, + dropout_p=0.0, + rowscale=None, + prenorm=False, + residual_in_fp32=False, + zero_centered_weight=False, + is_rms_norm=False, + return_dropout_mask=False, + out=None, + residual_out=None +): + return LayerNormFn.apply( + x, + weight, + bias, + residual, + x1, + weight1, + bias1, + eps, + dropout_p, + rowscale, + prenorm, + residual_in_fp32, + zero_centered_weight, + is_rms_norm, + return_dropout_mask, + out, + residual_out + ) + + +def rms_norm_fn( + x, + weight, + bias, + residual=None, + x1=None, + weight1=None, + bias1=None, + eps=1e-6, + dropout_p=0.0, + rowscale=None, + prenorm=False, + residual_in_fp32=False, + zero_centered_weight=False, + return_dropout_mask=False, + out=None, + residual_out=None +): + return LayerNormFn.apply( + x, + weight, + bias, + residual, + x1, + weight1, + bias1, + eps, + dropout_p, + rowscale, + prenorm, + residual_in_fp32, + zero_centered_weight, + True, + return_dropout_mask, + out, + residual_out + ) + + +class RMSNorm(torch.nn.Module): + + def __init__(self, hidden_size, eps=1e-5, dropout_p=0.0, zero_centered_weight=False, + device=None, dtype=None): + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__() + self.eps = eps + if dropout_p > 0.0: + self.drop = torch.nn.Dropout(dropout_p) + else: + self.drop = None + self.zero_centered_weight = zero_centered_weight + self.weight = torch.nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) + self.register_parameter("bias", None) + self.reset_parameters() + + def reset_parameters(self): + if not self.zero_centered_weight: + torch.nn.init.ones_(self.weight) + else: + torch.nn.init.zeros_(self.weight) + + def forward(self, x, residual=None, prenorm=False, residual_in_fp32=False): + return rms_norm_fn( + x, + self.weight, + self.bias, + residual=residual, + eps=self.eps, + dropout_p=self.drop.p if self.drop is not None and self.training else 0.0, + prenorm=prenorm, + residual_in_fp32=residual_in_fp32, + zero_centered_weight=self.zero_centered_weight, + ) + + +class LayerNormLinearFn(torch.autograd.Function): + @staticmethod + @custom_fwd + def forward( + ctx, + x, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual=None, + eps=1e-6, + prenorm=False, + residual_in_fp32=False, + is_rms_norm=False, + ): + x_shape_og = x.shape + # reshape input data into 2D tensor + x = x.reshape(-1, x.shape[-1]) + if x.stride(-1) != 1: + x = x.contiguous() + if residual is not None: + assert residual.shape == x_shape_og + residual = residual.reshape(-1, residual.shape[-1]) + if residual.stride(-1) != 1: + residual = residual.contiguous() + norm_weight = norm_weight.contiguous() + if norm_bias is not None: + norm_bias = norm_bias.contiguous() + residual_dtype = ( + residual.dtype + if residual is not None + else (torch.float32 if residual_in_fp32 else None) + ) + y, _, mean, rstd, residual_out, *rest = _layer_norm_fwd( + x, + norm_weight, + norm_bias, + eps, + residual, + out_dtype=None if not torch.is_autocast_enabled() else torch.get_autocast_dtype("cuda"), + residual_dtype=residual_dtype, + is_rms_norm=is_rms_norm, + ) + y = y.reshape(x_shape_og) + dtype = torch.get_autocast_dtype("cuda") if torch.is_autocast_enabled() else y.dtype + linear_weight = linear_weight.to(dtype) + linear_bias = linear_bias.to(dtype) if linear_bias is not None else None + out = F.linear(y.to(linear_weight.dtype), linear_weight, linear_bias) + # We don't store y, will be recomputed in the backward pass to save memory + ctx.save_for_backward(residual_out, norm_weight, norm_bias, linear_weight, mean, rstd) + ctx.x_shape_og = x_shape_og + ctx.eps = eps + ctx.is_rms_norm = is_rms_norm + ctx.has_residual = residual is not None + ctx.prenorm = prenorm + ctx.x_dtype = x.dtype + ctx.linear_bias_is_none = linear_bias is None + return out if not prenorm else (out, residual_out.reshape(x_shape_og)) + + @staticmethod + @custom_bwd + def backward(ctx, dout, *args): + x, norm_weight, norm_bias, linear_weight, mean, rstd = ctx.saved_tensors + dout = dout.reshape(-1, dout.shape[-1]) + dy = F.linear(dout, linear_weight.t()) + dlinear_bias = None if ctx.linear_bias_is_none else dout.sum(0) + if dy.stride(-1) != 1: + dy = dy.contiguous() + assert dy.shape == x.shape + if ctx.prenorm: + dresidual = args[0] + dresidual = dresidual.reshape(-1, dresidual.shape[-1]) + if dresidual.stride(-1) != 1: + dresidual = dresidual.contiguous() + assert dresidual.shape == x.shape + else: + dresidual = None + dx, dnorm_weight, dnorm_bias, dresidual_in, _, _, _, y = _layer_norm_bwd( + dy, + x, + norm_weight, + norm_bias, + ctx.eps, + mean, + rstd, + dresidual=dresidual, + has_residual=ctx.has_residual, + is_rms_norm=ctx.is_rms_norm, + x_dtype=ctx.x_dtype, + recompute_output=True, + ) + dlinear_weight = torch.einsum("bo,bi->oi", dout, y) + return ( + dx.reshape(ctx.x_shape_og), + dnorm_weight, + dnorm_bias, + dlinear_weight, + dlinear_bias, + dresidual_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, + None, + None, + None, + None, + ) + + +def layer_norm_linear_fn( + x, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual=None, + eps=1e-6, + prenorm=False, + residual_in_fp32=False, + is_rms_norm=False, +): + return LayerNormLinearFn.apply( + x, + norm_weight, + norm_bias, + linear_weight, + linear_bias, + residual, + eps, + prenorm, + residual_in_fp32, + is_rms_norm, + ) diff --git a/modules/processing_args.py b/modules/processing_args.py index 154f5e272..7721fe371 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -61,7 +61,7 @@ def task_specific_kwargs(p, model): p.width, p.height = p.width // vae_scale_factor * vae_scale_factor, p.height // vae_scale_factor * vae_scale_factor task_args['max_area'] = max_area task_args['width'], task_args['height'] = p.width, p.height - if model.__class__.__name__ == 'OmniGenPipeline': + elif model.__class__.__name__ == 'OmniGenPipeline' or model.__class__.__name__ == 'OmniGen2Pipeline': p.width, p.height = 16 * math.ceil(p.init_images[0].width / 16), 16 * math.ceil(p.init_images[0].height / 16) task_args = { 'width': p.width, @@ -285,7 +285,7 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t kwargs['output_type'] = 'np' # only set latent if model has vae # model specific - if 'Kandinsky' in model.__class__.__name__: + if 'Kandinsky' in model.__class__.__name__ or 'Cosmos2' in model.__class__.__name__ or 'OmniGen2' in model.__class__.__name__: kwargs['output_type'] = 'np' # only set latent if model has vae if 'StableCascade' in model.__class__.__name__: kwargs.pop("guidance_scale") # remove @@ -305,8 +305,6 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t args['control_strength'] = p.denoising_strength args['width'] = p.width args['height'] = p.height - if 'Cosmos2' in model.__class__.__name__: - kwargs['output_type'] = 'np' # cosmos uses wan-vae which is weird # set callbacks if 'prior_callback_steps' in possible: # Wuerstchen / Cascade args['prior_callback_steps'] = 1 diff --git a/modules/sd_detect.py b/modules/sd_detect.py index cb99f8d17..be57edb4b 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -88,6 +88,9 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): if 'omnigen' in f.lower(): guess = 'OmniGen' pipeline = 'custom' + if 'omnigen2' in f.lower(): + guess = 'OmniGen2' + pipeline = 'custom' if 'sd3' in f.lower(): guess = 'Stable Diffusion 3' if 'hidream' in f.lower(): diff --git a/modules/sd_models.py b/modules/sd_models.py index 97e36b2e6..3a74eac56 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -37,6 +37,7 @@ pipe_switch_task_exclude = [ 'InstantIRPipeline', 'LTXConditionPipeline', 'OmniGenPipeline', + 'OmniGen2Pipeline', 'PhotoMakerStableDiffusionXLPipeline', 'PixelSmithXLPipeline', 'StableDiffusion3ControlNetPipeline', @@ -364,6 +365,10 @@ def load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op=' from modules.model_meissonic import load_meissonic sd_model = load_meissonic(checkpoint_info, diffusers_load_config) allow_post_quant = True + elif model_type in ['OmniGen2']: # forced pipeline + from modules.model_omnigen2 import load_omnigen2 + sd_model = load_omnigen2(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['OmniGen']: # forced pipeline from modules.model_omnigen import load_omnigen sd_model = load_omnigen(checkpoint_info, diffusers_load_config) diff --git a/modules/sd_offload.py b/modules/sd_offload.py index 188275f71..037f46ca2 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -12,7 +12,7 @@ from modules.timer import process as process_timer debug = os.environ.get('SD_MOVE_DEBUG', None) is not None debug_move = log.trace if debug else lambda *args, **kwargs: None -offload_warn = ['sc', 'sd3', 'f1', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'cogview4', 'cosmos', 'chroma'] +offload_warn = ['sc', 'sd3', 'f1', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'omnigen2', 'cogview4', 'cosmos', 'chroma'] offload_post = ['h1'] offload_hook_instance = None balanced_offload_exclude = ['CogView4Pipeline'] diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 2d6c2858c..15e2cf1ad 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -9,7 +9,7 @@ from modules import shared, devices, processing, images, sd_vae_approx, sd_vae_t SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options']) approximation_indexes = { "Simple": 0, "Approximate": 1, "TAESD": 2, "Full VAE": 3 } -flow_models = ['f1', 'sd3', 'lumina', 'auraflow', 'sana', 'lumina2', 'cogview4', 'h1', 'cosmos', 'chroma'] +flow_models = ['f1', 'sd3', 'lumina', 'auraflow', 'sana', 'lumina2', 'cogview4', 'h1', 'cosmos', 'chroma', 'omnigen2'] warned = False queue_lock = threading.Lock() diff --git a/modules/shared.py b/modules/shared.py index cc224dc00..d87d5b252 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -522,11 +522,11 @@ options_templates.update(options_section(("quantization", "Quantization Settings "sdnq_quantize_weights_mode": OptionInfo("int8", "Quantization type", gr.Dropdown, {"choices": sdnq_quant_modes, "visible": native}), "sdnq_quantize_weights_mode_te": OptionInfo("default", "Quantization type for Text Encoders", gr.Dropdown, {"choices": ['default'] + sdnq_quant_modes, "visible": native}), "sdnq_quantize_weights_group_size": OptionInfo(0, "Group size", gr.Slider, {"minimum": -1, "maximum": 4096, "step": 1, "visible": native}), - "sdnq_quantize_conv_layers": OptionInfo(False, "Quantize the convolutional layers", gr.Checkbox, {"visible": native}), + "sdnq_quantize_conv_layers": OptionInfo(False, "Quantize convolutional layers", gr.Checkbox, {"visible": native}), "sdnq_dequantize_compile": OptionInfo(devices.has_triton(), "Dequantize using torch.compile", gr.Checkbox, {"visible": native}), - "sdnq_use_quantized_matmul": OptionInfo(False, "Use Quantized MatMul", gr.Checkbox, {"visible": native}), - "sdnq_use_quantized_matmul_conv": OptionInfo(False, "Use Quantized MatMul with convolutional layers", gr.Checkbox, {"visible": native}), - "sdnq_quantize_with_gpu": OptionInfo(True, "Quantize with the GPU", gr.Checkbox, {"visible": native}), + "sdnq_use_quantized_matmul": OptionInfo(False, "Use quantized MatMul", gr.Checkbox, {"visible": native}), + "sdnq_use_quantized_matmul_conv": OptionInfo(False, "Use quantized MatMul with convolutional layers", gr.Checkbox, {"visible": native}), + "sdnq_quantize_with_gpu": OptionInfo(True, "Quantize using GPU", gr.Checkbox, {"visible": native}), "sdnq_dequantize_fp32": OptionInfo(False, "Dequantize using full precision", gr.Checkbox, {"visible": native}), "sdnq_quantize_shuffle_weights": OptionInfo(False, "Shuffle weights in post mode", gr.Checkbox, {"visible": native}), diff --git a/wiki b/wiki index c1ae36ab4..711b61ebf 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit c1ae36ab4c2197487306c5c9fe4e328a038d1367 +Subproject commit 711b61ebfdb8cb09b06b4f8c627ae97519c6f74c From ab8b9b0eab215f83746fd164a6925e8353033718 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 28 Jun 2025 13:53:37 +0300 Subject: [PATCH 70/85] Cleanup Omnigen 2 --- modules/omnigen2/import_utils.py | 46 ----- .../omnigen2/models/attention_processor.py | 7 +- modules/omnigen2/models/embeddings.py | 171 ++++++++---------- .../models/transformers/block_lumina2.py | 122 +------------ .../models/transformers/components.py | 4 - .../transformers/transformer_omnigen2.py | 12 +- modules/sd_models.py | 1 + modules/sd_models_utils.py | 2 + modules/sd_samplers_common.py | 2 +- wiki | 2 +- 10 files changed, 93 insertions(+), 276 deletions(-) delete mode 100644 modules/omnigen2/import_utils.py delete mode 100644 modules/omnigen2/models/transformers/components.py diff --git a/modules/omnigen2/import_utils.py b/modules/omnigen2/import_utils.py deleted file mode 100644 index 148b88684..000000000 --- a/modules/omnigen2/import_utils.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2024 The HuggingFace Team. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Import utilities: Utilities related to imports and our lazy inits. -""" - -import importlib.util -import sys - -# The package importlib_metadata is in a different place, depending on the python version. -if sys.version_info < (3, 8): - import importlib_metadata -else: - import importlib.metadata as importlib_metadata - -def _is_package_available(pkg_name: str): - pkg_exists = importlib.util.find_spec(pkg_name) is not None - pkg_version = "N/A" - - if pkg_exists: - try: - pkg_version = importlib_metadata.version(pkg_name) - except (ImportError, importlib_metadata.PackageNotFoundError): - pkg_exists = False - - return pkg_exists, pkg_version - -_triton_available, _triton_version = _is_package_available("triton") -_flash_attn_available, _flash_attn_version = _is_package_available("flash_attn") - -def is_triton_available(): - return _triton_available - -def is_flash_attn_available(): - return _flash_attn_available diff --git a/modules/omnigen2/models/attention_processor.py b/modules/omnigen2/models/attention_processor.py index 592d6b503..b9d9d4bd4 100644 --- a/modules/omnigen2/models/attention_processor.py +++ b/modules/omnigen2/models/attention_processor.py @@ -24,9 +24,8 @@ import torch import torch.nn.functional as F from einops import repeat -from ..import_utils import is_flash_attn_available - -if is_flash_attn_available(): +from transformers.utils.import_utils import is_flash_attn_2_available +if is_flash_attn_2_available(): from flash_attn import flash_attn_varlen_func from flash_attn.bert_padding import index_first_axis, pad_input, unpad_input else: @@ -53,7 +52,7 @@ class OmniGen2AttnProcessorFlash2Varlen: def __init__(self) -> None: """Initialize the attention processor.""" - if not is_flash_attn_available(): + if not is_flash_attn_2_available(): raise ImportError( "OmniGen2AttnProcessorFlash2Varlen requires flash_attn. " "Please install flash_attn." diff --git a/modules/omnigen2/models/embeddings.py b/modules/omnigen2/models/embeddings.py index 047526f69..574ff01bd 100644 --- a/modules/omnigen2/models/embeddings.py +++ b/modules/omnigen2/models/embeddings.py @@ -15,112 +15,85 @@ from typing import List, Optional, Tuple, Union import torch from torch import nn +from modules import devices -from diffusers.models.activations import get_activation +# Omnigen uses x.shape[-1] // 2 instead of -1 +# Functionally the same but -1 does fail with when the shape becomes 0 +if devices.backend != "ipex": + def apply_rotary_emb( + x: torch.Tensor, + freqs_cis: Union[torch.Tensor, Tuple[torch.Tensor]], + use_real: bool = True, + use_real_unbind_dim: int = -1, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Apply rotary embeddings to input tensors using the given frequency tensor. This function applies rotary embeddings + to the given query or key 'x' tensors using the provided frequency tensor 'freqs_cis'. The input tensors are + reshaped as complex numbers, and the frequency tensor is reshaped for broadcasting compatibility. The resulting + tensors contain rotary embeddings and are returned as real tensors. + Args: + x (`torch.Tensor`): + Query or key tensor to apply rotary embeddings. [B, H, S, D] xk (torch.Tensor): Key tensor to apply + freqs_cis (`Tuple[torch.Tensor]`): Precomputed frequency tensor for complex exponentials. ([S, D], [S, D],) -class TimestepEmbedding(nn.Module): - def __init__( - self, - in_channels: int, - time_embed_dim: int, - act_fn: str = "silu", - out_dim: int = None, - post_act_fn: Optional[str] = None, - cond_proj_dim=None, - sample_proj_bias=True, - ): - super().__init__() + Returns: + Tuple[torch.Tensor, torch.Tensor]: Tuple of modified query tensor and key tensor with rotary embeddings. + """ + if use_real: + cos, sin = freqs_cis # [S, D] + cos = cos[None, None] + sin = sin[None, None] + cos, sin = cos.to(x.device), sin.to(x.device) - self.linear_1 = nn.Linear(in_channels, time_embed_dim, sample_proj_bias) + if use_real_unbind_dim == -1: + # Used for flux, cogvideox, hunyuan-dit + x_real, x_imag = x.reshape(*x.shape[:-1], -1, 2).unbind(-1) # [B, S, H, D//2] + x_rotated = torch.stack([-x_imag, x_real], dim=-1).flatten(3) + elif use_real_unbind_dim == -2: + # Used for Stable Audio, OmniGen and CogView4 + x_real, x_imag = x.reshape(*x.shape[:-1], 2, -1).unbind(-2) # [B, S, H, D//2] + x_rotated = torch.cat([-x_imag, x_real], dim=-1) + else: + raise ValueError(f"`use_real_unbind_dim={use_real_unbind_dim}` but should be -1 or -2.") - if cond_proj_dim is not None: - self.cond_proj = nn.Linear(cond_proj_dim, in_channels, bias=False) + out = (x.float() * cos + x_rotated.float() * sin).to(x.dtype) + + return out else: - self.cond_proj = None + # used for lumina + # x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2)) + x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], x.shape[-1] // 2, 2)) + freqs_cis = freqs_cis.unsqueeze(2) + x_out = torch.view_as_real(x_rotated * freqs_cis).flatten(3) - self.act = get_activation(act_fn) + return x_out.type_as(x) +else: + def apply_rotary_emb(x, freqs_cis, use_real: bool = True, use_real_unbind_dim: int = -1): + if use_real: + cos, sin = freqs_cis # [S, D] + cos = cos[None, None] + sin = sin[None, None] + cos, sin = cos.to(x.device), sin.to(x.device) - if out_dim is not None: - time_embed_dim_out = out_dim + if use_real_unbind_dim == -1: + # Used for flux, cogvideox, hunyuan-dit + x_real, x_imag = x.reshape(*x.shape[:-1], -1, 2).unbind(-1) # [B, S, H, D//2] + x_rotated = torch.stack([-x_imag, x_real], dim=-1).flatten(3) + elif use_real_unbind_dim == -2: + # Used for Stable Audio, OmniGen, CogView4 and Cosmos + x_real, x_imag = x.reshape(*x.shape[:-1], 2, -1).unbind(-2) # [B, S, H, D//2] + x_rotated = torch.cat([-x_imag, x_real], dim=-1) + else: + raise ValueError(f"`use_real_unbind_dim={use_real_unbind_dim}` but should be -1 or -2.") + + out = (x.to(dtype=torch.float32) * cos + x_rotated.to(dtype=torch.float32) * sin).to(x.dtype) + return out else: - time_embed_dim_out = time_embed_dim - self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim_out, sample_proj_bias) - - if post_act_fn is None: - self.post_act = None - else: - self.post_act = get_activation(post_act_fn) - - self.initialize_weights() - - def initialize_weights(self): - nn.init.normal_(self.linear_1.weight, std=0.02) - nn.init.zeros_(self.linear_1.bias) - nn.init.normal_(self.linear_2.weight, std=0.02) - nn.init.zeros_(self.linear_2.bias) - - def forward(self, sample, condition=None): - if condition is not None: - sample = sample + self.cond_proj(condition) - sample = self.linear_1(sample) - - if self.act is not None: - sample = self.act(sample) - - sample = self.linear_2(sample) - - if self.post_act is not None: - sample = self.post_act(sample) - return sample - - -def apply_rotary_emb( - x: torch.Tensor, - freqs_cis: Union[torch.Tensor, Tuple[torch.Tensor]], - use_real: bool = True, - use_real_unbind_dim: int = -1, -) -> Tuple[torch.Tensor, torch.Tensor]: - """ - Apply rotary embeddings to input tensors using the given frequency tensor. This function applies rotary embeddings - to the given query or key 'x' tensors using the provided frequency tensor 'freqs_cis'. The input tensors are - reshaped as complex numbers, and the frequency tensor is reshaped for broadcasting compatibility. The resulting - tensors contain rotary embeddings and are returned as real tensors. - - Args: - x (`torch.Tensor`): - Query or key tensor to apply rotary embeddings. [B, H, S, D] xk (torch.Tensor): Key tensor to apply - freqs_cis (`Tuple[torch.Tensor]`): Precomputed frequency tensor for complex exponentials. ([S, D], [S, D],) - - Returns: - Tuple[torch.Tensor, torch.Tensor]: Tuple of modified query tensor and key tensor with rotary embeddings. - """ - if use_real: - cos, sin = freqs_cis # [S, D] - cos = cos[None, None] - sin = sin[None, None] - cos, sin = cos.to(x.device), sin.to(x.device) - - if use_real_unbind_dim == -1: - # Used for flux, cogvideox, hunyuan-dit - x_real, x_imag = x.reshape(*x.shape[:-1], -1, 2).unbind(-1) # [B, S, H, D//2] - x_rotated = torch.stack([-x_imag, x_real], dim=-1).flatten(3) - elif use_real_unbind_dim == -2: - # Used for Stable Audio, OmniGen and CogView4 - x_real, x_imag = x.reshape(*x.shape[:-1], 2, -1).unbind(-2) # [B, S, H, D//2] - x_rotated = torch.cat([-x_imag, x_real], dim=-1) - else: - raise ValueError(f"`use_real_unbind_dim={use_real_unbind_dim}` but should be -1 or -2.") - - out = (x.float() * cos + x_rotated.float() * sin).to(x.dtype) - - return out - else: - # used for lumina - # x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2)) - x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], x.shape[-1] // 2, 2)) - freqs_cis = freqs_cis.unsqueeze(2) - x_out = torch.view_as_real(x_rotated * freqs_cis).flatten(3) - - return x_out.type_as(x) + # used for lumina + # force cpu with Alchemist + x_rotated = torch.view_as_complex(x.to("cpu").to(dtype=torch.float32).reshape(*x.shape[:-1], x.shape[-1] // 2, 2)) + freqs_cis = freqs_cis.to("cpu").unsqueeze(2) + x_out = torch.view_as_real(x_rotated * freqs_cis).flatten(3) + return x_out.type_as(x).to(x.device) diff --git a/modules/omnigen2/models/transformers/block_lumina2.py b/modules/omnigen2/models/transformers/block_lumina2.py index e6d36e0f1..1bd7a56ff 100644 --- a/modules/omnigen2/models/transformers/block_lumina2.py +++ b/modules/omnigen2/models/transformers/block_lumina2.py @@ -13,120 +13,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -import warnings from typing import Optional, Tuple import torch import torch.nn as nn +import torch.nn.functional as F -from diffusers.models.embeddings import Timesteps -from ..embeddings import TimestepEmbedding -from ...import_utils import is_flash_attn_available, is_triton_available - -if is_triton_available(): - from ...triton_layer_norm import RMSNorm -else: - from torch.nn import RMSNorm - warnings.warn("Cannot import triton, install triton to use fused RMSNorm for better performance") - -if is_flash_attn_available(): - from flash_attn.ops.activations import swiglu -else: - from .components import swiglu - warnings.warn("Cannot import flash_attn, install flash_attn to use fused SwiGLU for better performance") - -# try: -# from flash_attn.ops.activations import swiglu as fused_swiglu -# FUSEDSWIGLU_AVALIBLE = True -# except ImportError: - -# FUSEDSWIGLU_AVALIBLE = False -# warnings.warn("Cannot import apex RMSNorm, switch to vanilla implementation") - -class LuminaRMSNormZero(nn.Module): - """ - Norm layer adaptive RMS normalization zero. - - Parameters: - embedding_dim (`int`): The size of each embedding vector. - """ - - def __init__( - self, - embedding_dim: int, - norm_eps: float, - norm_elementwise_affine: bool, - ): - super().__init__() - self.silu = nn.SiLU() - self.linear = nn.Linear( - min(embedding_dim, 1024), - 4 * embedding_dim, - bias=True, - ) - - self.norm = RMSNorm(embedding_dim, eps=norm_eps) - - def forward( - self, - x: torch.Tensor, - emb: Optional[torch.Tensor] = None, - ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: - emb = self.linear(self.silu(emb)) - scale_msa, gate_msa, scale_mlp, gate_mlp = emb.chunk(4, dim=1) - x = self.norm(x) * (1 + scale_msa[:, None]) - return x, gate_msa, scale_mlp, gate_mlp - - -class LuminaLayerNormContinuous(nn.Module): - def __init__( - self, - embedding_dim: int, - conditioning_embedding_dim: int, - # NOTE: It is a bit weird that the norm layer can be configured to have scale and shift parameters - # because the output is immediately scaled and shifted by the projected conditioning embeddings. - # Note that AdaLayerNorm does not let the norm layer have scale and shift parameters. - # However, this is how it was implemented in the original code, and it's rather likely you should - # set `elementwise_affine` to False. - elementwise_affine=True, - eps=1e-5, - bias=True, - norm_type="layer_norm", - out_dim: Optional[int] = None, - ): - super().__init__() - - # AdaLN - self.silu = nn.SiLU() - self.linear_1 = nn.Linear(conditioning_embedding_dim, embedding_dim, bias=bias) - - if norm_type == "layer_norm": - self.norm = nn.LayerNorm(embedding_dim, eps, elementwise_affine, bias) - elif norm_type == "rms_norm": - self.norm = RMSNorm(embedding_dim, eps=eps, elementwise_affine=elementwise_affine) - else: - raise ValueError(f"unknown norm_type {norm_type}") - - self.linear_2 = None - if out_dim is not None: - self.linear_2 = nn.Linear(embedding_dim, out_dim, bias=bias) - - def forward( - self, - x: torch.Tensor, - conditioning_embedding: torch.Tensor, - ) -> torch.Tensor: - # convert back to the original dtype in case `conditioning_embedding`` is upcasted to float32 (needed for hunyuanDiT) - emb = self.linear_1(self.silu(conditioning_embedding).to(x.dtype)) - scale = emb - x = self.norm(x) * (1 + scale)[:, None, :] - - if self.linear_2 is not None: - x = self.linear_2(x) - - return x - +from torch.nn import RMSNorm +from diffusers.models.embeddings import Timesteps, TimestepEmbedding +from diffusers.models.activations import FP32SiLU +# Omnigen 2 uses swiglu key instead of silu +# But the functionality is exactly the same class LuminaFeedForward(nn.Module): r""" A feed-forward layer. @@ -150,8 +48,6 @@ class LuminaFeedForward(nn.Module): ffn_dim_multiplier: Optional[float] = None, ): super().__init__() - self.swiglu = swiglu - # custom hidden_size factor multiplier if ffn_dim_multiplier is not None: inner_dim = int(ffn_dim_multiplier * inner_dim) @@ -172,12 +68,14 @@ class LuminaFeedForward(nn.Module): inner_dim, bias=False, ) + self.swiglu = FP32SiLU() def forward(self, x): - h1, h2 = self.linear_1(x), self.linear_3(x) - return self.linear_2(self.swiglu(h1, h2)) + return self.linear_2(self.swiglu(self.linear_1(x)) * self.linear_3(x)) +# Makes timestep_scale configurable +# Omnigen 2 uses timestep_scale=1000 class Lumina2CombinedTimestepCaptionEmbedding(nn.Module): def __init__( self, diff --git a/modules/omnigen2/models/transformers/components.py b/modules/omnigen2/models/transformers/components.py deleted file mode 100644 index 05dd6f5f3..000000000 --- a/modules/omnigen2/models/transformers/components.py +++ /dev/null @@ -1,4 +0,0 @@ -import torch.nn.functional as F - -def swiglu(x, y): - return F.silu(x.float(), inplace=False).to(x.dtype) * y diff --git a/modules/omnigen2/models/transformers/transformer_omnigen2.py b/modules/omnigen2/models/transformers/transformer_omnigen2.py index fe324b0d2..a1644339f 100644 --- a/modules/omnigen2/models/transformers/transformer_omnigen2.py +++ b/modules/omnigen2/models/transformers/transformer_omnigen2.py @@ -1,10 +1,10 @@ -import warnings import itertools from typing import Any, Dict, List, Optional, Tuple, Union import torch import torch.nn as nn +from torch.nn import RMSNorm from einops import rearrange from diffusers.configuration_utils import ConfigMixin, register_to_config @@ -14,17 +14,11 @@ from diffusers.utils import USE_PEFT_BACKEND, logging, scale_lora_layers, unscal from diffusers.models.attention_processor import Attention from diffusers.models.modeling_outputs import Transformer2DModelOutput from diffusers.models.modeling_utils import ModelMixin +from diffusers.models.normalization import LuminaLayerNormContinuous, LuminaRMSNormZero +from .block_lumina2 import LuminaFeedForward, Lumina2CombinedTimestepCaptionEmbedding from ..attention_processor import OmniGen2AttnProcessorFlash2Varlen, OmniGen2AttnProcessor from .repo import OmniGen2RotaryPosEmbed -from .block_lumina2 import LuminaLayerNormContinuous, LuminaRMSNormZero, LuminaFeedForward, Lumina2CombinedTimestepCaptionEmbedding - -from ...import_utils import is_triton_available, is_flash_attn_available - -if is_triton_available(): - from ...triton_layer_norm import RMSNorm -else: - from torch.nn import RMSNorm logger = logging.get_logger(__name__) diff --git a/modules/sd_models.py b/modules/sd_models.py index 3a74eac56..363234d90 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -51,6 +51,7 @@ i2i_pipes = [ 'LEditsPPPipelineStableDiffusion', 'LEditsPPPipelineStableDiffusionXL', 'OmniGenPipeline', + 'OmniGen2Pipeline', 'StableDiffusionAdapterPipeline', 'StableDiffusionXLAdapterPipeline', 'StableDiffusionControlNetXSPipeline', 'StableDiffusionXLControlNetXSPipeline', ] diff --git a/modules/sd_models_utils.py b/modules/sd_models_utils.py index 70b0a2173..9ef0024b7 100644 --- a/modules/sd_models_utils.py +++ b/modules/sd_models_utils.py @@ -179,6 +179,8 @@ def apply_function_to_model(sd_model, function, options, op=None): sd_model.text_encoder_3 = function(sd_model.text_encoder_3, op="text_encoder_3", sd_model=sd_model) if hasattr(sd_model, 'text_encoder_4') and hasattr(sd_model.text_encoder_4, 'config'): sd_model.text_encoder_4 = function(sd_model.text_encoder_4, op="text_encoder_4", sd_model=sd_model) + if hasattr(sd_model, 'mllm') and hasattr(sd_model.mllm, 'config'): + sd_model.mllm = function(sd_model.mllm, op="text_encoder_mllm", sd_model=sd_model) if hasattr(sd_model, 'prior_pipe') and hasattr(sd_model.prior_pipe, 'text_encoder') and hasattr(sd_model.prior_pipe.text_encoder, 'config'): sd_model.prior_pipe.text_encoder = function(sd_model.prior_pipe.text_encoder, op="prior_pipe.text_encoder", sd_model=sd_model) if "VAE" in options: diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 15e2cf1ad..09a06352a 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -9,7 +9,7 @@ from modules import shared, devices, processing, images, sd_vae_approx, sd_vae_t SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options']) approximation_indexes = { "Simple": 0, "Approximate": 1, "TAESD": 2, "Full VAE": 3 } -flow_models = ['f1', 'sd3', 'lumina', 'auraflow', 'sana', 'lumina2', 'cogview4', 'h1', 'cosmos', 'chroma', 'omnigen2'] +flow_models = ['f1', 'sd3', 'lumina', 'auraflow', 'sana', 'lumina2', 'cogview4', 'h1', 'cosmos', 'chroma', 'omnigen', 'omnigen2'] warned = False queue_lock = threading.Lock() diff --git a/wiki b/wiki index 711b61ebf..b95139063 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 711b61ebfdb8cb09b06b4f8c627ae97519c6f74c +Subproject commit b95139063c469ab11a379804ddb606ccfaf6c5c6 From cb2fa7e1b4799f7ee31431fd8447919bf808e61d Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 28 Jun 2025 14:02:15 +0300 Subject: [PATCH 71/85] Omnigen2 use SDPA --- .../omnigen2/models/attention_processor.py | 217 +-- .../transformers/transformer_omnigen2.py | 8 +- modules/omnigen2/triton_layer_norm.py | 1257 ----------------- 3 files changed, 3 insertions(+), 1479 deletions(-) delete mode 100644 modules/omnigen2/triton_layer_norm.py diff --git a/modules/omnigen2/models/attention_processor.py b/modules/omnigen2/models/attention_processor.py index b9d9d4bd4..ffeb8e141 100644 --- a/modules/omnigen2/models/attention_processor.py +++ b/modules/omnigen2/models/attention_processor.py @@ -24,225 +24,10 @@ import torch import torch.nn.functional as F from einops import repeat -from transformers.utils.import_utils import is_flash_attn_2_available -if is_flash_attn_2_available(): - from flash_attn import flash_attn_varlen_func - from flash_attn.bert_padding import index_first_axis, pad_input, unpad_input -else: - warnings.warn("Cannot import flash_attn, install flash_attn to use Flash2Varlen attention for better performance") - - from diffusers.models.attention_processor import Attention from .embeddings import apply_rotary_emb -class OmniGen2AttnProcessorFlash2Varlen: - """ - Processor for implementing scaled dot-product attention with flash attention and variable length sequences. - - This processor implements: - - Flash attention with variable length sequences - - Rotary position embeddings (RoPE) - - Query-Key normalization - - Proportional attention scaling - - Args: - None - """ - - def __init__(self) -> None: - """Initialize the attention processor.""" - if not is_flash_attn_2_available(): - raise ImportError( - "OmniGen2AttnProcessorFlash2Varlen requires flash_attn. " - "Please install flash_attn." - ) - - def _upad_input( - self, - query_layer: torch.Tensor, - key_layer: torch.Tensor, - value_layer: torch.Tensor, - attention_mask: torch.Tensor, - query_length: int, - num_heads: int, - ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, Tuple[torch.Tensor, torch.Tensor], Tuple[int, int]]: - """ - Unpad the input tensors for flash attention. - - Args: - query_layer: Query tensor of shape (batch_size, seq_len, num_heads, head_dim) - key_layer: Key tensor of shape (batch_size, seq_len, num_kv_heads, head_dim) - value_layer: Value tensor of shape (batch_size, seq_len, num_kv_heads, head_dim) - attention_mask: Attention mask tensor of shape (batch_size, seq_len) - query_length: Length of the query sequence - num_heads: Number of attention heads - - Returns: - Tuple containing: - - Unpadded query tensor - - Unpadded key tensor - - Unpadded value tensor - - Query indices - - Tuple of cumulative sequence lengths for query and key - - Tuple of maximum sequence lengths for query and key - """ - def _get_unpad_data(attention_mask: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, int]: - """Helper function to get unpadding data from attention mask.""" - seqlens_in_batch = attention_mask.sum(dim=-1, dtype=torch.int32) - indices = torch.nonzero(attention_mask.flatten(), as_tuple=False).flatten() - max_seqlen_in_batch = seqlens_in_batch.max().item() - cu_seqlens = F.pad(torch.cumsum(seqlens_in_batch, dim=0, dtype=torch.int32), (1, 0)) - return indices, cu_seqlens, max_seqlen_in_batch - - indices_k, cu_seqlens_k, max_seqlen_in_batch_k = _get_unpad_data(attention_mask) - batch_size, kv_seq_len, num_key_value_heads, head_dim = key_layer.shape - - # Unpad key and value layers - key_layer = index_first_axis( - key_layer.reshape(batch_size * kv_seq_len, num_key_value_heads, head_dim), - indices_k, - ) - value_layer = index_first_axis( - value_layer.reshape(batch_size * kv_seq_len, num_key_value_heads, head_dim), - indices_k, - ) - - # Handle different query length cases - if query_length == kv_seq_len: - query_layer = index_first_axis( - query_layer.reshape(batch_size * kv_seq_len, num_heads, head_dim), - indices_k, - ) - cu_seqlens_q = cu_seqlens_k - max_seqlen_in_batch_q = max_seqlen_in_batch_k - indices_q = indices_k - elif query_length == 1: - max_seqlen_in_batch_q = 1 - cu_seqlens_q = torch.arange( - batch_size + 1, dtype=torch.int32, device=query_layer.device - ) - indices_q = cu_seqlens_q[:-1] - query_layer = query_layer.squeeze(1) - else: - attention_mask = attention_mask[:, -query_length:] - query_layer, indices_q, cu_seqlens_q, max_seqlen_in_batch_q = unpad_input(query_layer, attention_mask) - - return ( - query_layer, - key_layer, - value_layer, - indices_q, - (cu_seqlens_q, cu_seqlens_k), - (max_seqlen_in_batch_q, max_seqlen_in_batch_k), - ) - - def __call__( - self, - attn: Attention, - hidden_states: torch.Tensor, - encoder_hidden_states: torch.Tensor, - attention_mask: Optional[torch.Tensor] = None, - image_rotary_emb: Optional[torch.Tensor] = None, - base_sequence_length: Optional[int] = None, - ) -> torch.Tensor: - """ - Process attention computation with flash attention. - - Args: - attn: Attention module - hidden_states: Hidden states tensor of shape (batch_size, seq_len, hidden_dim) - encoder_hidden_states: Encoder hidden states tensor - attention_mask: Optional attention mask tensor - image_rotary_emb: Optional rotary embeddings for image tokens - base_sequence_length: Optional base sequence length for proportional attention - - Returns: - torch.Tensor: Processed hidden states after attention computation - """ - batch_size, sequence_length, _ = hidden_states.shape - - # Get Query-Key-Value Pair - query = attn.to_q(hidden_states) - key = attn.to_k(encoder_hidden_states) - value = attn.to_v(encoder_hidden_states) - - query_dim = query.shape[-1] - inner_dim = key.shape[-1] - head_dim = query_dim // attn.heads - dtype = query.dtype - - # Get key-value heads - kv_heads = inner_dim // head_dim - - # Reshape tensors for attention computation - query = query.view(batch_size, -1, attn.heads, head_dim) - key = key.view(batch_size, -1, kv_heads, head_dim) - value = value.view(batch_size, -1, kv_heads, head_dim) - - # Apply Query-Key normalization - if attn.norm_q is not None: - query = attn.norm_q(query) - if attn.norm_k is not None: - key = attn.norm_k(key) - - # Apply Rotary Position Embeddings - if image_rotary_emb is not None: - query = apply_rotary_emb(query, image_rotary_emb, use_real=False) - key = apply_rotary_emb(key, image_rotary_emb, use_real=False) - - query, key = query.to(dtype), key.to(dtype) - - # Calculate attention scale - if base_sequence_length is not None: - softmax_scale = math.sqrt(math.log(sequence_length, base_sequence_length)) * attn.scale - else: - softmax_scale = attn.scale - - # Unpad input for flash attention - ( - query_states, - key_states, - value_states, - indices_q, - cu_seq_lens, - max_seq_lens, - ) = self._upad_input(query, key, value, attention_mask, sequence_length, attn.heads) - - cu_seqlens_q, cu_seqlens_k = cu_seq_lens - max_seqlen_in_batch_q, max_seqlen_in_batch_k = max_seq_lens - - # Handle different number of heads - if kv_heads < attn.heads: - key_states = repeat(key_states, "l h c -> l (h k) c", k=attn.heads // kv_heads) - value_states = repeat(value_states, "l h c -> l (h k) c", k=attn.heads // kv_heads) - - # Apply flash attention - attn_output_unpad = flash_attn_varlen_func( - query_states, - key_states, - value_states, - cu_seqlens_q=cu_seqlens_q, - cu_seqlens_k=cu_seqlens_k, - max_seqlen_q=max_seqlen_in_batch_q, - max_seqlen_k=max_seqlen_in_batch_k, - dropout_p=0.0, - causal=False, - softmax_scale=softmax_scale, - ) - - # Pad output and apply final transformations - hidden_states = pad_input(attn_output_unpad, indices_q, batch_size, sequence_length) - hidden_states = hidden_states.flatten(-2) - hidden_states = hidden_states.type_as(query) - - # Apply output projection - hidden_states = attn.to_out[0](hidden_states) - hidden_states = attn.to_out[1](hidden_states) - - return hidden_states - - class OmniGen2AttnProcessor: """ Processor for implementing scaled dot-product attention with flash attention and variable length sequences. @@ -264,7 +49,7 @@ class OmniGen2AttnProcessor: """Initialize the attention processor.""" if not hasattr(F, "scaled_dot_product_attention"): raise ImportError( - "OmniGen2AttnProcessorFlash2Varlen requires PyTorch 2.0. " + "OmniGen2AttnProcessor requires PyTorch 2.0. " "Please upgrade PyTorch to version 2.0 or later." ) diff --git a/modules/omnigen2/models/transformers/transformer_omnigen2.py b/modules/omnigen2/models/transformers/transformer_omnigen2.py index a1644339f..5a2ccf03f 100644 --- a/modules/omnigen2/models/transformers/transformer_omnigen2.py +++ b/modules/omnigen2/models/transformers/transformer_omnigen2.py @@ -17,7 +17,7 @@ from diffusers.models.modeling_utils import ModelMixin from diffusers.models.normalization import LuminaLayerNormContinuous, LuminaRMSNormZero from .block_lumina2 import LuminaFeedForward, Lumina2CombinedTimestepCaptionEmbedding -from ..attention_processor import OmniGen2AttnProcessorFlash2Varlen, OmniGen2AttnProcessor +from ..attention_processor import OmniGen2AttnProcessor from .repo import OmniGen2RotaryPosEmbed logger = logging.get_logger(__name__) @@ -60,11 +60,7 @@ class OmniGen2TransformerBlock(nn.Module): self.head_dim = dim // num_attention_heads self.modulation = modulation - try: - processor = OmniGen2AttnProcessorFlash2Varlen() - except ImportError: - processor = OmniGen2AttnProcessor() - + processor = OmniGen2AttnProcessor() # Initialize attention layer self.attn = Attention( query_dim=dim, diff --git a/modules/omnigen2/triton_layer_norm.py b/modules/omnigen2/triton_layer_norm.py deleted file mode 100644 index 51a70b990..000000000 --- a/modules/omnigen2/triton_layer_norm.py +++ /dev/null @@ -1,1257 +0,0 @@ -# Copyright (c) 2024, Tri Dao. -# Implement dropout + residual + layer_norm / rms_norm. - -# Based on the Triton LayerNorm tutorial: https://triton-lang.org/main/getting-started/tutorials/05-layer-norm.html -# For the backward pass, we keep weight_grad and bias_grad in registers and accumulate. -# This is faster for dimensions up to 8k, but after that it's much slower due to register spilling. -# The models we train have hidden dim up to 8k anyway (e.g. Llama 70B), so this is fine. - -import math - -import torch -import torch.nn.functional as F - -import triton -import triton.language as tl - - -from typing import Callable - - -def custom_amp_decorator(dec: Callable, cuda_amp_deprecated: bool): - def decorator(*args, **kwargs): - if cuda_amp_deprecated: - kwargs["device_type"] = "cuda" - return dec(*args, **kwargs) - return decorator - - -if hasattr(torch.amp, "custom_fwd"): # type: ignore[attr-defined] - deprecated = True - from torch.amp import custom_fwd, custom_bwd # type: ignore[attr-defined] -else: - deprecated = False - from torch.cuda.amp import custom_fwd, custom_bwd - -custom_fwd = custom_amp_decorator(custom_fwd, deprecated) -custom_bwd = custom_amp_decorator(custom_bwd, deprecated) - - -def triton_autotune_configs(): - # Return configs with a valid warp count for the current device - configs=[] - # Maximum threads per block is architecture-dependent in theory, but in reality all are 1024 - max_threads_per_block=1024 - # Default to warp size 32 if not defined by device - warp_size=getattr(torch.cuda.get_device_properties(torch.cuda.current_device()), "warp_size", 32) - # Autotune for warp counts which are powers of 2 and do not exceed thread per block limit - warp_count=1 - while warp_count*warp_size <= max_threads_per_block: - configs.append(triton.Config({}, num_warps=warp_count)) - warp_count*=2 - return configs - -def layer_norm_ref( - x, - weight, - bias, - residual=None, - x1=None, - weight1=None, - bias1=None, - eps=1e-6, - dropout_p=0.0, - rowscale=None, - prenorm=False, - zero_centered_weight=False, - dropout_mask=None, - dropout_mask1=None, - upcast=False, -): - dtype = x.dtype - if upcast: - x = x.float() - weight = weight.float() - bias = bias.float() if bias is not None else None - residual = residual.float() if residual is not None else residual - x1 = x1.float() if x1 is not None else None - weight1 = weight1.float() if weight1 is not None else None - bias1 = bias1.float() if bias1 is not None else None - if zero_centered_weight: - weight = weight + 1.0 - if weight1 is not None: - weight1 = weight1 + 1.0 - if x1 is not None: - assert rowscale is None, "rowscale is not supported with parallel LayerNorm" - if rowscale is not None: - x = x * rowscale[..., None] - if dropout_p > 0.0: - if dropout_mask is not None: - x = x.masked_fill(~dropout_mask, 0.0) / (1.0 - dropout_p) - else: - x = F.dropout(x, p=dropout_p) - if x1 is not None: - if dropout_mask1 is not None: - x1 = x1.masked_fill(~dropout_mask1, 0.0) / (1.0 - dropout_p) - else: - x1 = F.dropout(x1, p=dropout_p) - if x1 is not None: - x = x + x1 - if residual is not None: - x = (x + residual).to(x.dtype) - out = F.layer_norm(x.to(weight.dtype), x.shape[-1:], weight=weight, bias=bias, eps=eps).to( - dtype - ) - if weight1 is None: - return out if not prenorm else (out, x) - else: - out1 = F.layer_norm( - x.to(weight1.dtype), x.shape[-1:], weight=weight1, bias=bias1, eps=eps - ).to(dtype) - return (out, out1) if not prenorm else (out, out1, x) - - -def rms_norm_ref( - x, - weight, - bias, - residual=None, - x1=None, - weight1=None, - bias1=None, - eps=1e-6, - dropout_p=0.0, - rowscale=None, - prenorm=False, - zero_centered_weight=False, - dropout_mask=None, - dropout_mask1=None, - upcast=False, -): - dtype = x.dtype - if upcast: - x = x.float() - weight = weight.float() - bias = bias.float() if bias is not None else None - residual = residual.float() if residual is not None else residual - x1 = x1.float() if x1 is not None else None - weight1 = weight1.float() if weight1 is not None else None - bias1 = bias1.float() if bias1 is not None else None - if zero_centered_weight: - weight = weight + 1.0 - if weight1 is not None: - weight1 = weight1 + 1.0 - if x1 is not None: - assert rowscale is None, "rowscale is not supported with parallel LayerNorm" - if rowscale is not None: - x = x * rowscale[..., None] - if dropout_p > 0.0: - if dropout_mask is not None: - x = x.masked_fill(~dropout_mask, 0.0) / (1.0 - dropout_p) - else: - x = F.dropout(x, p=dropout_p) - if x1 is not None: - if dropout_mask1 is not None: - x1 = x1.masked_fill(~dropout_mask1, 0.0) / (1.0 - dropout_p) - else: - x1 = F.dropout(x1, p=dropout_p) - if x1 is not None: - x = x + x1 - if residual is not None: - x = (x + residual).to(x.dtype) - rstd = 1 / torch.sqrt((x.square()).mean(dim=-1, keepdim=True) + eps) - out = ((x * rstd * weight) + bias if bias is not None else (x * rstd * weight)).to(dtype) - if weight1 is None: - return out if not prenorm else (out, x) - else: - out1 = ((x * rstd * weight1) + bias1 if bias1 is not None else (x * rstd * weight1)).to( - dtype - ) - return (out, out1) if not prenorm else (out, out1, x) - - -@triton.autotune( - configs=triton_autotune_configs(), - key=["N", "HAS_RESIDUAL", "STORE_RESIDUAL_OUT", "IS_RMS_NORM", "HAS_BIAS"], -) -# @triton.heuristics({"HAS_BIAS": lambda args: args["B"] is not None}) -# @triton.heuristics({"HAS_RESIDUAL": lambda args: args["RESIDUAL"] is not None}) -@triton.heuristics({"HAS_X1": lambda args: args["X1"] is not None}) -@triton.heuristics({"HAS_W1": lambda args: args["W1"] is not None}) -@triton.heuristics({"HAS_B1": lambda args: args["B1"] is not None}) -@triton.jit -def _layer_norm_fwd_1pass_kernel( - X, # pointer to the input - Y, # pointer to the output - W, # pointer to the weights - B, # pointer to the biases - RESIDUAL, # pointer to the residual - X1, - W1, - B1, - Y1, - RESIDUAL_OUT, # pointer to the residual - ROWSCALE, - SEEDS, # Dropout seeds for each row - DROPOUT_MASK, - Mean, # pointer to the mean - Rstd, # pointer to the 1/std - stride_x_row, # how much to increase the pointer when moving by 1 row - stride_y_row, - stride_res_row, - stride_res_out_row, - stride_x1_row, - stride_y1_row, - M, # number of rows in X - N, # number of columns in X - eps, # epsilon to avoid division by zero - dropout_p, # Dropout probability - zero_centered_weight, # If true, add 1.0 to the weight - IS_RMS_NORM: tl.constexpr, - BLOCK_N: tl.constexpr, - HAS_RESIDUAL: tl.constexpr, - STORE_RESIDUAL_OUT: tl.constexpr, - HAS_BIAS: tl.constexpr, - HAS_DROPOUT: tl.constexpr, - STORE_DROPOUT_MASK: tl.constexpr, - HAS_ROWSCALE: tl.constexpr, - HAS_X1: tl.constexpr, - HAS_W1: tl.constexpr, - HAS_B1: tl.constexpr, -): - # Map the program id to the row of X and Y it should compute. - row = tl.program_id(0) - X += row * stride_x_row - Y += row * stride_y_row - if HAS_RESIDUAL: - RESIDUAL += row * stride_res_row - if STORE_RESIDUAL_OUT: - RESIDUAL_OUT += row * stride_res_out_row - if HAS_X1: - X1 += row * stride_x1_row - if HAS_W1: - Y1 += row * stride_y1_row - # Compute mean and variance - cols = tl.arange(0, BLOCK_N) - x = tl.load(X + cols, mask=cols < N, other=0.0).to(tl.float32) - if HAS_ROWSCALE: - rowscale = tl.load(ROWSCALE + row).to(tl.float32) - x *= rowscale - if HAS_DROPOUT: - # Compute dropout mask - # 7 rounds is good enough, and reduces register pressure - keep_mask = tl.rand(tl.load(SEEDS + row).to(tl.uint32), cols, n_rounds=7) > dropout_p - x = tl.where(keep_mask, x / (1.0 - dropout_p), 0.0) - if STORE_DROPOUT_MASK: - tl.store(DROPOUT_MASK + row * N + cols, keep_mask, mask=cols < N) - if HAS_X1: - x1 = tl.load(X1 + cols, mask=cols < N, other=0.0).to(tl.float32) - if HAS_ROWSCALE: - rowscale = tl.load(ROWSCALE + M + row).to(tl.float32) - x1 *= rowscale - if HAS_DROPOUT: - # Compute dropout mask - # 7 rounds is good enough, and reduces register pressure - keep_mask = ( - tl.rand(tl.load(SEEDS + M + row).to(tl.uint32), cols, n_rounds=7) > dropout_p - ) - x1 = tl.where(keep_mask, x1 / (1.0 - dropout_p), 0.0) - if STORE_DROPOUT_MASK: - tl.store(DROPOUT_MASK + (M + row) * N + cols, keep_mask, mask=cols < N) - x += x1 - if HAS_RESIDUAL: - residual = tl.load(RESIDUAL + cols, mask=cols < N, other=0.0).to(tl.float32) - x += residual - if STORE_RESIDUAL_OUT: - tl.store(RESIDUAL_OUT + cols, x, mask=cols < N) - if not IS_RMS_NORM: - mean = tl.sum(x, axis=0) / N - tl.store(Mean + row, mean) - xbar = tl.where(cols < N, x - mean, 0.0) - var = tl.sum(xbar * xbar, axis=0) / N - else: - xbar = tl.where(cols < N, x, 0.0) - var = tl.sum(xbar * xbar, axis=0) / N - rstd = 1 / tl.sqrt(var + eps) - tl.store(Rstd + row, rstd) - # Normalize and apply linear transformation - mask = cols < N - w = tl.load(W + cols, mask=mask).to(tl.float32) - if zero_centered_weight: - w += 1.0 - if HAS_BIAS: - b = tl.load(B + cols, mask=mask).to(tl.float32) - x_hat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd - y = x_hat * w + b if HAS_BIAS else x_hat * w - # Write output - tl.store(Y + cols, y, mask=mask) - if HAS_W1: - w1 = tl.load(W1 + cols, mask=mask).to(tl.float32) - if zero_centered_weight: - w1 += 1.0 - if HAS_B1: - b1 = tl.load(B1 + cols, mask=mask).to(tl.float32) - y1 = x_hat * w1 + b1 if HAS_B1 else x_hat * w1 - tl.store(Y1 + cols, y1, mask=mask) - - -def _layer_norm_fwd( - x, - weight, - bias, - eps, - residual=None, - x1=None, - weight1=None, - bias1=None, - dropout_p=0.0, - rowscale=None, - out_dtype=None, - residual_dtype=None, - zero_centered_weight=False, - is_rms_norm=False, - return_dropout_mask=False, - out=None, - residual_out=None -): - if residual is not None: - residual_dtype = residual.dtype - M, N = x.shape - assert x.stride(-1) == 1 - if residual is not None: - assert residual.stride(-1) == 1 - assert residual.shape == (M, N) - assert weight.shape == (N,) - assert weight.stride(-1) == 1 - if bias is not None: - assert bias.stride(-1) == 1 - assert bias.shape == (N,) - if x1 is not None: - assert x1.shape == x.shape - assert rowscale is None - assert x1.stride(-1) == 1 - if weight1 is not None: - assert weight1.shape == (N,) - assert weight1.stride(-1) == 1 - if bias1 is not None: - assert bias1.shape == (N,) - assert bias1.stride(-1) == 1 - if rowscale is not None: - assert rowscale.is_contiguous() - assert rowscale.shape == (M,) - # allocate output - if out is None: - out = torch.empty_like(x, dtype=x.dtype if out_dtype is None else out_dtype) - else: - assert out.shape == x.shape - assert out.stride(-1) == 1 - if weight1 is not None: - y1 = torch.empty_like(out) - assert y1.stride(-1) == 1 - else: - y1 = None - if ( - residual is not None - or (residual_dtype is not None and residual_dtype != x.dtype) - or dropout_p > 0.0 - or rowscale is not None - or x1 is not None - ): - if residual_out is None: - residual_out = torch.empty( - M, N, device=x.device, dtype=residual_dtype if residual_dtype is not None else x.dtype - ) - else: - assert residual_out.shape == x.shape - assert residual_out.stride(-1) == 1 - else: - residual_out = None - mean = torch.empty((M,), dtype=torch.float32, device=x.device) if not is_rms_norm else None - rstd = torch.empty((M,), dtype=torch.float32, device=x.device) - if dropout_p > 0.0: - seeds = torch.randint( - 2**32, (M if x1 is None else 2 * M,), device=x.device, dtype=torch.int64 - ) - else: - seeds = None - if return_dropout_mask and dropout_p > 0.0: - dropout_mask = torch.empty(M if x1 is None else 2 * M, N, device=x.device, dtype=torch.bool) - else: - dropout_mask = None - # Less than 64KB per feature: enqueue fused kernel - MAX_FUSED_SIZE = 65536 // x.element_size() - BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(N)) - if N > BLOCK_N: - raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") - with torch.cuda.device(x.device.index): - _layer_norm_fwd_1pass_kernel[(M,)]( - x, - out, - weight, - bias, - residual, - x1, - weight1, - bias1, - y1, - residual_out, - rowscale, - seeds, - dropout_mask, - mean, - rstd, - x.stride(0), - out.stride(0), - residual.stride(0) if residual is not None else 0, - residual_out.stride(0) if residual_out is not None else 0, - x1.stride(0) if x1 is not None else 0, - y1.stride(0) if y1 is not None else 0, - M, - N, - eps, - dropout_p, - zero_centered_weight, - is_rms_norm, - BLOCK_N, - residual is not None, - residual_out is not None, - bias is not None, - dropout_p > 0.0, - dropout_mask is not None, - rowscale is not None, - ) - # residual_out is None if residual is None and residual_dtype == input_dtype and dropout_p == 0.0 - if dropout_mask is not None and x1 is not None: - dropout_mask, dropout_mask1 = dropout_mask.tensor_split(2, dim=0) - else: - dropout_mask1 = None - return ( - out, - y1, - mean, - rstd, - residual_out if residual_out is not None else x, - seeds, - dropout_mask, - dropout_mask1, - ) - - -@triton.autotune( - configs=triton_autotune_configs(), - key=["N", "HAS_DRESIDUAL", "STORE_DRESIDUAL", "IS_RMS_NORM", "HAS_BIAS", "HAS_DROPOUT"], -) -# @triton.heuristics({"HAS_BIAS": lambda args: args["B"] is not None}) -# @triton.heuristics({"HAS_DRESIDUAL": lambda args: args["DRESIDUAL"] is not None}) -# @triton.heuristics({"STORE_DRESIDUAL": lambda args: args["DRESIDUAL_IN"] is not None}) -@triton.heuristics({"HAS_ROWSCALE": lambda args: args["ROWSCALE"] is not None}) -@triton.heuristics({"HAS_DY1": lambda args: args["DY1"] is not None}) -@triton.heuristics({"HAS_DX1": lambda args: args["DX1"] is not None}) -@triton.heuristics({"HAS_B1": lambda args: args["DB1"] is not None}) -@triton.heuristics({"RECOMPUTE_OUTPUT": lambda args: args["Y"] is not None}) -@triton.jit -def _layer_norm_bwd_kernel( - X, # pointer to the input - W, # pointer to the weights - B, # pointer to the biases - Y, # pointer to the output to be recomputed - DY, # pointer to the output gradient - DX, # pointer to the input gradient - DW, # pointer to the partial sum of weights gradient - DB, # pointer to the partial sum of biases gradient - DRESIDUAL, - W1, - DY1, - DX1, - DW1, - DB1, - DRESIDUAL_IN, - ROWSCALE, - SEEDS, - Mean, # pointer to the mean - Rstd, # pointer to the 1/std - stride_x_row, # how much to increase the pointer when moving by 1 row - stride_y_row, - stride_dy_row, - stride_dx_row, - stride_dres_row, - stride_dy1_row, - stride_dx1_row, - stride_dres_in_row, - M, # number of rows in X - N, # number of columns in X - eps, # epsilon to avoid division by zero - dropout_p, - zero_centered_weight, - rows_per_program, - IS_RMS_NORM: tl.constexpr, - BLOCK_N: tl.constexpr, - HAS_DRESIDUAL: tl.constexpr, - STORE_DRESIDUAL: tl.constexpr, - HAS_BIAS: tl.constexpr, - HAS_DROPOUT: tl.constexpr, - HAS_ROWSCALE: tl.constexpr, - HAS_DY1: tl.constexpr, - HAS_DX1: tl.constexpr, - HAS_B1: tl.constexpr, - RECOMPUTE_OUTPUT: tl.constexpr, -): - # Map the program id to the elements of X, DX, and DY it should compute. - row_block_id = tl.program_id(0) - row_start = row_block_id * rows_per_program - # Do not early exit if row_start >= M, because we need to write DW and DB - cols = tl.arange(0, BLOCK_N) - mask = cols < N - X += row_start * stride_x_row - if HAS_DRESIDUAL: - DRESIDUAL += row_start * stride_dres_row - if STORE_DRESIDUAL: - DRESIDUAL_IN += row_start * stride_dres_in_row - DY += row_start * stride_dy_row - DX += row_start * stride_dx_row - if HAS_DY1: - DY1 += row_start * stride_dy1_row - if HAS_DX1: - DX1 += row_start * stride_dx1_row - if RECOMPUTE_OUTPUT: - Y += row_start * stride_y_row - w = tl.load(W + cols, mask=mask).to(tl.float32) - if zero_centered_weight: - w += 1.0 - if RECOMPUTE_OUTPUT and HAS_BIAS: - b = tl.load(B + cols, mask=mask, other=0.0).to(tl.float32) - if HAS_DY1: - w1 = tl.load(W1 + cols, mask=mask).to(tl.float32) - if zero_centered_weight: - w1 += 1.0 - dw = tl.zeros((BLOCK_N,), dtype=tl.float32) - if HAS_BIAS: - db = tl.zeros((BLOCK_N,), dtype=tl.float32) - if HAS_DY1: - dw1 = tl.zeros((BLOCK_N,), dtype=tl.float32) - if HAS_B1: - db1 = tl.zeros((BLOCK_N,), dtype=tl.float32) - row_end = min((row_block_id + 1) * rows_per_program, M) - for row in range(row_start, row_end): - # Load data to SRAM - x = tl.load(X + cols, mask=mask, other=0).to(tl.float32) - dy = tl.load(DY + cols, mask=mask, other=0).to(tl.float32) - if HAS_DY1: - dy1 = tl.load(DY1 + cols, mask=mask, other=0).to(tl.float32) - if not IS_RMS_NORM: - mean = tl.load(Mean + row) - rstd = tl.load(Rstd + row) - # Compute dx - xhat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd - xhat = tl.where(mask, xhat, 0.0) - if RECOMPUTE_OUTPUT: - y = xhat * w + b if HAS_BIAS else xhat * w - tl.store(Y + cols, y, mask=mask) - wdy = w * dy - dw += dy * xhat - if HAS_BIAS: - db += dy - if HAS_DY1: - wdy += w1 * dy1 - dw1 += dy1 * xhat - if HAS_B1: - db1 += dy1 - if not IS_RMS_NORM: - c1 = tl.sum(xhat * wdy, axis=0) / N - c2 = tl.sum(wdy, axis=0) / N - dx = (wdy - (xhat * c1 + c2)) * rstd - else: - c1 = tl.sum(xhat * wdy, axis=0) / N - dx = (wdy - xhat * c1) * rstd - if HAS_DRESIDUAL: - dres = tl.load(DRESIDUAL + cols, mask=mask, other=0).to(tl.float32) - dx += dres - # Write dx - if STORE_DRESIDUAL: - tl.store(DRESIDUAL_IN + cols, dx, mask=mask) - if HAS_DX1: - if HAS_DROPOUT: - keep_mask = ( - tl.rand(tl.load(SEEDS + M + row).to(tl.uint32), cols, n_rounds=7) > dropout_p - ) - dx1 = tl.where(keep_mask, dx / (1.0 - dropout_p), 0.0) - else: - dx1 = dx - tl.store(DX1 + cols, dx1, mask=mask) - if HAS_DROPOUT: - keep_mask = tl.rand(tl.load(SEEDS + row).to(tl.uint32), cols, n_rounds=7) > dropout_p - dx = tl.where(keep_mask, dx / (1.0 - dropout_p), 0.0) - if HAS_ROWSCALE: - rowscale = tl.load(ROWSCALE + row).to(tl.float32) - dx *= rowscale - tl.store(DX + cols, dx, mask=mask) - - X += stride_x_row - if HAS_DRESIDUAL: - DRESIDUAL += stride_dres_row - if STORE_DRESIDUAL: - DRESIDUAL_IN += stride_dres_in_row - if RECOMPUTE_OUTPUT: - Y += stride_y_row - DY += stride_dy_row - DX += stride_dx_row - if HAS_DY1: - DY1 += stride_dy1_row - if HAS_DX1: - DX1 += stride_dx1_row - tl.store(DW + row_block_id * N + cols, dw, mask=mask) - if HAS_BIAS: - tl.store(DB + row_block_id * N + cols, db, mask=mask) - if HAS_DY1: - tl.store(DW1 + row_block_id * N + cols, dw1, mask=mask) - if HAS_B1: - tl.store(DB1 + row_block_id * N + cols, db1, mask=mask) - - -def _layer_norm_bwd( - dy, - x, - weight, - bias, - eps, - mean, - rstd, - dresidual=None, - dy1=None, - weight1=None, - bias1=None, - seeds=None, - dropout_p=0.0, - rowscale=None, - has_residual=False, - has_x1=False, - zero_centered_weight=False, - is_rms_norm=False, - x_dtype=None, - recompute_output=False, -): - M, N = x.shape - assert x.stride(-1) == 1 - assert dy.stride(-1) == 1 - assert dy.shape == (M, N) - if dresidual is not None: - assert dresidual.stride(-1) == 1 - assert dresidual.shape == (M, N) - assert weight.shape == (N,) - assert weight.stride(-1) == 1 - if bias is not None: - assert bias.stride(-1) == 1 - assert bias.shape == (N,) - if dy1 is not None: - assert weight1 is not None - assert dy1.shape == dy.shape - assert dy1.stride(-1) == 1 - if weight1 is not None: - assert weight1.shape == (N,) - assert weight1.stride(-1) == 1 - if bias1 is not None: - assert bias1.shape == (N,) - assert bias1.stride(-1) == 1 - if seeds is not None: - assert seeds.is_contiguous() - assert seeds.shape == (M if not has_x1 else M * 2,) - if rowscale is not None: - assert rowscale.is_contiguous() - assert rowscale.shape == (M,) - # allocate output - dx = ( - torch.empty_like(x) - if x_dtype is None - else torch.empty(M, N, dtype=x_dtype, device=x.device) - ) - dresidual_in = ( - torch.empty_like(x) - if has_residual - and (dx.dtype != x.dtype or dropout_p > 0.0 or rowscale is not None or has_x1) - else None - ) - dx1 = torch.empty_like(dx) if (has_x1 and dropout_p > 0.0) else None - y = torch.empty(M, N, dtype=dy.dtype, device=dy.device) if recompute_output else None - if recompute_output: - assert weight1 is None, "recompute_output is not supported with parallel LayerNorm" - - # Less than 64KB per feature: enqueue fused kernel - MAX_FUSED_SIZE = 65536 // x.element_size() - BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(N)) - if N > BLOCK_N: - raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") - # Increasing the multiple (e.g. 8) will allow more thread blocks to be launched and hide the - # latency of the gmem reads/writes, but will increase the time of summing up dw / db. - sm_count = torch.cuda.get_device_properties(x.device).multi_processor_count * 8 - _dw = torch.empty((sm_count, N), dtype=torch.float32, device=weight.device) - _db = ( - torch.empty((sm_count, N), dtype=torch.float32, device=bias.device) - if bias is not None - else None - ) - _dw1 = torch.empty_like(_dw) if weight1 is not None else None - _db1 = torch.empty_like(_db) if bias1 is not None else None - rows_per_program = math.ceil(M / sm_count) - grid = (sm_count,) - with torch.cuda.device(x.device.index): - _layer_norm_bwd_kernel[grid]( - x, - weight, - bias, - y, - dy, - dx, - _dw, - _db, - dresidual, - weight1, - dy1, - dx1, - _dw1, - _db1, - dresidual_in, - rowscale, - seeds, - mean, - rstd, - x.stride(0), - 0 if not recompute_output else y.stride(0), - dy.stride(0), - dx.stride(0), - dresidual.stride(0) if dresidual is not None else 0, - dy1.stride(0) if dy1 is not None else 0, - dx1.stride(0) if dx1 is not None else 0, - dresidual_in.stride(0) if dresidual_in is not None else 0, - M, - N, - eps, - dropout_p, - zero_centered_weight, - rows_per_program, - is_rms_norm, - BLOCK_N, - dresidual is not None, - dresidual_in is not None, - bias is not None, - dropout_p > 0.0, - ) - dw = _dw.sum(0).to(weight.dtype) - db = _db.sum(0).to(bias.dtype) if bias is not None else None - dw1 = _dw1.sum(0).to(weight1.dtype) if weight1 is not None else None - db1 = _db1.sum(0).to(bias1.dtype) if bias1 is not None else None - # Don't need to compute dresidual_in separately in this case - if has_residual and dx.dtype == x.dtype and dropout_p == 0.0 and rowscale is None: - dresidual_in = dx - if has_x1 and dropout_p == 0.0: - dx1 = dx - return ( - (dx, dw, db, dresidual_in, dx1, dw1, db1) - if not recompute_output - else (dx, dw, db, dresidual_in, dx1, dw1, db1, y) - ) - - -class LayerNormFn(torch.autograd.Function): - @staticmethod - def forward( - ctx, - x, - weight, - bias, - residual=None, - x1=None, - weight1=None, - bias1=None, - eps=1e-6, - dropout_p=0.0, - rowscale=None, - prenorm=False, - residual_in_fp32=False, - zero_centered_weight=False, - is_rms_norm=False, - return_dropout_mask=False, - out=None, - residual_out=None - ): - x_shape_og = x.shape - # Check for zero sequence length - if x.numel() == 0: - ctx.zero_seq_length = True - # Only save minimal required tensors for backward - # ctx.save_for_backward(weight, bias, weight1, bias1) - ctx.x_shape_og = x_shape_og - ctx.weight_shape = weight.shape - ctx.weight_dtype = weight.dtype - ctx.weight_device = weight.device - - ctx.has_bias = bias is not None - ctx.bias_shape = bias.shape if bias is not None else None - ctx.bias_dtype = bias.dtype if bias is not None else None - ctx.bias_device = bias.device if bias is not None else None - - ctx.has_weight1 = weight1 is not None - ctx.weight1_shape = weight1.shape if weight1 is not None else None - ctx.weight1_dtype = weight1.dtype if weight1 is not None else None - ctx.weight1_device = weight1.device if weight1 is not None else None - - ctx.has_bias1 = bias1 is not None - ctx.bias1_shape = bias1.shape if bias1 is not None else None - ctx.bias1_dtype = bias1.dtype if bias1 is not None else None - ctx.bias1_device = bias1.device if bias1 is not None else None - - ctx.has_residual = residual is not None - ctx.has_x1 = x1 is not None - ctx.dropout_p = dropout_p - - # Handle output tensors with correct dtype - y = x # Preserve input tensor properties - y1 = torch.empty_like(x) if x1 is not None else None - - # Only create residual_out if prenorm is True - residual_out = torch.empty(x.shape, - dtype=torch.float32 if residual_in_fp32 else x.dtype, - device=x.device) if prenorm else None - - # Handle dropout masks - dropout_mask = None - dropout_mask1 = None - if return_dropout_mask: - dropout_mask = torch.empty_like(x, dtype=torch.uint8) - if x1 is not None: - dropout_mask1 = torch.empty_like(x, dtype=torch.uint8) - - # Return based on configuration - if not return_dropout_mask: - if weight1 is None: - return y if not prenorm else (y, residual_out) - else: - return (y, y1) if not prenorm else (y, y1, residual_out) - else: - if weight1 is None: - return ((y, dropout_mask, dropout_mask1) if not prenorm - else (y, residual_out, dropout_mask, dropout_mask1)) - else: - return ((y, y1, dropout_mask, dropout_mask1) if not prenorm - else (y, y1, residual_out, dropout_mask, dropout_mask1)) - - ctx.zero_seq_length = False - # reshape input data into 2D tensor - x = x.reshape(-1, x.shape[-1]) - if x.stride(-1) != 1: - x = x.contiguous() - if residual is not None: - assert residual.shape == x_shape_og - residual = residual.reshape(-1, residual.shape[-1]) - if residual.stride(-1) != 1: - residual = residual.contiguous() - if x1 is not None: - assert x1.shape == x_shape_og - assert rowscale is None, "rowscale is not supported with parallel LayerNorm" - x1 = x1.reshape(-1, x1.shape[-1]) - if x1.stride(-1) != 1: - x1 = x1.contiguous() - weight = weight.contiguous() - if bias is not None: - bias = bias.contiguous() - if weight1 is not None: - weight1 = weight1.contiguous() - if bias1 is not None: - bias1 = bias1.contiguous() - if rowscale is not None: - rowscale = rowscale.reshape(-1).contiguous() - residual_dtype = ( - residual.dtype - if residual is not None - else (torch.float32 if residual_in_fp32 else None) - ) - if out is not None: - out = out.reshape(-1, out.shape[-1]) - if residual_out is not None: - residual_out = residual_out.reshape(-1, residual_out.shape[-1]) - y, y1, mean, rstd, residual_out, seeds, dropout_mask, dropout_mask1 = _layer_norm_fwd( - x, - weight, - bias, - eps, - residual, - x1, - weight1, - bias1, - dropout_p=dropout_p, - rowscale=rowscale, - residual_dtype=residual_dtype, - zero_centered_weight=zero_centered_weight, - is_rms_norm=is_rms_norm, - return_dropout_mask=return_dropout_mask, - out=out, - residual_out=residual_out - ) - ctx.save_for_backward( - residual_out, weight, bias, weight1, bias1, rowscale, seeds, mean, rstd - ) - ctx.x_shape_og = x_shape_og - ctx.eps = eps - ctx.dropout_p = dropout_p - ctx.is_rms_norm = is_rms_norm - ctx.has_residual = residual is not None - ctx.has_x1 = x1 is not None - ctx.prenorm = prenorm - ctx.x_dtype = x.dtype - ctx.zero_centered_weight = zero_centered_weight - y = y.reshape(x_shape_og) - y1 = y1.reshape(x_shape_og) if y1 is not None else None - residual_out = residual_out.reshape(x_shape_og) if residual_out is not None else None - dropout_mask = dropout_mask.reshape(x_shape_og) if dropout_mask is not None else None - dropout_mask1 = dropout_mask1.reshape(x_shape_og) if dropout_mask1 is not None else None - if not return_dropout_mask: - if weight1 is None: - return y if not prenorm else (y, residual_out) - else: - return (y, y1) if not prenorm else (y, y1, residual_out) - else: - if weight1 is None: - return ( - (y, dropout_mask, dropout_mask1) - if not prenorm - else (y, residual_out, dropout_mask, dropout_mask1) - ) - else: - return ( - (y, y1, dropout_mask, dropout_mask1) - if not prenorm - else (y, y1, residual_out, dropout_mask, dropout_mask1) - ) - - @staticmethod - def backward(ctx, dy, *args): - if ctx.zero_seq_length: - return ( - torch.zeros(ctx.x_shape_og, dtype=dy.dtype, device=dy.device), - torch.zeros(ctx.weight_shape, dtype=ctx.weight_dtype, device=ctx.weight_device), - torch.zeros(ctx.bias_shape, dtype=ctx.bias_dtype, device=ctx.bias_device) if ctx.has_bias else None, - torch.zeros(ctx.x_shape_og, dtype=dy.dtype, device=dy.device) if ctx.has_residual else None, - torch.zeros(ctx.x_shape_og, dtype=dy.dtype, device=dy.device) if ctx.has_x1 and ctx.dropout_p > 0.0 else None, - torch.zeros(ctx.weight1_shape, dtype=ctx.weight1_dtype, device=ctx.weight1_device) if ctx.has_weight1 else None, - torch.zeros(ctx.bias1_shape, dtype=ctx.bias1_dtype, device=ctx.bias1_device) if ctx.has_bias1 else None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - ) - - x, weight, bias, weight1, bias1, rowscale, seeds, mean, rstd = ctx.saved_tensors - dy = dy.reshape(-1, dy.shape[-1]) - if dy.stride(-1) != 1: - dy = dy.contiguous() - assert dy.shape == x.shape - if weight1 is not None: - dy1, args = args[0], args[1:] - dy1 = dy1.reshape(-1, dy1.shape[-1]) - if dy1.stride(-1) != 1: - dy1 = dy1.contiguous() - assert dy1.shape == x.shape - else: - dy1 = None - if ctx.prenorm: - dresidual = args[0] - dresidual = dresidual.reshape(-1, dresidual.shape[-1]) - if dresidual.stride(-1) != 1: - dresidual = dresidual.contiguous() - assert dresidual.shape == x.shape - else: - dresidual = None - - dx, dw, db, dresidual_in, dx1, dw1, db1 = _layer_norm_bwd( - dy, - x, - weight, - bias, - ctx.eps, - mean, - rstd, - dresidual, - dy1, - weight1, - bias1, - seeds, - ctx.dropout_p, - rowscale, - ctx.has_residual, - ctx.has_x1, - ctx.zero_centered_weight, - ctx.is_rms_norm, - x_dtype=ctx.x_dtype, - ) - return ( - dx.reshape(ctx.x_shape_og), - dw, - db, - dresidual_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, - dx1.reshape(ctx.x_shape_og) if dx1 is not None else None, - dw1, - db1, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - ) - - -def layer_norm_fn( - x, - weight, - bias, - residual=None, - x1=None, - weight1=None, - bias1=None, - eps=1e-6, - dropout_p=0.0, - rowscale=None, - prenorm=False, - residual_in_fp32=False, - zero_centered_weight=False, - is_rms_norm=False, - return_dropout_mask=False, - out=None, - residual_out=None -): - return LayerNormFn.apply( - x, - weight, - bias, - residual, - x1, - weight1, - bias1, - eps, - dropout_p, - rowscale, - prenorm, - residual_in_fp32, - zero_centered_weight, - is_rms_norm, - return_dropout_mask, - out, - residual_out - ) - - -def rms_norm_fn( - x, - weight, - bias, - residual=None, - x1=None, - weight1=None, - bias1=None, - eps=1e-6, - dropout_p=0.0, - rowscale=None, - prenorm=False, - residual_in_fp32=False, - zero_centered_weight=False, - return_dropout_mask=False, - out=None, - residual_out=None -): - return LayerNormFn.apply( - x, - weight, - bias, - residual, - x1, - weight1, - bias1, - eps, - dropout_p, - rowscale, - prenorm, - residual_in_fp32, - zero_centered_weight, - True, - return_dropout_mask, - out, - residual_out - ) - - -class RMSNorm(torch.nn.Module): - - def __init__(self, hidden_size, eps=1e-5, dropout_p=0.0, zero_centered_weight=False, - device=None, dtype=None): - factory_kwargs = {"device": device, "dtype": dtype} - super().__init__() - self.eps = eps - if dropout_p > 0.0: - self.drop = torch.nn.Dropout(dropout_p) - else: - self.drop = None - self.zero_centered_weight = zero_centered_weight - self.weight = torch.nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) - self.register_parameter("bias", None) - self.reset_parameters() - - def reset_parameters(self): - if not self.zero_centered_weight: - torch.nn.init.ones_(self.weight) - else: - torch.nn.init.zeros_(self.weight) - - def forward(self, x, residual=None, prenorm=False, residual_in_fp32=False): - return rms_norm_fn( - x, - self.weight, - self.bias, - residual=residual, - eps=self.eps, - dropout_p=self.drop.p if self.drop is not None and self.training else 0.0, - prenorm=prenorm, - residual_in_fp32=residual_in_fp32, - zero_centered_weight=self.zero_centered_weight, - ) - - -class LayerNormLinearFn(torch.autograd.Function): - @staticmethod - @custom_fwd - def forward( - ctx, - x, - norm_weight, - norm_bias, - linear_weight, - linear_bias, - residual=None, - eps=1e-6, - prenorm=False, - residual_in_fp32=False, - is_rms_norm=False, - ): - x_shape_og = x.shape - # reshape input data into 2D tensor - x = x.reshape(-1, x.shape[-1]) - if x.stride(-1) != 1: - x = x.contiguous() - if residual is not None: - assert residual.shape == x_shape_og - residual = residual.reshape(-1, residual.shape[-1]) - if residual.stride(-1) != 1: - residual = residual.contiguous() - norm_weight = norm_weight.contiguous() - if norm_bias is not None: - norm_bias = norm_bias.contiguous() - residual_dtype = ( - residual.dtype - if residual is not None - else (torch.float32 if residual_in_fp32 else None) - ) - y, _, mean, rstd, residual_out, *rest = _layer_norm_fwd( - x, - norm_weight, - norm_bias, - eps, - residual, - out_dtype=None if not torch.is_autocast_enabled() else torch.get_autocast_dtype("cuda"), - residual_dtype=residual_dtype, - is_rms_norm=is_rms_norm, - ) - y = y.reshape(x_shape_og) - dtype = torch.get_autocast_dtype("cuda") if torch.is_autocast_enabled() else y.dtype - linear_weight = linear_weight.to(dtype) - linear_bias = linear_bias.to(dtype) if linear_bias is not None else None - out = F.linear(y.to(linear_weight.dtype), linear_weight, linear_bias) - # We don't store y, will be recomputed in the backward pass to save memory - ctx.save_for_backward(residual_out, norm_weight, norm_bias, linear_weight, mean, rstd) - ctx.x_shape_og = x_shape_og - ctx.eps = eps - ctx.is_rms_norm = is_rms_norm - ctx.has_residual = residual is not None - ctx.prenorm = prenorm - ctx.x_dtype = x.dtype - ctx.linear_bias_is_none = linear_bias is None - return out if not prenorm else (out, residual_out.reshape(x_shape_og)) - - @staticmethod - @custom_bwd - def backward(ctx, dout, *args): - x, norm_weight, norm_bias, linear_weight, mean, rstd = ctx.saved_tensors - dout = dout.reshape(-1, dout.shape[-1]) - dy = F.linear(dout, linear_weight.t()) - dlinear_bias = None if ctx.linear_bias_is_none else dout.sum(0) - if dy.stride(-1) != 1: - dy = dy.contiguous() - assert dy.shape == x.shape - if ctx.prenorm: - dresidual = args[0] - dresidual = dresidual.reshape(-1, dresidual.shape[-1]) - if dresidual.stride(-1) != 1: - dresidual = dresidual.contiguous() - assert dresidual.shape == x.shape - else: - dresidual = None - dx, dnorm_weight, dnorm_bias, dresidual_in, _, _, _, y = _layer_norm_bwd( - dy, - x, - norm_weight, - norm_bias, - ctx.eps, - mean, - rstd, - dresidual=dresidual, - has_residual=ctx.has_residual, - is_rms_norm=ctx.is_rms_norm, - x_dtype=ctx.x_dtype, - recompute_output=True, - ) - dlinear_weight = torch.einsum("bo,bi->oi", dout, y) - return ( - dx.reshape(ctx.x_shape_og), - dnorm_weight, - dnorm_bias, - dlinear_weight, - dlinear_bias, - dresidual_in.reshape(ctx.x_shape_og) if ctx.has_residual else None, - None, - None, - None, - None, - ) - - -def layer_norm_linear_fn( - x, - norm_weight, - norm_bias, - linear_weight, - linear_bias, - residual=None, - eps=1e-6, - prenorm=False, - residual_in_fp32=False, - is_rms_norm=False, -): - return LayerNormLinearFn.apply( - x, - norm_weight, - norm_bias, - linear_weight, - linear_bias, - residual, - eps, - prenorm, - residual_in_fp32, - is_rms_norm, - ) From 1d9fe88528e9b4ace98253a017da258dfb0645ae Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 28 Jun 2025 14:05:51 +0300 Subject: [PATCH 72/85] Cleanup --- modules/omnigen2/pipeline_utils.py | 62 ------------------------------ 1 file changed, 62 deletions(-) delete mode 100644 modules/omnigen2/pipeline_utils.py diff --git a/modules/omnigen2/pipeline_utils.py b/modules/omnigen2/pipeline_utils.py deleted file mode 100644 index 4efebc260..000000000 --- a/modules/omnigen2/pipeline_utils.py +++ /dev/null @@ -1,62 +0,0 @@ -import torch - - -def get_pipeline_embeds(pipeline, prompt, negative_prompt, device): - """ Get pipeline embeds for prompts bigger than the maxlength of the pipe - :param pipeline: - :param prompt: - :param negative_prompt: - :param device: - :return: - """ - max_length = pipeline.tokenizer.model_max_length - - # simple way to determine length of tokens - # count_prompt = len(prompt.split(" ")) - # count_negative_prompt = len(negative_prompt.split(" ")) - - # create the tensor based on which prompt is longer - # if count_prompt >= count_negative_prompt: - input_ids = pipeline.tokenizer(prompt, return_tensors="pt", truncation=False, padding='longest').input_ids.to(device) - # input_ids = pipeline.tokenizer(prompt, padding="max_length", - # max_length=pipeline.tokenizer.model_max_length, - # truncation=True, - # return_tensors="pt",).input_ids.to(device) - shape_max_length = input_ids.shape[-1] - - if negative_prompt is not None: - negative_ids = pipeline.tokenizer(negative_prompt, truncation=True, padding="max_length", - max_length=shape_max_length, return_tensors="pt").input_ids.to(device) - - # else: - # negative_ids = pipeline.tokenizer(negative_prompt, return_tensors="pt", truncation=False).input_ids.to(device) - # shape_max_length = negative_ids.shape[-1] - # input_ids = pipeline.tokenizer(prompt, return_tensors="pt", truncation=False, padding="max_length", - # max_length=shape_max_length).input_ids.to(device) - - concat_embeds = [] - neg_embeds = [] - for i in range(0, shape_max_length, max_length): - if hasattr(pipeline.text_encoder.config, "use_attention_mask") and pipeline.text_encoder.config.use_attention_mask: - attention_mask = input_ids[:, i: i + max_length].attention_mask.to(device) - else: - attention_mask = None - concat_embeds.append(pipeline.text_encoder(input_ids[:, i: i + max_length], - attention_mask=attention_mask)[0]) - - if negative_prompt is not None: - if hasattr(pipeline.text_encoder.config, "use_attention_mask") and pipeline.text_encoder.config.use_attention_mask: - attention_mask = negative_ids[:, i: i + max_length].attention_mask.to(device) - else: - attention_mask = None - neg_embeds.append(pipeline.text_encoder(negative_ids[:, i: i + max_length], - attention_mask=attention_mask)[0]) - - concat_embeds = torch.cat(concat_embeds, dim=1) - - if negative_prompt is not None: - neg_embeds = torch.cat(neg_embeds, dim=1) - else: - neg_embeds = None - - return concat_embeds, neg_embeds From ba45de1a08697af554f6ceb3a7f2b9d8006514f1 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 28 Jun 2025 14:14:50 +0300 Subject: [PATCH 73/85] Remove empty file --- modules/omnigen2/models/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 modules/omnigen2/models/__init__.py diff --git a/modules/omnigen2/models/__init__.py b/modules/omnigen2/models/__init__.py deleted file mode 100644 index e69de29bb..000000000 From 69f71cc32a8a387dd7b8bb464a64be314b8415c3 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 28 Jun 2025 07:50:33 -0400 Subject: [PATCH 74/85] update nunchaku Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 2 ++ modules/mit_nunchaku.py | 5 +---- modules/model_flux.py | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c11b9be5..41d4153e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - one of the best upscalers (traditional, non-diffusion) available today! - available via *process -> upscale -> chainner* - **Changes** + - Update all core requirements - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* - Add `--trace` command line param that enables trace logging - Use Diffusers version of *OmniGen* @@ -73,6 +74,7 @@ - Fix process batch with batch count - Fix process batch double image save - Fix unapply texture tiling + - Fix nunchaku batch support - Suppress torch empty logging - Improve TAESD live preview downscale handling diff --git a/modules/mit_nunchaku.py b/modules/mit_nunchaku.py index 9c0500790..798de661e 100644 --- a/modules/mit_nunchaku.py +++ b/modules/mit_nunchaku.py @@ -1,13 +1,10 @@ # MIT-Han-Lab Nunchaku: -# TODO nunchaku: cache-dir for transformer and t5 loader -# TODO nunchaku: batch support - from installer import log, pip from modules import devices -ver = '0.2.0' +ver = '0.3.1' ok = False diff --git a/modules/model_flux.py b/modules/model_flux.py index b164de963..518e396d7 100644 --- a/modules/model_flux.py +++ b/modules/model_flux.py @@ -138,6 +138,7 @@ def load_quants(kwargs, repo_id, cache_dir, allow_quant): nunchaku_repo = 'mit-han-lab/nunchaku-t5/awq-int4-flux.1-t5xxl.safetensors' shared.log.debug(f'Load module: quant=Nunchaku module=t5 repo="{nunchaku_repo}" precision={nunchaku_precision}') kwargs['text_encoder_2'] = nunchaku.NunchakuT5EncoderModel.from_pretrained(nunchaku_repo, torch_dtype=devices.dtype) + kwargs['text_encoder_2'].quantization_method = 'SVDQuant' elif 'text_encoder_2' not in kwargs and model_quant.check_quant('TE'): quant_args = model_quant.create_config(allow=allow_quant, module='TE') if quant_args: From 7fbac675fe4f8ac6f1ff32e3b00c7ee7e8cb6be5 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 28 Jun 2025 09:36:33 -0400 Subject: [PATCH 75/85] api update default sampler Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + modules/api/control.py | 2 +- modules/api/models.py | 2 +- modules/processing_class.py | 4 ++-- modules/processing_helpers.py | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d4153e0..17ba58a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - **API** - Add `/sdapi/v1/lora?lora=` endpoint that returns full lora info and metadata - Add `/sdapi/v1/controlnets?model_type=` endpoints that returns list of available controlnets for specific model type + - Set default sampler to `Default` - **Fixes** - IPEX with DPM2++ FlowMatch samplers - Invalid attention processor with ControlNet diff --git a/modules/api/control.py b/modules/api/control.py index 9d6512cd2..9e06d874a 100644 --- a/modules/api/control.py +++ b/modules/api/control.py @@ -22,7 +22,7 @@ ReqControl = models.create_model_from_signature( func = run.control_run, model_name = "StableDiffusionProcessingControl", additional_fields = [ - {"key": "sampler_name", "type": str, "default": "UniPC"}, + {"key": "sampler_name", "type": str, "default": "Default"}, {"key": "script_name", "type": Optional[str], "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "send_images", "type": bool, "default": True}, diff --git a/modules/api/models.py b/modules/api/models.py index f463a2dd6..a19c59858 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -198,7 +198,7 @@ ReqTxt2Img = PydanticModelGenerator( StableDiffusionProcessingTxt2Img, [ {"key": "sampler_index", "type": Union[int, str], "default": 0}, - {"key": "sampler_name", "type": str, "default": "UniPC"}, + {"key": "sampler_name", "type": str, "default": "Default"}, {"key": "hr_sampler_name", "type": str, "default": "Same as primary"}, {"key": "script_name", "type": Optional[str], "default": "none"}, {"key": "script_args", "type": list, "default": []}, diff --git a/modules/processing_class.py b/modules/processing_class.py index fc697d997..98e8d1c96 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -463,8 +463,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if all_subseeds is not None: self.all_subseeds = all_subseeds - if self.sampler_name == "PLMS": - self.sampler_name = 'UniPC' + if self.sampler_name == 'PLMS': + self.sampler_name = 'Default' if not shared.native: self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) if hasattr(self.sampler, "initialize"): diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index e31ebb028..5d1647f8f 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -97,10 +97,10 @@ def get_sampler_name(sampler_index: int, img: bool = False) -> str: if len(sd_samplers.samplers) > sampler_index: sampler_name = sd_samplers.samplers[sampler_index].name else: - sampler_name = "UniPC" + sampler_name = "Default" shared.log.warning(f'Sampler not found: index={sampler_index} available={[s.name for s in sd_samplers.samplers]} fallback={sampler_name}') if img and sampler_name == "PLMS": - sampler_name = "UniPC" + sampler_name = "Default" shared.log.warning(f'Sampler not compatible: name=PLMS fallback={sampler_name}') return sampler_name From bcea748d56d598d6b99c4645960a767b04a02566 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 28 Jun 2025 18:35:57 +0300 Subject: [PATCH 76/85] Omnigen 2 use LuminaFeedForward from diffusers --- .../models/transformers/block_lumina2.py | 51 ------------------- .../transformers/transformer_omnigen2.py | 3 +- 2 files changed, 2 insertions(+), 52 deletions(-) diff --git a/modules/omnigen2/models/transformers/block_lumina2.py b/modules/omnigen2/models/transformers/block_lumina2.py index 1bd7a56ff..601736841 100644 --- a/modules/omnigen2/models/transformers/block_lumina2.py +++ b/modules/omnigen2/models/transformers/block_lumina2.py @@ -21,57 +21,6 @@ import torch.nn.functional as F from torch.nn import RMSNorm from diffusers.models.embeddings import Timesteps, TimestepEmbedding -from diffusers.models.activations import FP32SiLU - -# Omnigen 2 uses swiglu key instead of silu -# But the functionality is exactly the same -class LuminaFeedForward(nn.Module): - r""" - A feed-forward layer. - - Parameters: - hidden_size (`int`): - The dimensionality of the hidden layers in the model. This parameter determines the width of the model's - hidden representations. - intermediate_size (`int`): The intermediate dimension of the feedforward layer. - multiple_of (`int`, *optional*): Value to ensure hidden dimension is a multiple - of this value. - ffn_dim_multiplier (float, *optional*): Custom multiplier for hidden - dimension. Defaults to None. - """ - - def __init__( - self, - dim: int, - inner_dim: int, - multiple_of: Optional[int] = 256, - ffn_dim_multiplier: Optional[float] = None, - ): - super().__init__() - # custom hidden_size factor multiplier - if ffn_dim_multiplier is not None: - inner_dim = int(ffn_dim_multiplier * inner_dim) - inner_dim = multiple_of * ((inner_dim + multiple_of - 1) // multiple_of) - - self.linear_1 = nn.Linear( - dim, - inner_dim, - bias=False, - ) - self.linear_2 = nn.Linear( - inner_dim, - dim, - bias=False, - ) - self.linear_3 = nn.Linear( - dim, - inner_dim, - bias=False, - ) - self.swiglu = FP32SiLU() - - def forward(self, x): - return self.linear_2(self.swiglu(self.linear_1(x)) * self.linear_3(x)) # Makes timestep_scale configurable diff --git a/modules/omnigen2/models/transformers/transformer_omnigen2.py b/modules/omnigen2/models/transformers/transformer_omnigen2.py index 5a2ccf03f..fe65b3170 100644 --- a/modules/omnigen2/models/transformers/transformer_omnigen2.py +++ b/modules/omnigen2/models/transformers/transformer_omnigen2.py @@ -15,8 +15,9 @@ from diffusers.models.attention_processor import Attention from diffusers.models.modeling_outputs import Transformer2DModelOutput from diffusers.models.modeling_utils import ModelMixin from diffusers.models.normalization import LuminaLayerNormContinuous, LuminaRMSNormZero +from diffusers.models.attention import LuminaFeedForward -from .block_lumina2 import LuminaFeedForward, Lumina2CombinedTimestepCaptionEmbedding +from .block_lumina2 import Lumina2CombinedTimestepCaptionEmbedding from ..attention_processor import OmniGen2AttnProcessor from .repo import OmniGen2RotaryPosEmbed From 99f323d105fbc77afe290503c2d550aab198607f Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sun, 29 Jun 2025 18:17:05 +0300 Subject: [PATCH 77/85] Fix Linfusiong with quantization / compile --- modules/sd_models.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 363234d90..8bd122a72 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -158,6 +158,12 @@ def set_diffuser_options(sd_model, vae=None, op:str='model', offload:bool=True, shared.log.quiet(quiet, f'Setting {op}: fused-qkv=True') except Exception as e: shared.log.error(f'Setting {op}: fused-qkv=True {e}') + if shared.opts.enable_linfusion: + try: + from modules import linfusion + linfusion.apply(sd_model) + except Exception as e: + shared.log.error(f'Setting {op}: LinFusion=True {e}') if shared.opts.diffusers_eval: def eval_model(model, op=None, sd_model=None): # pylint: disable=unused-argument if hasattr(model, "requires_grad_"): @@ -674,11 +680,6 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model = sd_models_compile.compile_diffusers(sd_model) timer.record("compile") - if shared.opts.enable_linfusion: - from modules import linfusion - linfusion.apply(sd_model) - timer.record("linfusion") - except Exception as e: shared.log.error(f"Load {op}: {e}") errors.display(e, "Model") From 1b4e1ff0ef60c27fe81f7189909af2ec4eef3a76 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 29 Jun 2025 11:48:02 -0400 Subject: [PATCH 78/85] enable quants for vlm-captioning Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + modules/interrogate/joycaption.py | 16 ++++++++--- modules/interrogate/vqa.py | 46 +++++++++++++++++++------------ wiki | 2 +- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17ba58a3e..d78d96980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - **Changes** - Update all core requirements - Support Remote VAE with *Omnigen, Lumina 2 and PixArt* + - Enable quantization for captioning: *Gemma, Qwen, SMOL, Florence, JoyCaption* - Add `--trace` command line param that enables trace logging - Use Diffusers version of *OmniGen* - Control move global settings to control elements -> control settings tab diff --git a/modules/interrogate/joycaption.py b/modules/interrogate/joycaption.py index 4941f7899..dc5e07213 100644 --- a/modules/interrogate/joycaption.py +++ b/modules/interrogate/joycaption.py @@ -3,7 +3,7 @@ from dataclasses import dataclass import torch from transformers import AutoProcessor, LlavaForConditionalGeneration -from modules import shared, devices +from modules import shared, devices, sd_models, model_quant """ @@ -66,8 +66,16 @@ def predict(question: str, image, vqa_model: str = None) -> str: llava_model = None if llava_model is None: shared.log.info(f'Interrogate: type=vlm model="JoyCaption" {str(opts)}') + processor = AutoProcessor.from_pretrained(opts.repo) - llava_model = LlavaForConditionalGeneration.from_pretrained(opts.repo, torch_dtype=devices.dtype, device_map="auto", cache_dir=shared.opts.hfcache_dir) + quant_args = model_quant.create_config(module='LLM') + llava_model = LlavaForConditionalGeneration.from_pretrained( + opts.repo, + torch_dtype=devices.dtype, + device_map="auto", + cache_dir=shared.opts.hfcache_dir, + **quant_args, + ) llava_model.eval() if len(question) < 2: @@ -80,7 +88,7 @@ def predict(question: str, image, vqa_model: str = None) -> str: convo_string = processor.apply_chat_template(convo, tokenize=False, add_generation_prompt=True) inputs = processor(text=[convo_string], images=[image], return_tensors="pt").to(devices.device) # Process the inputs inputs['pixel_values'] = inputs['pixel_values'].to(devices.dtype) - llava_model = llava_model.to(devices.device) + sd_models.move_model(llava_model, devices.device) with devices.inference_context(): generate_ids = llava_model.generate( # Generate the captions **inputs, @@ -97,6 +105,6 @@ def predict(question: str, image, vqa_model: str = None) -> str: )[0] generate_ids = generate_ids[inputs['input_ids'].shape[1]:] # Trim off the prompt caption = processor.tokenizer.decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False) # Decode the caption - llava_model = llava_model.to(devices.cpu) + sd_models.move_model(llava_model, devices.cpu, force=True) caption = caption.replace('\n\n', '\n').strip() return caption diff --git a/modules/interrogate/vqa.py b/modules/interrogate/vqa.py index f11065617..d2aeab61a 100644 --- a/modules/interrogate/vqa.py +++ b/modules/interrogate/vqa.py @@ -7,12 +7,13 @@ import torch import transformers import transformers.dynamic_module_utils from PIL import Image -from modules import shared, devices, errors, sd_models +from modules import shared, devices, errors, sd_models, model_quant processor = None model = None loaded: str = None +quant_args = {} vlm_models = { "Microsoft Florence 2 Base": "microsoft/Florence-2-base", # 0.5GB "Microsoft Florence 2 Large": "microsoft/Florence-2-large", # 1.5GB @@ -121,9 +122,10 @@ def qwen(question: str, image: Image.Image, repo: str = None, system_prompt: str model = None model = transformers.Qwen2VLForConditionalGeneration.from_pretrained( repo, - cache_dir=shared.opts.hfcache_dir + torch_dtype=devices.dtype, + cache_dir=shared.opts.hfcache_dir, + **quant_args, ) - model = model.to(devices.device, devices.dtype) processor = transformers.AutoProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) loaded = repo devices.torch_gc() @@ -166,8 +168,12 @@ def gemma(question: str, image: Image.Image, repo: str = None, system_prompt: st if model is None or loaded != repo: shared.log.debug(f'Interrogate load: vlm="{repo}"') model = None - model = transformers.Gemma3ForConditionalGeneration.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) - model = model.to(devices.device, devices.dtype) + model = transformers.Gemma3ForConditionalGeneration.from_pretrained( + repo, + torch_dtype=devices.dtype, + cache_dir=shared.opts.hfcache_dir, + **quant_args, + ) processor = transformers.AutoProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) loaded = repo devices.torch_gc() @@ -217,7 +223,6 @@ def paligemma(question: str, image: Image.Image, repo: str = None): cache_dir=shared.opts.hfcache_dir, torch_dtype=devices.dtype, ) - model = model.to(devices.device, devices.dtype) loaded = repo devices.torch_gc() sd_models.move_model(model, devices.device) @@ -251,7 +256,6 @@ def ovis(question: str, image: Image.Image, repo: str = None): trust_remote_code=True, cache_dir=shared.opts.hfcache_dir, ) - model = model.to(devices.device, devices.dtype) loaded = repo devices.torch_gc() sd_models.move_model(model, devices.device) @@ -292,8 +296,8 @@ def smol(question: str, image: Image.Image, repo: str = None, system_prompt: str cache_dir=shared.opts.hfcache_dir, torch_dtype=devices.dtype, _attn_implementation="eager", + **quant_args, ) - model.to(devices.device, devices.dtype) processor = transformers.AutoProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) loaded = repo devices.torch_gc() @@ -331,9 +335,9 @@ def git(question: str, image: Image.Image, repo: str = None): model = None model = transformers.GitForCausalLM.from_pretrained( repo, + torch_dtype=devices.dtype, cache_dir=shared.opts.hfcache_dir, ) - model.to(devices.device, devices.dtype) processor = transformers.GitProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) loaded = repo devices.torch_gc() @@ -359,9 +363,9 @@ def blip(question: str, image: Image.Image, repo: str = None): model = None model = transformers.BlipForQuestionAnswering.from_pretrained( repo, + torch_dtype=devices.dtype, cache_dir=shared.opts.hfcache_dir, ) - model.to(devices.device, devices.dtype) processor = transformers.BlipProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) loaded = repo devices.torch_gc() @@ -381,9 +385,9 @@ def vilt(question: str, image: Image.Image, repo: str = None): model = None model = transformers.ViltForQuestionAnswering.from_pretrained( repo, + torch_dtype=devices.dtype, cache_dir=shared.opts.hfcache_dir, ) - model.to(devices.device) processor = transformers.ViltProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) loaded = repo devices.torch_gc() @@ -405,9 +409,9 @@ def pix(question: str, image: Image.Image, repo: str = None): model = None model = transformers.Pix2StructForConditionalGeneration.from_pretrained( repo, + torch_dtype=devices.dtype, cache_dir=shared.opts.hfcache_dir, ) - model.to(devices.device) processor = transformers.Pix2StructProcessor.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) loaded = repo devices.torch_gc() @@ -431,11 +435,11 @@ def moondream(question: str, image: Image.Image, repo: str = None): repo, revision="2025-06-21", trust_remote_code=True, - cache_dir=shared.opts.hfcache_dir + torch_dtype=devices.dtype, + cache_dir=shared.opts.hfcache_dir, ) processor = transformers.AutoTokenizer.from_pretrained(repo, cache_dir=shared.opts.hfcache_dir) loaded = repo - model.to(devices.device, devices.dtype) model.eval() devices.torch_gc() sd_models.move_model(model, devices.device) @@ -475,12 +479,13 @@ def florence(question: str, image: Image.Image, repo: str = None, revision: str repo, trust_remote_code=True, revision=revision, + torch_dtype=devices.dtype, cache_dir=shared.opts.hfcache_dir, + **quant_args, ) processor = transformers.AutoProcessor.from_pretrained(repo, trust_remote_code=True, revision=revision, cache_dir=shared.opts.hfcache_dir) transformers.dynamic_module_utils.get_imports = _get_imports loaded = repo - model.to(devices.device, devices.dtype) model.eval() devices.torch_gc() sd_models.move_model(model, devices.device) @@ -512,7 +517,6 @@ def sa2(question: str, image: Image.Image, repo: str = None): low_cpu_mem_usage=True, use_flash_attn=False, trust_remote_code=True) - model = model.to(devices.device, devices.dtype) model = model.eval() processor = transformers.AutoTokenizer.from_pretrained( repo, @@ -539,9 +543,11 @@ def sa2(question: str, image: Image.Image, repo: str = None): def interrogate(question:str='', system_prompt:str=None, prompt:str=None, image:Image.Image=None, model_name:str=None, quiet:bool=False): + global quant_args # pylint: disable=global-statement if not quiet: shared.state.begin('Interrogate') t0 = time.time() + quant_args = model_quant.create_config(module='LLM') model_name = model_name or shared.opts.interrogate_vlm_model if isinstance(image, list): image = image[0] if len(image) > 0 else None @@ -560,8 +566,10 @@ def interrogate(question:str='', system_prompt:str=None, prompt:str=None, image: if shared.native and shared.sd_loaded: from modules.sd_models import apply_balanced_offload # prevent circular import apply_balanced_offload(shared.sd_model) + from modules import modelloader modelloader.hf_login() + try: if model_name is None: shared.log.error(f'Interrogate: type=vlm model="{model_name}" no model selected') @@ -573,6 +581,7 @@ def interrogate(question:str='', system_prompt:str=None, prompt:str=None, image: if image is None: shared.log.error(f'Interrogate: type=vlm model="{model_name}" no input image') return '' + if 'git' in vqa_model.lower(): answer = git(question, image, vqa_model) elif 'vilt' in vqa_model.lower(): @@ -611,9 +620,10 @@ def interrogate(question:str='', system_prompt:str=None, prompt:str=None, image: except Exception as e: errors.display(e, 'VQA') answer = 'error' + if shared.opts.interrogate_offload and model is not None: - sd_models.move_model(model, devices.cpu) - devices.torch_gc() + sd_models.move_model(model, devices.cpu, force=True) + devices.torch_gc(force=True) answer = clean(answer, question) t1 = time.time() if not quiet: diff --git a/wiki b/wiki index b95139063..45c389a37 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit b95139063c469ab11a379804ddb606ccfaf6c5c6 +Subproject commit 45c389a3747cb1caceb4035535041c400c29cad2 From ff103bc849141dc7fcfd964b33c0562da8c8d488 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 29 Jun 2025 12:52:37 -0400 Subject: [PATCH 79/85] fix LoRA change detection on pipeline type change Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 1 + TODO.md | 4 ++-- modules/lora/extra_networks_lora.py | 8 ++++++-- modules/sd_models.py | 2 ++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d78d96980..8428ad8ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ - Fix process batch double image save - Fix unapply texture tiling - Fix nunchaku batch support + - Fix LoRA change detection on pipeline type change - Suppress torch empty logging - Improve TAESD live preview downscale handling diff --git a/TODO.md b/TODO.md index 17963a7af..abf0c8d5a 100644 --- a/TODO.md +++ b/TODO.md @@ -8,9 +8,8 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - Refactor: Move `model_*` stuff into subfolder - Refactor: sampler options -- Fix LoRA unload for HiRes - Simplify Control tab -- Common repo for T5 and CLiP +- Common repo for `T5` and `CLiP` - Upgrade: unblock `numpy`: see `gradio` - Upgrade: unblock `pydantic`: see @@ -24,6 +23,7 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - [IPAdapter negative guidance](https://github.com/huggingface/diffusers/discussions/7167) - [IPAdapter composition](https://huggingface.co/ostris/ip-composition-adapter) +- [Refactor attention](https://github.com/huggingface/diffusers/pull/11311) - [STG](https://github.com/huggingface/diffusers/blob/main/examples/community/README.md#spatiotemporal-skip-guidance) - [LBM](https://github.com/gojasper/LBM) - [SmoothCache](https://github.com/huggingface/diffusers/issues/11135) diff --git a/modules/lora/extra_networks_lora.py b/modules/lora/extra_networks_lora.py index 40982caac..6f5e1b42a 100644 --- a/modules/lora/extra_networks_lora.py +++ b/modules/lora/extra_networks_lora.py @@ -141,10 +141,14 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): def changed(self, requested: List[str], include: List[str], exclude: List[str]): if shared.opts.lora_force_reload: return True - sd_model = getattr(shared.sd_model, "pipe", shared.sd_model) + sd_model = shared.sd_model.pipe if hasattr(shared.sd_model, 'pipe') else shared.sd_model if not hasattr(sd_model, 'loaded_loras'): sd_model.loaded_loras = {} - key = f'{",".join(include)}:{",".join(exclude)}' + if include is None or len(include) == 0: + include = ['all'] + if exclude is None or len(exclude) == 0: + exclude = ['none'] + key = f'include={",".join(include)}:exclude={",".join(exclude)}' loaded = sd_model.loaded_loras.get(key, []) debug_log(f'Network load: type=LoRA key="{key}" requested={requested} loaded={loaded}') if len(requested) != len(loaded): diff --git a/modules/sd_models.py b/modules/sd_models.py index 8bd122a72..e998abefd 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -858,6 +858,7 @@ def set_diffuser_pipe(pipe, new_pipe_type): sd_checkpoint_info = getattr(pipe, "sd_checkpoint_info", None) sd_model_checkpoint = getattr(pipe, "sd_model_checkpoint", None) embedding_db = getattr(pipe, "embedding_db", None) + loaded_loras = getattr(pipe, "loaded_loras", None) sd_model_hash = getattr(pipe, "sd_model_hash", None) has_accelerate = getattr(pipe, "has_accelerate", None) current_attn_name = getattr(pipe, "current_attn_name", None) @@ -910,6 +911,7 @@ def set_diffuser_pipe(pipe, new_pipe_type): new_pipe.has_accelerate = has_accelerate new_pipe.current_attn_name = current_attn_name new_pipe.default_scheduler = default_scheduler + new_pipe.loaded_loras = loaded_loras if loaded_loras is not None else {} if image_encoder is not None: new_pipe.image_encoder = image_encoder if feature_extractor is not None: From 198382c027e99336bdc2b6a2fca72429293f3b95 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 29 Jun 2025 12:58:54 -0400 Subject: [PATCH 80/85] cleanup Signed-off-by: Vladimir Mandic --- modules/processing_class.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/processing_class.py b/modules/processing_class.py index 98e8d1c96..fc611b3ce 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -258,7 +258,8 @@ class StableDiffusionProcessing: self.all_subseeds = None # a1111 compatibility items - shared.opts.data['clip_skip'] = int(self.clip_skip) # for compatibility with a1111 sd_hijack_clip + if not shared.native: + shared.opts.data['clip_skip'] = int(self.clip_skip) # for compatibility with a1111 sd_hijack_clip self.seed_enable_extras: bool = True self.is_using_inpainting_conditioning = False # a111 compatibility self.batch_index = 0 From f3d6245511d43f4631a9a28c70a39ab6c51a2f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= <1809172+Trojaner@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:20:31 +0300 Subject: [PATCH 81/85] Fix not applying text encoders of LoRAs on prompts which caused the related LoRAs to load --- modules/processing_args.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/processing_args.py b/modules/processing_args.py index 7721fe371..36f637873 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -148,6 +148,8 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t steps = kwargs.get("num_inference_steps", None) or len(getattr(p, 'timesteps', ['1'])) clip_skip = kwargs.pop("clip_skip", 1) + extra_networks.activate(p, include=['text_encoder', 'text_encoder_2', 'text_encoder_3']) + parser = 'fixed' prompt_attention = prompt_attention or shared.opts.prompt_attention if (prompt_attention != 'fixed') and ('Onnx' not in model.__class__.__name__) and ('prompt' not in p.task_args) and ( @@ -168,7 +170,6 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t else: prompt_parser_diffusers.embedder = None - extra_networks.activate(p, include=['text_encoder', 'text_encoder_2', 'text_encoder_3']) if 'prompt' in possible: if 'OmniGen' in model.__class__.__name__: prompts = [p.replace('|image|', '<|image_1|>') for p in prompts] From 1eab847a8e275f3390ea0ea3d726c6eb797ec347 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 29 Jun 2025 21:31:09 -0400 Subject: [PATCH 82/85] update changelog Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8428ad8ec..6e637bee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2025-06-27 +## Update for 2025-06-29 - **Models** - [Models Wiki page](https://vladmandic.github.io/sdnext-docs/Models/) is updated will all new models @@ -77,7 +77,8 @@ - Fix process batch double image save - Fix unapply texture tiling - Fix nunchaku batch support - - Fix LoRA change detection on pipeline type change + - Fix LoRA change detection on pipeline type change + - Fix LoRA load order when it includes text-encoder data - Suppress torch empty logging - Improve TAESD live preview downscale handling From 5d2f5dd6e7dbacd3e2ba648517548b43fdde80cc Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 30 Jun 2025 08:19:26 -0400 Subject: [PATCH 83/85] fix typo Signed-off-by: Vladimir Mandic --- modules/postprocess/yolo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/postprocess/yolo.py b/modules/postprocess/yolo.py index d3b148805..67dbb7b4d 100644 --- a/modules/postprocess/yolo.py +++ b/modules/postprocess/yolo.py @@ -383,7 +383,7 @@ class YoloRestorer(Detailer): max_size = shared.opts.detailer_max_size if shared.opts.detailer_max_size < 1 and shared.opts.detailer_max_size > 0 else 1.0 max_size = gr.Slider(label="Max size", elem_id=f"{tab}_detailer_max_size", value=max_size, minimum=0.0, maximum=1.0, step=0.05) with gr.Row(elem_classes=['flex-break']): - renoise_value = gr.Slider(minimum=0.5, maximum=1.5, step=0.01, label='Renoiose', value=shared.opts.detailer_sigma_adjust, elem_id=f"{tab}_detailer_renoise") + renoise_value = gr.Slider(minimum=0.5, maximum=1.5, step=0.01, label='Renoise', value=shared.opts.detailer_sigma_adjust, elem_id=f"{tab}_detailer_renoise") renoise_end = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Renoise end', value=shared.opts.detailer_sigma_adjust_max, elem_id=f"{tab}_detailer_renoise_end") detailers.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) classes.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) From e5c625e9b53e193bd1b317e89674d9fd788f52d6 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 30 Jun 2025 08:28:09 -0400 Subject: [PATCH 84/85] update changelog Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e637bee6..bc7e9f782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Change Log for SD.Next -## Update for 2025-06-29 +## Update for 2025-06-30 + +### Highlights for 2025-06-30 + +New release with ~100 commits...So what's new? Well, its been a busy few weeks with new models coming out quite frequently: +- New T2I/I2I models: **OmniGen-2, Cosmos-Predict2, FLUX.1-Kontext, Chroma** +- Additional VLM models: **JoyCaption Beta, MoonDream 2** +- Additional upscalers: **UltraSharp v2** + +And (as always) many bugfixes and improvements to existing features! + +[ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) + +### Details for 2025-06-30 - **Models** - [Models Wiki page](https://vladmandic.github.io/sdnext-docs/Models/) is updated will all new models @@ -88,24 +101,20 @@ - Support for Python 3.13 - TeaCache support for Lumina 2 - Custom UNet and VAE loading support for Lumina 2 - - **Changes** - Increase the medvram mode threshold from 8GB to 12GB - Set CPU backend to use FP32 by default - Relax Python version checks for Zluda - Make VAE options not require model reload - Add warning about incompatible attention processors - - **Torch** - Set default to `torch==2.7.1` - Force upgrade pip when installing Torch - - **ROCm** - Support ROCm 6.4 with `--use-nightly` - Don't override user set gfx version - Don't override gfx version with RX 9000 - Fix flash-atten repo - - **SDNQ Quantization** - Add group size support for convolutional layers - Add quantized matmul support for for convolutional layers @@ -116,7 +125,6 @@ - Fix VAE with conv quant - Don't ignore the Quantize with GPU option with offload mode `none` and `model` - High VRAM usage with Lumina 2 - - **Fixes** - Meissonic with multiple generators - OmniGen with new transformers From d6db6ff3496ce1251bedb17734823f917423dab4 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 30 Jun 2025 13:00:58 -0400 Subject: [PATCH 85/85] pre release cleanup Signed-off-by: Vladimir Mandic --- TODO.md | 1 - extensions-builtin/sdnext-modernui | 2 +- installer.py | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index abf0c8d5a..29a2dcf94 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - Refactor: Move `model_*` stuff into subfolder - Refactor: sampler options -- Simplify Control tab - Common repo for `T5` and `CLiP` - Upgrade: unblock `numpy`: see `gradio` - Upgrade: unblock `pydantic`: see diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index ab51d26c8..6ad9a291b 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit ab51d26c83d051ab6af2bf8647b36507e626787e +Subproject commit 6ad9a291b515041adbefdb3eb94f87fb2892c902 diff --git a/installer.py b/installer.py index e86b89cd6..4de6f8103 100644 --- a/installer.py +++ b/installer.py @@ -535,9 +535,9 @@ def check_python(supported_minors=[], experimental_minors=[], reason=None): log.info(f'Python: version={platform.python_version()} platform={platform.system()} bin="{sys.executable}" venv="{sys.prefix}"') if not (int(sys.version_info.major) == 3 and int(sys.version_info.minor) in supported_minors): if (int(sys.version_info.major) == 3 and int(sys.version_info.minor) in experimental_minors): - log.warning(f"Python version experimental: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} recommended 3.{supported_minors}") + log.warning(f"Python experimental: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}") else: - log.error(f"Python version incompatible: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} required 3.{supported_minors}") + log.error(f"Python incompatible: current {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} required 3.{supported_minors}") if reason is not None: log.error(reason) if not args.ignore and not args.experimental: