diff --git a/CHANGELOG.md b/CHANGELOG.md index b3bea1252..1f890337f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2025-09-20 +## Update for 2025-09-21 - **Models** - [WAN 2.2 14B VACE](https://huggingface.co/alibaba-pai/Wan2.2-VACE-Fun-A14B) @@ -12,6 +12,8 @@ note that nunchaku optimized and prequantized unet is replacement for base unet, so its only applicable to base models, not any of finetunes *how to use*: enable nunchaku in settings -> quantization and then load either sdxl-base or sdxl-base-turbo reference models *note*: sdxl support for nunchaku is not in released version of `nunchaku==1.0.0`, so you need to build [nunchaku](https://nunchaku.tech/docs/nunchaku/installation/installation.html) from source + - [Nunchaku Flux.1 PulID](https://nunchaku.tech/docs/nunchaku/python_api/nunchaku.pipeline.pipeline_flux_pulid.html) + automatically enabled if loaded model is FLUX.1 with Nunchaku engine enabled and when PulID script is enabled - **Offloading** - improve offloading for pipelines with multiple stages such as *wan-2.2-14b* - add timers to measure onload/offload times during generate diff --git a/installer.py b/installer.py index 618e9958d..b31ef0e7e 100644 --- a/installer.py +++ b/installer.py @@ -1269,9 +1269,12 @@ def install_pydantic(): def install_insightface(): install('git+https://github.com/deepinsight/insightface@29b6cd65aa0e9ae3b6602de3c52e9d8949c8ee86#subdirectory=python-package', 'insightface') # insightface==0.7.3 with patches - # install('albumentations==1.4.3', ignore=True, quiet=True) - uninstall('albumentations') - install('albumentationsx') + if args.new: + uninstall('albumentations') + install('albumentationsx') + else: + uninstall('albumentationsx') + install('albumentations==1.4.3', ignore=True, quiet=True) install_pydantic() diff --git a/scripts/pulid/__init__.py b/scripts/pulid/__init__.py index 958ee9c1e..864b758d1 100644 --- a/scripts/pulid/__init__.py +++ b/scripts/pulid/__init__.py @@ -8,6 +8,7 @@ from modules.errors import log sys.path.append(os.path.dirname(__file__)) try: from pulid_sdxl import StableDiffusionXLPuLIDPipeline, StableDiffusionXLPuLIDPipelineImage, StableDiffusionXLPuLIDPipelineInpaint + from pulid_flux import apply_flux, unapply_flux from pulid_utils import resize_numpy_image_long as resize import attention_processor as attention import pulid_sampling as sampling diff --git a/scripts/pulid/pulid_flux.py b/scripts/pulid/pulid_flux.py new file mode 100644 index 000000000..a419ca9c0 --- /dev/null +++ b/scripts/pulid/pulid_flux.py @@ -0,0 +1,35 @@ +from types import MethodType +import accelerate +from diffusers import FluxPipeline +from modules import shared, sd_models + + +def apply_flux(pipe: FluxPipeline): + if not hasattr(pipe, 'transformer') or not 'Nunchaku' in pipe.transformer.__class__.__name__: + shared.log.error('PuLID: flux support requires nunchaku') + return pipe + + from nunchaku.pipeline.pipeline_flux_pulid import PuLIDFluxPipeline + if not isinstance(pipe, PuLIDFluxPipeline): + from nunchaku.models.pulid.pulid_forward import pulid_forward + sd_models.clear_caches(full=True) + accelerate.hooks.remove_hook_from_module(pipe.transformer, recurse=True) + pipe = sd_models.switch_pipe(PuLIDFluxPipeline, pipe) + pipe.transformer.orig_forward = pipe.transformer.forward + pipe.transformer.forward = MethodType(pulid_forward, pipe.transformer) + pipe = sd_models.apply_balanced_offload(pipe) + pipe.pulid_model = sd_models.apply_balanced_offload(pipe.pulid_model) + shared.log.info(f'PuLID: flux applied cls={pipe.__class__.__name__} pipe={pipe.pulid_model.__class__.__name__}') + return pipe + + +def unapply_flux(pipe: FluxPipeline): + from nunchaku.pipeline.pipeline_flux_pulid import PuLIDFluxPipeline + if isinstance(pipe, PuLIDFluxPipeline) and hasattr(pipe.transformer, 'orig_forward'): + sd_models.clear_caches(full=True) + accelerate.hooks.remove_hook_from_module(pipe.transformer, recurse=True) + pipe.transformer.forward = MethodType(pipe.transformer.orig_forward, pipe.transformer) + del pipe.transformer.orig_forward + pipe = sd_models.switch_pipe(FluxPipeline, pipe) + pipe = sd_models.apply_balanced_offload(pipe) + return pipe diff --git a/scripts/pulid_ext.py b/scripts/pulid_ext.py index df9a099e6..ede691902 100644 --- a/scripts/pulid_ext.py +++ b/scripts/pulid_ext.py @@ -200,14 +200,12 @@ class Script(scripts_manager.Script): errors.display(e, 'PuLID') return None elif shared.sd_model_type == 'f1': - # TODO nunchaku: pulid-f1 - shared.log.error('PuLID: f1 not supported') - return None + shared.sd_model = self.pulid.apply_flux(shared.sd_model) if shared.sd_model_type == 'sdxl': processed = self.run_sdxl(p, images, strength, zero, sampler, ortho, restore, offload, version) elif shared.sd_model_type == 'f1': - processed = None + processed = self.run_flux(p, images, strength) else: shared.log.error(f'PuLID: class={shared.sd_model.__class__.__name__} model={shared.sd_model_type} required={supported_model_list}') processed = None @@ -227,6 +225,12 @@ class Script(scripts_manager.Script): shared.sd_model = shared.sd_model.pipe devices.torch_gc(force=True, reason='pulid') shared.log.debug(f'PuLID complete: class={shared.sd_model.__class__.__name__} preprocess={self.preprocess:.2f} pipe={"restore" if restore else "cache"}') + if shared.sd_model_type == "f1": + restore = getattr(p, 'pulid_restore', restore) + if restore: + shared.sd_model = self.pulid.unapply_flux(shared.sd_model) + devices.torch_gc(force=True, reason='pulid') + shared.log.debug(f'PuLID complete: class={shared.sd_model.__class__.__name__} pipe={"restore" if restore else "cache"}') return processed def run_sdxl(self, p: processing.StableDiffusionProcessing, images: list, strength: float, zero: int, sampler: str, ortho: str, restore: bool, offload: bool, version: str): @@ -285,3 +289,13 @@ class Script(scripts_manager.Script): # interim = [Image.fromarray(img) for img in shared.sd_model.debug_img_list] # shared.log.debug(f'PuLID: time={t1-t0:.2f}') return processed + + def run_flux(self, p: processing.StableDiffusionProcessing, images: list, strength: float): + image = Image.fromarray(images[0]) # takes single pil image + p.task_args['id_image'] = image + p.task_args['id_weight'] = strength + shared.log.info(f'PuLID: class={shared.sd_model.__class__.__name__} strength={strength} image={image}') + p.extra_generation_params["PuLID"] = f'Strength={strength}' + + processed: processing.Processed = processing.process_images(p) # runs processing using main loop + return processed