From a25334b3520c86f642a7d8e59296315470541fbd Mon Sep 17 00:00:00 2001 From: Yingda Chen Date: Tue, 8 Jul 2025 17:15:21 +0800 Subject: [PATCH 01/51] Add files via upload --- .github/workflows/logo.gif | Bin 0 -> 149067 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/workflows/logo.gif diff --git a/.github/workflows/logo.gif b/.github/workflows/logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..186290f877da955714a6ad5c3c412beae860ccff GIT binary patch literal 149067 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∈g9<^M6N$Vdh&LpY_b^EUlw8}uJ1 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�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

);A_s0ie(S|1Fpr{^d4D#L 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 X!A|!sZ}B8g_!DpVG*9+*6c7MA-&Z?f literal 0 HcmV?d00001 From 3a3d9010b89a7fef8a1a6c1f6ac8f7fcc7f49e60 Mon Sep 17 00:00:00 2001 From: Yingda Chen Date: Tue, 8 Jul 2025 17:24:39 +0800 Subject: [PATCH 02/51] Update README.md --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3c93df8..2633a1b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -# DiffSynth Studio +

DiffSynth-Studio
+

+ +

+ [![PyPI](https://img.shields.io/pypi/v/DiffSynth)](https://pypi.org/project/DiffSynth/) [![license](https://img.shields.io/github/license/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/blob/master/LICENSE) [![open issues](https://isitmaintained.com/badge/open/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/issues) @@ -9,7 +13,8 @@ modelscope%2FDiffSynth-Studio | Trendshift

-Document: https://diffsynth-studio.readthedocs.io/zh-cn/latest/index.html +## Documentation +Refer to [Diffsynth-Studio Docs](https://diffsynth-studio.readthedocs.io/zh-cn/latest/index.html). ## Introduction From 629e9be4ce1909fac82f327801f01223a8fc4ba9 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 8 Jul 2025 19:55:27 +0800 Subject: [PATCH 03/51] support flux any training --- diffsynth/models/lora.py | 5 +-- diffsynth/pipelines/flux_image_new.py | 20 ++++++----- .../model_training/full/FLEX.2-preview.sh | 12 +++++++ .../FLUX.1-dev-Controlnet-Inpainting-Beta.sh | 14 ++++++++ .../full/FLUX.1-dev-Controlnet-Union-alpha.sh | 14 ++++++++ .../full/FLUX.1-dev-Controlnet-Upscaler.sh | 14 ++++++++ .../full/FLUX.1-dev-InfiniteYou.sh | 14 ++++++++ .../flux/model_training/full/Step1X-Edit.sh | 14 ++++++++ .../model_training/lora/FLEX.2-preview.sh | 15 +++++++++ .../FLUX.1-dev-Controlnet-Inpainting-Beta.sh | 17 ++++++++++ .../lora/FLUX.1-dev-Controlnet-Union-alpha.sh | 17 ++++++++++ .../lora/FLUX.1-dev-Controlnet-Upscaler.sh | 17 ++++++++++ .../lora/FLUX.1-dev-IP-Adapter.sh | 17 ++++++++++ .../lora/FLUX.1-dev-InfiniteYou.sh | 17 ++++++++++ .../flux/model_training/lora/Step1X-Edit.sh | 17 ++++++++++ examples/flux/model_training/train.py | 13 ++++++-- .../validate_full/FLEX.2-preview.py | 20 +++++++++++ .../FLUX.1-dev-Controlnet-Inpainting-Beta.py | 31 +++++++++++++++++ .../FLUX.1-dev-Controlnet-Union-alpha.py | 31 +++++++++++++++++ .../FLUX.1-dev-Controlnet-Upscaler.py | 30 +++++++++++++++++ .../validate_full/FLUX.1-dev-InfiniteYou.py | 33 +++++++++++++++++++ .../validate_full/Step1X-Edit.py | 25 ++++++++++++++ .../validate_lora/FLEX.2-preview.py | 18 ++++++++++ .../FLUX.1-dev-Controlnet-Inpainting-Beta.py | 29 ++++++++++++++++ .../FLUX.1-dev-Controlnet-Union-alpha.py | 29 ++++++++++++++++ .../FLUX.1-dev-Controlnet-Upscaler.py | 28 ++++++++++++++++ .../validate_lora/FLUX.1-dev-IP-Adapter.py | 26 +++++++++++++++ .../validate_lora/FLUX.1-dev-InfiniteYou.py | 28 ++++++++++++++++ .../validate_lora/Step1X-Edit.py | 23 +++++++++++++ 29 files changed, 575 insertions(+), 13 deletions(-) create mode 100644 examples/flux/model_training/full/FLEX.2-preview.sh create mode 100644 examples/flux/model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh create mode 100644 examples/flux/model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh create mode 100644 examples/flux/model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh create mode 100644 examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh create mode 100644 examples/flux/model_training/full/Step1X-Edit.sh create mode 100644 examples/flux/model_training/lora/FLEX.2-preview.sh create mode 100644 examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh create mode 100644 examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh create mode 100644 examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh create mode 100644 examples/flux/model_training/lora/FLUX.1-dev-IP-Adapter.sh create mode 100644 examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh create mode 100644 examples/flux/model_training/lora/Step1X-Edit.sh create mode 100644 examples/flux/model_training/validate_full/FLEX.2-preview.py create mode 100644 examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py create mode 100644 examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py create mode 100644 examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py create mode 100644 examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py create mode 100644 examples/flux/model_training/validate_full/Step1X-Edit.py create mode 100644 examples/flux/model_training/validate_lora/FLEX.2-preview.py create mode 100644 examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py create mode 100644 examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py create mode 100644 examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py create mode 100644 examples/flux/model_training/validate_lora/FLUX.1-dev-IP-Adapter.py create mode 100644 examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py create mode 100644 examples/flux/model_training/validate_lora/Step1X-Edit.py diff --git a/diffsynth/models/lora.py b/diffsynth/models/lora.py index 05e1d99..11b34e3 100644 --- a/diffsynth/models/lora.py +++ b/diffsynth/models/lora.py @@ -277,7 +277,7 @@ class FluxLoRAConverter: pass @staticmethod - def align_to_opensource_format(state_dict, alpha=1.0): + def align_to_opensource_format(state_dict, alpha=None): prefix_rename_dict = { "single_blocks": "lora_unet_single_blocks", "blocks": "lora_unet_double_blocks", @@ -316,7 +316,8 @@ class FluxLoRAConverter: rename = prefix_rename_dict[prefix] + "_" + block_id + "_" + middle_rename_dict[middle] + "." + suffix_rename_dict[suffix] state_dict_[rename] = param if rename.endswith("lora_up.weight"): - state_dict_[rename.replace("lora_up.weight", "alpha")] = torch.tensor((alpha,))[0] + lora_alpha = alpha if alpha is not None else param.shape[-1] + state_dict_[rename.replace("lora_up.weight", "alpha")] = torch.tensor((lora_alpha,))[0] return state_dict_ @staticmethod diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index c8985dc..4b7c68d 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -704,7 +704,8 @@ class FluxImageUnit_Step1x(PipelineUnit): image = pipe.preprocess_image(image).to(device=pipe.device, dtype=pipe.torch_dtype) image = pipe.vae_encoder(image) inputs_posi.update({"step1x_llm_embedding": embs[0:1], "step1x_mask": masks[0:1], "step1x_reference_latents": image}) - inputs_nega.update({"step1x_llm_embedding": embs[1:2], "step1x_mask": masks[1:2], "step1x_reference_latents": image}) + if inputs_shared.get("cfg_scale", 1) != 1: + inputs_nega.update({"step1x_llm_embedding": embs[1:2], "step1x_mask": masks[1:2], "step1x_reference_latents": image}) return inputs_shared, inputs_posi, inputs_nega @@ -727,6 +728,8 @@ class FluxImageUnit_Flex(PipelineUnit): def process(self, pipe: FluxImagePipeline, latents, flex_inpaint_image, flex_inpaint_mask, flex_control_image, flex_control_strength, flex_control_stop, tiled, tile_size, tile_stride): if pipe.dit.input_dim == 196: + if flex_control_stop is None: + flex_control_stop = 1 pipe.load_models_to_device(self.onload_model_names) if flex_inpaint_image is None: flex_inpaint_image = torch.zeros_like(latents) @@ -760,14 +763,15 @@ class FluxImageUnit_InfiniteYou(PipelineUnit): def process(self, pipe: FluxImagePipeline, infinityou_id_image, infinityou_guidance): if infinityou_id_image is not None: - return pipe.infinityou_processor.prepare_infinite_you(pipe.image_proj_model, infinityou_id_image, infinityou_guidance) + return pipe.infinityou_processor.prepare_infinite_you(pipe.image_proj_model, infinityou_id_image, infinityou_guidance, pipe.device) else: return {} -class InfinitYou: +class InfinitYou(torch.nn.Module): def __init__(self, device="cuda", torch_dtype=torch.bfloat16): + super().__init__() from facexlib.recognition import init_recognition_model from insightface.app import FaceAnalysis self.device = device @@ -791,16 +795,16 @@ class InfinitYou: face_info = self.app_160.get(id_image_cv2) return face_info - def extract_arcface_bgr_embedding(self, in_image, landmark): + def extract_arcface_bgr_embedding(self, in_image, landmark, device): from insightface.utils import face_align arc_face_image = face_align.norm_crop(in_image, landmark=np.array(landmark), image_size=112) arc_face_image = torch.from_numpy(arc_face_image).unsqueeze(0).permute(0, 3, 1, 2) / 255. arc_face_image = 2 * arc_face_image - 1 - arc_face_image = arc_face_image.contiguous().to(self.device) + arc_face_image = arc_face_image.contiguous().to(device=device, dtype=self.torch_dtype) face_emb = self.arcface_model(arc_face_image)[0] # [512], normalized return face_emb - def prepare_infinite_you(self, model, id_image, infinityou_guidance): + def prepare_infinite_you(self, model, id_image, infinityou_guidance, device): import cv2 if id_image is None: return {'id_emb': None} @@ -809,9 +813,9 @@ class InfinitYou: if len(face_info) == 0: raise ValueError('No face detected in the input ID image') landmark = sorted(face_info, key=lambda x:(x['bbox'][2]-x['bbox'][0])*(x['bbox'][3]-x['bbox'][1]))[-1]['kps'] # only use the maximum face - id_emb = self.extract_arcface_bgr_embedding(id_image_cv2, landmark) + id_emb = self.extract_arcface_bgr_embedding(id_image_cv2, landmark, device) id_emb = model(id_emb.unsqueeze(0).reshape([1, -1, 512]).to(dtype=self.torch_dtype)) - infinityou_guidance = torch.Tensor([infinityou_guidance]).to(device=self.device, dtype=self.torch_dtype) + infinityou_guidance = torch.Tensor([infinityou_guidance]).to(device=device, dtype=self.torch_dtype) return {'id_emb': id_emb, 'infinityou_guidance': infinityou_guidance} diff --git a/examples/flux/model_training/full/FLEX.2-preview.sh b/examples/flux/model_training/full/FLEX.2-preview.sh new file mode 100644 index 0000000..451aeb5 --- /dev/null +++ b/examples/flux/model_training/full/FLEX.2-preview.sh @@ -0,0 +1,12 @@ +accelerate launch --config_file examples/flux/model_training/full/accelerate_config.yaml examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata.csv \ + --max_pixels 1048576 \ + --dataset_repeat 200 \ + --model_id_with_origin_paths "ostris/Flex.2-preview:Flex.2-preview.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLEX.2-preview_full" \ + --trainable_models "dit" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh b/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh new file mode 100644 index 0000000..1ef6a40 --- /dev/null +++ b/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh @@ -0,0 +1,14 @@ +accelerate launch --config_file examples/flux/model_training/full/accelerate_config.yaml examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_controlnet_inpaint.csv \ + --data_file_keys "image,controlnet_image,controlnet_inpaint_mask" \ + --max_pixels 1048576 \ + --dataset_repeat 400 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta:diffusion_pytorch_model.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe.controlnet.models.0." \ + --output_path "./models/train/FLUX.1-dev-Controlnet-Inpainting-Beta_full" \ + --trainable_models "controlnet" \ + --extra_inputs "controlnet_image,controlnet_inpaint_mask" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh b/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh new file mode 100644 index 0000000..f905bca --- /dev/null +++ b/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh @@ -0,0 +1,14 @@ +accelerate launch --config_file examples/flux/model_training/full/accelerate_config.yaml examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_controlnet_canny.csv \ + --data_file_keys "image,controlnet_image" \ + --max_pixels 1048576 \ + --dataset_repeat 400 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,InstantX/FLUX.1-dev-Controlnet-Union-alpha:diffusion_pytorch_model.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe.controlnet.models.0." \ + --output_path "./models/train/FLUX.1-dev-Controlnet-Union-alpha_full" \ + --trainable_models "controlnet" \ + --extra_inputs "controlnet_image,controlnet_processor_id" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh b/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh new file mode 100644 index 0000000..e2dd5d8 --- /dev/null +++ b/examples/flux/model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh @@ -0,0 +1,14 @@ +accelerate launch --config_file examples/flux/model_training/full/accelerate_config.yaml examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_controlnet_upscale.csv \ + --data_file_keys "image,controlnet_image" \ + --max_pixels 1048576 \ + --dataset_repeat 400 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,jasperai/Flux.1-dev-Controlnet-Upscaler:diffusion_pytorch_model.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe.controlnet.models.0." \ + --output_path "./models/train/FLUX.1-dev-Controlnet-Upscaler_full" \ + --trainable_models "controlnet" \ + --extra_inputs "controlnet_image" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh b/examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh new file mode 100644 index 0000000..6040fa5 --- /dev/null +++ b/examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh @@ -0,0 +1,14 @@ +accelerate launch --config_file examples/flux/model_training/full/accelerate_config.yaml examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_infiniteyou.csv \ + --data_file_keys "image,controlnet_image,infinityou_id_image" \ + --max_pixels 1048576 \ + --dataset_repeat 400 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,ByteDance/InfiniteYou:infu_flux_v1.0/aes_stage2/image_proj_model.bin,ByteDance/InfiniteYou:infu_flux_v1.0/aes_stage2/InfuseNetModel/*.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe." \ + --output_path "./models/train/FLUX.1-dev-InfiniteYou_full" \ + --trainable_models "controlnet,image_proj_model" \ + --extra_inputs "controlnet_image,infinityou_id_image,infinityou_guidance" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/full/Step1X-Edit.sh b/examples/flux/model_training/full/Step1X-Edit.sh new file mode 100644 index 0000000..98c45ce --- /dev/null +++ b/examples/flux/model_training/full/Step1X-Edit.sh @@ -0,0 +1,14 @@ +accelerate launch --config_file examples/flux/model_training/full/accelerate_config.yaml examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_step1x.csv \ + --data_file_keys "image,step1x_reference_image" \ + --max_pixels 1048576 \ + --dataset_repeat 400 \ + --model_id_with_origin_paths "Qwen/Qwen2.5-VL-7B-Instruct:,stepfun-ai/Step1X-Edit:step1x-edit-i1258.safetensors,stepfun-ai/Step1X-Edit:vae.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Step1X-Edit_full" \ + --trainable_models "dit" \ + --extra_inputs "step1x_reference_image" \ + --use_gradient_checkpointing_offload diff --git a/examples/flux/model_training/lora/FLEX.2-preview.sh b/examples/flux/model_training/lora/FLEX.2-preview.sh new file mode 100644 index 0000000..6abeb57 --- /dev/null +++ b/examples/flux/model_training/lora/FLEX.2-preview.sh @@ -0,0 +1,15 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata.csv \ + --max_pixels 1048576 \ + --dataset_repeat 50 \ + --model_id_with_origin_paths "ostris/Flex.2-preview:Flex.2-preview.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLEX.2-preview_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --align_to_opensource_format \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh b/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh new file mode 100644 index 0000000..0de6a30 --- /dev/null +++ b/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_controlnet_inpaint.csv \ + --data_file_keys "image,controlnet_image,controlnet_inpaint_mask" \ + --max_pixels 1048576 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta:diffusion_pytorch_model.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-dev-Controlnet-Inpainting-Beta_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --extra_inputs "controlnet_image,controlnet_inpaint_mask" \ + --align_to_opensource_format \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh b/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh new file mode 100644 index 0000000..5a65af8 --- /dev/null +++ b/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_controlnet_canny.csv \ + --data_file_keys "image,controlnet_image" \ + --max_pixels 1048576 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,InstantX/FLUX.1-dev-Controlnet-Union-alpha:diffusion_pytorch_model.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-dev-Controlnet-Union-alpha_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --extra_inputs "controlnet_image,controlnet_processor_id" \ + --align_to_opensource_format \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh b/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh new file mode 100644 index 0000000..72e8971 --- /dev/null +++ b/examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_controlnet_upscale.csv \ + --data_file_keys "image,controlnet_image" \ + --max_pixels 1048576 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,jasperai/Flux.1-dev-Controlnet-Upscaler:diffusion_pytorch_model.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-dev-Controlnet-Upscaler_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --extra_inputs "controlnet_image" \ + --align_to_opensource_format \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/lora/FLUX.1-dev-IP-Adapter.sh b/examples/flux/model_training/lora/FLUX.1-dev-IP-Adapter.sh new file mode 100644 index 0000000..0495c11 --- /dev/null +++ b/examples/flux/model_training/lora/FLUX.1-dev-IP-Adapter.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_ipadapter.csv \ + --data_file_keys "image,ipadapter_images" \ + --max_pixels 1048576 \ + --dataset_repeat 50 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,InstantX/FLUX.1-dev-IP-Adapter:ip-adapter.bin,google/siglip-so400m-patch14-384:" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-dev-IP-Adapter_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --extra_inputs "ipadapter_images" \ + --align_to_opensource_format \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh b/examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh new file mode 100644 index 0000000..b51a565 --- /dev/null +++ b/examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_infiniteyou.csv \ + --data_file_keys "image,controlnet_image,infinityou_id_image" \ + --max_pixels 1048576 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,ByteDance/InfiniteYou:infu_flux_v1.0/aes_stage2/image_proj_model.bin,ByteDance/InfiniteYou:infu_flux_v1.0/aes_stage2/InfuseNetModel/*.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-dev-InfiniteYou_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --extra_inputs "controlnet_image,infinityou_id_image,infinityou_guidance" \ + --align_to_opensource_format \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/lora/Step1X-Edit.sh b/examples/flux/model_training/lora/Step1X-Edit.sh new file mode 100644 index 0000000..01ac260 --- /dev/null +++ b/examples/flux/model_training/lora/Step1X-Edit.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_step1x.csv \ + --data_file_keys "image,step1x_reference_image" \ + --max_pixels 1048576 \ + --dataset_repeat 50 \ + --model_id_with_origin_paths "Qwen/Qwen2.5-VL-7B-Instruct:,stepfun-ai/Step1X-Edit:step1x-edit-i1258.safetensors,stepfun-ai/Step1X-Edit:vae.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Step1X-Edit_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --extra_inputs "step1x_reference_image" \ + --align_to_opensource_format \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/train.py b/examples/flux/model_training/train.py index 6717c9e..ca52ff4 100644 --- a/examples/flux/model_training/train.py +++ b/examples/flux/model_training/train.py @@ -1,5 +1,5 @@ import torch, os, json -from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput from diffsynth.trainers.utils import DiffusionTrainingModule, ImageDataset, ModelLogger, launch_training_task, flux_parser from diffsynth.models.lora import FluxLoRAConverter os.environ["TOKENIZERS_PARALLELISM"] = "false" @@ -51,7 +51,7 @@ class FluxTrainingModule(DiffusionTrainingModule): def forward_preprocess(self, data): # CFG-sensitive parameters inputs_posi = {"prompt": data["prompt"]} - inputs_nega = {} + inputs_nega = {"negative_prompt": ""} # CFG-unsensitive parameters inputs_shared = { @@ -72,8 +72,14 @@ class FluxTrainingModule(DiffusionTrainingModule): } # Extra inputs + controlnet_input = {} for extra_input in self.extra_inputs: - inputs_shared[extra_input] = data[extra_input] + if extra_input.startswith("controlnet_"): + controlnet_input[extra_input.replace("controlnet_", "")] = data[extra_input] + else: + inputs_shared[extra_input] = data[extra_input] + if len(controlnet_input) > 0: + inputs_shared["controlnet_inputs"] = [ControlNetInput(**controlnet_input)] # Pipeline units will automatically process the input parameters. for unit in self.pipe.units: @@ -100,6 +106,7 @@ if __name__ == "__main__": lora_base_model=args.lora_base_model, lora_target_modules=args.lora_target_modules, lora_rank=args.lora_rank, + use_gradient_checkpointing=args.use_gradient_checkpointing, use_gradient_checkpointing_offload=args.use_gradient_checkpointing_offload, extra_inputs=args.extra_inputs, ) diff --git a/examples/flux/model_training/validate_full/FLEX.2-preview.py b/examples/flux/model_training/validate_full/FLEX.2-preview.py new file mode 100644 index 0000000..78f64b6 --- /dev/null +++ b/examples/flux/model_training/validate_full/FLEX.2-preview.py @@ -0,0 +1,20 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from diffsynth import load_state_dict + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="ostris/Flex.2-preview", origin_file_pattern="Flex.2-preview.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) +state_dict = load_state_dict("models/train/FLEX.2-preview_full/epoch-0.safetensors") +pipe.dit.load_state_dict(state_dict) + +image = pipe(prompt="dog,white and brown dog, sitting on wall, under pink flowers", seed=0) +image.save("image_FLEX.2-preview_full.jpg") diff --git a/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py b/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py new file mode 100644 index 0000000..1d35f7e --- /dev/null +++ b/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py @@ -0,0 +1,31 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from diffsynth import load_state_dict +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta", origin_file_pattern="diffusion_pytorch_model.safetensors"), + ], +) +state_dict = load_state_dict("models/train/FLUX.1-dev-Controlnet-Inpainting-Beta_full/epoch-0.safetensors") +pipe.controlnet.models[0].load_state_dict(state_dict) + +image = pipe( + prompt="a cat sitting on a chair, wearing sunglasses", + controlnet_inputs=[ControlNetInput( + image=Image.open("data/example_image_dataset/inpaint/image_1.jpg"), + inpaint_mask=Image.open("data/example_image_dataset/inpaint/mask.jpg"), + scale=0.9 + )], + height=1024, width=1024, + seed=0, rand_device="cuda", +) +image.save("image_FLUX.1-dev-Controlnet-Inpainting-Beta_full.jpg") diff --git a/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py b/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py new file mode 100644 index 0000000..ceaadd8 --- /dev/null +++ b/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py @@ -0,0 +1,31 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from diffsynth import load_state_dict +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="InstantX/FLUX.1-dev-Controlnet-Union-alpha", origin_file_pattern="diffusion_pytorch_model.safetensors"), + ], +) +state_dict = load_state_dict("models/train/FLUX.1-dev-Controlnet-Union-alpha_full/epoch-0.safetensors") +pipe.controlnet.models[0].load_state_dict(state_dict) + +image = pipe( + prompt="a dog", + controlnet_inputs=[ControlNetInput( + image=Image.open("data/example_image_dataset/canny/image_1.jpg"), + scale=0.9, + processor_id="canny", + )], + height=768, width=768, + seed=0, rand_device="cuda", +) +image.save("image_FLUX.1-dev-Controlnet-Union-alpha_full.jpg") diff --git a/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py b/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py new file mode 100644 index 0000000..3ff8319 --- /dev/null +++ b/examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py @@ -0,0 +1,30 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from diffsynth import load_state_dict +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="jasperai/Flux.1-dev-Controlnet-Upscaler", origin_file_pattern="diffusion_pytorch_model.safetensors"), + ], +) +state_dict = load_state_dict("models/train/FLUX.1-dev-Controlnet-Upscaler_full/epoch-0.safetensors") +pipe.controlnet.models[0].load_state_dict(state_dict) + +image = pipe( + prompt="a dog", + controlnet_inputs=[ControlNetInput( + image=Image.open("data/example_image_dataset/upscale/image_1.jpg"), + scale=0.9 + )], + height=768, width=768, + seed=0, rand_device="cuda", +) +image.save("image_FLUX.1-dev-Controlnet-Upscaler_full.jpg") diff --git a/examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py b/examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py new file mode 100644 index 0000000..55b7275 --- /dev/null +++ b/examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py @@ -0,0 +1,33 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from diffsynth import load_state_dict +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="ByteDance/InfiniteYou", origin_file_pattern="infu_flux_v1.0/aes_stage2/image_proj_model.bin"), + ModelConfig(model_id="ByteDance/InfiniteYou", origin_file_pattern="infu_flux_v1.0/aes_stage2/InfuseNetModel/*.safetensors"), + ], +) +state_dict = load_state_dict("models/train/FLUX.1-dev-InfiniteYou_full/epoch-0.safetensors") +state_dict_projector = {i.replace("image_proj_model.", ""): state_dict[i] for i in state_dict if i.startswith("image_proj_model.")} +pipe.image_proj_model.load_state_dict(state_dict_projector) +state_dict_controlnet = {i.replace("controlnet.models.0.", ""): state_dict[i] for i in state_dict if i.startswith("controlnet.models.0.")} +pipe.controlnet.models[0].load_state_dict(state_dict_controlnet) + +image = pipe( + prompt="a man with a red hat", + controlnet_inputs=[ControlNetInput( + image=Image.open("data/example_image_dataset/infiniteyou/image_1.jpg"), + )], + height=1024, width=1024, + seed=0, rand_device="cuda", +) +image.save("image_FLUX.1-dev-InfiniteYou_full.jpg") diff --git a/examples/flux/model_training/validate_full/Step1X-Edit.py b/examples/flux/model_training/validate_full/Step1X-Edit.py new file mode 100644 index 0000000..ab184e7 --- /dev/null +++ b/examples/flux/model_training/validate_full/Step1X-Edit.py @@ -0,0 +1,25 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from diffsynth import load_state_dict +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Qwen/Qwen2.5-VL-7B-Instruct"), + ModelConfig(model_id="stepfun-ai/Step1X-Edit", origin_file_pattern="step1x-edit-i1258.safetensors"), + ModelConfig(model_id="stepfun-ai/Step1X-Edit", origin_file_pattern="vae.safetensors"), + ], +) +state_dict = load_state_dict("models/train/Step1X-Edit_full/epoch-0.safetensors") +pipe.dit.load_state_dict(state_dict) + +image = pipe( + prompt="Make the dog turn its head around.", + step1x_reference_image=Image.open("data/example_image_dataset/2.jpg").resize((768, 768)), + height=768, width=768, cfg_scale=6, + seed=0 +) +image.save("image_Step1X-Edit_full.jpg") diff --git a/examples/flux/model_training/validate_lora/FLEX.2-preview.py b/examples/flux/model_training/validate_lora/FLEX.2-preview.py new file mode 100644 index 0000000..1ef0142 --- /dev/null +++ b/examples/flux/model_training/validate_lora/FLEX.2-preview.py @@ -0,0 +1,18 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="ostris/Flex.2-preview", origin_file_pattern="Flex.2-preview.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) +pipe.load_lora(pipe.dit, "models/train/FLEX.2-preview_lora/epoch-4.safetensors", alpha=1) + +image = pipe(prompt="dog,white and brown dog, sitting on wall, under pink flowers", seed=0) +image.save("image_FLEX.2-preview_lora.jpg") diff --git a/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py b/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py new file mode 100644 index 0000000..6d88b8c --- /dev/null +++ b/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py @@ -0,0 +1,29 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta", origin_file_pattern="diffusion_pytorch_model.safetensors"), + ], +) +pipe.load_lora(pipe.dit, "models/train/FLUX.1-dev-Controlnet-Inpainting-Beta_lora/epoch-4.safetensors", alpha=1) + +image = pipe( + prompt="a cat sitting on a chair, wearing sunglasses", + controlnet_inputs=[ControlNetInput( + image=Image.open("data/example_image_dataset/inpaint/image_1.jpg"), + inpaint_mask=Image.open("data/example_image_dataset/inpaint/mask.jpg"), + scale=0.9 + )], + height=1024, width=1024, + seed=0, rand_device="cuda", +) +image.save("image_FLUX.1-dev-Controlnet-Inpainting-Beta_lora.jpg") diff --git a/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py b/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py new file mode 100644 index 0000000..240d8b6 --- /dev/null +++ b/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py @@ -0,0 +1,29 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="InstantX/FLUX.1-dev-Controlnet-Union-alpha", origin_file_pattern="diffusion_pytorch_model.safetensors"), + ], +) +pipe.load_lora(pipe.dit, "models/train/FLUX.1-dev-Controlnet-Union-alpha_lora/epoch-4.safetensors", alpha=1) + +image = pipe( + prompt="a dog", + controlnet_inputs=[ControlNetInput( + image=Image.open("data/example_image_dataset/canny/image_1.jpg"), + scale=0.9, + processor_id="canny", + )], + height=768, width=768, + seed=0, rand_device="cuda", +) +image.save("image_FLUX.1-dev-Controlnet-Union-alpha_lora.jpg") diff --git a/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py b/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py new file mode 100644 index 0000000..b27896c --- /dev/null +++ b/examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py @@ -0,0 +1,28 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="jasperai/Flux.1-dev-Controlnet-Upscaler", origin_file_pattern="diffusion_pytorch_model.safetensors"), + ], +) +pipe.load_lora(pipe.dit, "models/train/FLUX.1-dev-Controlnet-Upscaler_lora/epoch-4.safetensors", alpha=1) + +image = pipe( + prompt="a dog", + controlnet_inputs=[ControlNetInput( + image=Image.open("data/example_image_dataset/upscale/image_1.jpg"), + scale=0.9 + )], + height=768, width=768, + seed=0, rand_device="cuda", +) +image.save("image_FLUX.1-dev-Controlnet-Upscaler_lora.jpg") diff --git a/examples/flux/model_training/validate_lora/FLUX.1-dev-IP-Adapter.py b/examples/flux/model_training/validate_lora/FLUX.1-dev-IP-Adapter.py new file mode 100644 index 0000000..b085ad7 --- /dev/null +++ b/examples/flux/model_training/validate_lora/FLUX.1-dev-IP-Adapter.py @@ -0,0 +1,26 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="InstantX/FLUX.1-dev-IP-Adapter", origin_file_pattern="ip-adapter.bin"), + ModelConfig(model_id="google/siglip-so400m-patch14-384"), + ], +) +pipe.load_lora(pipe.dit, "models/train/FLUX.1-dev-IP-Adapter_lora/epoch-4.safetensors", alpha=1) + +image = pipe( + prompt="dog,white and brown dog, sitting on wall, under pink flowers", + ipadapter_images=Image.open("data/example_image_dataset/1.jpg"), + height=768, width=768, + seed=0 +) +image.save("image_FLUX.1-dev-IP-Adapter_lora.jpg") diff --git a/examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py b/examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py new file mode 100644 index 0000000..1d9d8a2 --- /dev/null +++ b/examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py @@ -0,0 +1,28 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="ByteDance/InfiniteYou", origin_file_pattern="infu_flux_v1.0/aes_stage2/image_proj_model.bin"), + ModelConfig(model_id="ByteDance/InfiniteYou", origin_file_pattern="infu_flux_v1.0/aes_stage2/InfuseNetModel/*.safetensors"), + ], +) +pipe.load_lora(pipe.dit, "models/train/FLUX.1-dev-InfiniteYou_lora/epoch-4.safetensors", alpha=1) + +image = pipe( + prompt="a man with a red hat", + controlnet_inputs=[ControlNetInput( + image=Image.open("data/example_image_dataset/infiniteyou/image_1.jpg"), + )], + height=1024, width=1024, + seed=0, rand_device="cuda", +) +image.save("image_FLUX.1-dev-InfiniteYou_lora.jpg") diff --git a/examples/flux/model_training/validate_lora/Step1X-Edit.py b/examples/flux/model_training/validate_lora/Step1X-Edit.py new file mode 100644 index 0000000..886af3f --- /dev/null +++ b/examples/flux/model_training/validate_lora/Step1X-Edit.py @@ -0,0 +1,23 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Qwen/Qwen2.5-VL-7B-Instruct"), + ModelConfig(model_id="stepfun-ai/Step1X-Edit", origin_file_pattern="step1x-edit-i1258.safetensors"), + ModelConfig(model_id="stepfun-ai/Step1X-Edit", origin_file_pattern="vae.safetensors"), + ], +) +pipe.load_lora(pipe.dit, "models/train/Step1X-Edit_lora/epoch-4.safetensors", alpha=1) + +image = pipe( + prompt="Make the dog turn its head around.", + step1x_reference_image=Image.open("data/example_image_dataset/2.jpg").resize((768, 768)), + height=768, width=768, cfg_scale=6, + seed=0 +) +image.save("image_Step1X-Edit_lora.jpg") From dee40753806ee28838eca3127e1213730a03d25d Mon Sep 17 00:00:00 2001 From: "lzw478614@alibaba-inc.com" Date: Wed, 9 Jul 2025 13:59:43 +0800 Subject: [PATCH 04/51] support other lora format --- diffsynth/lora/flux_lora.py | 172 ++++++++++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 17 deletions(-) diff --git a/diffsynth/lora/flux_lora.py b/diffsynth/lora/flux_lora.py index cc9d725..c6e5115 100644 --- a/diffsynth/lora/flux_lora.py +++ b/diffsynth/lora/flux_lora.py @@ -10,9 +10,51 @@ class FluxLoRALoader(GeneralLoRALoader): def load(self, model: torch.nn.Module, state_dict_lora, alpha=1.0): super().load(model, state_dict_lora, alpha) - def convert_state_dict(self, state_dict): - # TODO: support other lora format - rename_dict = { + + self.diffusers_rename_dict = { + "transformer.single_transformer_blocks.blockid.attn.to_k.lora_A.weight":"single_blocks.blockid.a_to_k.lora_A.default.weight", + "transformer.single_transformer_blocks.blockid.attn.to_k.lora_B.weight":"single_blocks.blockid.a_to_k.lora_B.default.weight", + "transformer.single_transformer_blocks.blockid.attn.to_q.lora_A.weight":"single_blocks.blockid.a_to_q.lora_A.default.weight", + "transformer.single_transformer_blocks.blockid.attn.to_q.lora_B.weight":"single_blocks.blockid.a_to_q.lora_B.default.weight", + "transformer.single_transformer_blocks.blockid.attn.to_v.lora_A.weight":"single_blocks.blockid.a_to_v.lora_A.default.weight", + "transformer.single_transformer_blocks.blockid.attn.to_v.lora_B.weight":"single_blocks.blockid.a_to_v.lora_B.default.weight", + "transformer.single_transformer_blocks.blockid.norm.linear.lora_A.weight":"single_blocks.blockid.norm.linear.lora_A.default.weight", + "transformer.single_transformer_blocks.blockid.norm.linear.lora_B.weight":"single_blocks.blockid.norm.linear.lora_B.default.weight", + "transformer.single_transformer_blocks.blockid.proj_mlp.lora_A.weight":"single_blocks.blockid.proj_in_besides_attn.lora_A.default.weight", + "transformer.single_transformer_blocks.blockid.proj_mlp.lora_B.weight":"single_blocks.blockid.proj_in_besides_attn.lora_B.default.weight", + "transformer.single_transformer_blocks.blockid.proj_out.lora_A.weight":"single_blocks.blockid.proj_out.lora_A.default.weight", + "transformer.single_transformer_blocks.blockid.proj_out.lora_B.weight":"single_blocks.blockid.proj_out.lora_B.default.weight", + "transformer.transformer_blocks.blockid.attn.add_k_proj.lora_A.weight":"blocks.blockid.attn.b_to_k.lora_A.default.weight", + "transformer.transformer_blocks.blockid.attn.add_k_proj.lora_B.weight":"blocks.blockid.attn.b_to_k.lora_B.default.weight", + "transformer.transformer_blocks.blockid.attn.add_q_proj.lora_A.weight":"blocks.blockid.attn.b_to_q.lora_A.default.weight", + "transformer.transformer_blocks.blockid.attn.add_q_proj.lora_B.weight":"blocks.blockid.attn.b_to_q.lora_B.default.weight", + "transformer.transformer_blocks.blockid.attn.add_v_proj.lora_A.weight":"blocks.blockid.attn.b_to_v.lora_A.default.weight", + "transformer.transformer_blocks.blockid.attn.add_v_proj.lora_B.weight":"blocks.blockid.attn.b_to_v.lora_B.default.weight", + "transformer.transformer_blocks.blockid.attn.to_add_out.lora_A.weight":"blocks.blockid.attn.b_to_out.lora_A.default.weight", + "transformer.transformer_blocks.blockid.attn.to_add_out.lora_B.weight":"blocks.blockid.attn.b_to_out.lora_B.default.weight", + "transformer.transformer_blocks.blockid.attn.to_k.lora_A.weight":"blocks.blockid.attn.a_to_k.lora_A.default.weight", + "transformer.transformer_blocks.blockid.attn.to_k.lora_B.weight":"blocks.blockid.attn.a_to_k.lora_B.default.weight", + "transformer.transformer_blocks.blockid.attn.to_out.0.lora_A.weight":"blocks.blockid.attn.a_to_out.lora_A.default.weight", + "transformer.transformer_blocks.blockid.attn.to_out.0.lora_B.weight":"blocks.blockid.attn.a_to_out.lora_B.default.weight", + "transformer.transformer_blocks.blockid.attn.to_q.lora_A.weight":"blocks.blockid.attn.a_to_q.lora_A.default.weight", + "transformer.transformer_blocks.blockid.attn.to_q.lora_B.weight":"blocks.blockid.attn.a_to_q.lora_B.default.weight", + "transformer.transformer_blocks.blockid.attn.to_v.lora_A.weight":"blocks.blockid.attn.a_to_v.lora_A.default.weight", + "transformer.transformer_blocks.blockid.attn.to_v.lora_B.weight":"blocks.blockid.attn.a_to_v.lora_B.default.weight", + "transformer.transformer_blocks.blockid.ff.net.0.proj.lora_A.weight":"blocks.blockid.ff_a.0.lora_A.default.weight", + "transformer.transformer_blocks.blockid.ff.net.0.proj.lora_B.weight":"blocks.blockid.ff_a.0.lora_B.default.weight", + "transformer.transformer_blocks.blockid.ff.net.2.lora_A.weight":"blocks.blockid.ff_a.2.lora_A.default.weight", + "transformer.transformer_blocks.blockid.ff.net.2.lora_B.weight":"blocks.blockid.ff_a.2.lora_B.default.weight", + "transformer.transformer_blocks.blockid.ff_context.net.0.proj.lora_A.weight":"blocks.blockid.ff_b.0.lora_A.default.weight", + "transformer.transformer_blocks.blockid.ff_context.net.0.proj.lora_B.weight":"blocks.blockid.ff_b.0.lora_B.default.weight", + "transformer.transformer_blocks.blockid.ff_context.net.2.lora_A.weight":"blocks.blockid.ff_b.2.lora_A.default.weight", + "transformer.transformer_blocks.blockid.ff_context.net.2.lora_B.weight":"blocks.blockid.ff_b.2.lora_B.default.weight", + "transformer.transformer_blocks.blockid.norm1.linear.lora_A.weight":"blocks.blockid.norm1_a.linear.lora_A.default.weight", + "transformer.transformer_blocks.blockid.norm1.linear.lora_B.weight":"blocks.blockid.norm1_a.linear.lora_B.default.weight", + "transformer.transformer_blocks.blockid.norm1_context.linear.lora_A.weight":"blocks.blockid.norm1_b.linear.lora_A.default.weight", + "transformer.transformer_blocks.blockid.norm1_context.linear.lora_B.weight":"blocks.blockid.norm1_b.linear.lora_B.default.weight", + } + + self.civitai_rename_dict = { "lora_unet_double_blocks_blockid_img_mod_lin.lora_down.weight": "blocks.blockid.norm1_a.linear.lora_A.default.weight", "lora_unet_double_blocks_blockid_img_mod_lin.lora_up.weight": "blocks.blockid.norm1_a.linear.lora_B.default.weight", "lora_unet_double_blocks_blockid_txt_mod_lin.lora_down.weight": "blocks.blockid.norm1_b.linear.lora_A.default.weight", @@ -40,25 +82,55 @@ class FluxLoRALoader(GeneralLoRALoader): "lora_unet_single_blocks_blockid_linear2.lora_down.weight": "single_blocks.blockid.proj_out.lora_A.default.weight", "lora_unet_single_blocks_blockid_linear2.lora_up.weight": "single_blocks.blockid.proj_out.lora_B.default.weight", } - def guess_block_id(name): - names = name.split("_") - for i in names: - if i.isdigit(): - return i, name.replace(f"_{i}_", "_blockid_") + + def load(self, model: torch.nn.Module, state_dict_lora, alpha=1.0): + super().load(model, state_dict_lora, alpha) + + + def convert_state_dict(self,state_dict): + + def guess_block_id(name,model_resource): + if model_resource == 'civitai': + names = name.split("_") + for i in names: + if i.isdigit(): + return i, name.replace(f"_{i}_", "_blockid_") + if model_resource == 'diffusers': + names = name.split(".") + for i in names: + if i.isdigit(): + return i, name.replace(f"transformer_blocks.{i}.", "transformer_blocks.blockid.") return None, None + + def guess_resource(state_dict): + for k in state_dict: + if "lora_unet_" in k: + return 'civitai' + elif "transformer." in k: + return 'diffusers' + else: + None + + model_resource = guess_resource(state_dict) + + rename_dict = self.diffusers_rename_dict if model_resource == 'diffusers' else self.civitai_rename_dict def guess_alpha(state_dict): - for name, param in state_dict.items(): - if ".alpha" in name: - name_ = name.replace(".alpha", ".lora_down.weight") - if name_ in state_dict: - lora_alpha = param.item() / state_dict[name_].shape[0] - lora_alpha = math.sqrt(lora_alpha) - return lora_alpha - return 1 + for name, param in state_dict.items(): + if ".alpha" in name: + for suffix in [".lora_down.weight", ".lora_A.weight"]: + name_ = name.replace(".alpha", suffix) + if name_ in state_dict: + lora_alpha = param.item() / state_dict[name_].shape[0] + lora_alpha = math.sqrt(lora_alpha) + return lora_alpha + + return 1 + alpha = guess_alpha(state_dict) + state_dict_ = {} for name, param in state_dict.items(): - block_id, source_name = guess_block_id(name) + block_id, source_name = guess_block_id(name,model_resource) if alpha != 1: param *= alpha if source_name in rename_dict: @@ -67,6 +139,72 @@ class FluxLoRALoader(GeneralLoRALoader): state_dict_[target_name] = param else: state_dict_[name] = param + + if model_resource == 'diffusers': + for name in list(state_dict_.keys()): + if "single_blocks." in name and ".a_to_q." in name: + mlp = state_dict_.get(name.replace(".a_to_q.", ".proj_in_besides_attn."), None) + if mlp is None: + dim = 4 + if 'lora_A' in name: + dim = 1 + mlp = torch.zeros(dim * state_dict_[name].shape[0], + *state_dict_[name].shape[1:], + dtype=state_dict_[name].dtype) + else: + state_dict_.pop(name.replace(".a_to_q.", ".proj_in_besides_attn.")) + if 'lora_A' in name: + param = torch.concat([ + state_dict_.pop(name), + state_dict_.pop(name.replace(".a_to_q.", ".a_to_k.")), + state_dict_.pop(name.replace(".a_to_q.", ".a_to_v.")), + mlp, + ], dim=0) + elif 'lora_B' in name: + d, r = state_dict_[name].shape + param = torch.zeros((3*d+mlp.shape[0], 3*r+mlp.shape[1]), dtype=state_dict_[name].dtype, device=state_dict_[name].device) + param[:d, :r] = state_dict_.pop(name) + param[d:2*d, r:2*r] = state_dict_.pop(name.replace(".a_to_q.", ".a_to_k.")) + param[2*d:3*d, 2*r:3*r] = state_dict_.pop(name.replace(".a_to_q.", ".a_to_v.")) + param[3*d:, 3*r:] = mlp + else: + param = torch.concat([ + state_dict_.pop(name), + state_dict_.pop(name.replace(".a_to_q.", ".a_to_k.")), + state_dict_.pop(name.replace(".a_to_q.", ".a_to_v.")), + mlp, + ], dim=0) + name_ = name.replace(".a_to_q.", ".to_qkv_mlp.") + state_dict_[name_] = param + for name in list(state_dict_.keys()): + for component in ["a", "b"]: + if f".{component}_to_q." in name: + name_ = name.replace(f".{component}_to_q.", f".{component}_to_qkv.") + concat_dim = 0 + if 'lora_A' in name: + param = torch.concat([ + state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_q.")], + state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_k.")], + state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_v.")], + ], dim=0) + elif 'lora_B' in name: + origin = state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_q.")] + d, r = origin.shape + # print(d, r) + param = torch.zeros((3*d, 3*r), dtype=origin.dtype, device=origin.device) + param[:d, :r] = state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_q.")] + param[d:2*d, r:2*r] = state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_k.")] + param[2*d:3*d, 2*r:3*r] = state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_v.")] + else: + param = torch.concat([ + state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_q.")], + state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_k.")], + state_dict_[name.replace(f".{component}_to_q.", f".{component}_to_v.")], + ], dim=0) + state_dict_[name_] = param + state_dict_.pop(name.replace(f".{component}_to_q.", f".{component}_to_q.")) + state_dict_.pop(name.replace(f".{component}_to_q.", f".{component}_to_k.")) + state_dict_.pop(name.replace(f".{component}_to_q.", f".{component}_to_v.")) return state_dict_ From f0106cd48c1cf6ff7964b9c6ff06b109f906e66e Mon Sep 17 00:00:00 2001 From: "lzw478614@alibaba-inc.com" Date: Wed, 9 Jul 2025 14:01:49 +0800 Subject: [PATCH 05/51] support other lora forma --- diffsynth/lora/flux_lora.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/diffsynth/lora/flux_lora.py b/diffsynth/lora/flux_lora.py index c6e5115..8d0372d 100644 --- a/diffsynth/lora/flux_lora.py +++ b/diffsynth/lora/flux_lora.py @@ -6,10 +6,6 @@ from diffsynth.models.lora import FluxLoRAFromCivitai class FluxLoRALoader(GeneralLoRALoader): def __init__(self, device="cpu", torch_dtype=torch.float32): super().__init__(device=device, torch_dtype=torch_dtype) - - def load(self, model: torch.nn.Module, state_dict_lora, alpha=1.0): - super().load(model, state_dict_lora, alpha) - self.diffusers_rename_dict = { "transformer.single_transformer_blocks.blockid.attn.to_k.lora_A.weight":"single_blocks.blockid.a_to_k.lora_A.default.weight", From 0a24d0819fba499cd8f745c62cb969e073000cae Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Mon, 14 Jul 2025 13:37:55 +0800 Subject: [PATCH 06/51] support flux value controller --- diffsynth/configs/model_config.py | 3 + diffsynth/models/flux_value_control.py | 59 +++++++++++++++++++ diffsynth/pipelines/flux_image_new.py | 45 +++++++++++++- .../FLUX.1-dev-ValueControl.py | 20 +++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 diffsynth/models/flux_value_control.py create mode 100644 examples/flux/model_inference/FLUX.1-dev-ValueControl.py diff --git a/diffsynth/configs/model_config.py b/diffsynth/configs/model_config.py index 0713b7c..7a0b72b 100644 --- a/diffsynth/configs/model_config.py +++ b/diffsynth/configs/model_config.py @@ -64,6 +64,8 @@ from ..models.wan_video_vace import VaceWanModel from ..models.step1x_connector import Qwen2Connector +from ..models.flux_value_control import SingleValueEncoder + from ..lora.flux_lora import FluxLoraPatcher @@ -104,6 +106,7 @@ model_loader_configs = [ (None, "023f054d918a84ccf503481fd1e3379e", ["flux_dit"], [FluxDiT], "civitai"), (None, "d02f41c13549fa5093d3521f62a5570a", ["flux_dit"], [FluxDiT], "civitai"), (None, "605c56eab23e9e2af863ad8f0813a25d", ["flux_dit"], [FluxDiT], "diffusers"), + (None, "3ede90c44b2c161240b659f3b8393c9d", ["flux_value_controller"], [SingleValueEncoder], "civitai"), (None, "280189ee084bca10f70907bf6ce1649d", ["cog_vae_encoder", "cog_vae_decoder"], [CogVAEEncoder, CogVAEDecoder], "diffusers"), (None, "9b9313d104ac4df27991352fec013fd4", ["rife"], [IFNet], "civitai"), (None, "6b7116078c4170bfbeaedc8fe71f6649", ["esrgan"], [RRDBNet], "civitai"), diff --git a/diffsynth/models/flux_value_control.py b/diffsynth/models/flux_value_control.py new file mode 100644 index 0000000..54eaa07 --- /dev/null +++ b/diffsynth/models/flux_value_control.py @@ -0,0 +1,59 @@ +import torch +from diffsynth.models.svd_unet import TemporalTimesteps + + +class MultiValueEncoder(torch.nn.Module): + def __init__(self, encoders=()): + super().__init__() + self.encoders = torch.nn.ModuleList(encoders) + + def __call__(self, values, dtype): + emb = [] + for encoder, value in zip(self.encoders, values): + if value is not None: + value = value.unsqueeze(0) + emb.append(encoder(value, dtype)) + emb = torch.concat(emb, dim=0) + return emb + + +class SingleValueEncoder(torch.nn.Module): + def __init__(self, dim_in=256, dim_out=3072, prefer_len=32, computation_device=None): + super().__init__() + self.prefer_len = prefer_len + self.prefer_proj = TemporalTimesteps(num_channels=dim_in, flip_sin_to_cos=True, downscale_freq_shift=0, computation_device=computation_device) + self.prefer_value_embedder = torch.nn.Sequential( + torch.nn.Linear(dim_in, dim_out), torch.nn.SiLU(), torch.nn.Linear(dim_out, dim_out) + ) + self.positional_embedding = torch.nn.Parameter( + torch.randn(self.prefer_len, dim_out) + ) + self._initialize_weights() + + def _initialize_weights(self): + last_linear = self.prefer_value_embedder[-1] + torch.nn.init.zeros_(last_linear.weight) + torch.nn.init.zeros_(last_linear.bias) + + def forward(self, value, dtype): + value = value * 1000 + emb = self.prefer_proj(value).to(dtype) + emb = self.prefer_value_embedder(emb).squeeze(0) + base_embeddings = emb.expand(self.prefer_len, -1) + learned_embeddings = base_embeddings + self.positional_embedding + return learned_embeddings + + @staticmethod + def state_dict_converter(): + return SingleValueEncoderStateDictConverter() + + +class SingleValueEncoderStateDictConverter: + def __init__(self): + pass + + def from_diffusers(self, state_dict): + return state_dict + + def from_civitai(self, state_dict): + return state_dict diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index 4b7c68d..b7f13a1 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -18,6 +18,7 @@ from ..models import ModelManager, load_state_dict, SD3TextEncoder1, FluxTextEnc from ..models.step1x_connector import Qwen2Connector from ..models.flux_controlnet import FluxControlNet from ..models.flux_ipadapter import FluxIpAdapter +from ..models.flux_value_control import MultiValueEncoder from ..models.flux_infiniteyou import InfiniteYouImageProjector from ..models.tiler import FastTileWorker from .wan_video_new import BasePipeline, ModelConfig, PipelineUnitRunner, PipelineUnit @@ -93,6 +94,7 @@ class FluxImagePipeline(BasePipeline): self.ipadapter_image_encoder = None self.qwenvl = None self.step1x_connector: Qwen2Connector = None + self.value_controller: MultiValueEncoder = None self.infinityou_processor: InfinitYou = None self.image_proj_model: InfiniteYouImageProjector = None self.lora_patcher: FluxLoraPatcher = None @@ -113,6 +115,7 @@ class FluxImagePipeline(BasePipeline): FluxImageUnit_TeaCache(), FluxImageUnit_Flex(), FluxImageUnit_Step1x(), + FluxImageUnit_ValueControl(), ] self.model_fn = model_fn_flux_image @@ -341,7 +344,16 @@ class FluxImagePipeline(BasePipeline): for model_name, model in zip(model_manager.model_name, model_manager.model): if model_name == "flux_controlnet": controlnets.append(model) - pipe.controlnet = MultiControlNet(controlnets) + if len(controlnets) > 0: + pipe.controlnet = MultiControlNet(controlnets) + + # Value Controller + value_controllers = [] + for model_name, model in zip(model_manager.model_name, model_manager.model): + if model_name == "flux_value_controller": + value_controllers.append(model) + if len(value_controllers) > 0: + pipe.value_controller = MultiValueEncoder(value_controllers) return pipe @@ -393,6 +405,8 @@ class FluxImagePipeline(BasePipeline): flex_control_image: Image.Image = None, flex_control_strength: float = 0.5, flex_control_stop: float = 0.5, + # Value Controller + value_controller_inputs: list[float] = None, # Step1x step1x_reference_image: Image.Image = None, # TeaCache @@ -426,6 +440,7 @@ class FluxImagePipeline(BasePipeline): "eligen_entity_prompts": eligen_entity_prompts, "eligen_entity_masks": eligen_entity_masks, "eligen_enable_on_negative": eligen_enable_on_negative, "eligen_enable_inpaint": eligen_enable_inpaint, "infinityou_id_image": infinityou_id_image, "infinityou_guidance": infinityou_guidance, "flex_inpaint_image": flex_inpaint_image, "flex_inpaint_mask": flex_inpaint_mask, "flex_control_image": flex_control_image, "flex_control_strength": flex_control_strength, "flex_control_stop": flex_control_stop, + "value_controller_inputs": value_controller_inputs, "step1x_reference_image": step1x_reference_image, "tea_cache_l1_thresh": tea_cache_l1_thresh, "tiled": tiled, "tile_size": tile_size, "tile_stride": tile_stride, @@ -724,7 +739,7 @@ class FluxImageUnit_Flex(PipelineUnit): super().__init__( input_params=("latents", "flex_inpaint_image", "flex_inpaint_mask", "flex_control_image", "flex_control_strength", "flex_control_stop", "tiled", "tile_size", "tile_stride"), onload_model_names=("vae_encoder",) - ) + ) def process(self, pipe: FluxImagePipeline, latents, flex_inpaint_image, flex_inpaint_mask, flex_control_image, flex_control_strength, flex_control_stop, tiled, tile_size, tile_stride): if pipe.dit.input_dim == 196: @@ -769,6 +784,24 @@ class FluxImageUnit_InfiniteYou(PipelineUnit): +class FluxImageUnit_ValueControl(PipelineUnit): + def __init__(self): + super().__init__( + input_params=("value_controller_inputs",), + onload_model_names=("value_controller",) + ) + + def process(self, pipe: FluxImagePipeline, value_controller_inputs): + if value_controller_inputs is None: + return {} + value_controller_inputs = torch.tensor(value_controller_inputs).to(dtype=pipe.torch_dtype, device=pipe.device) + pipe.load_models_to_device(["value_controller"]) + value_emb = pipe.value_controller(value_controller_inputs, pipe.torch_dtype) + value_emb = value_emb.unsqueeze(0) + return {"value_emb": value_emb} + + + class InfinitYou(torch.nn.Module): def __init__(self, device="cuda", torch_dtype=torch.bfloat16): super().__init__() @@ -888,6 +921,7 @@ def model_fn_flux_image( flex_condition=None, flex_uncondition=None, flex_control_stop_timestep=None, + value_emb=None, step1x_llm_embedding=None, step1x_mask=None, step1x_reference_latents=None, @@ -988,10 +1022,17 @@ def model_fn_flux_image( hidden_states = dit.x_embedder(hidden_states) + # EliGen if entity_prompt_emb is not None and entity_masks is not None: prompt_emb, image_rotary_emb, attention_mask = dit.process_entity_masks(hidden_states, prompt_emb, entity_prompt_emb, entity_masks, text_ids, image_ids) else: prompt_emb = dit.context_embedder(prompt_emb) + # Value Control + if value_emb is not None: + prompt_emb = torch.concat([prompt_emb, value_emb], dim=1) + value_text_ids = torch.zeros((value_emb.shape[0], value_emb.shape[1], 3), device=value_emb.device, dtype=value_emb.dtype) + text_ids = torch.concat([text_ids, value_text_ids], dim=1) + # Original FLUX inference image_rotary_emb = dit.pos_embedder(torch.cat((text_ids, image_ids), dim=1)) attention_mask = None diff --git a/examples/flux/model_inference/FLUX.1-dev-ValueControl.py b/examples/flux/model_inference/FLUX.1-dev-ValueControl.py new file mode 100644 index 0000000..0bb3ed0 --- /dev/null +++ b/examples/flux/model_inference/FLUX.1-dev-ValueControl.py @@ -0,0 +1,20 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-ValueController", origin_file_pattern="single/prefer_embed/value.ckpt") + ], +) +pipe.load_lora(pipe.dit, ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-ValueController", origin_file_pattern="single/dit_lora/dit_value.ckpt")) + +for i in range(10): + image = pipe(prompt="a cat", seed=0, value_controller_inputs=[i/10]) + image.save(f"value_control_{i}.jpg") \ No newline at end of file From dc066aca2dbe80c296c0555e4cc4c8fb1fe433bc Mon Sep 17 00:00:00 2001 From: "lzw478614@alibaba-inc.com" Date: Mon, 14 Jul 2025 14:08:22 +0800 Subject: [PATCH 07/51] update flux lora convert state dict --- diffsynth/lora/flux_lora.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/diffsynth/lora/flux_lora.py b/diffsynth/lora/flux_lora.py index 8d0372d..45baeaa 100644 --- a/diffsynth/lora/flux_lora.py +++ b/diffsynth/lora/flux_lora.py @@ -102,12 +102,14 @@ class FluxLoRALoader(GeneralLoRALoader): for k in state_dict: if "lora_unet_" in k: return 'civitai' - elif "transformer." in k: + elif k.startswith("transformer."): return 'diffusers' else: None model_resource = guess_resource(state_dict) + if model_resource is None: + return state_dict rename_dict = self.diffusers_rename_dict if model_resource == 'diffusers' else self.civitai_rename_dict def guess_alpha(state_dict): From af6b1d4246a86b785e6c047c50c3a68f5aef7c10 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 15 Jul 2025 20:11:02 +0800 Subject: [PATCH 08/51] flux series vram management --- diffsynth/models/flux_infiniteyou.py | 1 + diffsynth/models/flux_value_control.py | 3 +- diffsynth/models/step1x_connector.py | 4 +- diffsynth/pipelines/flux_image_new.py | 80 +++++++--- .../flux/model_inference_low_vram/EliGen.py | 148 ++++++++++++++++++ .../FLEX.2-preview.py | 51 ++++++ .../FLUX.1-Kontext-dev.py | 55 +++++++ .../FLUX.1-dev-Controlnet-Inpainting-Beta.py | 38 +++++ .../FLUX.1-dev-Controlnet-Union-alpha.py | 41 +++++ .../FLUX.1-dev-Controlnet-Upscaler.py | 34 ++++ .../FLUX.1-dev-IP-Adapter.py | 25 +++ .../FLUX.1-dev-InfiniteYou.py | 60 +++++++ .../FLUX.1-dev-LoRAFusion.py | 35 +++++ .../FLUX.1-dev-ValueControl.py | 21 +++ .../model_inference_low_vram/FLUX.1-dev.py | 27 ++++ .../model_inference_low_vram/Step1X-Edit.py | 33 ++++ 16 files changed, 629 insertions(+), 27 deletions(-) create mode 100644 examples/flux/model_inference_low_vram/EliGen.py create mode 100644 examples/flux/model_inference_low_vram/FLEX.2-preview.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-Kontext-dev.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev-IP-Adapter.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev-LoRAFusion.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev-ValueControl.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev.py create mode 100644 examples/flux/model_inference_low_vram/Step1X-Edit.py diff --git a/diffsynth/models/flux_infiniteyou.py b/diffsynth/models/flux_infiniteyou.py index 2015de4..861538a 100644 --- a/diffsynth/models/flux_infiniteyou.py +++ b/diffsynth/models/flux_infiniteyou.py @@ -104,6 +104,7 @@ class InfiniteYouImageProjector(nn.Module): def forward(self, x): latents = self.latents.repeat(x.size(0), 1, 1) + latents = latents.to(dtype=x.dtype, device=x.device) x = self.proj_in(x) diff --git a/diffsynth/models/flux_value_control.py b/diffsynth/models/flux_value_control.py index 54eaa07..0ff68d3 100644 --- a/diffsynth/models/flux_value_control.py +++ b/diffsynth/models/flux_value_control.py @@ -40,7 +40,8 @@ class SingleValueEncoder(torch.nn.Module): emb = self.prefer_proj(value).to(dtype) emb = self.prefer_value_embedder(emb).squeeze(0) base_embeddings = emb.expand(self.prefer_len, -1) - learned_embeddings = base_embeddings + self.positional_embedding + positional_embedding = self.positional_embedding.to(dtype=base_embeddings.dtype, device=base_embeddings.device) + learned_embeddings = base_embeddings + positional_embedding return learned_embeddings @staticmethod diff --git a/diffsynth/models/step1x_connector.py b/diffsynth/models/step1x_connector.py index b4abe40..9d5f0d9 100644 --- a/diffsynth/models/step1x_connector.py +++ b/diffsynth/models/step1x_connector.py @@ -162,7 +162,7 @@ class TimestepEmbedder(nn.Module): def forward(self, t): t_freq = self.timestep_embedding( t, self.frequency_embedding_size, self.max_period - ).type(self.mlp[0].weight.dtype) # type: ignore + ).type(t.dtype) # type: ignore t_emb = self.mlp(t_freq) return t_emb @@ -656,7 +656,7 @@ class Qwen2Connector(torch.nn.Module): mask_float = mask.unsqueeze(-1) # [b, s1, 1] x_mean = (x * mask_float).sum( dim=1 - ) / mask_float.sum(dim=1) * (1 + self.scale_factor) + ) / mask_float.sum(dim=1) * (1 + self.scale_factor.to(dtype=x.dtype, device=x.device)) global_out=self.global_proj_out(x_mean) encoder_hidden_states = self.S(x,t,mask) diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index b7f13a1..3dbb9b8 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -24,7 +24,6 @@ from ..models.tiler import FastTileWorker from .wan_video_new import BasePipeline, ModelConfig, PipelineUnitRunner, PipelineUnit from ..lora.flux_lora import FluxLoRALoader, FluxLoraPatcher -from transformers.models.t5.modeling_t5 import T5LayerNorm, T5DenseActDense, T5DenseGatedActDense from ..models.flux_dit import RMSNorm from ..vram_management import gradient_checkpoint_forward, enable_vram_management, AutoWrappedModule, AutoWrappedLinear @@ -185,22 +184,18 @@ class FluxImagePipeline(BasePipeline): return loss - def enable_vram_management(self, num_persistent_param_in_dit=None, vram_limit=None, vram_buffer=0.5): - self.vram_management_enabled = True - if num_persistent_param_in_dit is not None: - vram_limit = None - else: - if vram_limit is None: - vram_limit = self.get_vram() - vram_limit = vram_limit - vram_buffer - if self.text_encoder_1 is not None: - dtype = next(iter(self.text_encoder_1.parameters())).dtype + def _enable_vram_management_with_default_config(self, model, vram_limit): + if model is not None: + dtype = next(iter(model.parameters())).dtype enable_vram_management( - self.text_encoder_1, + model, module_map = { torch.nn.Linear: AutoWrappedLinear, torch.nn.Embedding: AutoWrappedModule, torch.nn.LayerNorm: AutoWrappedModule, + torch.nn.Conv2d: AutoWrappedModule, + torch.nn.GroupNorm: AutoWrappedModule, + RMSNorm: AutoWrappedModule, }, module_config = dict( offload_dtype=dtype, @@ -212,7 +207,25 @@ class FluxImagePipeline(BasePipeline): ), vram_limit=vram_limit, ) + + + def enable_vram_management(self, num_persistent_param_in_dit=None, vram_limit=None, vram_buffer=0.5): + self.vram_management_enabled = True + if num_persistent_param_in_dit is not None: + vram_limit = None + else: + if vram_limit is None: + vram_limit = self.get_vram() + vram_limit = vram_limit - vram_buffer + + # Default config + default_vram_management_models = ["text_encoder_1", "vae_decoder", "vae_encoder", "controlnet", "image_proj_model", "ipadapter", "lora_patcher", "value_controller", "step1x_connector"] + for model_name in default_vram_management_models: + self._enable_vram_management_with_default_config(getattr(self, model_name), vram_limit) + + # Special config if self.text_encoder_2 is not None: + from transformers.models.t5.modeling_t5 import T5LayerNorm, T5DenseActDense, T5DenseGatedActDense dtype = next(iter(self.text_encoder_2.parameters())).dtype enable_vram_management( self.text_encoder_2, @@ -261,14 +274,18 @@ class FluxImagePipeline(BasePipeline): ), vram_limit=vram_limit, ) - if self.vae_decoder is not None: - dtype = next(iter(self.vae_decoder.parameters())).dtype + if self.ipadapter_image_encoder is not None: + from transformers.models.siglip.modeling_siglip import SiglipVisionEmbeddings, SiglipEncoder, SiglipMultiheadAttentionPoolingHead + dtype = next(iter(self.ipadapter_image_encoder.parameters())).dtype enable_vram_management( - self.vae_decoder, + self.ipadapter_image_encoder, module_map = { + SiglipVisionEmbeddings: AutoWrappedModule, + SiglipEncoder: AutoWrappedModule, + SiglipMultiheadAttentionPoolingHead: AutoWrappedModule, + torch.nn.MultiheadAttention: AutoWrappedModule, torch.nn.Linear: AutoWrappedLinear, - torch.nn.Conv2d: AutoWrappedModule, - torch.nn.GroupNorm: AutoWrappedModule, + torch.nn.LayerNorm: AutoWrappedModule, }, module_config = dict( offload_dtype=dtype, @@ -280,14 +297,25 @@ class FluxImagePipeline(BasePipeline): ), vram_limit=vram_limit, ) - if self.vae_encoder is not None: - dtype = next(iter(self.vae_encoder.parameters())).dtype + if self.qwenvl is not None: + from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import ( + Qwen2_5_VisionPatchEmbed, Qwen2_5_VLVisionBlock, Qwen2_5_VLPatchMerger, + Qwen2_5_VLDecoderLayer, Qwen2_5_VisionRotaryEmbedding, Qwen2_5_VLRotaryEmbedding, Qwen2RMSNorm + ) + dtype = next(iter(self.qwenvl.parameters())).dtype enable_vram_management( - self.vae_encoder, + self.qwenvl, module_map = { + Qwen2_5_VisionPatchEmbed: AutoWrappedModule, + Qwen2_5_VLVisionBlock: AutoWrappedModule, + Qwen2_5_VLPatchMerger: AutoWrappedModule, + Qwen2_5_VLDecoderLayer: AutoWrappedModule, + Qwen2_5_VisionRotaryEmbedding: AutoWrappedModule, + Qwen2_5_VLRotaryEmbedding: AutoWrappedModule, + Qwen2RMSNorm: AutoWrappedModule, + torch.nn.Embedding: AutoWrappedModule, torch.nn.Linear: AutoWrappedLinear, - torch.nn.Conv2d: AutoWrappedModule, - torch.nn.GroupNorm: AutoWrappedModule, + torch.nn.LayerNorm: AutoWrappedModule, }, module_config = dict( offload_dtype=dtype, @@ -774,9 +802,13 @@ class FluxImageUnit_Flex(PipelineUnit): class FluxImageUnit_InfiniteYou(PipelineUnit): def __init__(self): - super().__init__(input_params=("infinityou_id_image", "infinityou_guidance")) + super().__init__( + input_params=("infinityou_id_image", "infinityou_guidance"), + onload_model_names=("infinityou_processor",) + ) def process(self, pipe: FluxImagePipeline, infinityou_id_image, infinityou_guidance): + pipe.load_models_to_device("infinityou_processor") if infinityou_id_image is not None: return pipe.infinityou_processor.prepare_infinite_you(pipe.image_proj_model, infinityou_id_image, infinityou_guidance, pipe.device) else: @@ -816,7 +848,7 @@ class InfinitYou(torch.nn.Module): self.app_320.prepare(ctx_id=0, det_size=(320, 320)) self.app_160 = FaceAnalysis(name='antelopev2', root=insightface_root_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) self.app_160.prepare(ctx_id=0, det_size=(160, 160)) - self.arcface_model = init_recognition_model('arcface', device=self.device) + self.arcface_model = init_recognition_model('arcface', device=self.device).to(torch_dtype) def _detect_face(self, id_image_cv2): face_info = self.app_640.get(id_image_cv2) diff --git a/examples/flux/model_inference_low_vram/EliGen.py b/examples/flux/model_inference_low_vram/EliGen.py new file mode 100644 index 0000000..55b4b27 --- /dev/null +++ b/examples/flux/model_inference_low_vram/EliGen.py @@ -0,0 +1,148 @@ +import random +import torch +from PIL import Image, ImageDraw, ImageFont +from diffsynth import download_customized_models +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from modelscope import dataset_snapshot_download + + +def visualize_masks(image, masks, mask_prompts, output_path, font_size=35, use_random_colors=False): + # Create a blank image for overlays + overlay = Image.new('RGBA', image.size, (0, 0, 0, 0)) + + colors = [ + (165, 238, 173, 80), + (76, 102, 221, 80), + (221, 160, 77, 80), + (204, 93, 71, 80), + (145, 187, 149, 80), + (134, 141, 172, 80), + (157, 137, 109, 80), + (153, 104, 95, 80), + (165, 238, 173, 80), + (76, 102, 221, 80), + (221, 160, 77, 80), + (204, 93, 71, 80), + (145, 187, 149, 80), + (134, 141, 172, 80), + (157, 137, 109, 80), + (153, 104, 95, 80), + ] + # Generate random colors for each mask + if use_random_colors: + colors = [(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), 80) for _ in range(len(masks))] + + # Font settings + try: + font = ImageFont.truetype("arial", font_size) # Adjust as needed + except IOError: + font = ImageFont.load_default(font_size) + + # Overlay each mask onto the overlay image + for mask, mask_prompt, color in zip(masks, mask_prompts, colors): + # Convert mask to RGBA mode + mask_rgba = mask.convert('RGBA') + mask_data = mask_rgba.getdata() + new_data = [(color if item[:3] == (255, 255, 255) else (0, 0, 0, 0)) for item in mask_data] + mask_rgba.putdata(new_data) + + # Draw the mask prompt text on the mask + draw = ImageDraw.Draw(mask_rgba) + mask_bbox = mask.getbbox() # Get the bounding box of the mask + text_position = (mask_bbox[0] + 10, mask_bbox[1] + 10) # Adjust text position based on mask position + draw.text(text_position, mask_prompt, fill=(255, 255, 255, 255), font=font) + + # Alpha composite the overlay with this mask + overlay = Image.alpha_composite(overlay, mask_rgba) + + # Composite the overlay onto the original image + result = Image.alpha_composite(image.convert('RGBA'), overlay) + + # Save or display the resulting image + result.save(output_path) + + return result + +def example(pipe, seeds, example_id, global_prompt, entity_prompts): + dataset_snapshot_download(dataset_id="DiffSynth-Studio/examples_in_diffsynth", local_dir="./", allow_file_pattern=f"data/examples/eligen/entity_control/example_{example_id}/*.png") + masks = [Image.open(f"./data/examples/eligen/entity_control/example_{example_id}/{i}.png").convert('RGB') for i in range(len(entity_prompts))] + negative_prompt = "worst quality, low quality, monochrome, zombie, interlocked fingers, Aissist, cleavage, nsfw," + for seed in seeds: + # generate image + image = pipe( + prompt=global_prompt, + cfg_scale=3.0, + negative_prompt=negative_prompt, + num_inference_steps=50, + embedded_guidance=3.5, + seed=seed, + height=1024, + width=1024, + eligen_entity_prompts=entity_prompts, + eligen_entity_masks=masks, + ) + image.save(f"eligen_example_{example_id}_{seed}.png") + visualize_masks(image, masks, entity_prompts, f"eligen_example_{example_id}_mask_{seed}.png") + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +download_from_modelscope = True +if download_from_modelscope: + model_id = "DiffSynth-Studio/Eligen" + downloading_priority = ["ModelScope"] +else: + model_id = "modelscope/EliGen" + downloading_priority = ["HuggingFace"] +EliGen_path = download_customized_models( + model_id=model_id, + origin_file_path="model_bf16.safetensors", + local_dir="models/lora/entity_control", + downloading_priority=downloading_priority)[0] +pipe.load_lora(pipe.dit, EliGen_path, alpha=1) + +# example 1 +global_prompt = "A breathtaking beauty of Raja Ampat by the late-night moonlight , one beautiful woman from behind wearing a pale blue long dress with soft glow, sitting at the top of a cliff looking towards the beach,pastell light colors, a group of small distant birds flying in far sky, a boat sailing on the sea, best quality, realistic, whimsical, fantastic, splash art, intricate detailed, hyperdetailed, maximalist style, photorealistic, concept art, sharp focus, harmony, serenity, tranquility, soft pastell colors,ambient occlusion, cozy ambient lighting, masterpiece, liiv1, linquivera, metix, mentixis, masterpiece, award winning, view from above\n" +entity_prompts = ["cliff", "sea", "moon", "sailing boat", "a seated beautiful woman", "pale blue long dress with soft glow"] +example(pipe, [0], 1, global_prompt, entity_prompts) + +# example 2 +global_prompt = "samurai girl wearing a kimono, she's holding a sword glowing with red flame, her long hair is flowing in the wind, she is looking at a small bird perched on the back of her hand. ultra realist style. maximum image detail. maximum realistic render." +entity_prompts = ["flowing hair", "sword glowing with red flame", "A cute bird", "blue belt"] +example(pipe, [0], 2, global_prompt, entity_prompts) + +# example 3 +global_prompt = "Image of a neverending staircase up to a mysterious palace in the sky, The ancient palace stood majestically atop a mist-shrouded mountain, sunrise, two traditional monk walk in the stair looking at the sunrise, fog,see-through, best quality, whimsical, fantastic, splash art, intricate detailed, hyperdetailed, photorealistic, concept art, harmony, serenity, tranquility, ambient occlusion, halation, cozy ambient lighting, dynamic lighting,masterpiece, liiv1, linquivera, metix, mentixis, masterpiece, award winning," +entity_prompts = ["ancient palace", "stone staircase with railings", "a traditional monk", "a traditional monk"] +example(pipe, [27], 3, global_prompt, entity_prompts) + +# example 4 +global_prompt = "A beautiful girl wearing shirt and shorts in the street, holding a sign 'Entity Control'" +entity_prompts = ["A beautiful girl", "sign 'Entity Control'", "shorts", "shirt"] +example(pipe, [21], 4, global_prompt, entity_prompts) + +# example 5 +global_prompt = "A captivating, dramatic scene in a painting that exudes mystery and foreboding. A white sky, swirling blue clouds, and a crescent yellow moon illuminate a solitary woman standing near the water's edge. Her long dress flows in the wind, silhouetted against the eerie glow. The water mirrors the fiery sky and moonlight, amplifying the uneasy atmosphere." +entity_prompts = ["crescent yellow moon", "a solitary woman", "water", "swirling blue clouds"] +example(pipe, [0], 5, global_prompt, entity_prompts) + +# example 6 +global_prompt = "Snow White and the 6 Dwarfs." +entity_prompts = ["Dwarf 1", "Dwarf 2", "Dwarf 3", "Snow White", "Dwarf 4", "Dwarf 5", "Dwarf 6"] +example(pipe, [8], 6, global_prompt, entity_prompts) + +# example 7, same prompt with different seeds +seeds = range(5, 9) +global_prompt = "A beautiful woman wearing white dress, holding a mirror, with a warm light background;" +entity_prompts = ["A beautiful woman", "mirror", "necklace", "glasses", "earring", "white dress", "jewelry headpiece"] +example(pipe, seeds, 7, global_prompt, entity_prompts) diff --git a/examples/flux/model_inference_low_vram/FLEX.2-preview.py b/examples/flux/model_inference_low_vram/FLEX.2-preview.py new file mode 100644 index 0000000..e94cc98 --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLEX.2-preview.py @@ -0,0 +1,51 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from diffsynth.controlnets.processors import Annotator +import numpy as np +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="ostris/Flex.2-preview", origin_file_pattern="Flex.2-preview.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +image = pipe( + prompt="portrait of a beautiful Asian girl, long hair, red t-shirt, sunshine, beach", + num_inference_steps=50, embedded_guidance=3.5, + seed=0 +) +image.save(f"image_1.jpg") + +mask = np.zeros((1024, 1024, 3), dtype=np.uint8) +mask[200:400, 400:700] = 255 +mask = Image.fromarray(mask) +mask.save(f"image_mask.jpg") + +inpaint_image = image + +image = pipe( + prompt="portrait of a beautiful Asian girl with sunglasses, long hair, red t-shirt, sunshine, beach", + num_inference_steps=50, embedded_guidance=3.5, + flex_inpaint_image=inpaint_image, flex_inpaint_mask=mask, + seed=4 +) +image.save(f"image_2_new.jpg") + +control_image = Annotator("canny")(image) +control_image.save("image_control.jpg") + +image = pipe( + prompt="portrait of a beautiful Asian girl with sunglasses, long hair, yellow t-shirt, sunshine, beach", + num_inference_steps=50, embedded_guidance=3.5, + flex_control_image=control_image, + seed=4 +) +image.save(f"image_3_new.jpg") diff --git a/examples/flux/model_inference_low_vram/FLUX.1-Kontext-dev.py b/examples/flux/model_inference_low_vram/FLUX.1-Kontext-dev.py new file mode 100644 index 0000000..c36c0dd --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-Kontext-dev.py @@ -0,0 +1,55 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-Kontext-dev", origin_file_pattern="flux1-kontext-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +image_1 = pipe( + prompt="a beautiful Asian long-haired female college student.", + embedded_guidance=2.5, + seed=1, +) +image_1.save("image_1.jpg") + +image_2 = pipe( + prompt="transform the style to anime style.", + kontext_images=image_1, + embedded_guidance=2.5, + seed=2, +) +image_2.save("image_2.jpg") + +image_3 = pipe( + prompt="let her smile.", + kontext_images=image_1, + embedded_guidance=2.5, + seed=3, +) +image_3.save("image_3.jpg") + +image_4 = pipe( + prompt="let the girl play basketball.", + kontext_images=image_1, + embedded_guidance=2.5, + seed=4, +) +image_4.save("image_4.jpg") + +image_5 = pipe( + prompt="move the girl to a park, let her sit on a chair.", + kontext_images=image_1, + embedded_guidance=2.5, + seed=5, +) +image_5.save("image_5.jpg") \ No newline at end of file diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py new file mode 100644 index 0000000..2dcc190 --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py @@ -0,0 +1,38 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +import numpy as np +from PIL import Image + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta", origin_file_pattern="diffusion_pytorch_model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +image_1 = pipe( + prompt="a cat sitting on a chair", + height=1024, width=1024, + seed=8, rand_device="cuda", +) +image_1.save("image_1.jpg") + +mask = np.zeros((1024, 1024, 3), dtype=np.uint8) +mask[100:350, 350: -300] = 255 +mask = Image.fromarray(mask) +mask.save("mask.jpg") + +image_2 = pipe( + prompt="a cat sitting on a chair, wearing sunglasses", + controlnet_inputs=[ControlNetInput(image=image_1, inpaint_mask=mask, scale=0.9)], + height=1024, width=1024, + seed=9, rand_device="cuda", +) +image_2.save("image_2.jpg") \ No newline at end of file diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py new file mode 100644 index 0000000..62eeee0 --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py @@ -0,0 +1,41 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from diffsynth.controlnets.processors import Annotator +from diffsynth import download_models + + + +download_models(["Annotators:Depth"]) +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="InstantX/FLUX.1-dev-Controlnet-Union-alpha", origin_file_pattern="diffusion_pytorch_model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +image_1 = pipe( + prompt="a beautiful Asian girl, full body, red dress, summer", + height=1024, width=1024, + seed=6, rand_device="cuda", +) +image_1.save("image_1.jpg") + +image_canny = Annotator("canny")(image_1) +image_depth = Annotator("depth")(image_1) + +image_2 = pipe( + prompt="a beautiful Asian girl, full body, red dress, winter", + controlnet_inputs=[ + ControlNetInput(image=image_canny, scale=0.3, processor_id="canny"), + ControlNetInput(image=image_depth, scale=0.3, processor_id="depth"), + ], + height=1024, width=1024, + seed=7, rand_device="cuda", +) +image_2.save("image_2.jpg") diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py new file mode 100644 index 0000000..58c3b9d --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py @@ -0,0 +1,34 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="jasperai/Flux.1-dev-Controlnet-Upscaler", origin_file_pattern="diffusion_pytorch_model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +image_1 = pipe( + prompt="a photo of a cat, highly detailed", + height=768, width=768, + seed=0, rand_device="cuda", +) +image_1.save("image_1.jpg") + +image_1 = image_1.resize((2048, 2048)) +image_2 = pipe( + prompt="a photo of a cat, highly detailed", + controlnet_inputs=[ControlNetInput(image=image_1, scale=0.7)], + input_image=image_1, + denoising_strength=0.99, + height=2048, width=2048, tiled=True, + seed=1, rand_device="cuda", +) +image_2.save("image_2.jpg") \ No newline at end of file diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-IP-Adapter.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-IP-Adapter.py new file mode 100644 index 0000000..83439a9 --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-IP-Adapter.py @@ -0,0 +1,25 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="InstantX/FLUX.1-dev-IP-Adapter", origin_file_pattern="ip-adapter.bin", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="google/siglip-so400m-patch14-384", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +origin_prompt = "a rabbit in a garden, colorful flowers" +image = pipe(prompt=origin_prompt, height=1280, width=960, seed=42) +image.save("style image.jpg") + +image = pipe(prompt="A piggy", height=1280, width=960, seed=42, + ipadapter_images=[image], ipadapter_scale=0.7) +image.save("A piggy.jpg") diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py new file mode 100644 index 0000000..dbe719a --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py @@ -0,0 +1,60 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig, ControlNetInput +from modelscope import dataset_snapshot_download +from modelscope import snapshot_download +from PIL import Image +import numpy as np + + +snapshot_download( + "ByteDance/InfiniteYou", + allow_file_pattern="supports/insightface/models/antelopev2/*", + local_dir="models/ByteDance/InfiniteYou", +) +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="ByteDance/InfiniteYou", origin_file_pattern="infu_flux_v1.0/aes_stage2/image_proj_model.bin", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="ByteDance/InfiniteYou", origin_file_pattern="infu_flux_v1.0/aes_stage2/InfuseNetModel/*.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +dataset_snapshot_download( + dataset_id="DiffSynth-Studio/examples_in_diffsynth", + local_dir="./", + allow_file_pattern=f"data/examples/infiniteyou/*", +) + +height, width = 1024, 1024 +controlnet_image = Image.fromarray(np.zeros([height, width, 3]).astype(np.uint8)) +controlnet_inputs = [ControlNetInput(image=controlnet_image, scale=1.0, processor_id="None")] + +prompt = "A man, portrait, cinematic" +id_image = "data/examples/infiniteyou/man.jpg" +id_image = Image.open(id_image).convert('RGB') +image = pipe( + prompt=prompt, seed=1, + infinityou_id_image=id_image, infinityou_guidance=1.0, + controlnet_inputs=controlnet_inputs, + num_inference_steps=50, embedded_guidance=3.5, + height=height, width=width, +) +image.save("man.jpg") + +prompt = "A woman, portrait, cinematic" +id_image = "data/examples/infiniteyou/woman.jpg" +id_image = Image.open(id_image).convert('RGB') +image = pipe( + prompt=prompt, seed=1, + infinityou_id_image=id_image, infinityou_guidance=1.0, + controlnet_inputs=controlnet_inputs, + num_inference_steps=50, embedded_guidance=3.5, + height=height, width=width, +) +image.save("woman.jpg") \ No newline at end of file diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-LoRAFusion.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-LoRAFusion.py new file mode 100644 index 0000000..44ad3a5 --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-LoRAFusion.py @@ -0,0 +1,35 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-LoRAFusion", origin_file_pattern="model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn) + ], +) +pipe.enable_vram_management() +pipe.enable_lora_patcher() +pipe.load_lora( + pipe.dit, + ModelConfig(model_id="yangyufeng/fgao", origin_file_pattern="30.safetensors"), + hotload=True +) +pipe.load_lora( + pipe.dit, + ModelConfig(model_id="bobooblue/LoRA-bling-mai", origin_file_pattern="10.safetensors"), + hotload=True +) +pipe.load_lora( + pipe.dit, + ModelConfig(model_id="JIETANGAB/E", origin_file_pattern="17.safetensors"), + hotload=True +) + +image = pipe(prompt="This is a digital painting in a soft, ethereal style. a beautiful Asian girl Shine like a diamond. Everywhere is shining with bling bling luster.The background is a textured blue with visible brushstrokes, giving the image an impressionistic style reminiscent of Vincent van Gogh's work", seed=0) +image.save("flux.jpg") diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-ValueControl.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-ValueControl.py new file mode 100644 index 0000000..bb6be21 --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-ValueControl.py @@ -0,0 +1,21 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-ValueController", origin_file_pattern="single/prefer_embed/value.ckpt", offload_device="cpu", offload_dtype=torch.float8_e4m3fn) + ], +) +pipe.load_lora(pipe.dit, ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-ValueController", origin_file_pattern="single/dit_lora/dit_value.ckpt")) +pipe.enable_vram_management() + +for i in range(10): + image = pipe(prompt="a cat", seed=0, value_controller_inputs=[i/10]) + image.save(f"value_control_{i}.jpg") \ No newline at end of file diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev.py b/examples/flux/model_inference_low_vram/FLUX.1-dev.py new file mode 100644 index 0000000..41d05a4 --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev.py @@ -0,0 +1,27 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +prompt = "CG, masterpiece, best quality, solo, long hair, wavy hair, silver hair, blue eyes, blue dress, medium breasts, dress, underwater, air bubble, floating hair, refraction, portrait. The girl's flowing silver hair shimmers with every color of the rainbow and cascades down, merging with the floating flora around her." +negative_prompt = "worst quality, low quality, monochrome, zombie, interlocked fingers, Aissist, cleavage, nsfw," + +image = pipe(prompt=prompt, seed=0) +image.save("flux.jpg") + +image = pipe( + prompt=prompt, negative_prompt=negative_prompt, + seed=0, cfg_scale=2, num_inference_steps=50, +) +image.save("flux_cfg.jpg") diff --git a/examples/flux/model_inference_low_vram/Step1X-Edit.py b/examples/flux/model_inference_low_vram/Step1X-Edit.py new file mode 100644 index 0000000..aad034f --- /dev/null +++ b/examples/flux/model_inference_low_vram/Step1X-Edit.py @@ -0,0 +1,33 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from PIL import Image +import numpy as np + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Qwen/Qwen2.5-VL-7B-Instruct", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="stepfun-ai/Step1X-Edit", origin_file_pattern="step1x-edit-i1258.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="stepfun-ai/Step1X-Edit", origin_file_pattern="vae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() + +image = Image.fromarray(np.zeros((1248, 832, 3), dtype=np.uint8) + 255) +image = pipe( + prompt="draw red flowers in Chinese ink painting style", + step1x_reference_image=image, + width=832, height=1248, cfg_scale=6, + seed=1, rand_device='cuda' +) +image.save("image_1.jpg") + +image = pipe( + prompt="add more flowers in Chinese ink painting style", + step1x_reference_image=image, + width=832, height=1248, cfg_scale=6, + seed=2, rand_device='cuda' +) +image.save("image_2.jpg") From 05c6b49b901f85449c39359b2ea493922294b430 Mon Sep 17 00:00:00 2001 From: ziyannchen <1041276865@qq.com> Date: Wed, 16 Jul 2025 10:30:33 +0000 Subject: [PATCH 09/51] fix a bug in sliding_window inference --- diffsynth/pipelines/wan_video_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 9f52ddc..59b4690 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -1047,7 +1047,7 @@ class TemporalTiler_BCTHW: mask = self.build_mask( model_output, is_bound=(t == 0, t_ == T), - border_width=(sliding_window_size - sliding_window_stride,) + border_width=(sliding_window_size - sliding_window_stride + 1,) ).to(device=data_device, dtype=data_dtype) value[:, :, t: t_, :, :] += model_output * mask weight[:, :, t: t_, :, :] += mask From 1384de03538510dc3a536bdd1ac041bfbedcb9bb Mon Sep 17 00:00:00 2001 From: Zhongjie Duan <35051019+Artiprocher@users.noreply.github.com> Date: Sat, 19 Jul 2025 20:44:03 +0800 Subject: [PATCH 10/51] Support LoRA encoder (#695) * lora_encoder --- diffsynth/configs/model_config.py | 2 + diffsynth/models/flux_lora_encoder.py | 111 ++++++++++++++++++ diffsynth/models/utils.py | 2 +- diffsynth/pipelines/flux_image_new.py | 98 +++++++++++++++- diffsynth/pipelines/wan_video_new.py | 2 + .../FLUX.1-dev-LoRA-Encoder.py | 40 +++++++ .../FLUX.1-dev-LoRA-Encoder.py | 41 +++++++ 7 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 diffsynth/models/flux_lora_encoder.py create mode 100644 examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py create mode 100644 examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py diff --git a/diffsynth/configs/model_config.py b/diffsynth/configs/model_config.py index 7a0b72b..c2e050d 100644 --- a/diffsynth/configs/model_config.py +++ b/diffsynth/configs/model_config.py @@ -67,6 +67,7 @@ from ..models.step1x_connector import Qwen2Connector from ..models.flux_value_control import SingleValueEncoder from ..lora.flux_lora import FluxLoraPatcher +from ..models.flux_lora_encoder import FluxLoRAEncoder model_loader_configs = [ @@ -150,6 +151,7 @@ model_loader_configs = [ (None, "dbd5ec76bbf977983f972c151d545389", ["wan_video_motion_controller"], [WanMotionControllerModel], "civitai"), (None, "d30fb9e02b1dbf4e509142f05cf7dd50", ["flux_dit", "step1x_connector"], [FluxDiT, Qwen2Connector], "civitai"), (None, "30143afb2dea73d1ac580e0787628f8c", ["flux_lora_patcher"], [FluxLoraPatcher], "civitai"), + (None, "77c2e4dd2440269eb33bfaa0d004f6ab", ["flux_lora_encoder"], [FluxLoRAEncoder], "civitai"), ] huggingface_model_loader_configs = [ # These configs are provided for detecting model type automatically. diff --git a/diffsynth/models/flux_lora_encoder.py b/diffsynth/models/flux_lora_encoder.py new file mode 100644 index 0000000..695640a --- /dev/null +++ b/diffsynth/models/flux_lora_encoder.py @@ -0,0 +1,111 @@ +import torch +from .sd_text_encoder import CLIPEncoderLayer + + +class LoRALayerBlock(torch.nn.Module): + def __init__(self, L, dim_in, dim_out): + super().__init__() + self.x = torch.nn.Parameter(torch.randn(1, L, dim_in)) + self.layer_norm = torch.nn.LayerNorm(dim_out) + + def forward(self, lora_A, lora_B): + x = self.x @ lora_A.T @ lora_B.T + x = self.layer_norm(x) + return x + + +class LoRAEmbedder(torch.nn.Module): + def __init__(self, lora_patterns=None, L=1, out_dim=2048): + super().__init__() + if lora_patterns is None: + lora_patterns = self.default_lora_patterns() + + model_dict = {} + for lora_pattern in lora_patterns: + name, dim = lora_pattern["name"], lora_pattern["dim"] + model_dict[name.replace(".", "___")] = LoRALayerBlock(L, dim[0], dim[1]) + self.model_dict = torch.nn.ModuleDict(model_dict) + + proj_dict = {} + for lora_pattern in lora_patterns: + layer_type, dim = lora_pattern["type"], lora_pattern["dim"] + if layer_type not in proj_dict: + proj_dict[layer_type.replace(".", "___")] = torch.nn.Linear(dim[1], out_dim) + self.proj_dict = torch.nn.ModuleDict(proj_dict) + + self.lora_patterns = lora_patterns + + + def default_lora_patterns(self): + lora_patterns = [] + lora_dict = { + "attn.a_to_qkv": (3072, 9216), "attn.a_to_out": (3072, 3072), "ff_a.0": (3072, 12288), "ff_a.2": (12288, 3072), "norm1_a.linear": (3072, 18432), + "attn.b_to_qkv": (3072, 9216), "attn.b_to_out": (3072, 3072), "ff_b.0": (3072, 12288), "ff_b.2": (12288, 3072), "norm1_b.linear": (3072, 18432), + } + for i in range(19): + for suffix in lora_dict: + lora_patterns.append({ + "name": f"blocks.{i}.{suffix}", + "dim": lora_dict[suffix], + "type": suffix, + }) + lora_dict = {"to_qkv_mlp": (3072, 21504), "proj_out": (15360, 3072), "norm.linear": (3072, 9216)} + for i in range(38): + for suffix in lora_dict: + lora_patterns.append({ + "name": f"single_blocks.{i}.{suffix}", + "dim": lora_dict[suffix], + "type": suffix, + }) + return lora_patterns + + def forward(self, lora): + lora_emb = [] + for lora_pattern in self.lora_patterns: + name, layer_type = lora_pattern["name"], lora_pattern["type"] + lora_A = lora[name + ".lora_A.default.weight"] + lora_B = lora[name + ".lora_B.default.weight"] + lora_out = self.model_dict[name.replace(".", "___")](lora_A, lora_B) + lora_out = self.proj_dict[layer_type.replace(".", "___")](lora_out) + lora_emb.append(lora_out) + lora_emb = torch.concat(lora_emb, dim=1) + return lora_emb + + +class FluxLoRAEncoder(torch.nn.Module): + def __init__(self, embed_dim=4096, encoder_intermediate_size=8192, num_encoder_layers=1, num_embeds_per_lora=16, num_special_embeds=1): + super().__init__() + self.num_embeds_per_lora = num_embeds_per_lora + # embedder + self.embedder = LoRAEmbedder(L=num_embeds_per_lora, out_dim=embed_dim) + + # encoders + self.encoders = torch.nn.ModuleList([CLIPEncoderLayer(embed_dim, encoder_intermediate_size, num_heads=32, head_dim=128) for _ in range(num_encoder_layers)]) + + # special embedding + self.special_embeds = torch.nn.Parameter(torch.randn(1, num_special_embeds, embed_dim)) + self.num_special_embeds = num_special_embeds + + # final layer + self.final_layer_norm = torch.nn.LayerNorm(embed_dim) + self.final_linear = torch.nn.Linear(embed_dim, embed_dim) + + def forward(self, lora): + lora_embeds = self.embedder(lora) + special_embeds = self.special_embeds.to(dtype=lora_embeds.dtype, device=lora_embeds.device) + embeds = torch.concat([special_embeds, lora_embeds], dim=1) + for encoder_id, encoder in enumerate(self.encoders): + embeds = encoder(embeds) + embeds = embeds[:, :self.num_special_embeds] + embeds = self.final_layer_norm(embeds) + embeds = self.final_linear(embeds) + return embeds + + @staticmethod + def state_dict_converter(): + return FluxLoRAEncoderStateDictConverter() + + +class FluxLoRAEncoderStateDictConverter: + def from_civitai(self, state_dict): + return state_dict diff --git a/diffsynth/models/utils.py b/diffsynth/models/utils.py index 0d58e4e..86104d0 100644 --- a/diffsynth/models/utils.py +++ b/diffsynth/models/utils.py @@ -71,7 +71,7 @@ def load_state_dict(file_path, torch_dtype=None, device="cpu"): def load_state_dict_from_safetensors(file_path, torch_dtype=None, device="cpu"): state_dict = {} - with safe_open(file_path, framework="pt", device=device) as f: + with safe_open(file_path, framework="pt", device=str(device)) as f: for k in f.keys(): state_dict[k] = f.get_tensor(k) if torch_dtype is not None: diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index 3dbb9b8..811b119 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -20,6 +20,7 @@ from ..models.flux_controlnet import FluxControlNet from ..models.flux_ipadapter import FluxIpAdapter from ..models.flux_value_control import MultiValueEncoder from ..models.flux_infiniteyou import InfiniteYouImageProjector +from ..models.flux_lora_encoder import FluxLoRAEncoder, LoRALayerBlock from ..models.tiler import FastTileWorker from .wan_video_new import BasePipeline, ModelConfig, PipelineUnitRunner, PipelineUnit from ..lora.flux_lora import FluxLoRALoader, FluxLoraPatcher @@ -97,6 +98,7 @@ class FluxImagePipeline(BasePipeline): self.infinityou_processor: InfinitYou = None self.image_proj_model: InfiniteYouImageProjector = None self.lora_patcher: FluxLoraPatcher = None + self.lora_encoder: FluxLoRAEncoder = None self.unit_runner = PipelineUnitRunner() self.in_iteration_models = ("dit", "step1x_connector", "controlnet", "lora_patcher") self.units = [ @@ -115,6 +117,7 @@ class FluxImagePipeline(BasePipeline): FluxImageUnit_Flex(), FluxImageUnit_Step1x(), FluxImageUnit_ValueControl(), + FluxImageUnit_LoRAEncode(), ] self.model_fn = model_fn_flux_image @@ -196,6 +199,7 @@ class FluxImagePipeline(BasePipeline): torch.nn.Conv2d: AutoWrappedModule, torch.nn.GroupNorm: AutoWrappedModule, RMSNorm: AutoWrappedModule, + LoRALayerBlock: AutoWrappedModule, }, module_config = dict( offload_dtype=dtype, @@ -207,6 +211,33 @@ class FluxImagePipeline(BasePipeline): ), vram_limit=vram_limit, ) + + + def enable_lora_magic(self): + if self.dit is not None: + if not (hasattr(self.dit, "vram_management_enabled") and self.dit.vram_management_enabled): + dtype = next(iter(self.dit.parameters())).dtype + enable_vram_management( + self.dit, + module_map = { + torch.nn.Linear: AutoWrappedLinear, + }, + module_config = dict( + offload_dtype=dtype, + offload_device=self.device, + onload_dtype=dtype, + onload_device=self.device, + computation_dtype=self.torch_dtype, + computation_device=self.device, + ), + vram_limit=None, + ) + if self.lora_patcher is not None: + for name, module in self.dit.named_modules(): + if isinstance(module, AutoWrappedLinear): + merger_name = name.replace(".", "___") + if merger_name in self.lora_patcher.model_dict: + module.lora_merger = self.lora_patcher.model_dict[merger_name] def enable_vram_management(self, num_persistent_param_in_dit=None, vram_limit=None, vram_buffer=0.5): @@ -219,7 +250,7 @@ class FluxImagePipeline(BasePipeline): vram_limit = vram_limit - vram_buffer # Default config - default_vram_management_models = ["text_encoder_1", "vae_decoder", "vae_encoder", "controlnet", "image_proj_model", "ipadapter", "lora_patcher", "value_controller", "step1x_connector"] + default_vram_management_models = ["text_encoder_1", "vae_decoder", "vae_encoder", "controlnet", "image_proj_model", "ipadapter", "lora_patcher", "value_controller", "step1x_connector", "lora_encoder"] for model_name in default_vram_management_models: self._enable_vram_management_with_default_config(getattr(self, model_name), vram_limit) @@ -366,6 +397,7 @@ class FluxImagePipeline(BasePipeline): if pipe.image_proj_model is not None: pipe.infinityou_processor = InfinitYou(device=device) pipe.lora_patcher = model_manager.fetch_model("flux_lora_patcher") + pipe.lora_encoder = model_manager.fetch_model("flux_lora_encoder") # ControlNet controlnets = [] @@ -437,6 +469,9 @@ class FluxImagePipeline(BasePipeline): value_controller_inputs: list[float] = None, # Step1x step1x_reference_image: Image.Image = None, + # LoRA Encoder + lora_encoder_inputs: Union[list[ModelConfig], ModelConfig, str] = None, + lora_encoder_scale: float = 1.0, # TeaCache tea_cache_l1_thresh: float = None, # Tile @@ -470,6 +505,7 @@ class FluxImagePipeline(BasePipeline): "flex_inpaint_image": flex_inpaint_image, "flex_inpaint_mask": flex_inpaint_mask, "flex_control_image": flex_control_image, "flex_control_strength": flex_control_strength, "flex_control_stop": flex_control_stop, "value_controller_inputs": value_controller_inputs, "step1x_reference_image": step1x_reference_image, + "lora_encoder_inputs": lora_encoder_inputs, "lora_encoder_scale": lora_encoder_scale, "tea_cache_l1_thresh": tea_cache_l1_thresh, "tiled": tiled, "tile_size": tile_size, "tile_stride": tile_stride, "progress_bar_cmd": progress_bar_cmd, @@ -884,6 +920,66 @@ class InfinitYou(torch.nn.Module): return {'id_emb': id_emb, 'infinityou_guidance': infinityou_guidance} + +class FluxImageUnit_LoRAEncode(PipelineUnit): + def __init__(self): + super().__init__( + take_over=True, + onload_model_names=("lora_encoder",) + ) + + def parse_lora_encoder_inputs(self, lora_encoder_inputs): + if not isinstance(lora_encoder_inputs, list): + lora_encoder_inputs = [lora_encoder_inputs] + lora_configs = [] + for lora_encoder_input in lora_encoder_inputs: + if isinstance(lora_encoder_input, str): + lora_encoder_input = ModelConfig(path=lora_encoder_input) + lora_encoder_input.download_if_necessary() + lora_configs.append(lora_encoder_input) + return lora_configs + + def load_lora(self, lora_config, dtype, device): + loader = FluxLoRALoader(torch_dtype=dtype, device=device) + lora = load_state_dict(lora_config.path, torch_dtype=dtype, device=device) + lora = loader.convert_state_dict(lora) + return lora + + def lora_embedding(self, pipe, lora_encoder_inputs): + lora_emb = [] + for lora_config in self.parse_lora_encoder_inputs(lora_encoder_inputs): + lora = self.load_lora(lora_config, pipe.torch_dtype, pipe.device) + lora_emb.append(pipe.lora_encoder(lora)) + lora_emb = torch.concat(lora_emb, dim=1) + return lora_emb + + def add_to_text_embedding(self, prompt_emb, text_ids, lora_emb): + prompt_emb = torch.concat([prompt_emb, lora_emb], dim=1) + extra_text_ids = torch.zeros((lora_emb.shape[0], lora_emb.shape[1], 3), device=lora_emb.device, dtype=lora_emb.dtype) + text_ids = torch.concat([text_ids, extra_text_ids], dim=1) + return prompt_emb, text_ids + + def process(self, pipe: FluxImagePipeline, inputs_shared, inputs_posi, inputs_nega): + if inputs_shared.get("lora_encoder_inputs", None) is None: + return inputs_shared, inputs_posi, inputs_nega + + # Encode + pipe.load_models_to_device(["lora_encoder"]) + lora_encoder_inputs = inputs_shared["lora_encoder_inputs"] + lora_emb = self.lora_embedding(pipe, lora_encoder_inputs) + + # Scale + lora_encoder_scale = inputs_shared.get("lora_encoder_scale", None) + if lora_encoder_scale is not None: + lora_emb = lora_emb * lora_encoder_scale + + # Add to prompt embedding + inputs_posi["prompt_emb"], inputs_posi["text_ids"] = self.add_to_text_embedding( + inputs_posi["prompt_emb"], inputs_posi["text_ids"], lora_emb) + return inputs_shared, inputs_posi, inputs_nega + + + class TeaCache: def __init__(self, num_inference_steps, rel_l1_thresh): self.num_inference_steps = num_inference_steps diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 9f52ddc..6902011 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -165,6 +165,7 @@ class ModelConfig: download_resource: str = "ModelScope" offload_device: Optional[Union[str, torch.device]] = None offload_dtype: Optional[torch.dtype] = None + skip_download: bool = False def download_if_necessary(self, local_model_path="./models", skip_download=False, use_usp=False): if self.path is None: @@ -190,6 +191,7 @@ class ModelConfig: is_folder = False # Download + skip_download = skip_download or self.skip_download if not skip_download: downloaded_files = glob.glob(self.origin_file_pattern, root_dir=os.path.join(local_model_path, self.model_id)) snapshot_download( diff --git a/examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py b/examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py new file mode 100644 index 0000000..d133024 --- /dev/null +++ b/examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py @@ -0,0 +1,40 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev", origin_file_pattern="model.safetensors"), + ], +) +pipe.enable_lora_magic() + +lora = ModelConfig(model_id="VoidOc/flux_animal_forest1", origin_file_pattern="20.safetensors") +pipe.load_lora(pipe.dit, lora, hotload=True) # Use `pipe.clear_lora()` to drop the loaded LoRA. + +# Empty prompt can automatically activate LoRA capabilities. +image = pipe(prompt="", seed=0, lora_encoder_inputs=lora) +image.save("image_1.jpg") + +image = pipe(prompt="", seed=0) +image.save("image_1_origin.jpg") + +# Prompt without trigger words can also activate LoRA capabilities. +image = pipe(prompt="a car", seed=0, lora_encoder_inputs=lora) +image.save("image_2.jpg") + +image = pipe(prompt="a car", seed=0,) +image.save("image_2_origin.jpg") + +# Adjust the activation intensity through the scale parameter. +image = pipe(prompt="a cat", seed=0, lora_encoder_inputs=lora, lora_encoder_scale=1.0) +image.save("image_3.jpg") + +image = pipe(prompt="a cat", seed=0, lora_encoder_inputs=lora, lora_encoder_scale=0.5) +image.save("image_3_scale.jpg") diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py new file mode 100644 index 0000000..54322bd --- /dev/null +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py @@ -0,0 +1,41 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev", origin_file_pattern="model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() +pipe.enable_lora_magic() + +lora = ModelConfig(model_id="VoidOc/flux_animal_forest1", origin_file_pattern="20.safetensors") +pipe.load_lora(pipe.dit, lora, hotload=True) # Use `pipe.clear_lora()` to drop the loaded LoRA. + +# Empty prompt can automatically activate LoRA capabilities. +image = pipe(prompt="", seed=0, lora_encoder_inputs=lora) +image.save("image_1.jpg") + +image = pipe(prompt="", seed=0) +image.save("image_1_origin.jpg") + +# Prompt without trigger words can also activate LoRA capabilities. +image = pipe(prompt="a car", seed=0, lora_encoder_inputs=lora) +image.save("image_2.jpg") + +image = pipe(prompt="a car", seed=0,) +image.save("image_2_origin.jpg") + +# Adjust the activation intensity through the scale parameter. +image = pipe(prompt="a cat", seed=0, lora_encoder_inputs=lora, lora_encoder_scale=1.0) +image.save("image_3.jpg") + +image = pipe(prompt="a cat", seed=0, lora_encoder_inputs=lora, lora_encoder_scale=0.5) +image.save("image_3_scale.jpg") From 55951590f5d13ef7d1d5e399e80d983018be2108 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Sun, 20 Jul 2025 18:13:50 +0800 Subject: [PATCH 11/51] support wan2.2 5B T2V --- diffsynth/configs/model_config.py | 4 +- diffsynth/models/wan_video_dit.py | 14 + diffsynth/models/wan_video_vae.py | 576 +++++++++++++++++- diffsynth/pipelines/wan_video_new.py | 3 +- .../model_inference/Wan2.2-TI2V-5B.py | 34 ++ 5 files changed, 628 insertions(+), 3 deletions(-) create mode 100644 examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py diff --git a/diffsynth/configs/model_config.py b/diffsynth/configs/model_config.py index 7a0b72b..0903f79 100644 --- a/diffsynth/configs/model_config.py +++ b/diffsynth/configs/model_config.py @@ -58,7 +58,7 @@ from ..models.stepvideo_dit import StepVideoModel from ..models.wan_video_dit import WanModel from ..models.wan_video_text_encoder import WanTextEncoder from ..models.wan_video_image_encoder import WanImageEncoder -from ..models.wan_video_vae import WanVideoVAE +from ..models.wan_video_vae import WanVideoVAE, WanVideoVAE38 from ..models.wan_video_motion_controller import WanMotionControllerModel from ..models.wan_video_vace import VaceWanModel @@ -140,6 +140,7 @@ model_loader_configs = [ (None, "26bde73488a92e64cc20b0a7485b9e5b", ["wan_video_dit"], [WanModel], "civitai"), (None, "ac6a5aa74f4a0aab6f64eb9a72f19901", ["wan_video_dit"], [WanModel], "civitai"), (None, "b61c605c2adbd23124d152ed28e049ae", ["wan_video_dit"], [WanModel], "civitai"), + (None, "1f5ab7703c6fc803fdded85ff040c316", ["wan_video_dit"], [WanModel], "civitai"), (None, "a61453409b67cd3246cf0c3bebad47ba", ["wan_video_dit", "wan_video_vace"], [WanModel, VaceWanModel], "civitai"), (None, "7a513e1f257a861512b1afd387a8ecd9", ["wan_video_dit", "wan_video_vace"], [WanModel, VaceWanModel], "civitai"), (None, "cb104773c6c2cb6df4f9529ad5c60d0b", ["wan_video_dit"], [WanModel], "diffusers"), @@ -147,6 +148,7 @@ model_loader_configs = [ (None, "5941c53e207d62f20f9025686193c40b", ["wan_video_image_encoder"], [WanImageEncoder], "civitai"), (None, "1378ea763357eea97acdef78e65d6d96", ["wan_video_vae"], [WanVideoVAE], "civitai"), (None, "ccc42284ea13e1ad04693284c7a09be6", ["wan_video_vae"], [WanVideoVAE], "civitai"), + (None, "e1de6c02cdac79f8b739f4d3698cd216", ["wan_video_vae"], [WanVideoVAE38], "civitai"), (None, "dbd5ec76bbf977983f972c151d545389", ["wan_video_motion_controller"], [WanMotionControllerModel], "civitai"), (None, "d30fb9e02b1dbf4e509142f05cf7dd50", ["flux_dit", "step1x_connector"], [FluxDiT, Qwen2Connector], "civitai"), (None, "30143afb2dea73d1ac580e0787628f8c", ["flux_lora_patcher"], [FluxLoraPatcher], "civitai"), diff --git a/diffsynth/models/wan_video_dit.py b/diffsynth/models/wan_video_dit.py index 50c06bf..2daf1b4 100644 --- a/diffsynth/models/wan_video_dit.py +++ b/diffsynth/models/wan_video_dit.py @@ -659,6 +659,20 @@ class WanModelStateDictConverter: "add_control_adapter": True, "in_dim_control_adapter": 24, } + elif hash_state_dict_keys(state_dict) == "1f5ab7703c6fc803fdded85ff040c316": + config = { + "has_image_input": False, + "patch_size": [1, 2, 2], + "in_dim": 48, + "dim": 3072, + "ffn_dim": 14336, + "freq_dim": 256, + "text_dim": 4096, + "out_dim": 48, + "num_heads": 24, + "num_layers": 30, + "eps": 1e-6, + } else: config = {} return state_dict, config diff --git a/diffsynth/models/wan_video_vae.py b/diffsynth/models/wan_video_vae.py index 137fd28..d737e2f 100644 --- a/diffsynth/models/wan_video_vae.py +++ b/diffsynth/models/wan_video_vae.py @@ -195,6 +195,75 @@ class Resample(nn.Module): nn.init.zeros_(conv.bias.data) + +def patchify(x, patch_size): + if patch_size == 1: + return x + if x.dim() == 4: + x = rearrange(x, "b c (h q) (w r) -> b (c r q) h w", q=patch_size, r=patch_size) + elif x.dim() == 5: + x = rearrange(x, + "b c f (h q) (w r) -> b (c r q) f h w", + q=patch_size, + r=patch_size) + else: + raise ValueError(f"Invalid input shape: {x.shape}") + return x + + +def unpatchify(x, patch_size): + if patch_size == 1: + return x + if x.dim() == 4: + x = rearrange(x, "b (c r q) h w -> b c (h q) (w r)", q=patch_size, r=patch_size) + elif x.dim() == 5: + x = rearrange(x, + "b (c r q) f h w -> b c f (h q) (w r)", + q=patch_size, + r=patch_size) + return x + + +class Resample38(Resample): + + def __init__(self, dim, mode): + assert mode in ( + "none", + "upsample2d", + "upsample3d", + "downsample2d", + "downsample3d", + ) + super(Resample, self).__init__() + self.dim = dim + self.mode = mode + + # layers + if mode == "upsample2d": + self.resample = nn.Sequential( + Upsample(scale_factor=(2.0, 2.0), mode="nearest-exact"), + nn.Conv2d(dim, dim, 3, padding=1), + ) + elif mode == "upsample3d": + self.resample = nn.Sequential( + Upsample(scale_factor=(2.0, 2.0), mode="nearest-exact"), + nn.Conv2d(dim, dim, 3, padding=1), + ) + self.time_conv = CausalConv3d(dim, dim * 2, (3, 1, 1), padding=(1, 0, 0)) + elif mode == "downsample2d": + self.resample = nn.Sequential( + nn.ZeroPad2d((0, 1, 0, 1)), nn.Conv2d(dim, dim, 3, stride=(2, 2)) + ) + elif mode == "downsample3d": + self.resample = nn.Sequential( + nn.ZeroPad2d((0, 1, 0, 1)), nn.Conv2d(dim, dim, 3, stride=(2, 2)) + ) + self.time_conv = CausalConv3d( + dim, dim, (3, 1, 1), stride=(2, 1, 1), padding=(0, 0, 0) + ) + else: + self.resample = nn.Identity() + class ResidualBlock(nn.Module): def __init__(self, in_dim, out_dim, dropout=0.0): @@ -273,6 +342,178 @@ class AttentionBlock(nn.Module): return x + identity +class AvgDown3D(nn.Module): + def __init__( + self, + in_channels, + out_channels, + factor_t, + factor_s=1, + ): + super().__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.factor_t = factor_t + self.factor_s = factor_s + self.factor = self.factor_t * self.factor_s * self.factor_s + + assert in_channels * self.factor % out_channels == 0 + self.group_size = in_channels * self.factor // out_channels + + def forward(self, x: torch.Tensor) -> torch.Tensor: + pad_t = (self.factor_t - x.shape[2] % self.factor_t) % self.factor_t + pad = (0, 0, 0, 0, pad_t, 0) + x = F.pad(x, pad) + B, C, T, H, W = x.shape + x = x.view( + B, + C, + T // self.factor_t, + self.factor_t, + H // self.factor_s, + self.factor_s, + W // self.factor_s, + self.factor_s, + ) + x = x.permute(0, 1, 3, 5, 7, 2, 4, 6).contiguous() + x = x.view( + B, + C * self.factor, + T // self.factor_t, + H // self.factor_s, + W // self.factor_s, + ) + x = x.view( + B, + self.out_channels, + self.group_size, + T // self.factor_t, + H // self.factor_s, + W // self.factor_s, + ) + x = x.mean(dim=2) + return x + + +class DupUp3D(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + factor_t, + factor_s=1, + ): + super().__init__() + self.in_channels = in_channels + self.out_channels = out_channels + + self.factor_t = factor_t + self.factor_s = factor_s + self.factor = self.factor_t * self.factor_s * self.factor_s + + assert out_channels * self.factor % in_channels == 0 + self.repeats = out_channels * self.factor // in_channels + + def forward(self, x: torch.Tensor, first_chunk=False) -> torch.Tensor: + x = x.repeat_interleave(self.repeats, dim=1) + x = x.view( + x.size(0), + self.out_channels, + self.factor_t, + self.factor_s, + self.factor_s, + x.size(2), + x.size(3), + x.size(4), + ) + x = x.permute(0, 1, 5, 2, 6, 3, 7, 4).contiguous() + x = x.view( + x.size(0), + self.out_channels, + x.size(2) * self.factor_t, + x.size(4) * self.factor_s, + x.size(6) * self.factor_s, + ) + if first_chunk: + x = x[:, :, self.factor_t - 1 :, :, :] + return x + + +class Down_ResidualBlock(nn.Module): + def __init__( + self, in_dim, out_dim, dropout, mult, temperal_downsample=False, down_flag=False + ): + super().__init__() + + # Shortcut path with downsample + self.avg_shortcut = AvgDown3D( + in_dim, + out_dim, + factor_t=2 if temperal_downsample else 1, + factor_s=2 if down_flag else 1, + ) + + # Main path with residual blocks and downsample + downsamples = [] + for _ in range(mult): + downsamples.append(ResidualBlock(in_dim, out_dim, dropout)) + in_dim = out_dim + + # Add the final downsample block + if down_flag: + mode = "downsample3d" if temperal_downsample else "downsample2d" + downsamples.append(Resample38(out_dim, mode=mode)) + + self.downsamples = nn.Sequential(*downsamples) + + def forward(self, x, feat_cache=None, feat_idx=[0]): + x_copy = x.clone() + for module in self.downsamples: + x = module(x, feat_cache, feat_idx) + + return x + self.avg_shortcut(x_copy) + + +class Up_ResidualBlock(nn.Module): + def __init__( + self, in_dim, out_dim, dropout, mult, temperal_upsample=False, up_flag=False + ): + super().__init__() + # Shortcut path with upsample + if up_flag: + self.avg_shortcut = DupUp3D( + in_dim, + out_dim, + factor_t=2 if temperal_upsample else 1, + factor_s=2 if up_flag else 1, + ) + else: + self.avg_shortcut = None + + # Main path with residual blocks and upsample + upsamples = [] + for _ in range(mult): + upsamples.append(ResidualBlock(in_dim, out_dim, dropout)) + in_dim = out_dim + + # Add the final upsample block + if up_flag: + mode = "upsample3d" if temperal_upsample else "upsample2d" + upsamples.append(Resample38(out_dim, mode=mode)) + + self.upsamples = nn.Sequential(*upsamples) + + def forward(self, x, feat_cache=None, feat_idx=[0], first_chunk=False): + x_main = x.clone() + for module in self.upsamples: + x_main = module(x_main, feat_cache, feat_idx) + if self.avg_shortcut is not None: + x_shortcut = self.avg_shortcut(x, first_chunk) + return x_main + x_shortcut + else: + return x_main + + class Encoder3d(nn.Module): def __init__(self, @@ -376,6 +617,122 @@ class Encoder3d(nn.Module): return x +class Encoder3d_38(nn.Module): + + def __init__(self, + dim=128, + z_dim=4, + dim_mult=[1, 2, 4, 4], + num_res_blocks=2, + attn_scales=[], + temperal_downsample=[False, True, True], + dropout=0.0): + super().__init__() + self.dim = dim + self.z_dim = z_dim + self.dim_mult = dim_mult + self.num_res_blocks = num_res_blocks + self.attn_scales = attn_scales + self.temperal_downsample = temperal_downsample + + # dimensions + dims = [dim * u for u in [1] + dim_mult] + scale = 1.0 + + # init block + self.conv1 = CausalConv3d(12, dims[0], 3, padding=1) + + # downsample blocks + downsamples = [] + for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])): + t_down_flag = ( + temperal_downsample[i] if i < len(temperal_downsample) else False + ) + downsamples.append( + Down_ResidualBlock( + in_dim=in_dim, + out_dim=out_dim, + dropout=dropout, + mult=num_res_blocks, + temperal_downsample=t_down_flag, + down_flag=i != len(dim_mult) - 1, + ) + ) + scale /= 2.0 + self.downsamples = nn.Sequential(*downsamples) + + # middle blocks + self.middle = nn.Sequential( + ResidualBlock(out_dim, out_dim, dropout), + AttentionBlock(out_dim), + ResidualBlock(out_dim, out_dim, dropout), + ) + + # # output blocks + self.head = nn.Sequential( + RMS_norm(out_dim, images=False), + nn.SiLU(), + CausalConv3d(out_dim, z_dim, 3, padding=1), + ) + + + def forward(self, x, feat_cache=None, feat_idx=[0]): + + if feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + cache_x = torch.cat( + [ + feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device), + cache_x, + ], + dim=2, + ) + x = self.conv1(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = self.conv1(x) + + ## downsamples + for layer in self.downsamples: + if feat_cache is not None: + x = layer(x, feat_cache, feat_idx) + else: + x = layer(x) + + ## middle + for layer in self.middle: + if isinstance(layer, ResidualBlock) and feat_cache is not None: + x = layer(x, feat_cache, feat_idx) + else: + x = layer(x) + + ## head + for layer in self.head: + if isinstance(layer, CausalConv3d) and feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + cache_x = torch.cat( + [ + feat_cache[idx][:, :, -1, :, :] + .unsqueeze(2) + .to(cache_x.device), + cache_x, + ], + dim=2, + ) + x = layer(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = layer(x) + + return x + + class Decoder3d(nn.Module): def __init__(self, @@ -481,10 +838,112 @@ class Decoder3d(nn.Module): return x + +class Decoder3d_38(nn.Module): + + def __init__(self, + dim=128, + z_dim=4, + dim_mult=[1, 2, 4, 4], + num_res_blocks=2, + attn_scales=[], + temperal_upsample=[False, True, True], + dropout=0.0): + super().__init__() + self.dim = dim + self.z_dim = z_dim + self.dim_mult = dim_mult + self.num_res_blocks = num_res_blocks + self.attn_scales = attn_scales + self.temperal_upsample = temperal_upsample + + # dimensions + dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]] + scale = 1.0 / 2 ** (len(dim_mult) - 2) + # init block + self.conv1 = CausalConv3d(z_dim, dims[0], 3, padding=1) + + # middle blocks + self.middle = nn.Sequential(ResidualBlock(dims[0], dims[0], dropout), + AttentionBlock(dims[0]), + ResidualBlock(dims[0], dims[0], dropout)) + + # upsample blocks + upsamples = [] + for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])): + t_up_flag = temperal_upsample[i] if i < len(temperal_upsample) else False + upsamples.append( + Up_ResidualBlock(in_dim=in_dim, + out_dim=out_dim, + dropout=dropout, + mult=num_res_blocks + 1, + temperal_upsample=t_up_flag, + up_flag=i != len(dim_mult) - 1)) + self.upsamples = nn.Sequential(*upsamples) + + # output blocks + self.head = nn.Sequential(RMS_norm(out_dim, images=False), nn.SiLU(), + CausalConv3d(out_dim, 12, 3, padding=1)) + + + def forward(self, x, feat_cache=None, feat_idx=[0], first_chunk=False): + if feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + cache_x = torch.cat( + [ + feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device), + cache_x, + ], + dim=2, + ) + x = self.conv1(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = self.conv1(x) + + for layer in self.middle: + if check_is_instance(layer, ResidualBlock) and feat_cache is not None: + x = layer(x, feat_cache, feat_idx) + else: + x = layer(x) + + ## upsamples + for layer in self.upsamples: + if feat_cache is not None: + x = layer(x, feat_cache, feat_idx, first_chunk) + else: + x = layer(x) + + ## head + for layer in self.head: + if check_is_instance(layer, CausalConv3d) and feat_cache is not None: + idx = feat_idx[0] + cache_x = x[:, :, -CACHE_T:, :, :].clone() + if cache_x.shape[2] < 2 and feat_cache[idx] is not None: + cache_x = torch.cat( + [ + feat_cache[idx][:, :, -1, :, :] + .unsqueeze(2) + .to(cache_x.device), + cache_x, + ], + dim=2, + ) + x = layer(x, feat_cache[idx]) + feat_cache[idx] = cache_x + feat_idx[0] += 1 + else: + x = layer(x) + return x + + def count_conv3d(model): count = 0 for m in model.modules(): - if check_is_instance(m, CausalConv3d): + if isinstance(m, CausalConv3d): count += 1 return count @@ -798,3 +1257,118 @@ class WanVideoVAEStateDictConverter: for name in state_dict: state_dict_['model.' + name] = state_dict[name] return state_dict_ + + +class VideoVAE38_(VideoVAE_): + + def __init__(self, + dim=160, + z_dim=48, + dec_dim=256, + dim_mult=[1, 2, 4, 4], + num_res_blocks=2, + attn_scales=[], + temperal_downsample=[False, True, True], + dropout=0.0): + super(VideoVAE_, self).__init__() + self.dim = dim + self.z_dim = z_dim + self.dim_mult = dim_mult + self.num_res_blocks = num_res_blocks + self.attn_scales = attn_scales + self.temperal_downsample = temperal_downsample + self.temperal_upsample = temperal_downsample[::-1] + + # modules + self.encoder = Encoder3d_38(dim, z_dim * 2, dim_mult, num_res_blocks, + attn_scales, self.temperal_downsample, dropout) + self.conv1 = CausalConv3d(z_dim * 2, z_dim * 2, 1) + self.conv2 = CausalConv3d(z_dim, z_dim, 1) + self.decoder = Decoder3d_38(dec_dim, z_dim, dim_mult, num_res_blocks, + attn_scales, self.temperal_upsample, dropout) + + + def encode(self, x, scale): + self.clear_cache() + x = patchify(x, patch_size=2) + t = x.shape[2] + iter_ = 1 + (t - 1) // 4 + for i in range(iter_): + self._enc_conv_idx = [0] + if i == 0: + out = self.encoder(x[:, :, :1, :, :], + feat_cache=self._enc_feat_map, + feat_idx=self._enc_conv_idx) + else: + out_ = self.encoder(x[:, :, 1 + 4 * (i - 1):1 + 4 * i, :, :], + feat_cache=self._enc_feat_map, + feat_idx=self._enc_conv_idx) + out = torch.cat([out, out_], 2) + mu, log_var = self.conv1(out).chunk(2, dim=1) + if isinstance(scale[0], torch.Tensor): + scale = [s.to(dtype=mu.dtype, device=mu.device) for s in scale] + mu = (mu - scale[0].view(1, self.z_dim, 1, 1, 1)) * scale[1].view( + 1, self.z_dim, 1, 1, 1) + else: + scale = scale.to(dtype=mu.dtype, device=mu.device) + mu = (mu - scale[0]) * scale[1] + self.clear_cache() + return mu + + + def decode(self, z, scale): + self.clear_cache() + if isinstance(scale[0], torch.Tensor): + scale = [s.to(dtype=z.dtype, device=z.device) for s in scale] + z = z / scale[1].view(1, self.z_dim, 1, 1, 1) + scale[0].view( + 1, self.z_dim, 1, 1, 1) + else: + scale = scale.to(dtype=z.dtype, device=z.device) + z = z / scale[1] + scale[0] + iter_ = z.shape[2] + x = self.conv2(z) + for i in range(iter_): + self._conv_idx = [0] + if i == 0: + out = self.decoder(x[:, :, i:i + 1, :, :], + feat_cache=self._feat_map, + feat_idx=self._conv_idx, + first_chunk=True) + else: + out_ = self.decoder(x[:, :, i:i + 1, :, :], + feat_cache=self._feat_map, + feat_idx=self._conv_idx) + out = torch.cat([out, out_], 2) + out = unpatchify(out, patch_size=2) + self.clear_cache() + return out + + +class WanVideoVAE38(WanVideoVAE): + + def __init__(self, z_dim=48, dim=160): + super(WanVideoVAE, self).__init__() + + mean = [ + -0.2289, -0.0052, -0.1323, -0.2339, -0.2799, 0.0174, 0.1838, 0.1557, + -0.1382, 0.0542, 0.2813, 0.0891, 0.1570, -0.0098, 0.0375, -0.1825, + -0.2246, -0.1207, -0.0698, 0.5109, 0.2665, -0.2108, -0.2158, 0.2502, + -0.2055, -0.0322, 0.1109, 0.1567, -0.0729, 0.0899, -0.2799, -0.1230, + -0.0313, -0.1649, 0.0117, 0.0723, -0.2839, -0.2083, -0.0520, 0.3748, + 0.0152, 0.1957, 0.1433, -0.2944, 0.3573, -0.0548, -0.1681, -0.0667 + ] + std = [ + 0.4765, 1.0364, 0.4514, 1.1677, 0.5313, 0.4990, 0.4818, 0.5013, + 0.8158, 1.0344, 0.5894, 1.0901, 0.6885, 0.6165, 0.8454, 0.4978, + 0.5759, 0.3523, 0.7135, 0.6804, 0.5833, 1.4146, 0.8986, 0.5659, + 0.7069, 0.5338, 0.4889, 0.4917, 0.4069, 0.4999, 0.6866, 0.4093, + 0.5709, 0.6065, 0.6415, 0.4944, 0.5726, 1.2042, 0.5458, 1.6887, + 0.3971, 1.0600, 0.3943, 0.5537, 0.5444, 0.4089, 0.7468, 0.7744 + ] + self.mean = torch.tensor(mean) + self.std = torch.tensor(std) + self.scale = [self.mean, 1.0 / self.std] + + # init model + self.model = VideoVAE38_(z_dim=z_dim, dim=dim).eval().requires_grad_(False) + self.upsampling_factor = 16 diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 9f52ddc..91a6f7b 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -679,7 +679,8 @@ class WanVideoUnit_NoiseInitializer(PipelineUnit): length = (num_frames - 1) // 4 + 1 if vace_reference_image is not None: length += 1 - noise = pipe.generate_noise((1, 16, length, height//8, width//8), seed=seed, rand_device=rand_device) + shape = (1, pipe.vae.model.z_dim, length, height // pipe.vae.upsampling_factor, width // pipe.vae.upsampling_factor) + noise = pipe.generate_noise(shape, seed=seed, rand_device=rand_device) if vace_reference_image is not None: noise = torch.concat((noise[:, :, -1:], noise[:, :, :-1]), dim=2) return {"noise": noise} diff --git a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py new file mode 100644 index 0000000..93ac975 --- /dev/null +++ b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py @@ -0,0 +1,34 @@ +import torch +from PIL import Image +from diffsynth import save_video, VideoData +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig +from modelscope import snapshot_download +from diffsynth.models.utils import load_state_dict, hash_state_dict_keys +from modelscope import dataset_snapshot_download + +dataset_snapshot_download( + dataset_id="DiffSynth-Studio/examples_in_diffsynth", + local_dir="./", + allow_file_pattern=["data/examples/wan/depth_video.mp4", "data/examples/wan/cat_fightning.jpg"] +) + + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.1-T2V-14B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="model_shards/model-*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="Wan2.2_VAE.safetensors", offload_device="cpu"), + ], +) +pipe.enable_vram_management() + +# Text-to-video +video = pipe( + prompt="一名宇航员身穿太空服,面朝镜头骑着一匹机械马在火星表面驰骋。红色的荒凉地表延伸至远方,点缀着巨大的陨石坑和奇特的岩石结构。机械马的步伐稳健,扬起微弱的尘埃,展现出未来科技与原始探索的完美结合。宇航员手持操控装置,目光坚定,仿佛正在开辟人类的新疆域。背景是深邃的宇宙和蔚蓝的地球,画面既科幻又充满希望,让人不禁畅想未来的星际生活。", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + seed=0, tiled=True, + height=704, width=1280, +) +save_video(video, "video1.mp4", fps=15, quality=5) From c05b1a2fd0ba27be6fe36c120b26a6f20d780107 Mon Sep 17 00:00:00 2001 From: ziyannchen <1041276865@qq.com> Date: Sun, 20 Jul 2025 11:13:20 +0000 Subject: [PATCH 12/51] fix a bug in sliding window inference --- diffsynth/pipelines/wan_video_new.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 59b4690..f1a4dfe 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -1012,12 +1012,16 @@ class TemporalTiler_BCTHW: def __init__(self): pass - def build_1d_mask(self, length, left_bound, right_bound, border_width): + def build_1d_mask(length, left_bound, right_bound, border_width): x = torch.ones((length,)) + if border_width == 0: + return x + + shift = 0.5 if not left_bound: - x[:border_width] = (torch.arange(border_width) + 1) / border_width + x[:border_width] = (torch.arange(border_width) + shift) / border_width if not right_bound: - x[-border_width:] = torch.flip((torch.arange(border_width) + 1) / border_width, dims=(0,)) + x[-border_width:] = torch.flip((torch.arange(border_width) + shift) / border_width, dims=(0,)) return x def build_mask(self, data, is_bound, border_width): @@ -1047,7 +1051,7 @@ class TemporalTiler_BCTHW: mask = self.build_mask( model_output, is_bound=(t == 0, t_ == T), - border_width=(sliding_window_size - sliding_window_stride + 1,) + border_width=(sliding_window_size - sliding_window_stride,) ).to(device=data_device, dtype=data_dtype) value[:, :, t: t_, :, :] += model_output * mask weight[:, :, t: t_, :, :] += mask From f1f00c425521209eff96f43b092563be908a172a Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Mon, 21 Jul 2025 14:47:58 +0800 Subject: [PATCH 13/51] support wan2.2 5B I2V --- diffsynth/models/wan_video_dit.py | 20 +++++- diffsynth/pipelines/wan_video_new.py | 66 +++++++++++++++++-- .../model_inference/Wan2.2-TI2V-5B.py | 31 ++++++--- 3 files changed, 99 insertions(+), 18 deletions(-) diff --git a/diffsynth/models/wan_video_dit.py b/diffsynth/models/wan_video_dit.py index 2daf1b4..b7df3ff 100644 --- a/diffsynth/models/wan_video_dit.py +++ b/diffsynth/models/wan_video_dit.py @@ -212,9 +212,16 @@ class DiTBlock(nn.Module): self.gate = GateModule() def forward(self, x, context, t_mod, freqs): + has_seq = len(t_mod.shape) == 4 + chunk_dim = 2 if has_seq else 1 # msa: multi-head self-attention mlp: multi-layer perceptron shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = ( - self.modulation.to(dtype=t_mod.dtype, device=t_mod.device) + t_mod).chunk(6, dim=1) + self.modulation.to(dtype=t_mod.dtype, device=t_mod.device) + t_mod).chunk(6, dim=chunk_dim) + if has_seq: + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = ( + shift_msa.squeeze(2), scale_msa.squeeze(2), gate_msa.squeeze(2), + shift_mlp.squeeze(2), scale_mlp.squeeze(2), gate_mlp.squeeze(2), + ) input_x = modulate(self.norm1(x), shift_msa, scale_msa) x = self.gate(x, gate_msa, self.self_attn(input_x, freqs)) x = x + self.cross_attn(self.norm3(x), context) @@ -253,8 +260,12 @@ class Head(nn.Module): self.modulation = nn.Parameter(torch.randn(1, 2, dim) / dim**0.5) def forward(self, x, t_mod): - shift, scale = (self.modulation.to(dtype=t_mod.dtype, device=t_mod.device) + t_mod).chunk(2, dim=1) - x = (self.head(self.norm(x) * (1 + scale) + shift)) + if len(t_mod.shape) == 3: + shift, scale = (self.modulation.unsqueeze(0).to(dtype=t_mod.dtype, device=t_mod.device) + t_mod.unsqueeze(2)).chunk(2, dim=2) + x = (self.head(self.norm(x) * (1 + scale.squeeze(2)) + shift.squeeze(2))) + else: + shift, scale = (self.modulation.to(dtype=t_mod.dtype, device=t_mod.device) + t_mod).chunk(2, dim=1) + x = (self.head(self.norm(x) * (1 + scale) + shift)) return x @@ -276,12 +287,14 @@ class WanModel(torch.nn.Module): has_ref_conv: bool = False, add_control_adapter: bool = False, in_dim_control_adapter: int = 24, + is_5b: bool = False, ): super().__init__() self.dim = dim self.freq_dim = freq_dim self.has_image_input = has_image_input self.patch_size = patch_size + self.is_5b = is_5b self.patch_embedding = nn.Conv3d( in_dim, dim, kernel_size=patch_size, stride=patch_size) @@ -672,6 +685,7 @@ class WanModelStateDictConverter: "num_heads": 24, "num_layers": 30, "eps": 1e-6, + "is_5b": True, } else: config = {} diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 91a6f7b..1136403 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -237,6 +237,7 @@ class WanVideoPipeline(BasePipeline): WanVideoUnit_InputVideoEmbedder(), WanVideoUnit_PromptEmbedder(), WanVideoUnit_ImageEmbedder(), + WanVideoUnit_ImageEmbedder5B(), WanVideoUnit_FunControl(), WanVideoUnit_FunReference(), WanVideoUnit_FunCameraControl(), @@ -736,7 +737,7 @@ class WanVideoUnit_ImageEmbedder(PipelineUnit): ) def process(self, pipe: WanVideoPipeline, input_image, end_image, num_frames, height, width, tiled, tile_size, tile_stride): - if input_image is None: + if input_image is None or pipe.dit.is_5b: return {} pipe.load_models_to_device(self.onload_model_names) image = pipe.preprocess_image(input_image.resize((width, height))).to(pipe.device) @@ -764,7 +765,55 @@ class WanVideoUnit_ImageEmbedder(PipelineUnit): y = y.to(dtype=pipe.torch_dtype, device=pipe.device) return {"clip_feature": clip_context, "y": y} - + +class WanVideoUnit_ImageEmbedder5B(PipelineUnit): + def __init__(self): + super().__init__( + input_params=("input_image", "noise", "num_frames", "height", "width", "tiled", "tile_size", "tile_stride"), + onload_model_names=("vae") + ) + + def process(self, pipe: WanVideoPipeline, input_image, noise, num_frames, height, width, tiled, tile_size, tile_stride): + if input_image is None or not pipe.dit.is_5b: + return {} + pipe.load_models_to_device(self.onload_model_names) + image = pipe.preprocess_image(input_image.resize((width, height))).transpose(0, 1).to(pipe.device) + z = pipe.vae.encode([image.to(dtype=pipe.torch_dtype, device=pipe.device)], device=pipe.device, tiled=tiled, tile_size=tile_size, tile_stride=tile_stride)[0] + + _, mask2 = self.masks_like([noise.squeeze(0)], zero=True) + latents = (1. - mask2[0]) * z + mask2[0] * noise.squeeze(0) + latents = latents.unsqueeze(0) + + seq_len = ((num_frames - 1) // 4 + 1) * (height // pipe.vae.upsampling_factor) * (width // pipe.vae.upsampling_factor) // (2 * 2) + if hasattr(pipe, "use_unified_sequence_parallel") and pipe.use_unified_sequence_parallel: + import math + seq_len = int(math.ceil(seq_len / pipe.sp_size)) * pipe.sp_size + + return {"latents": latents, "mask_5b": mask2[0].unsqueeze(0), "seq_len": seq_len} + + @staticmethod + def masks_like(tensor, zero=False, generator=None, p=0.2): + assert isinstance(tensor, list) + out1 = [torch.ones(u.shape, dtype=u.dtype, device=u.device) for u in tensor] + out2 = [torch.ones(u.shape, dtype=u.dtype, device=u.device) for u in tensor] + + if zero: + if generator is not None: + for u, v in zip(out1, out2): + random_num = torch.rand(1, generator=generator, device=generator.device).item() + if random_num < p: + u[:, 0] = torch.normal(mean=-3.5, std=0.5, size=(1,), device=u.device, generator=generator).expand_as(u[:, 0]).exp() + v[:, 0] = torch.zeros_like(v[:, 0]) + else: + u[:, 0] = u[:, 0] + v[:, 0] = v[:, 0] + else: + for u, v in zip(out1, out2): + u[:, 0] = torch.zeros_like(u[:, 0]) + v[:, 0] = torch.zeros_like(v[:, 0]) + + return out1, out2 + class WanVideoUnit_FunControl(PipelineUnit): def __init__(self): @@ -1112,9 +1161,16 @@ def model_fn_wan_video( from xfuser.core.distributed import (get_sequence_parallel_rank, get_sequence_parallel_world_size, get_sp_group) - - t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep)) - t_mod = dit.time_projection(t).unflatten(1, (6, dit.dim)) + + if dit.is_5b and "mask_5b" in kwargs: + temp_ts = (kwargs["mask_5b"][0][0][:, ::2, ::2] * timestep).flatten() + temp_ts= torch.cat([temp_ts, temp_ts.new_ones(kwargs["seq_len"] - temp_ts.size(0)) * timestep]) + timestep = temp_ts.unsqueeze(0).flatten() + t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep).unflatten(0, (latents.size(0), kwargs["seq_len"]))) + t_mod = dit.time_projection(t).unflatten(2, (6, dit.dim)) + else: + t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep)) + t_mod = dit.time_projection(t).unflatten(1, (6, dit.dim)) if motion_bucket_id is not None and motion_controller is not None: t_mod = t_mod + motion_controller(motion_bucket_id).unflatten(1, (6, dit.dim)) context = dit.text_embedding(context) diff --git a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py index 93ac975..b737b47 100644 --- a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py +++ b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py @@ -6,13 +6,6 @@ from modelscope import snapshot_download from diffsynth.models.utils import load_state_dict, hash_state_dict_keys from modelscope import dataset_snapshot_download -dataset_snapshot_download( - dataset_id="DiffSynth-Studio/examples_in_diffsynth", - local_dir="./", - allow_file_pattern=["data/examples/wan/depth_video.mp4", "data/examples/wan/cat_fightning.jpg"] -) - - pipe = WanVideoPipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", @@ -26,9 +19,27 @@ pipe.enable_vram_management() # Text-to-video video = pipe( - prompt="一名宇航员身穿太空服,面朝镜头骑着一匹机械马在火星表面驰骋。红色的荒凉地表延伸至远方,点缀着巨大的陨石坑和奇特的岩石结构。机械马的步伐稳健,扬起微弱的尘埃,展现出未来科技与原始探索的完美结合。宇航员手持操控装置,目光坚定,仿佛正在开辟人类的新疆域。背景是深邃的宇宙和蔚蓝的地球,画面既科幻又充满希望,让人不禁畅想未来的星际生活。", + prompt="两只可爱的橘猫戴上拳击手套,站在一个拳击台上搏斗。", negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", - seed=0, tiled=True, - height=704, width=1280, + seed=0, tiled=False, + height=704, width=1248, + num_frames=121, ) save_video(video, "video1.mp4", fps=15, quality=5) + +# Image-to-video +dataset_snapshot_download( + dataset_id="DiffSynth-Studio/examples_in_diffsynth", + local_dir="./", + allow_file_pattern=["data/examples/wan/cat_fightning.jpg"] +) +input_image = Image.open("data/examples/wan/cat_fightning.jpg").resize((1248, 704)) +video = pipe( + prompt="两只可爱的橘猫戴上拳击手套,站在一个拳击台上搏斗。", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + seed=0, tiled=False, + height=704, width=1248, + input_image=input_image, + num_frames=121, +) +save_video(video, "video2.mp4", fps=15, quality=5) From dbee3a1ae0e527ba150a5f632030d83cc194e148 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Mon, 21 Jul 2025 15:07:13 +0800 Subject: [PATCH 14/51] add nexus-gen news --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 2633a1b..aa7f0f0 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,13 @@ Until now, DiffSynth-Studio has supported the following models: * [Stable Diffusion](https://huggingface.co/runwayml/stable-diffusion-v1-5) ## News +- **July 11, 2025** 🔥🔥🔥 We propose Nexus-Gen, a unified model that synergizes the language reasoning capabilities of LLMs with the image synthesis power of diffusion models. This framework enables seamless image understanding, generation, and editing tasks. + - Paper: [Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) + - Github Repo: https://github.com/modelscope/Nexus-Gen + - Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2), [HuggingFace](https://huggingface.co/modelscope/Nexus-GenV2) + - Training Dataset: [ModelScope Dataset](https://www.modelscope.cn/datasets/DiffSynth-Studio/Nexus-Gen-Training-Dataset) + - Online Demo: [ModelScope Nexus-Gen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/Nexus-Gen) + - **June 15, 2025** ModelScope's official evaluation framework, [EvalScope](https://github.com/modelscope/evalscope), now supports text-to-image generation evaluation. Try it with the [Best Practices](https://evalscope.readthedocs.io/zh-cn/latest/best_practice/t2i_eval.html) guide. - **March 31, 2025** We support InfiniteYou, an identity preserving method for FLUX. Please refer to [./examples/InfiniteYou/](./examples/InfiniteYou/) for more details. From 22705a44b45d8d0f815a2c3e0ad64d3138bcccfd Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Mon, 21 Jul 2025 16:30:06 +0800 Subject: [PATCH 15/51] update value controller --- diffsynth/configs/model_config.py | 2 +- diffsynth/models/flux_value_control.py | 2 +- diffsynth/pipelines/flux_image_new.py | 21 +++++++++++-------- ...alueControl.py => FLUX.1-dev-AttriCtrl.py} | 9 ++++---- 4 files changed, 18 insertions(+), 16 deletions(-) rename examples/flux/model_inference/{FLUX.1-dev-ValueControl.py => FLUX.1-dev-AttriCtrl.py} (60%) diff --git a/diffsynth/configs/model_config.py b/diffsynth/configs/model_config.py index c2e050d..b60c200 100644 --- a/diffsynth/configs/model_config.py +++ b/diffsynth/configs/model_config.py @@ -107,7 +107,7 @@ model_loader_configs = [ (None, "023f054d918a84ccf503481fd1e3379e", ["flux_dit"], [FluxDiT], "civitai"), (None, "d02f41c13549fa5093d3521f62a5570a", ["flux_dit"], [FluxDiT], "civitai"), (None, "605c56eab23e9e2af863ad8f0813a25d", ["flux_dit"], [FluxDiT], "diffusers"), - (None, "3ede90c44b2c161240b659f3b8393c9d", ["flux_value_controller"], [SingleValueEncoder], "civitai"), + (None, "0629116fce1472503a66992f96f3eb1a", ["flux_value_controller"], [SingleValueEncoder], "civitai"), (None, "280189ee084bca10f70907bf6ce1649d", ["cog_vae_encoder", "cog_vae_decoder"], [CogVAEEncoder, CogVAEDecoder], "diffusers"), (None, "9b9313d104ac4df27991352fec013fd4", ["rife"], [IFNet], "civitai"), (None, "6b7116078c4170bfbeaedc8fe71f6649", ["esrgan"], [RRDBNet], "civitai"), diff --git a/diffsynth/models/flux_value_control.py b/diffsynth/models/flux_value_control.py index 0ff68d3..6981344 100644 --- a/diffsynth/models/flux_value_control.py +++ b/diffsynth/models/flux_value_control.py @@ -18,7 +18,7 @@ class MultiValueEncoder(torch.nn.Module): class SingleValueEncoder(torch.nn.Module): - def __init__(self, dim_in=256, dim_out=3072, prefer_len=32, computation_device=None): + def __init__(self, dim_in=256, dim_out=4096, prefer_len=32, computation_device=None): super().__init__() self.prefer_len = prefer_len self.prefer_proj = TemporalTimesteps(num_channels=dim_in, flip_sin_to_cos=True, downscale_freq_shift=0, computation_device=computation_device) diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index 811b119..330667c 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -855,18 +855,28 @@ class FluxImageUnit_InfiniteYou(PipelineUnit): class FluxImageUnit_ValueControl(PipelineUnit): def __init__(self): super().__init__( + seperate_cfg=True, + input_params_posi={"prompt_emb": "prompt_emb", "text_ids": "text_ids"}, + input_params_nega={"prompt_emb": "prompt_emb", "text_ids": "text_ids"}, input_params=("value_controller_inputs",), onload_model_names=("value_controller",) ) + + def add_to_text_embedding(self, prompt_emb, text_ids, value_emb): + prompt_emb = torch.concat([prompt_emb, value_emb], dim=1) + extra_text_ids = torch.zeros((value_emb.shape[0], value_emb.shape[1], 3), device=value_emb.device, dtype=value_emb.dtype) + text_ids = torch.concat([text_ids, extra_text_ids], dim=1) + return prompt_emb, text_ids - def process(self, pipe: FluxImagePipeline, value_controller_inputs): + def process(self, pipe: FluxImagePipeline, prompt_emb, text_ids, value_controller_inputs): if value_controller_inputs is None: return {} value_controller_inputs = torch.tensor(value_controller_inputs).to(dtype=pipe.torch_dtype, device=pipe.device) pipe.load_models_to_device(["value_controller"]) value_emb = pipe.value_controller(value_controller_inputs, pipe.torch_dtype) value_emb = value_emb.unsqueeze(0) - return {"value_emb": value_emb} + prompt_emb, text_ids = self.add_to_text_embedding(prompt_emb, text_ids, value_emb) + return {"prompt_emb": prompt_emb, "text_ids": text_ids} @@ -1049,7 +1059,6 @@ def model_fn_flux_image( flex_condition=None, flex_uncondition=None, flex_control_stop_timestep=None, - value_emb=None, step1x_llm_embedding=None, step1x_mask=None, step1x_reference_latents=None, @@ -1155,12 +1164,6 @@ def model_fn_flux_image( prompt_emb, image_rotary_emb, attention_mask = dit.process_entity_masks(hidden_states, prompt_emb, entity_prompt_emb, entity_masks, text_ids, image_ids) else: prompt_emb = dit.context_embedder(prompt_emb) - # Value Control - if value_emb is not None: - prompt_emb = torch.concat([prompt_emb, value_emb], dim=1) - value_text_ids = torch.zeros((value_emb.shape[0], value_emb.shape[1], 3), device=value_emb.device, dtype=value_emb.dtype) - text_ids = torch.concat([text_ids, value_text_ids], dim=1) - # Original FLUX inference image_rotary_emb = dit.pos_embedder(torch.cat((text_ids, image_ids), dim=1)) attention_mask = None diff --git a/examples/flux/model_inference/FLUX.1-dev-ValueControl.py b/examples/flux/model_inference/FLUX.1-dev-AttriCtrl.py similarity index 60% rename from examples/flux/model_inference/FLUX.1-dev-ValueControl.py rename to examples/flux/model_inference/FLUX.1-dev-AttriCtrl.py index 0bb3ed0..7dd4574 100644 --- a/examples/flux/model_inference/FLUX.1-dev-ValueControl.py +++ b/examples/flux/model_inference/FLUX.1-dev-AttriCtrl.py @@ -10,11 +10,10 @@ pipe = FluxImagePipeline.from_pretrained( ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), - ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-ValueController", origin_file_pattern="single/prefer_embed/value.ckpt") + ModelConfig(model_id="DiffSynth-Studio/AttriCtrl-FLUX.1-Dev", origin_file_pattern="models/brightness.safetensors") ], ) -pipe.load_lora(pipe.dit, ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-ValueController", origin_file_pattern="single/dit_lora/dit_value.ckpt")) -for i in range(10): - image = pipe(prompt="a cat", seed=0, value_controller_inputs=[i/10]) - image.save(f"value_control_{i}.jpg") \ No newline at end of file +for i in [0.1, 0.3, 0.5, 0.7, 0.9]: + image = pipe(prompt="A woman.", seed=602, value_controller_inputs=[i], rand_device="cuda") + image.save(f"value_control_{i}.jpg") From e3c5d2540b5b2c81358b2ae65d7d3e4acd360cff Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Mon, 21 Jul 2025 19:16:30 +0800 Subject: [PATCH 16/51] support value controller training --- diffsynth/pipelines/flux_image_new.py | 4 +++- .../model_inference/FLUX.1-dev-AttriCtrl.py | 2 +- ...alueControl.py => FLUX.1-dev-AttriCtrl.py} | 9 ++++---- .../full/FLUX.1-dev-AttriCtrl.sh | 14 +++++++++++++ .../lora/FLUX.1-dev-AttriCtrl.sh | 17 +++++++++++++++ .../validate_full/FLUX.1-dev-AttriCtrl.py | 21 +++++++++++++++++++ .../validate_lora/FLUX.1-dev-AttriCtrl.py | 19 +++++++++++++++++ 7 files changed, 79 insertions(+), 7 deletions(-) rename examples/flux/model_inference_low_vram/{FLUX.1-dev-ValueControl.py => FLUX.1-dev-AttriCtrl.py} (65%) create mode 100644 examples/flux/model_training/full/FLUX.1-dev-AttriCtrl.sh create mode 100644 examples/flux/model_training/lora/FLUX.1-dev-AttriCtrl.sh create mode 100644 examples/flux/model_training/validate_full/FLUX.1-dev-AttriCtrl.py create mode 100644 examples/flux/model_training/validate_lora/FLUX.1-dev-AttriCtrl.py diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index 330667c..6525dd4 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -466,7 +466,7 @@ class FluxImagePipeline(BasePipeline): flex_control_strength: float = 0.5, flex_control_stop: float = 0.5, # Value Controller - value_controller_inputs: list[float] = None, + value_controller_inputs: Union[list[float], float] = None, # Step1x step1x_reference_image: Image.Image = None, # LoRA Encoder @@ -871,6 +871,8 @@ class FluxImageUnit_ValueControl(PipelineUnit): def process(self, pipe: FluxImagePipeline, prompt_emb, text_ids, value_controller_inputs): if value_controller_inputs is None: return {} + if not isinstance(value_controller_inputs, list): + value_controller_inputs = [value_controller_inputs] value_controller_inputs = torch.tensor(value_controller_inputs).to(dtype=pipe.torch_dtype, device=pipe.device) pipe.load_models_to_device(["value_controller"]) value_emb = pipe.value_controller(value_controller_inputs, pipe.torch_dtype) diff --git a/examples/flux/model_inference/FLUX.1-dev-AttriCtrl.py b/examples/flux/model_inference/FLUX.1-dev-AttriCtrl.py index 7dd4574..6c8d870 100644 --- a/examples/flux/model_inference/FLUX.1-dev-AttriCtrl.py +++ b/examples/flux/model_inference/FLUX.1-dev-AttriCtrl.py @@ -15,5 +15,5 @@ pipe = FluxImagePipeline.from_pretrained( ) for i in [0.1, 0.3, 0.5, 0.7, 0.9]: - image = pipe(prompt="A woman.", seed=602, value_controller_inputs=[i], rand_device="cuda") + image = pipe(prompt="a cat on the beach", seed=2, value_controller_inputs=[i]) image.save(f"value_control_{i}.jpg") diff --git a/examples/flux/model_inference_low_vram/FLUX.1-dev-ValueControl.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-AttriCtrl.py similarity index 65% rename from examples/flux/model_inference_low_vram/FLUX.1-dev-ValueControl.py rename to examples/flux/model_inference_low_vram/FLUX.1-dev-AttriCtrl.py index bb6be21..52edf2c 100644 --- a/examples/flux/model_inference_low_vram/FLUX.1-dev-ValueControl.py +++ b/examples/flux/model_inference_low_vram/FLUX.1-dev-AttriCtrl.py @@ -10,12 +10,11 @@ pipe = FluxImagePipeline.from_pretrained( ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), - ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-ValueController", origin_file_pattern="single/prefer_embed/value.ckpt", offload_device="cpu", offload_dtype=torch.float8_e4m3fn) + ModelConfig(model_id="DiffSynth-Studio/AttriCtrl-FLUX.1-Dev", origin_file_pattern="models/brightness.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn) ], ) -pipe.load_lora(pipe.dit, ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-ValueController", origin_file_pattern="single/dit_lora/dit_value.ckpt")) pipe.enable_vram_management() -for i in range(10): - image = pipe(prompt="a cat", seed=0, value_controller_inputs=[i/10]) - image.save(f"value_control_{i}.jpg") \ No newline at end of file +for i in [0.1, 0.3, 0.5, 0.7, 0.9]: + image = pipe(prompt="a cat on the beach", seed=2, value_controller_inputs=[i]) + image.save(f"value_control_{i}.jpg") diff --git a/examples/flux/model_training/full/FLUX.1-dev-AttriCtrl.sh b/examples/flux/model_training/full/FLUX.1-dev-AttriCtrl.sh new file mode 100644 index 0000000..91dc0cf --- /dev/null +++ b/examples/flux/model_training/full/FLUX.1-dev-AttriCtrl.sh @@ -0,0 +1,14 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_attrictrl.csv \ + --data_file_keys "image" \ + --max_pixels 1048576 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,DiffSynth-Studio/AttriCtrl-FLUX.1-Dev:models/brightness.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe.value_controller.encoders.0." \ + --output_path "./models/train/FLUX.1-dev-AttriCtrl_full" \ + --trainable_models "value_controller" \ + --extra_inputs "value_controller_inputs" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/lora/FLUX.1-dev-AttriCtrl.sh b/examples/flux/model_training/lora/FLUX.1-dev-AttriCtrl.sh new file mode 100644 index 0000000..7763c5f --- /dev/null +++ b/examples/flux/model_training/lora/FLUX.1-dev-AttriCtrl.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_attrictrl.csv \ + --data_file_keys "image" \ + --max_pixels 1048576 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,DiffSynth-Studio/AttriCtrl-FLUX.1-Dev:models/brightness.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-dev-AttriCtrl_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --extra_inputs "value_controller_inputs" \ + --align_to_opensource_format \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/validate_full/FLUX.1-dev-AttriCtrl.py b/examples/flux/model_training/validate_full/FLUX.1-dev-AttriCtrl.py new file mode 100644 index 0000000..17384fc --- /dev/null +++ b/examples/flux/model_training/validate_full/FLUX.1-dev-AttriCtrl.py @@ -0,0 +1,21 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from diffsynth import load_state_dict + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/AttriCtrl-FLUX.1-Dev", origin_file_pattern="models/brightness.safetensors") + ], +) +state_dict = load_state_dict("models/train/FLUX.1-dev-AttriCtrl_full/epoch-0.safetensors") +pipe.value_controller.encoders[0].load_state_dict(state_dict) + +image = pipe(prompt="a cat", seed=0, value_controller_inputs=0.1, rand_device="cuda") +image.save("image_FLUX.1-dev-AttriCtrl_full.jpg") diff --git a/examples/flux/model_training/validate_lora/FLUX.1-dev-AttriCtrl.py b/examples/flux/model_training/validate_lora/FLUX.1-dev-AttriCtrl.py new file mode 100644 index 0000000..f44df0d --- /dev/null +++ b/examples/flux/model_training/validate_lora/FLUX.1-dev-AttriCtrl.py @@ -0,0 +1,19 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/AttriCtrl-FLUX.1-Dev", origin_file_pattern="models/brightness.safetensors") + ], +) +pipe.load_lora(pipe.dit, "models/train/FLUX.1-dev-AttriCtrl_lora/epoch-3.safetensors", alpha=1) + +image = pipe(prompt="a cat", seed=0, value_controller_inputs=0.1, rand_device="cuda") +image.save("image_FLUX.1-dev-AttriCtrl_lora.jpg") From ff95c568842bcf8643e970bbbbdf7cb65c9f92e7 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 13:22:47 +0800 Subject: [PATCH 17/51] refine readme --- examples/flux/README_zh.md | 84 ++++++++++++++++--- .../{EliGen.py => FLUX.1-dev-EliGen.py} | 0 .../{EliGen.py => FLUX.1-dev-EliGen.py} | 0 .../full/FLUX.1-dev-LoRA-Encoder.sh | 14 ++++ .../validate_full/FLUX.1-dev-LoRA-Encoder.py | 25 ++++++ examples/wanvideo/README_zh.md | 12 ++- 6 files changed, 120 insertions(+), 15 deletions(-) rename examples/flux/model_inference/{EliGen.py => FLUX.1-dev-EliGen.py} (100%) rename examples/flux/model_inference_low_vram/{EliGen.py => FLUX.1-dev-EliGen.py} (100%) create mode 100644 examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh create mode 100644 examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py diff --git a/examples/flux/README_zh.md b/examples/flux/README_zh.md index e4bab2c..9cdaafa 100644 --- a/examples/flux/README_zh.md +++ b/examples/flux/README_zh.md @@ -18,7 +18,7 @@ pip install -e . ## 快速开始 -通过运行以下代码可以快速加载 FLUX.1-dev 模型并进行推理。 +通过运行以下代码可以快速加载 [black-forest-labs/FLUX.1-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-dev) 模型并进行推理。 ```python import torch @@ -41,12 +41,20 @@ image.save("image.jpg") ## 模型总览 -**FLUX 系列模型的全新框架支持正在开发中,敬请期待!** - -|模型 ID|额外参数|推理|全量训练|全量训练后验证|LoRA 训练|LoRA 训练后验证| -|-|-|-|-|-|-|-| -|[black-forest-labs/FLUX.1-dev](https://modelscope.cn/models/black-forest-labs/FLUX.1-dev)||[code](./model_inference/FLUX.1-dev.py)|[code](./model_training/full/FLUX.1-dev.sh)|[code](./model_training/validate_full/FLUX.1-dev.py)|[code](./model_training/lora/FLUX.1-dev.sh)|[code](./model_training/validate_lora/FLUX.1-dev.py)| -|[black-forest-labs/FLUX.1-Kontext-dev](https://modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev)|`kontext_images`|[code](./model_inference/FLUX.1-Kontext-dev.py)|[code](./model_training/full/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_full/FLUX.1-Kontext-dev.py)|[code](./model_training/lora/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_lora/FLUX.1-Kontext-dev.py)| +|模型 ID|额外参数|推理|低显存推理|全量训练|全量训练后验证|LoRA 训练|LoRA 训练后验证| +|-|-|-|-|-|-|-|-| +|[FLUX.1-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-dev)||[code](./model_inference/FLUX.1-dev.py)|[code](./model_inference_low_vram/FLUX.1-dev.py)|[code](./model_training/full/FLUX.1-dev.sh)|[code](./model_training/validate_full/FLUX.1-dev.py)|[code](./model_training/lora/FLUX.1-dev.sh)|[code](./model_training/validate_lora/FLUX.1-dev.py)| +|[FLUX.1-Kontext-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev)|`kontext_images`|[code](./model_inference/FLUX.1-Kontext-dev.py)|[code](./model_inference_low_vram/FLUX.1-Kontext-dev.py)|[code](./model_training/full/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_full/FLUX.1-Kontext-dev.py)|[code](./model_training/lora/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_lora/FLUX.1-Kontext-dev.py)| +|[FLUX.1-dev-Controlnet-Inpainting-Beta](https://www.modelscope.cn/models/alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py)| +|[FLUX.1-dev-Controlnet-Union-alpha](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-Controlnet-Union-alpha)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py)| +|[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| +|[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| +|[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)||||| +|[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| +|[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./model_inference/Step1X-Edit.py)|[code](./model_inference_low_vram/Step1X-Edit.py)|[code](./model_training/full/Step1X-Edit.sh)|[code](./model_training/validate_full/Step1X-Edit.py)|[code](./model_training/lora/Step1X-Edit.sh)|[code](./model_training/validate_lora/Step1X-Edit.py)| +|[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./model_inference/FLEX.2-preview.py)|[code](./model_inference_low_vram/FLEX.2-preview.py)|[code](./model_training/full/FLEX.2-preview.sh)|[code](./model_training/validate_full/FLEX.2-preview.py)|[code](./model_training/lora/FLEX.2-preview.sh)|[code](./model_training/validate_lora/FLEX.2-preview.py)| +|[Nexus-Gen](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|||||||| ## 模型推理 @@ -59,6 +67,9 @@ image.save("image.jpg") 模型通过 `from_pretrained` 加载: ```python +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + pipe = FluxImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", @@ -123,9 +134,41 @@ pipe = FluxImagePipeline.from_pretrained( pipe.enable_vram_management() ``` -`enable_vram_management` 函数提供了以下参数,用于控制显存使用情况: +FP8 量化功能也是支持的: -* `vram_limit`: 显存占用量(GB),默认占用设备上的剩余显存。注意这不是一个绝对限制,当设置的显存不足以支持模型进行推理,但实际可用显存足够时,将会以最小化显存占用的形式进行推理。将其设置为0时,将会实现理论最小显存占用。 +```python +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() +``` + +FP8 量化和 offload 可同时开启: + +```python +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() +``` + +开启显存管理后,框架会自动根据设备上的剩余显存确定显存管理策略。对于大多数 FLUX 系列模型,最低 8GB 显存即可进行推理。`enable_vram_management` 函数提供了以下参数,用于手动控制显存管理策略: + +* `vram_limit`: 显存占用量限制(GB),默认占用设备上的剩余显存。注意这不是一个绝对限制,当设置的显存不足以支持模型进行推理,但实际可用显存足够时,将会以最小化显存占用的形式进行推理。将其设置为0时,将会实现理论最小显存占用。 * `vram_buffer`: 显存缓冲区大小(GB),默认为 0.5GB。由于部分较大的神经网络层在 onload 阶段会不可控地占用更多显存,因此一个显存缓冲区是必要的,理论上的最优值为模型中最大的层所占的显存。 * `num_persistent_param_in_dit`: DiT 模型中常驻显存的参数数量(个),默认为无限制。我们将会在未来删除这个参数,请不要依赖这个参数。 @@ -163,6 +206,25 @@ Pipeline 在推理阶段能够接收以下输入参数: * `controlnet_inputs`: ControlNet 模型的输入。 * `ipadapter_images`: IP-Adapter 模型的输入图像。 * `ipadapter_scale`: IP-Adapter 模型的控制强度。 +* `eligen_entity_prompts`: EliGen 模型的图像局部提示词。 +* `eligen_entity_masks`: EliGen 模型的局部提示词控制区域,与 `eligen_entity_prompts` 一一对应。 +* `eligen_enable_on_negative`: 是否在负向提示词一侧启用 EliGen,仅在 `cfg_scale > 1` 时生效。 +* `eligen_enable_inpaint`: 是否启用 EliGen 局部重绘。 +* `infinityou_id_image`: InfiniteYou 模型的人脸图像。 +* `infinityou_guidance`: InfiniteYou 模型的控制强度。 +* `flex_inpaint_image`: FLEX 模型用于局部重绘的图像。 +* `flex_inpaint_mask`: FLEX 模型用于局部重绘的区域。 +* `flex_control_image`: FLEX 模型用于结构控制的图像。 +* `flex_control_strength`: FLEX 模型用于结构控制的强度。 +* `flex_control_stop`: FLEX 模型结构控制的结束点,1表示全程启用,0.5表示在前半段启用,0表示不启用。 +* `step1x_reference_image`: Step1x-Edit 模型用于图像编辑的输入图像。 +* `lora_encoder_inputs`: LoRA 编码器的输入,格式为 ModelConfig 或本地路径。 +* `lora_encoder_scale`: LoRA 编码器的激活强度,默认值为1,数值越小,LoRA 激活越弱。 +* `tea_cache_l1_thresh`: TeaCache 的阈值,数值越大,速度越快,画面质量越差。请注意,开启 TeaCache 后推理速度并非均匀,因此进度条上显示的剩余时间将会变得不准确。 +* `tiled`: 是否启用 VAE 分块推理,默认为 `False`。设置为 `True` 时可显著减少 VAE 编解码阶段的显存占用,会产生少许误差,以及少量推理时间延长。 +* `tile_size`: VAE 编解码阶段的分块大小,默认为 128,仅在 `tiled=True` 时生效。 +* `tile_stride`: VAE 编解码阶段的分块步长,默认为 64,仅在 `tiled=True` 时生效,需保证其数值小于或等于 `tile_size`。 +* `progress_bar_cmd`: 进度条,默认为 `tqdm.tqdm`。可通过设置为 `lambda x:x` 来屏蔽进度条。 @@ -190,7 +252,7 @@ FLUX 系列模型训练通过统一的 [`./model_training/train.py`](./model_tra * `--model_id_with_origin_paths`: 带原始路径的模型 ID,例如 black-forest-labs/FLUX.1-dev:flux1-dev.safetensors。用逗号分隔。 * 训练 * `--learning_rate`: 学习率。 - * `--num_epochs`: 轮数(Epoch)数量。 + * `--num_epochs`: 轮数(Epoch)。 * `--output_path`: 保存路径。 * `--remove_prefix_in_ckpt`: 在 ckpt 中移除前缀。 * 可训练模块 @@ -205,7 +267,7 @@ FLUX 系列模型训练通过统一的 [`./model_training/train.py`](./model_tra * `--use_gradient_checkpointing_offload`: 是否将 gradient checkpointing 卸载到内存中。 * `--gradient_accumulation_steps`: 梯度累积步数。 * 其他 - * `--align_to_opensource_format`: 是否将 FLUX DiT LoRA 的格式与开源版本对齐,仅对 FLUX.1-dev 和 FLUX.1-Kontext-dev 的 LoRA 训练生效。 + * `--align_to_opensource_format`: 是否将 FLUX DiT LoRA 的格式与开源版本对齐,仅对 LoRA 训练生效。 此外,训练框架基于 [`accelerate`](https://huggingface.co/docs/accelerate/index) 构建,在开始训练前运行 `accelerate config` 可配置 GPU 的相关参数。对于部分模型训练(例如模型的全量训练)脚本,我们提供了建议的 `accelerate` 配置文件,可在对应的训练脚本中查看。 diff --git a/examples/flux/model_inference/EliGen.py b/examples/flux/model_inference/FLUX.1-dev-EliGen.py similarity index 100% rename from examples/flux/model_inference/EliGen.py rename to examples/flux/model_inference/FLUX.1-dev-EliGen.py diff --git a/examples/flux/model_inference_low_vram/EliGen.py b/examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py similarity index 100% rename from examples/flux/model_inference_low_vram/EliGen.py rename to examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py diff --git a/examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh b/examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh new file mode 100644 index 0000000..f0d4f97 --- /dev/null +++ b/examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh @@ -0,0 +1,14 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_lora_encoder.csv \ + --data_file_keys "image" \ + --max_pixels 1048576 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors,DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev:model.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe.lora_encoder." \ + --output_path "./models/train/FLUX.1-dev-LoRA-Encoder_full" \ + --trainable_models "lora_encoder" \ + --extra_inputs "lora_encoder_inputs" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py b/examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py new file mode 100644 index 0000000..166f5a4 --- /dev/null +++ b/examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py @@ -0,0 +1,25 @@ +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from diffsynth import load_state_dict + + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev", origin_file_pattern="model.safetensors"), + ], +) +pipe.enable_lora_magic() +state_dict = load_state_dict("models/train/FLUX.1-dev-LoRA-Encoder_full/epoch-0.safetensors") +pipe.lora_encoder.load_state_dict(state_dict) + +lora = ModelConfig(model_id="VoidOc/flux_animal_forest1", origin_file_pattern="20.safetensors") +pipe.load_lora(pipe.dit, lora, hotload=True) # Use `pipe.clear_lora()` to drop the loaded LoRA. + +image = pipe(prompt="", seed=0, lora_encoder_inputs=lora) +image.save("image_FLUX.1-dev-LoRA-Encoder_full.jpg") diff --git a/examples/wanvideo/README_zh.md b/examples/wanvideo/README_zh.md index 860ff83..d9cd43b 100644 --- a/examples/wanvideo/README_zh.md +++ b/examples/wanvideo/README_zh.md @@ -18,6 +18,8 @@ pip install -e . ## 快速开始 +通过运行以下代码可以快速加载 [Wan-AI/Wan2.1-T2V-1.3B](https://www.modelscope.cn/models/Wan-AI/Wan2.1-T2V-1.3B) 模型并进行推理 + ```python import torch from diffsynth import save_video @@ -70,7 +72,6 @@ save_video(video, "video1.mp4", fps=15, quality=5) 以下部分将会帮助您理解我们的功能并编写推理代码。 -
加载模型 @@ -78,6 +79,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) 模型通过 `from_pretrained` 加载: ```python +import torch +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig + pipe = WanVideoPipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", @@ -178,9 +182,9 @@ pipe.enable_vram_management() FP8 量化能够大幅度减少显存占用,但不会加速,部分模型在 FP8 量化下会出现精度不足导致的画面模糊、撕裂、失真问题,请谨慎使用 FP8 量化。 -`enable_vram_management` 函数提供了以下参数,用于控制显存使用情况: +开启显存管理后,框架会自动根据设备上的剩余显存确定显存管理策略。`enable_vram_management` 函数提供了以下参数,用于手动控制显存管理策略: -* `vram_limit`: 显存占用量(GB),默认占用设备上的剩余显存。注意这不是一个绝对限制,当设置的显存不足以支持模型进行推理,但实际可用显存足够时,将会以最小化显存占用的形式进行推理。 +* `vram_limit`: 显存占用量限制(GB),默认占用设备上的剩余显存。注意这不是一个绝对限制,当设置的显存不足以支持模型进行推理,但实际可用显存足够时,将会以最小化显存占用的形式进行推理。将其设置为0时,将会实现理论最小显存占用。 * `vram_buffer`: 显存缓冲区大小(GB),默认为 0.5GB。由于部分较大的神经网络层在 onload 阶段会不可控地占用更多显存,因此一个显存缓冲区是必要的,理论上的最优值为模型中最大的层所占的显存。 * `num_persistent_param_in_dit`: DiT 模型中常驻显存的参数数量(个),默认为无限制。我们将会在未来删除这个参数,请不要依赖这个参数。 @@ -276,7 +280,7 @@ Wan 系列模型训练通过统一的 [`./model_training/train.py`](./model_trai * `--model_id_with_origin_paths`: 带原始路径的模型 ID,例如 Wan-AI/Wan2.1-T2V-1.3B:diffusion_pytorch_model*.safetensors。用逗号分隔。 * 训练 * `--learning_rate`: 学习率。 - * `--num_epochs`: 轮数(Epoch)数量。 + * `--num_epochs`: 轮数(Epoch)。 * `--output_path`: 保存路径。 * `--remove_prefix_in_ckpt`: 在 ckpt 中移除前缀。 * 可训练模块 From ebeda322159fbc60d94853db57a5527fd573d926 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:02:21 +0800 Subject: [PATCH 18/51] update readme --- README_zh.md | 422 ++++++++++++++++++ examples/CogVideoX/README.md | 39 ++ .../cogvideo_text_to_video.py | 0 examples/flux/README_zh.md | 3 +- examples/video_synthesis/README.md | 40 -- 5 files changed, 462 insertions(+), 42 deletions(-) create mode 100644 README_zh.md create mode 100644 examples/CogVideoX/README.md rename examples/{video_synthesis => CogVideoX}/cogvideo_text_to_video.py (100%) diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..308a3fc --- /dev/null +++ b/README_zh.md @@ -0,0 +1,422 @@ +

DiffSynth-Studio
+

+ +

+ +[![PyPI](https://img.shields.io/pypi/v/DiffSynth)](https://pypi.org/project/DiffSynth/) +[![license](https://img.shields.io/github/license/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/blob/master/LICENSE) +[![open issues](https://isitmaintained.com/badge/open/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/issues) +[![GitHub pull-requests](https://img.shields.io/github/issues-pr/modelscope/DiffSynth-Studio.svg)](https://GitHub.com/modelscope/DiffSynth-Studio/pull/) +[![GitHub latest commit](https://badgen.net/github/last-commit/modelscope/DiffSynth-Studio)](https://GitHub.com/modelscope/DiffSynth-Studio/commit/) + +

+modelscope%2FDiffSynth-Studio | Trendshift +

+ +## 简介 + +欢迎来到 Diffusion 模型的魔法世界!DiffSynth-Studio 是由[魔搭社区](https://www.modelscope.cn/)团队开发和维护的开源 Diffusion 模型引擎。我们期望以框架建设孵化技术创新,凝聚开源社区的力量,探索生成式模型技术的边界! + +DiffSynth 目前包括两个开源项目: +* [DiffSynth-Studio](https://github.com/modelscope/DiffSynth-Studio): 聚焦于激进的技术探索,面向学术界,提供更前沿的模型能力支持。 +* [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine): 聚焦于稳定的模型部署,面向工业界,提供更高的计算性能与更稳定的功能。 + +## 安装 + +从源码安装(推荐): + +``` +git clone https://github.com/modelscope/DiffSynth-Studio.git +cd DiffSynth-Studio +pip install -e . +``` + +
+其他安装方式 + +从 pypi 安装(存在版本更新延迟,如需使用最新功能,请从源码安装) + +``` +pip install diffsynth +``` + +如果在安装过程中遇到问题,可能是由上游依赖包导致的,请参考这些包的文档: + +* [torch](https://pytorch.org/get-started/locally/) +* [sentencepiece](https://github.com/google/sentencepiece) +* [cmake](https://cmake.org) +* [cupy](https://docs.cupy.dev/en/stable/install.html) + +
+ + + +## 基础框架 + +DiffSynth-Studio 为主流 Diffusion 模型(包括 FLUX、Wan 等)重新设计了推理和训练流水线。 + +### FLUX 系列 + +详细页面:[./examples/flux/](./examples/flux/) + +![Image](https://github.com/user-attachments/assets/c01258e2-f251-441a-aa1e-ebb22f02594d) + +
+ +快速开始 + +```python +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) + +image = pipe(prompt="a cat", seed=0) +image.save("image.jpg") +``` + +
+ +
+ +模型总览 + +|模型 ID|额外参数|推理|低显存推理|全量训练|全量训练后验证|LoRA 训练|LoRA 训练后验证| +|-|-|-|-|-|-|-|-| +|[FLUX.1-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev.py)|[code](./examples/flux/model_training/full/FLUX.1-dev.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev.py)| +|[FLUX.1-Kontext-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev)|`kontext_images`|[code](./examples/flux/model_inference/FLUX.1-Kontext-dev.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-Kontext-dev.py)|[code](./examples/flux/model_training/full/FLUX.1-Kontext-dev.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-Kontext-dev.py)|[code](./examples/flux/model_training/lora/FLUX.1-Kontext-dev.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-Kontext-dev.py)| +|[FLUX.1-dev-Controlnet-Inpainting-Beta](https://www.modelscope.cn/models/alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta)|`controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py)| +|[FLUX.1-dev-Controlnet-Union-alpha](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-Controlnet-Union-alpha)|`controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py)| +|[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| +|[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| +|[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./examples/flux/model_inference/FLUX.1-dev-EliGen.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| +|[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| +|[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| +|[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| + +
+ +### Wan 系列 + +详细页面:[./examples/wanvideo/](./examples/wanvideo/) + +https://github.com/user-attachments/assets/1d66ae74-3b02-40a9-acc3-ea95fc039314 + +
+ +快速开始 + +```python +import torch +from diffsynth import save_video +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="Wan2.1_VAE.pth", offload_device="cpu"), + ], +) +pipe.enable_vram_management() + +video = pipe( + prompt="纪实摄影风格画面,一只活泼的小狗在绿茵茵的草地上迅速奔跑。小狗毛色棕黄,两只耳朵立起,神情专注而欢快。阳光洒在它身上,使得毛发看上去格外柔软而闪亮。背景是一片开阔的草地,偶尔点缀着几朵野花,远处隐约可见蓝天和几片白云。透视感鲜明,捕捉小狗奔跑时的动感和四周草地的生机。中景侧面移动视角。", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + seed=0, tiled=True, +) +save_video(video, "video1.mp4", fps=15, quality=5) +``` + +
+ +
+ +模型总览 + +|模型 ID|额外参数|推理|全量训练|全量训练后验证|LoRA 训练|LoRA 训练后验证| +|-|-|-|-|-|-|-| +|[Wan-AI/Wan2.1-T2V-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-1.3B)||[code](./examples/wanvideo/model_inference/Wan2.1-T2V-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-T2V-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-T2V-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-T2V-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-T2V-1.3B.py)| +|[Wan-AI/Wan2.1-T2V-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-14B)||[code](./examples/wanvideo/model_inference/Wan2.1-T2V-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-T2V-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-T2V-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-T2V-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-T2V-14B.py)| +|[Wan-AI/Wan2.1-I2V-14B-480P](https://modelscope.cn/models/Wan-AI/Wan2.1-I2V-14B-480P)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-I2V-14B-480P.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-I2V-14B-480P.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-I2V-14B-480P.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-I2V-14B-480P.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-I2V-14B-480P.py)| +|[Wan-AI/Wan2.1-I2V-14B-720P](https://modelscope.cn/models/Wan-AI/Wan2.1-I2V-14B-720P)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-I2V-14B-720P.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-I2V-14B-720P.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-I2V-14B-720P.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-I2V-14B-720P.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-I2V-14B-720P.py)| +|[Wan-AI/Wan2.1-FLF2V-14B-720P](https://modelscope.cn/models/Wan-AI/Wan2.1-FLF2V-14B-720P)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-FLF2V-14B-720P.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-FLF2V-14B-720P.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-FLF2V-14B-720P.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-FLF2V-14B-720P.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-FLF2V-14B-720P.py)| +|[PAI/Wan2.1-Fun-1.3B-InP](https://modelscope.cn/models/PAI/Wan2.1-Fun-1.3B-InP)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-1.3B-InP.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-1.3B-InP.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-1.3B-InP.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-1.3B-InP.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-1.3B-InP.py)| +|[PAI/Wan2.1-Fun-1.3B-Control](https://modelscope.cn/models/PAI/Wan2.1-Fun-1.3B-Control)|`control_video`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-1.3B-Control.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-1.3B-Control.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-1.3B-Control.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-1.3B-Control.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-1.3B-Control.py)| +|[PAI/Wan2.1-Fun-14B-InP](https://modelscope.cn/models/PAI/Wan2.1-Fun-14B-InP)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-14B-InP.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-14B-InP.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-14B-InP.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-14B-InP.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-14B-InP.py)| +|[PAI/Wan2.1-Fun-14B-Control](https://modelscope.cn/models/PAI/Wan2.1-Fun-14B-Control)|`control_video`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-14B-Control.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-14B-Control.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-14B-Control.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-14B-Control.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-14B-Control.py)| +|[PAI/Wan2.1-Fun-V1.1-1.3B-Control](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-1.3B-Control)|`control_video`, `reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-1.3B-Control.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-1.3B-Control.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-1.3B-Control.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-1.3B-Control.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-1.3B-Control.py)| +|[PAI/Wan2.1-Fun-V1.1-14B-Control](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-14B-Control)|`control_video`, `reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-14B-Control.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-14B-Control.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-14B-Control.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-14B-Control.sh)|[code](./examples/wanvideo/examples/wanmodel_training/validate_lora/Wan2.1-Fun-V1.1-14B-Control.py)| +|[PAI/Wan2.1-Fun-V1.1-1.3B-InP](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-1.3B-InP)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-1.3B-InP.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-1.3B-InP.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-1.3B-InP.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-1.3B-InP.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-1.3B-InP.py)| +|[PAI/Wan2.1-Fun-V1.1-14B-InP](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-14B-InP)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-14B-InP.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-14B-InP.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-14B-InP.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-14B-InP.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-14B-InP.py)| +|[PAI/Wan2.1-Fun-V1.1-1.3B-Control-Camera](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-1.3B-Control-Camera)|`control_camera_video`, `input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-1.3B-Control-Camera.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-1.3B-Control-Camera.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-1.3B-Control-Camera.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-1.3B-Control-Camera.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-1.3B-Control-Camera.py)| +|[PAI/Wan2.1-Fun-V1.1-14B-Control-Camera](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-14B-Control-Camera)|`control_camera_video`, `input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-14B-Control-Camera.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-14B-Control-Camera.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-14B-Control-Camera.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-14B-Control-Camera.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-14B-Control-Camera.py)| +|[iic/VACE-Wan2.1-1.3B-Preview](https://modelscope.cn/models/iic/VACE-Wan2.1-1.3B-Preview)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-1.3B-Preview.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-1.3B-Preview.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-1.3B-Preview.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-1.3B-Preview.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-1.3B-Preview.py)| +|[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-1.3B.py)| +|[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-14B.py)| +|[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./examples/wanvideo/model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| + +
+ + + +### 更多模型 + + + +
+图像生成模型 + +详细页面:[./examples/image_synthesis/](./examples/image_synthesis/) + +|FLUX|Stable Diffusion 3| +|-|-| +|![image_1024_cfg](https://github.com/user-attachments/assets/984561e9-553d-4952-9443-79ce144f379f)|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/4df346db-6f91-420a-b4c1-26e205376098)| + +|Kolors|Hunyuan-DiT| +|-|-| +|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/53ef6f41-da11-4701-8665-9f64392607bf)|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/60b022c8-df3f-4541-95ab-bf39f2fa8bb5)| + +|Stable Diffusion|Stable Diffusion XL| +|-|-| +|![1024](https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/6fc84611-8da6-4a1f-8fee-9a34eba3b4a5)|![1024](https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/67687748-e738-438c-aee5-96096f09ac90)| + +
+ + + +
+视频生成模型 + +- HunyuanVideo:[./examples/HunyuanVideo/](./examples/HunyuanVideo/) + +https://github.com/user-attachments/assets/48dd24bb-0cc6-40d2-88c3-10feed3267e9 + +- StepVideo:[./examples/stepvideo/](./examples/stepvideo/) + +https://github.com/user-attachments/assets/5954fdaa-a3cf-45a3-bd35-886e3cc4581b + +- CogVideoX:[./examples/CogVideoX/](./examples/CogVideoX/) + +https://github.com/user-attachments/assets/26b044c1-4a60-44a4-842f-627ff289d006 + +
+ + + +
+图像质量评估模型 + +我们集成了一系列图像质量评估模型,这些模型可以用于图像生成模型的评测、对齐训练等场景中。 + +详细页面:[./examples/image_quality_metric/](./examples/image_quality_metric/) + +* [ImageReward](https://github.com/THUDM/ImageReward) +* [Aesthetic](https://github.com/christophschuhmann/improved-aesthetic-predictor) +* [PickScore](https://github.com/yuvalkirstain/pickscore) +* [CLIP](https://github.com/openai/CLIP) +* [HPSv2](https://github.com/tgxs002/HPSv2) +* [HPSv2.1](https://github.com/tgxs002/HPSv2) +* [MPS](https://github.com/Kwai-Kolors/MPS) + +
+ + + +## 创新成果 + +DiffSynth-Studio 不仅仅是一个工程化的模型框架,更是创新成果的孵化器。 + +
+Nexus-Gen: 统一架构的图像理解、生成、编辑 + +- 详细页面:https://github.com/modelscope/Nexus-Gen +- 论文:[Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) +- 模型:[ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2), [HuggingFace](https://huggingface.co/modelscope/Nexus-GenV2) +- 数据集:[ModelScope Dataset](https://www.modelscope.cn/datasets/DiffSynth-Studio/Nexus-Gen-Training-Dataset) +- 在线体验:[ModelScope Nexus-Gen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/Nexus-Gen) + +![](https://github.com/modelscope/Nexus-Gen/raw/main/assets/illustrations/gen_edit.jpg) + +
+ + + +
+ArtAug: 图像生成模型的美学提升 + +- 详细页面:[./examples/ArtAug/](./examples/ArtAug/) +- 论文:[ArtAug: Enhancing Text-to-Image Generation through Synthesis-Understanding Interaction](https://arxiv.org/abs/2412.12888) +- 模型:[ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/ArtAug-lora-FLUX.1dev-v1), [HuggingFace](https://huggingface.co/ECNU-CILab/ArtAug-lora-FLUX.1dev-v1) +- 在线体验:[ModelScope AIGC Tab](https://www.modelscope.cn/aigc/imageGeneration?tab=advanced&versionId=7228&modelType=LoRA&sdVersion=FLUX_1&modelUrl=modelscope%3A%2F%2FDiffSynth-Studio%2FArtAug-lora-FLUX.1dev-v1%3Frevision%3Dv1.0) + +|FLUX.1-dev|FLUX.1-dev + ArtAug LoRA| +|-|-| +|![image_1_base](https://github.com/user-attachments/assets/e1d5c505-b423-45fe-be01-25c2758f5417)|![image_1_enhance](https://github.com/user-attachments/assets/335908e3-d0bd-41c2-9d99-d10528a2d719)| + +
+ + + +
+ +EliGen: 精准的图像分区控制 + +- 详细页面:[./examples/EntityControl/](./examples/EntityControl/) +- 论文:[EliGen: Entity-Level Controlled Image Generation with Regional Attention](https://arxiv.org/abs/2501.01097) +- 模型:[ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen), [HuggingFace](https://huggingface.co/modelscope/EliGen) +- 在线体验:[ModelScope EliGen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/EliGen) +- 数据集:[EliGen Train Set](https://www.modelscope.cn/datasets/DiffSynth-Studio/EliGenTrainSet) + +|实体控制区域|生成图像| +|-|-| +|![eligen_example_2_mask_0](https://github.com/user-attachments/assets/1c6d9445-5022-4d91-ad2e-dc05321883d1)|![eligen_example_2_0](https://github.com/user-attachments/assets/86739945-cb07-4a49-b3b3-3bb65c90d14f)| + +
+ + + +
+ +ExVideo: 视频生成模型的扩展训练 + +- 项目页面:[Project Page](https://ecnu-cilab.github.io/ExVideoProjectPage/) +- 论文:[ExVideo: Extending Video Diffusion Models via Parameter-Efficient Post-Tuning](https://arxiv.org/abs/2406.14130) +- 代码样例:[./examples/ExVideo/](./examples/ExVideo/) +- 模型:[ModelScope](https://modelscope.cn/models/ECNU-CILab/ExVideo-SVD-128f-v1), [HuggingFace](https://huggingface.co/ECNU-CILab/ExVideo-SVD-128f-v1) + +https://github.com/modelscope/DiffSynth-Studio/assets/35051019/d97f6aa9-8064-4b5b-9d49-ed6001bb9acc + +
+ + + +
+ +Diffutoon: 高分辨率动漫风格视频渲染 + +- 项目页面:[Project Page](https://ecnu-cilab.github.io/DiffutoonProjectPage/) +- 论文:[Diffutoon: High-Resolution Editable Toon Shading via Diffusion Models](https://arxiv.org/abs/2401.16224) +- 代码样例:[./examples/Diffutoon/](./examples/Diffutoon/) + +https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/b54c05c5-d747-4709-be5e-b39af82404dd + +
+ + + +
+ +DiffSynth: 本项目的初代版本 + +- 项目页面:[Project Page](https://ecnu-cilab.github.io/DiffSynth.github.io/) +- 论文:[DiffSynth: Latent In-Iteration Deflickering for Realistic Video Synthesis](https://arxiv.org/abs/2308.03463) +- 代码样例:[./examples/diffsynth/](./examples/diffsynth/) + +https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-4481-b79f-0c3a7361a1ea + +
+ + + +## 版本更新历史 + +- **July 11, 2025** 🔥🔥🔥 We propose Nexus-Gen, a unified model that synergizes the language reasoning capabilities of LLMs with the image synthesis power of diffusion models. This framework enables seamless image understanding, generation, and editing tasks. + - Paper: [Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) + - Github Repo: https://github.com/modelscope/Nexus-Gen + - Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2), [HuggingFace](https://huggingface.co/modelscope/Nexus-GenV2) + - Training Dataset: [ModelScope Dataset](https://www.modelscope.cn/datasets/DiffSynth-Studio/Nexus-Gen-Training-Dataset) + - Online Demo: [ModelScope Nexus-Gen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/Nexus-Gen) + +- **June 15, 2025** ModelScope's official evaluation framework, [EvalScope](https://github.com/modelscope/evalscope), now supports text-to-image generation evaluation. Try it with the [Best Practices](https://evalscope.readthedocs.io/zh-cn/latest/best_practice/t2i_eval.html) guide. + +- **March 31, 2025** We support InfiniteYou, an identity preserving method for FLUX. Please refer to [./examples/InfiniteYou/](./examples/InfiniteYou/) for more details. + +- **March 25, 2025** Our new open-source project, [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine), is now open-sourced! Focused on stable model deployment. Geared towards industry. Offers better engineering support, higher computational performance, and more stable functionality. + +- **March 13, 2025** We support HunyuanVideo-I2V, the image-to-video generation version of HunyuanVideo open-sourced by Tencent. Please refer to [./examples/HunyuanVideo/](./examples/HunyuanVideo/) for more details. + +- **February 25, 2025** We support Wan-Video, a collection of SOTA video synthesis models open-sourced by Alibaba. See [./examples/wanvideo/](./examples/wanvideo/). + +- **February 17, 2025** We support [StepVideo](https://modelscope.cn/models/stepfun-ai/stepvideo-t2v/summary)! State-of-the-art video synthesis model! See [./examples/stepvideo](./examples/stepvideo/). + +- **December 31, 2024** We propose EliGen, a novel framework for precise entity-level controlled text-to-image generation, complemented by an inpainting fusion pipeline to extend its capabilities to image inpainting tasks. EliGen seamlessly integrates with existing community models, such as IP-Adapter and In-Context LoRA, enhancing its versatility. For more details, see [./examples/EntityControl](./examples/EntityControl/). + - Paper: [EliGen: Entity-Level Controlled Image Generation with Regional Attention](https://arxiv.org/abs/2501.01097) + - Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen), [HuggingFace](https://huggingface.co/modelscope/EliGen) + - Online Demo: [ModelScope EliGen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/EliGen) + - Training Dataset: [EliGen Train Set](https://www.modelscope.cn/datasets/DiffSynth-Studio/EliGenTrainSet) + +- **December 19, 2024** We implement advanced VRAM management for HunyuanVideo, making it possible to generate videos at a resolution of 129x720x1280 using 24GB of VRAM, or at 129x512x384 resolution with just 6GB of VRAM. Please refer to [./examples/HunyuanVideo/](./examples/HunyuanVideo/) for more details. + +- **December 18, 2024** We propose ArtAug, an approach designed to improve text-to-image synthesis models through synthesis-understanding interactions. We have trained an ArtAug enhancement module for FLUX.1-dev in the format of LoRA. This model integrates the aesthetic understanding of Qwen2-VL-72B into FLUX.1-dev, leading to an improvement in the quality of generated images. + - Paper: https://arxiv.org/abs/2412.12888 + - Examples: https://github.com/modelscope/DiffSynth-Studio/tree/main/examples/ArtAug + - Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/ArtAug-lora-FLUX.1dev-v1), [HuggingFace](https://huggingface.co/ECNU-CILab/ArtAug-lora-FLUX.1dev-v1) + - Demo: [ModelScope](https://modelscope.cn/aigc/imageGeneration?tab=advanced&versionId=7228&modelType=LoRA&sdVersion=FLUX_1&modelUrl=modelscope%3A%2F%2FDiffSynth-Studio%2FArtAug-lora-FLUX.1dev-v1%3Frevision%3Dv1.0), HuggingFace (Coming soon) + +- **October 25, 2024** We provide extensive FLUX ControlNet support. This project supports many different ControlNet models that can be freely combined, even if their structures differ. Additionally, ControlNet models are compatible with high-resolution refinement and partition control techniques, enabling very powerful controllable image generation. See [`./examples/ControlNet/`](./examples/ControlNet/). + +- **October 8, 2024.** We release the extended LoRA based on CogVideoX-5B and ExVideo. You can download this model from [ModelScope](https://modelscope.cn/models/ECNU-CILab/ExVideo-CogVideoX-LoRA-129f-v1) or [HuggingFace](https://huggingface.co/ECNU-CILab/ExVideo-CogVideoX-LoRA-129f-v1). + +- **August 22, 2024.** CogVideoX-5B is supported in this project. See [here](/examples/video_synthesis/). We provide several interesting features for this text-to-video model, including + - Text to video + - Video editing + - Self-upscaling + - Video interpolation + +- **August 22, 2024.** We have implemented an interesting painter that supports all text-to-image models. Now you can create stunning images using the painter, with assistance from AI! + - Use it in our [WebUI](#usage-in-webui). + +- **August 21, 2024.** FLUX is supported in DiffSynth-Studio. + - Enable CFG and highres-fix to improve visual quality. See [here](/examples/image_synthesis/README.md) + - LoRA, ControlNet, and additional models will be available soon. + +- **June 21, 2024.** We propose ExVideo, a post-tuning technique aimed at enhancing the capability of video generation models. We have extended Stable Video Diffusion to achieve the generation of long videos up to 128 frames. + - [Project Page](https://ecnu-cilab.github.io/ExVideoProjectPage/) + - Source code is released in this repo. See [`examples/ExVideo`](./examples/ExVideo/). + - Models are released on [HuggingFace](https://huggingface.co/ECNU-CILab/ExVideo-SVD-128f-v1) and [ModelScope](https://modelscope.cn/models/ECNU-CILab/ExVideo-SVD-128f-v1). + - Technical report is released on [arXiv](https://arxiv.org/abs/2406.14130). + - You can try ExVideo in this [Demo](https://huggingface.co/spaces/modelscope/ExVideo-SVD-128f-v1)! + +- **June 13, 2024.** DiffSynth Studio is transferred to ModelScope. The developers have transitioned from "I" to "we". Of course, I will still participate in development and maintenance. + +- **Jan 29, 2024.** We propose Diffutoon, a fantastic solution for toon shading. + - [Project Page](https://ecnu-cilab.github.io/DiffutoonProjectPage/) + - The source codes are released in this project. + - The technical report (IJCAI 2024) is released on [arXiv](https://arxiv.org/abs/2401.16224). + +- **Dec 8, 2023.** We decide to develop a new Project, aiming to release the potential of diffusion models, especially in video synthesis. The development of this project is started. + +- **Nov 15, 2023.** We propose FastBlend, a powerful video deflickering algorithm. + - The sd-webui extension is released on [GitHub](https://github.com/Artiprocher/sd-webui-fastblend). + - Demo videos are shown on Bilibili, including three tasks. + - [Video deflickering](https://www.bilibili.com/video/BV1d94y1W7PE) + - [Video interpolation](https://www.bilibili.com/video/BV1Lw411m71p) + - [Image-driven video rendering](https://www.bilibili.com/video/BV1RB4y1Z7LF) + - The technical report is released on [arXiv](https://arxiv.org/abs/2311.09265). + - An unofficial ComfyUI extension developed by other users is released on [GitHub](https://github.com/AInseven/ComfyUI-fastblend). + +- **Oct 1, 2023.** We release an early version of this project, namely FastSDXL. A try for building a diffusion engine. + - The source codes are released on [GitHub](https://github.com/Artiprocher/FastSDXL). + - FastSDXL includes a trainable OLSS scheduler for efficiency improvement. + - The original repo of OLSS is [here](https://github.com/alibaba/EasyNLP/tree/master/diffusion/olss_scheduler). + - The technical report (CIKM 2023) is released on [arXiv](https://arxiv.org/abs/2305.14677). + - A demo video is shown on [Bilibili](https://www.bilibili.com/video/BV1w8411y7uj). + - Since OLSS requires additional training, we don't implement it in this project. + +- **Aug 29, 2023.** We propose DiffSynth, a video synthesis framework. + - [Project Page](https://ecnu-cilab.github.io/DiffSynth.github.io/). + - The source codes are released in [EasyNLP](https://github.com/alibaba/EasyNLP/tree/master/diffusion/DiffSynth). + - The technical report (ECML PKDD 2024) is released on [arXiv](https://arxiv.org/abs/2308.03463). diff --git a/examples/CogVideoX/README.md b/examples/CogVideoX/README.md new file mode 100644 index 0000000..95d8daa --- /dev/null +++ b/examples/CogVideoX/README.md @@ -0,0 +1,39 @@ +# CogVideoX + +### Example: Text-to-Video using CogVideoX-5B (Experimental) + +See [cogvideo_text_to_video.py](cogvideo_text_to_video.py). + +First, we generate a video using prompt "an astronaut riding a horse on Mars". + +https://github.com/user-attachments/assets/4c91c1cd-e4a0-471a-bd8d-24d761262941 + +Then, we convert the astronaut to a robot. + +https://github.com/user-attachments/assets/225a00a4-2bc8-4740-8e86-a64b460a29ec + +Upscale the video using the model itself. + +https://github.com/user-attachments/assets/c02cb30c-de60-473c-8242-32c67b3155ad + +Make the video look smoother by interpolating frames. + +https://github.com/user-attachments/assets/f0e465b4-45df-4435-ab10-7a084ca2b0a0 + +Here is another example. + +First, we generate a video using prompt "a dog is running". + +https://github.com/user-attachments/assets/e3696297-99f5-4d0c-a5ca-1d1566db85b4 + +Then, we add a blue collar to the dog. + +https://github.com/user-attachments/assets/7ff22be7-4390-4d33-ae6c-53f6f056e18d + +Upscale the video using the model itself. + +https://github.com/user-attachments/assets/a909c32c-0b7d-495c-a53c-d23a99a3d3e9 + +Make the video look smoother by interpolating frames. + +https://github.com/user-attachments/assets/ea37c150-97a0-4858-8003-0c2e5eef3331 diff --git a/examples/video_synthesis/cogvideo_text_to_video.py b/examples/CogVideoX/cogvideo_text_to_video.py similarity index 100% rename from examples/video_synthesis/cogvideo_text_to_video.py rename to examples/CogVideoX/cogvideo_text_to_video.py diff --git a/examples/flux/README_zh.md b/examples/flux/README_zh.md index 9cdaafa..aeade8f 100644 --- a/examples/flux/README_zh.md +++ b/examples/flux/README_zh.md @@ -50,11 +50,10 @@ image.save("image.jpg") |[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| |[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| -|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)||||| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./model_inference/Step1X-Edit.py)|[code](./model_inference_low_vram/Step1X-Edit.py)|[code](./model_training/full/Step1X-Edit.sh)|[code](./model_training/validate_full/Step1X-Edit.py)|[code](./model_training/lora/Step1X-Edit.sh)|[code](./model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./model_inference/FLEX.2-preview.py)|[code](./model_inference_low_vram/FLEX.2-preview.py)|[code](./model_training/full/FLEX.2-preview.sh)|[code](./model_training/validate_full/FLEX.2-preview.py)|[code](./model_training/lora/FLEX.2-preview.sh)|[code](./model_training/validate_lora/FLEX.2-preview.py)| -|[Nexus-Gen](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|||||||| ## 模型推理 diff --git a/examples/video_synthesis/README.md b/examples/video_synthesis/README.md index 2d2ff1c..f6c0715 100644 --- a/examples/video_synthesis/README.md +++ b/examples/video_synthesis/README.md @@ -1,45 +1,5 @@ # Text to Video -In DiffSynth Studio, we can use some video models to generate videos. - -### Example: Text-to-Video using CogVideoX-5B (Experimental) - -See [cogvideo_text_to_video.py](cogvideo_text_to_video.py). - -First, we generate a video using prompt "an astronaut riding a horse on Mars". - -https://github.com/user-attachments/assets/4c91c1cd-e4a0-471a-bd8d-24d761262941 - -Then, we convert the astronaut to a robot. - -https://github.com/user-attachments/assets/225a00a4-2bc8-4740-8e86-a64b460a29ec - -Upscale the video using the model itself. - -https://github.com/user-attachments/assets/c02cb30c-de60-473c-8242-32c67b3155ad - -Make the video look smoother by interpolating frames. - -https://github.com/user-attachments/assets/f0e465b4-45df-4435-ab10-7a084ca2b0a0 - -Here is another example. - -First, we generate a video using prompt "a dog is running". - -https://github.com/user-attachments/assets/e3696297-99f5-4d0c-a5ca-1d1566db85b4 - -Then, we add a blue collar to the dog. - -https://github.com/user-attachments/assets/7ff22be7-4390-4d33-ae6c-53f6f056e18d - -Upscale the video using the model itself. - -https://github.com/user-attachments/assets/a909c32c-0b7d-495c-a53c-d23a99a3d3e9 - -Make the video look smoother by interpolating frames. - -https://github.com/user-attachments/assets/ea37c150-97a0-4858-8003-0c2e5eef3331 - ### Example: Text-to-Video using AnimateDiff Generate a video using a Stable Diffusion model and an AnimateDiff model. We can break the limitation of number of frames! See [sd_text_to_video.py](./sd_text_to_video.py). From 0d509241c06947da96cd2019e1ae09cf952890a6 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:20:56 +0800 Subject: [PATCH 19/51] update readme --- README.md | 459 +++++++++++++++++++++++++++++++++------------------ README_zh.md | 138 ++++++++-------- 2 files changed, 369 insertions(+), 228 deletions(-) diff --git a/README.md b/README.md index aa7f0f0..3a67e04 100644 --- a/README.md +++ b/README.md @@ -13,40 +13,312 @@ modelscope%2FDiffSynth-Studio | Trendshift

-## Documentation -Refer to [Diffsynth-Studio Docs](https://diffsynth-studio.readthedocs.io/zh-cn/latest/index.html). +[切换到中文](./README_zh.md) ## Introduction -Welcome to the magic world of Diffusion models! +Welcome to the magic world of Diffusion models! DiffSynth-Studio is an open-source Diffusion model engine developed and maintained by [ModelScope](https://www.modelscope.cn/) team. We aim to foster technical innovation through framework development, bring together the power of the open-source community, and explore the limits of generative models! -DiffSynth consists of two open-source projects: -* [DiffSynth-Studio](https://github.com/modelscope/DiffSynth-Studio): Focused on aggressive technological exploration. Targeted at academia. Provides more cutting-edge technical support and novel inference capabilities. -* [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine): Focused on stable model deployment. Geared towards industry. Offers better engineering support, higher computational performance, and more stable functionality. +DiffSynth currently includes two open-source projects: +* [DiffSynth-Studio](https://github.com/modelscope/DiffSynth-Studio): Focused on aggressive technical exploration, for academia, providing support for more cutting-edge model capabilities. +* [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine): Focused on stable model deployment, for industry, offering higher computing performance and more stable features. -DiffSynth-Studio is an open-source project aimed at exploring innovations in AIGC technology. We have integrated numerous open-source Diffusion models, including FLUX and Wan, among others. Through this open-source project, we hope to connect models within the open-source community and explore new technologies based on diffusion models. +## Installation -Until now, DiffSynth-Studio has supported the following models: +Install from source (recommended): -* [Wan-Video](https://github.com/Wan-Video/Wan2.1) -* [StepVideo](https://github.com/stepfun-ai/Step-Video-T2V) -* [HunyuanVideo](https://github.com/Tencent/HunyuanVideo), [HunyuanVideo-I2V]() -* [CogVideoX](https://huggingface.co/THUDM/CogVideoX-5b) -* [FLUX](https://huggingface.co/black-forest-labs/FLUX.1-dev) -* [ExVideo](https://huggingface.co/ECNU-CILab/ExVideo-SVD-128f-v1) -* [Kolors](https://huggingface.co/Kwai-Kolors/Kolors) -* [Stable Diffusion 3](https://huggingface.co/stabilityai/stable-diffusion-3-medium) -* [Stable Video Diffusion](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid-xt) -* [Hunyuan-DiT](https://github.com/Tencent/HunyuanDiT) -* [RIFE](https://github.com/hzwer/ECCV2022-RIFE) -* [ESRGAN](https://github.com/xinntao/ESRGAN) -* [Ip-Adapter](https://github.com/tencent-ailab/IP-Adapter) -* [AnimateDiff](https://github.com/guoyww/animatediff/) -* [ControlNet](https://github.com/lllyasviel/ControlNet) -* [Stable Diffusion XL](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0) -* [Stable Diffusion](https://huggingface.co/runwayml/stable-diffusion-v1-5) +``` +git clone https://github.com/modelscope/DiffSynth-Studio.git +cd DiffSynth-Studio +pip install -e . +``` + +
+Other installation methods + +Install from PyPI (version updates may be delayed; for latest features, install from source) + +``` +pip install diffsynth +``` + +If you meet problems during installation, they might be caused by upstream dependencies. Please check the docs of these packages: + +* [torch](https://pytorch.org/get-started/locally/) +* [sentencepiece](https://github.com/google/sentencepiece) +* [cmake](https://cmake.org) +* [cupy](https://docs.cupy.dev/en/stable/install.html) + +
+ +## Basic Framework + +DiffSynth-Studio redesigns the inference and training pipelines for mainstream Diffusion models (including FLUX, Wan, etc.), enabling efficient memory management and flexible model training. + +### FLUX Series + +Detail page: [./examples/flux/](./examples/flux/) + +![Image](https://github.com/user-attachments/assets/c01258e2-f251-441a-aa1e-ebb22f02594d) + +
+ +Quick Start + +```python +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) + +image = pipe(prompt="a cat", seed=0) +image.save("image.jpg") +``` + +
+ +
+ +Model Overview + +| Model ID | Extra Parameters | Inference | Low VRAM Inference | Full Training | Validate After Full Training | LoRA Training | Validate After LoRA Training | +|-|-|-|-|-|-|-|-| +|[FLUX.1-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev.py)|[code](./examples/flux/model_training/full/FLUX.1-dev.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev.py)| +|[FLUX.1-Kontext-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev)|`kontext_images`|[code](./examples/flux/model_inference/FLUX.1-Kontext-dev.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-Kontext-dev.py)|[code](./examples/flux/model_training/full/FLUX.1-Kontext-dev.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-Kontext-dev.py)|[code](./examples/flux/model_training/lora/FLUX.1-Kontext-dev.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-Kontext-dev.py)| +|[FLUX.1-dev-Controlnet-Inpainting-Beta](https://www.modelscope.cn/models/alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta)|`controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py)| +|[FLUX.1-dev-Controlnet-Union-alpha](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-Controlnet-Union-alpha)|`controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py)| +|[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| +|[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| +|[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./examples/flux/model_inference/FLUX.1-dev-EliGen.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| +|[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| +|[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| +|[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| + +
+ + + +### Wan Series + +Detail page: [./examples/wanvideo/](./examples/wanvideo/) + +https://github.com/user-attachments/assets/1d66ae74-3b02-40a9-acc3-ea95fc039314 + +
+ +Quick Start + +```python +import torch +from diffsynth import save_video +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="Wan2.1_VAE.pth", offload_device="cpu"), + ], +) +pipe.enable_vram_management() + +video = pipe( + prompt="A documentary photography style scene: a lively puppy rapidly running on green grass. The puppy has brown-yellow fur, upright ears, and looks focused and joyful. Sunlight shines on its body, making the fur appear soft and shiny. The background is an open field with occasional wildflowers, and faint blue sky and clouds in the distance. Strong sense of perspective captures the motion of the puppy and the vitality of the surrounding grass. Mid-shot side-moving view.", + negative_prompt="Bright colors, overexposed, static, blurry details, subtitles, style, artwork, image, still, overall gray, worst quality, low quality, JPEG compression artifacts, ugly, deformed, extra fingers, poorly drawn hands, poorly drawn face, malformed limbs, fused fingers, still frame, messy background, three legs, crowded background people, walking backwards", + seed=0, tiled=True, +) +save_video(video, "video1.mp4", fps=15, quality=5) +``` + +
+ +
+ +Model Overview + +| Model ID | Extra Parameters | Inference | Full Training | Validate After Full Training | LoRA Training | Validate After LoRA Training | +|-|-|-|-|-|-|-| +|[Wan-AI/Wan2.1-T2V-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-1.3B)||[code](./examples/wanvideo/model_inference/Wan2.1-T2V-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-T2V-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-T2V-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-T2V-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-T2V-1.3B.py)| +|[Wan-AI/Wan2.1-T2V-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-14B)||[code](./examples/wanvideo/model_inference/Wan2.1-T2V-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-T2V-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-T2V-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-T2V-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-T2V-14B.py)| +|[Wan-AI/Wan2.1-I2V-14B-480P](https://modelscope.cn/models/Wan-AI/Wan2.1-I2V-14B-480P)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-I2V-14B-480P.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-I2V-14B-480P.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-I2V-14B-480P.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-I2V-14B-480P.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-I2V-14B-480P.py)| +|[Wan-AI/Wan2.1-I2V-14B-720P](https://modelscope.cn/models/Wan-AI/Wan2.1-I2V-14B-720P)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-I2V-14B-720P.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-I2V-14B-720P.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-I2V-14B-720P.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-I2V-14B-720P.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-I2V-14B-720P.py)| +|[Wan-AI/Wan2.1-FLF2V-14B-720P](https://modelscope.cn/models/Wan-AI/Wan2.1-FLF2V-14B-720P)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-FLF2V-14B-720P.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-FLF2V-14B-720P.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-FLF2V-14B-720P.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-FLF2V-14B-720P.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-FLF2V-14B-720P.py)| +|[PAI/Wan2.1-Fun-1.3B-InP](https://modelscope.cn/models/PAI/Wan2.1-Fun-1.3B-InP)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-1.3B-InP.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-1.3B-InP.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-1.3B-InP.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-1.3B-InP.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-1.3B-InP.py)| +|[PAI/Wan2.1-Fun-1.3B-Control](https://modelscope.cn/models/PAI/Wan2.1-Fun-1.3B-Control)|`control_video`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-1.3B-Control.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-1.3B-Control.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-1.3B-Control.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-1.3B-Control.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-1.3B-Control.py)| +|[PAI/Wan2.1-Fun-14B-InP](https://modelscope.cn/models/PAI/Wan2.1-Fun-14B-InP)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-14B-InP.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-14B-InP.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-14B-InP.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-14B-InP.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-14B-InP.py)| +|[PAI/Wan2.1-Fun-14B-Control](https://modelscope.cn/models/PAI/Wan2.1-Fun-14B-Control)|`control_video`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-14B-Control.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-14B-Control.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-14B-Control.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-14B-Control.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-14B-Control.py)| +|[PAI/Wan2.1-Fun-V1.1-1.3B-Control](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-1.3B-Control)|`control_video`, `reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-1.3B-Control.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-1.3B-Control.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-1.3B-Control.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-1.3B-Control.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-1.3B-Control.py)| +|[PAI/Wan2.1-Fun-V1.1-14B-Control](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-14B-Control)|`control_video`, `reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-14B-Control.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-14B-Control.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-14B-Control.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-14B-Control.sh)|[code](./examples/wanvideo/examples/wanmodel_training/validate_lora/Wan2.1-Fun-V1.1-14B-Control.py)| +|[PAI/Wan2.1-Fun-V1.1-1.3B-InP](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-1.3B-InP)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-1.3B-InP.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-1.3B-InP.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-1.3B-InP.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-1.3B-InP.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-1.3B-InP.py)| +|[PAI/Wan2.1-Fun-V1.1-14B-InP](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-14B-InP)|`input_image`, `end_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-14B-InP.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-14B-InP.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-14B-InP.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-14B-InP.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-14B-InP.py)| +|[PAI/Wan2.1-Fun-V1.1-1.3B-Control-Camera](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-1.3B-Control-Camera)|`control_camera_video`, `input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-1.3B-Control-Camera.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-1.3B-Control-Camera.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-1.3B-Control-Camera.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-1.3B-Control-Camera.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-1.3B-Control-Camera.py)| +|[PAI/Wan2.1-Fun-V1.1-14B-Control-Camera](https://modelscope.cn/models/PAI/Wan2.1-Fun-V1.1-14B-Control-Camera)|`control_camera_video`, `input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-Fun-V1.1-14B-Control-Camera.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-Fun-V1.1-14B-Control-Camera.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-Fun-V1.1-14B-Control-Camera.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-Fun-V1.1-14B-Control-Camera.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-Fun-V1.1-14B-Control-Camera.py)| +|[iic/VACE-Wan2.1-1.3B-Preview](https://modelscope.cn/models/iic/VACE-Wan2.1-1.3B-Preview)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-1.3B-Preview.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-1.3B-Preview.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-1.3B-Preview.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-1.3B-Preview.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-1.3B-Preview.py)| +|[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-1.3B.py)| +|[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-14B.py)| +|[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./examples/wanvideo/model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| + +
+ +### More Models + + + +
+Image Generation Models + +Detail page: [./examples/image_synthesis/](./examples/image_synthesis/) + +|FLUX|Stable Diffusion 3| +|-|-| +|![image_1024_cfg](https://github.com/user-attachments/assets/984561e9-553d-4952-9443-79ce144f379f)|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/4df346db-6f91-420a-b4c1-26e205376098)| + +|Kolors|Hunyuan-DiT| +|-|-| +|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/53ef6f41-da11-4701-8665-9f64392607bf)|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/60b022c8-df3f-4541-95ab-bf39f2fa8bb5)| + +|Stable Diffusion|Stable Diffusion XL| +|-|-| +|![1024](https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/6fc84611-8da6-4a1f-8fee-9a34eba3b4a5)|![1024](https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/67687748-e738-438c-aee5-96096f09ac90)| + +
+ + + +
+Video Generation Models + +- HunyuanVideo: [./examples/HunyuanVideo/](./examples/HunyuanVideo/) + +https://github.com/user-attachments/assets/48dd24bb-0cc6-40d2-88c3-10feed3267e9 + +- StepVideo: [./examples/stepvideo/](./examples/stepvideo/) + +https://github.com/user-attachments/assets/5954fdaa-a3cf-45a3-bd35-886e3cc4581b + +- CogVideoX: [./examples/CogVideoX/](./examples/CogVideoX/) + +https://github.com/user-attachments/assets/26b044c1-4a60-44a4-842f-627ff289d006 + +
+ + + +
+Image Quality Assessment Models + +We have integrated a series of image quality assessment models. These models can be used for evaluating image generation models, alignment training, and similar tasks. + +Detail page: [./examples/image_quality_metric/](./examples/image_quality_metric/) + +* [ImageReward](https://github.com/THUDM/ImageReward) +* [Aesthetic](https://github.com/christophschuhmann/improved-aesthetic-predictor) +* [PickScore](https://github.com/yuvalkirstain/pickscore) +* [CLIP](https://github.com/openai/CLIP) +* [HPSv2](https://github.com/tgxs002/HPSv2) +* [HPSv2.1](https://github.com/tgxs002/HPSv2) +* [MPS](https://github.com/Kwai-Kolors/MPS) + +
+ + + +## Innovative Achievements + +DiffSynth-Studio is not just an engineering model framework, but also a platform for incubating innovative results. + +
+Nexus-Gen: Unified Architecture for Image Understanding, Generation, and Editing + +- Detail page: https://github.com/modelscope/Nexus-Gen +- Paper: [Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) +- Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2), [HuggingFace](https://huggingface.co/modelscope/Nexus-GenV2) +- Dataset: [ModelScope Dataset](https://www.modelscope.cn/datasets/DiffSynth-Studio/Nexus-Gen-Training-Dataset) +- Online Demo: [ModelScope Nexus-Gen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/Nexus-Gen) + +![](https://github.com/modelscope/Nexus-Gen/raw/main/assets/illustrations/gen_edit.jpg) + +
+ +
+ArtAug: Aesthetic Enhancement for Image Generation Models + +- Detail page: [./examples/ArtAug/](./examples/ArtAug/) +- Paper: [ArtAug: Enhancing Text-to-Image Generation through Synthesis-Understanding Interaction](https://arxiv.org/abs/2412.12888) +- Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/ArtAug-lora-FLUX.1dev-v1), [HuggingFace](https://huggingface.co/ECNU-CILab/ArtAug-lora-FLUX.1dev-v1) +- Online Demo: [ModelScope AIGC Tab](https://www.modelscope.cn/aigc/imageGeneration?tab=advanced&versionId=7228&modelType=LoRA&sdVersion=FLUX_1&modelUrl=modelscope%3A%2F%2FDiffSynth-Studio%2FArtAug-lora-FLUX.1dev-v1%3Frevision%3Dv1.0) + +|FLUX.1-dev|FLUX.1-dev + ArtAug LoRA| +|-|-| +|![image_1_base](https://github.com/user-attachments/assets/e1d5c505-b423-45fe-be01-25c2758f5417)|![image_1_enhance](https://github.com/user-attachments/assets/335908e3-d0bd-41c2-9d99-d10528a2d719)| + +
+ +
+EliGen: Precise Image Region Control + +- Detail page: [./examples/EntityControl/](./examples/EntityControl/) +- Paper: [EliGen: Entity-Level Controlled Image Generation with Regional Attention](https://arxiv.org/abs/2501.01097) +- Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen), [HuggingFace](https://huggingface.co/modelscope/EliGen) +- Online Demo: [ModelScope EliGen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/EliGen) +- Dataset: [EliGen Train Set](https://www.modelscope.cn/datasets/DiffSynth-Studio/EliGenTrainSet) + +|Entity Control Mask|Generated Image| +|-|-| +|![eligen_example_2_mask_0](https://github.com/user-attachments/assets/1c6d9445-5022-4d91-ad2e-dc05321883d1)|![eligen_example_2_0](https://github.com/user-attachments/assets/86739945-cb07-4a49-b3b3-3bb65c90d14f)| + +
+ +
+ExVideo: Extended Training for Video Generation Models + +- Project Page: [Project Page](https://ecnu-cilab.github.io/ExVideoProjectPage/) +- Paper: [ExVideo: Extending Video Diffusion Models via Parameter-Efficient Post-Tuning](https://arxiv.org/abs/2406.14130) +- Code Example: [./examples/ExVideo/](./examples/ExVideo/) +- Model: [ModelScope](https://modelscope.cn/models/ECNU-CILab/ExVideo-SVD-128f-v1), [HuggingFace](https://huggingface.co/ECNU-CILab/ExVideo-SVD-128f-v1) + +https://github.com/modelscope/DiffSynth-Studio/assets/35051019/d97f6aa9-8064-4b5b-9d49-ed6001bb9acc + +
+ +
+Diffutoon: High-Resolution Anime-Style Video Rendering + +- Project Page: [Project Page](https://ecnu-cilab.github.io/DiffutoonProjectPage/) +- Paper: [Diffutoon: High-Resolution Editable Toon Shading via Diffusion Models](https://arxiv.org/abs/2401.16224) +- Code Example: [./examples/Diffutoon/](./examples/Diffutoon/) + +https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/b54c05c5-d747-4709-be5e-b39af82404dd + +
+ +
+DiffSynth: The Initial Version of This Project + +- Project Page: [Project Page](https://ecnu-cilab.github.io/DiffSynth.github.io/) +- Paper: [DiffSynth: Latent In-Iteration Deflickering for Realistic Video Synthesis](https://arxiv.org/abs/2308.03463) +- Code Example: [./examples/diffsynth/](./examples/diffsynth/) + +https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-4481-b79f-0c3a7361a1ea + +
+ + + +## Update History -## News - **July 11, 2025** 🔥🔥🔥 We propose Nexus-Gen, a unified model that synergizes the language reasoning capabilities of LLMs with the image synthesis power of diffusion models. This framework enables seamless image understanding, generation, and editing tasks. - Paper: [Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) - Github Repo: https://github.com/modelscope/Nexus-Gen @@ -58,7 +330,7 @@ Until now, DiffSynth-Studio has supported the following models: - **March 31, 2025** We support InfiniteYou, an identity preserving method for FLUX. Please refer to [./examples/InfiniteYou/](./examples/InfiniteYou/) for more details. -- **March 25, 2025** 🔥🔥🔥 Our new open-source project, [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine), is now open-sourced! Focused on stable model deployment. Geared towards industry. Offers better engineering support, higher computational performance, and more stable functionality. +- **March 25, 2025** Our new open-source project, [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine), is now open-sourced! Focused on stable model deployment. Geared towards industry. Offers better engineering support, higher computational performance, and more stable functionality. - **March 13, 2025** We support HunyuanVideo-I2V, the image-to-video generation version of HunyuanVideo open-sourced by Tencent. Please refer to [./examples/HunyuanVideo/](./examples/HunyuanVideo/) for more details. @@ -134,136 +406,3 @@ Until now, DiffSynth-Studio has supported the following models: - [Project Page](https://ecnu-cilab.github.io/DiffSynth.github.io/). - The source codes are released in [EasyNLP](https://github.com/alibaba/EasyNLP/tree/master/diffusion/DiffSynth). - The technical report (ECML PKDD 2024) is released on [arXiv](https://arxiv.org/abs/2308.03463). - - -## Installation - -Install from source code (recommended): - -``` -git clone https://github.com/modelscope/DiffSynth-Studio.git -cd DiffSynth-Studio -pip install -e . -``` - -Or install from pypi (There is a delay in the update. If you want to experience the latest features, please do not use this installation method.): - -``` -pip install diffsynth -``` - -If you encounter issues during installation, it may be caused by the packages we depend on. Please refer to the documentation of the package that caused the problem. - -* [torch](https://pytorch.org/get-started/locally/) -* [sentencepiece](https://github.com/google/sentencepiece) -* [cmake](https://cmake.org) -* [cupy](https://docs.cupy.dev/en/stable/install.html) - -## Usage (in Python code) - -The Python examples are in [`examples`](./examples/). We provide an overview here. - -### Download Models - -Download the pre-set models. Model IDs can be found in [config file](/diffsynth/configs/model_config.py). - -```python -from diffsynth import download_models - -download_models(["FLUX.1-dev", "Kolors"]) -``` - -Download your own models. - -```python -from diffsynth.models.downloader import download_from_huggingface, download_from_modelscope - -# From Modelscope (recommended) -download_from_modelscope("Kwai-Kolors/Kolors", "vae/diffusion_pytorch_model.fp16.bin", "models/kolors/Kolors/vae") -# From Huggingface -download_from_huggingface("Kwai-Kolors/Kolors", "vae/diffusion_pytorch_model.fp16.safetensors", "models/kolors/Kolors/vae") -``` - -### Video Synthesis - -#### Text-to-video using CogVideoX-5B - -CogVideoX-5B is released by ZhiPu. We provide an improved pipeline, supporting text-to-video, video editing, self-upscaling and video interpolation. [`examples/video_synthesis`](./examples/video_synthesis/) - -The video on the left is generated using the original text-to-video pipeline, while the video on the right is the result after editing and frame interpolation. - -https://github.com/user-attachments/assets/26b044c1-4a60-44a4-842f-627ff289d006 - -#### Long Video Synthesis - -We trained extended video synthesis models, which can generate 128 frames. [`examples/ExVideo`](./examples/ExVideo/) - -https://github.com/modelscope/DiffSynth-Studio/assets/35051019/d97f6aa9-8064-4b5b-9d49-ed6001bb9acc - -https://github.com/user-attachments/assets/321ee04b-8c17-479e-8a95-8cbcf21f8d7e - -#### Toon Shading - -Render realistic videos in a flatten style and enable video editing features. [`examples/Diffutoon`](./examples/Diffutoon/) - -https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/b54c05c5-d747-4709-be5e-b39af82404dd - -https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/20528af5-5100-474a-8cdc-440b9efdd86c - -#### Video Stylization - -Video stylization without video models. [`examples/diffsynth`](./examples/diffsynth/) - -https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-4481-b79f-0c3a7361a1ea - -### Image Synthesis - -Generate high-resolution images, by breaking the limitation of diffusion models! [`examples/image_synthesis`](./examples/image_synthesis/). - -LoRA fine-tuning is supported in [`examples/train`](./examples/train/). - -|FLUX|Stable Diffusion 3| -|-|-| -|![image_1024_cfg](https://github.com/user-attachments/assets/984561e9-553d-4952-9443-79ce144f379f)|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/4df346db-6f91-420a-b4c1-26e205376098)| - -|Kolors|Hunyuan-DiT| -|-|-| -|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/53ef6f41-da11-4701-8665-9f64392607bf)|![image_1024](https://github.com/modelscope/DiffSynth-Studio/assets/35051019/60b022c8-df3f-4541-95ab-bf39f2fa8bb5)| - -|Stable Diffusion|Stable Diffusion XL| -|-|-| -|![1024](https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/6fc84611-8da6-4a1f-8fee-9a34eba3b4a5)|![1024](https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/67687748-e738-438c-aee5-96096f09ac90)| - -## Usage (in WebUI) - -Create stunning images using the painter, with assistance from AI! - -https://github.com/user-attachments/assets/95265d21-cdd6-4125-a7cb-9fbcf6ceb7b0 - -**This video is not rendered in real-time.** - -Before launching the WebUI, please download models to the folder `./models`. See [here](#download-models). - -* `Gradio` version - -``` -pip install gradio -``` - -``` -python apps/gradio/DiffSynth_Studio.py -``` - -![20240822102002](https://github.com/user-attachments/assets/59613157-de51-4109-99b3-97cbffd88076) - -* `Streamlit` version - -``` -pip install streamlit streamlit-drawable-canvas -``` - -``` -python -m streamlit run apps/streamlit/DiffSynth_Studio.py -``` - -https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/93085557-73f3-4eee-a205-9829591ef954 diff --git a/README_zh.md b/README_zh.md index 308a3fc..dce90b9 100644 --- a/README_zh.md +++ b/README_zh.md @@ -13,6 +13,8 @@ modelscope%2FDiffSynth-Studio | Trendshift

+[Switch to English](./README.md) + ## 简介 欢迎来到 Diffusion 模型的魔法世界!DiffSynth-Studio 是由[魔搭社区](https://www.modelscope.cn/)团队开发和维护的开源 Diffusion 模型引擎。我们期望以框架建设孵化技术创新,凝聚开源社区的力量,探索生成式模型技术的边界! @@ -53,7 +55,7 @@ pip install diffsynth ## 基础框架 -DiffSynth-Studio 为主流 Diffusion 模型(包括 FLUX、Wan 等)重新设计了推理和训练流水线。 +DiffSynth-Studio 为主流 Diffusion 模型(包括 FLUX、Wan 等)重新设计了推理和训练流水线,能够实现高效的显存管理、灵活的模型训练。 ### FLUX 系列 @@ -331,92 +333,92 @@ https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-44 -## 版本更新历史 +## 更新历史 -- **July 11, 2025** 🔥🔥🔥 We propose Nexus-Gen, a unified model that synergizes the language reasoning capabilities of LLMs with the image synthesis power of diffusion models. This framework enables seamless image understanding, generation, and editing tasks. - - Paper: [Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) - - Github Repo: https://github.com/modelscope/Nexus-Gen - - Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2), [HuggingFace](https://huggingface.co/modelscope/Nexus-GenV2) - - Training Dataset: [ModelScope Dataset](https://www.modelscope.cn/datasets/DiffSynth-Studio/Nexus-Gen-Training-Dataset) - - Online Demo: [ModelScope Nexus-Gen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/Nexus-Gen) +- **2025年7月11日** 🔥🔥🔥 我们提出 Nexus-Gen,一个将大语言模型(LLM)的语言推理能力与扩散模型的图像生成能力相结合的统一框架。该框架支持无缝的图像理解、生成和编辑任务。 + - 论文: [Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) + - Github 仓库: https://github.com/modelscope/Nexus-Gen + - 模型: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2), [HuggingFace](https://huggingface.co/modelscope/Nexus-GenV2) + - 训练数据集: [ModelScope Dataset](https://www.modelscope.cn/datasets/DiffSynth-Studio/Nexus-Gen-Training-Dataset) + - 在线体验: [ModelScope Nexus-Gen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/Nexus-Gen) -- **June 15, 2025** ModelScope's official evaluation framework, [EvalScope](https://github.com/modelscope/evalscope), now supports text-to-image generation evaluation. Try it with the [Best Practices](https://evalscope.readthedocs.io/zh-cn/latest/best_practice/t2i_eval.html) guide. +- **2025年6月15日** ModelScope 官方评测框架 [EvalScope](https://github.com/modelscope/evalscope) 现已支持文生图生成评测。请参考[最佳实践](https://evalscope.readthedocs.io/zh-cn/latest/best_practice/t2i_eval.html)指南进行尝试。 -- **March 31, 2025** We support InfiniteYou, an identity preserving method for FLUX. Please refer to [./examples/InfiniteYou/](./examples/InfiniteYou/) for more details. +- **2025年3月31日** 我们支持 InfiniteYou,一种用于 FLUX 的人脸特征保留方法。更多细节请参考 [./examples/InfiniteYou/](./examples/InfiniteYou/)。 -- **March 25, 2025** Our new open-source project, [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine), is now open-sourced! Focused on stable model deployment. Geared towards industry. Offers better engineering support, higher computational performance, and more stable functionality. +- **2025年3月25日** 我们的新开源项目 [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine) 现已开源!专注于稳定的模型部署,面向工业界,提供更好的工程支持、更高的计算性能和更稳定的功能。 -- **March 13, 2025** We support HunyuanVideo-I2V, the image-to-video generation version of HunyuanVideo open-sourced by Tencent. Please refer to [./examples/HunyuanVideo/](./examples/HunyuanVideo/) for more details. +- **2025年3月13日** 我们支持 HunyuanVideo-I2V,即腾讯开源的 HunyuanVideo 的图像到视频生成版本。更多细节请参考 [./examples/HunyuanVideo/](./examples/HunyuanVideo/)。 -- **February 25, 2025** We support Wan-Video, a collection of SOTA video synthesis models open-sourced by Alibaba. See [./examples/wanvideo/](./examples/wanvideo/). +- **2025年2月25日** 我们支持 Wan-Video,这是阿里巴巴开源的一系列最先进的视频合成模型。详见 [./examples/wanvideo/](./examples/wanvideo/)。 -- **February 17, 2025** We support [StepVideo](https://modelscope.cn/models/stepfun-ai/stepvideo-t2v/summary)! State-of-the-art video synthesis model! See [./examples/stepvideo](./examples/stepvideo/). +- **2025年2月17日** 我们支持 [StepVideo](https://modelscope.cn/models/stepfun-ai/stepvideo-t2v/summary)!先进的视频合成模型!详见 [./examples/stepvideo](./examples/stepvideo/)。 -- **December 31, 2024** We propose EliGen, a novel framework for precise entity-level controlled text-to-image generation, complemented by an inpainting fusion pipeline to extend its capabilities to image inpainting tasks. EliGen seamlessly integrates with existing community models, such as IP-Adapter and In-Context LoRA, enhancing its versatility. For more details, see [./examples/EntityControl](./examples/EntityControl/). - - Paper: [EliGen: Entity-Level Controlled Image Generation with Regional Attention](https://arxiv.org/abs/2501.01097) - - Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen), [HuggingFace](https://huggingface.co/modelscope/EliGen) - - Online Demo: [ModelScope EliGen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/EliGen) - - Training Dataset: [EliGen Train Set](https://www.modelscope.cn/datasets/DiffSynth-Studio/EliGenTrainSet) +- **2024年12月31日** 我们提出 EliGen,一种用于精确实体级别控制的文本到图像生成的新框架,并辅以修复融合管道,将其能力扩展到图像修复任务。EliGen 可以无缝集成现有的社区模型,如 IP-Adapter 和 In-Context LoRA,提升其通用性。更多详情,请见 [./examples/EntityControl](./examples/EntityControl/)。 + - 论文: [EliGen: Entity-Level Controlled Image Generation with Regional Attention](https://arxiv.org/abs/2501.01097) + - 模型: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen), [HuggingFace](https://huggingface.co/modelscope/EliGen) + - 在线体验: [ModelScope EliGen Studio](https://www.modelscope.cn/studios/DiffSynth-Studio/EliGen) + - 训练数据集: [EliGen Train Set](https://www.modelscope.cn/datasets/DiffSynth-Studio/EliGenTrainSet) -- **December 19, 2024** We implement advanced VRAM management for HunyuanVideo, making it possible to generate videos at a resolution of 129x720x1280 using 24GB of VRAM, or at 129x512x384 resolution with just 6GB of VRAM. Please refer to [./examples/HunyuanVideo/](./examples/HunyuanVideo/) for more details. +- **2024年12月19日** 我们为 HunyuanVideo 实现了高级显存管理,使得在 24GB 显存下可以生成分辨率为 129x720x1280 的视频,或在仅 6GB 显存下生成分辨率为 129x512x384 的视频。更多细节请参考 [./examples/HunyuanVideo/](./examples/HunyuanVideo/)。 -- **December 18, 2024** We propose ArtAug, an approach designed to improve text-to-image synthesis models through synthesis-understanding interactions. We have trained an ArtAug enhancement module for FLUX.1-dev in the format of LoRA. This model integrates the aesthetic understanding of Qwen2-VL-72B into FLUX.1-dev, leading to an improvement in the quality of generated images. - - Paper: https://arxiv.org/abs/2412.12888 - - Examples: https://github.com/modelscope/DiffSynth-Studio/tree/main/examples/ArtAug - - Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/ArtAug-lora-FLUX.1dev-v1), [HuggingFace](https://huggingface.co/ECNU-CILab/ArtAug-lora-FLUX.1dev-v1) - - Demo: [ModelScope](https://modelscope.cn/aigc/imageGeneration?tab=advanced&versionId=7228&modelType=LoRA&sdVersion=FLUX_1&modelUrl=modelscope%3A%2F%2FDiffSynth-Studio%2FArtAug-lora-FLUX.1dev-v1%3Frevision%3Dv1.0), HuggingFace (Coming soon) +- **2024年12月18日** 我们提出 ArtAug,一种通过合成-理解交互来改进文生图模型的方法。我们以 LoRA 格式为 FLUX.1-dev 训练了一个 ArtAug 增强模块。该模型将 Qwen2-VL-72B 的美学理解融入 FLUX.1-dev,从而提升了生成图像的质量。 + - 论文: https://arxiv.org/abs/2412.12888 + - 示例: https://github.com/modelscope/DiffSynth-Studio/tree/main/examples/ArtAug + - 模型: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/ArtAug-lora-FLUX.1dev-v1), [HuggingFace](https://huggingface.co/ECNU-CILab/ArtAug-lora-FLUX.1dev-v1) + - 演示: [ModelScope](https://modelscope.cn/aigc/imageGeneration?tab=advanced&versionId=7228&modelType=LoRA&sdVersion=FLUX_1&modelUrl=modelscope%3A%2F%2FDiffSynth-Studio%2FArtAug-lora-FLUX.1dev-v1%3Frevision%3Dv1.0), HuggingFace (即将上线) -- **October 25, 2024** We provide extensive FLUX ControlNet support. This project supports many different ControlNet models that can be freely combined, even if their structures differ. Additionally, ControlNet models are compatible with high-resolution refinement and partition control techniques, enabling very powerful controllable image generation. See [`./examples/ControlNet/`](./examples/ControlNet/). +- **2024年10月25日** 我们提供了广泛的 FLUX ControlNet 支持。该项目支持许多不同的 ControlNet 模型,并且可以自由组合,即使它们的结构不同。此外,ControlNet 模型兼容高分辨率优化和分区控制技术,能够实现非常强大的可控图像生成。详见 [`./examples/ControlNet/`](./examples/ControlNet/)。 -- **October 8, 2024.** We release the extended LoRA based on CogVideoX-5B and ExVideo. You can download this model from [ModelScope](https://modelscope.cn/models/ECNU-CILab/ExVideo-CogVideoX-LoRA-129f-v1) or [HuggingFace](https://huggingface.co/ECNU-CILab/ExVideo-CogVideoX-LoRA-129f-v1). +- **2024年10月8日** 我们发布了基于 CogVideoX-5B 和 ExVideo 的扩展 LoRA。您可以从 [ModelScope](https://modelscope.cn/models/ECNU-CILab/ExVideo-CogVideoX-LoRA-129f-v1) 或 [HuggingFace](https://huggingface.co/ECNU-CILab/ExVideo-CogVideoX-LoRA-129f-v1) 下载此模型。 -- **August 22, 2024.** CogVideoX-5B is supported in this project. See [here](/examples/video_synthesis/). We provide several interesting features for this text-to-video model, including - - Text to video - - Video editing - - Self-upscaling - - Video interpolation +- **2024年8月22日** 本项目现已支持 CogVideoX-5B。详见 [此处](/examples/video_synthesis/)。我们为这个文生视频模型提供了几个有趣的功能,包括: + - 文本到视频 + - 视频编辑 + - 自我超分 + - 视频插帧 -- **August 22, 2024.** We have implemented an interesting painter that supports all text-to-image models. Now you can create stunning images using the painter, with assistance from AI! - - Use it in our [WebUI](#usage-in-webui). +- **2024年8月22日** 我们实现了一个有趣的画笔功能,支持所有文生图模型。现在,您可以在 AI 的辅助下使用画笔创作惊艳的图像了! + - 在我们的 [WebUI](#usage-in-webui) 中使用它。 -- **August 21, 2024.** FLUX is supported in DiffSynth-Studio. - - Enable CFG and highres-fix to improve visual quality. See [here](/examples/image_synthesis/README.md) - - LoRA, ControlNet, and additional models will be available soon. +- **2024年8月21日** DiffSynth-Studio 现已支持 FLUX。 + - 启用 CFG 和高分辨率修复以提升视觉质量。详见 [此处](/examples/image_synthesis/README.md) + - LoRA、ControlNet 和其他附加模型将很快推出。 -- **June 21, 2024.** We propose ExVideo, a post-tuning technique aimed at enhancing the capability of video generation models. We have extended Stable Video Diffusion to achieve the generation of long videos up to 128 frames. - - [Project Page](https://ecnu-cilab.github.io/ExVideoProjectPage/) - - Source code is released in this repo. See [`examples/ExVideo`](./examples/ExVideo/). - - Models are released on [HuggingFace](https://huggingface.co/ECNU-CILab/ExVideo-SVD-128f-v1) and [ModelScope](https://modelscope.cn/models/ECNU-CILab/ExVideo-SVD-128f-v1). - - Technical report is released on [arXiv](https://arxiv.org/abs/2406.14130). - - You can try ExVideo in this [Demo](https://huggingface.co/spaces/modelscope/ExVideo-SVD-128f-v1)! +- **2024年6月21日** 我们提出 ExVideo,一种旨在增强视频生成模型能力的后训练微调技术。我们将 Stable Video Diffusion 进行了扩展,实现了长达 128 帧的长视频生成。 + - [项目页面](https://ecnu-cilab.github.io/ExVideoProjectPage/) + - 源代码已在此仓库中发布。详见 [`examples/ExVideo`](./examples/ExVideo/)。 + - 模型已发布于 [HuggingFace](https://huggingface.co/ECNU-CILab/ExVideo-SVD-128f-v1) 和 [ModelScope](https://modelscope.cn/models/ECNU-CILab/ExVideo-SVD-128f-v1)。 + - 技术报告已发布于 [arXiv](https://arxiv.org/abs/2406.14130)。 + - 您可以在此 [演示](https://huggingface.co/spaces/modelscope/ExVideo-SVD-128f-v1) 中试用 ExVideo! -- **June 13, 2024.** DiffSynth Studio is transferred to ModelScope. The developers have transitioned from "I" to "we". Of course, I will still participate in development and maintenance. +- **2024年6月13日** DiffSynth Studio 已迁移至 ModelScope。开发团队也从“我”转变为“我们”。当然,我仍会参与后续的开发和维护工作。 -- **Jan 29, 2024.** We propose Diffutoon, a fantastic solution for toon shading. - - [Project Page](https://ecnu-cilab.github.io/DiffutoonProjectPage/) - - The source codes are released in this project. - - The technical report (IJCAI 2024) is released on [arXiv](https://arxiv.org/abs/2401.16224). +- **2024年1月29日** 我们提出 Diffutoon,这是一个出色的卡通着色解决方案。 + - [项目页面](https://ecnu-cilab.github.io/DiffutoonProjectPage/) + - 源代码已在此项目中发布。 + - 技术报告(IJCAI 2024)已发布于 [arXiv](https://arxiv.org/abs/2401.16224)。 -- **Dec 8, 2023.** We decide to develop a new Project, aiming to release the potential of diffusion models, especially in video synthesis. The development of this project is started. +- **2023年12月8日** 我们决定启动一个新项目,旨在释放扩散模型的潜力,尤其是在视频合成方面。该项目的开发工作正式开始。 -- **Nov 15, 2023.** We propose FastBlend, a powerful video deflickering algorithm. - - The sd-webui extension is released on [GitHub](https://github.com/Artiprocher/sd-webui-fastblend). - - Demo videos are shown on Bilibili, including three tasks. - - [Video deflickering](https://www.bilibili.com/video/BV1d94y1W7PE) - - [Video interpolation](https://www.bilibili.com/video/BV1Lw411m71p) - - [Image-driven video rendering](https://www.bilibili.com/video/BV1RB4y1Z7LF) - - The technical report is released on [arXiv](https://arxiv.org/abs/2311.09265). - - An unofficial ComfyUI extension developed by other users is released on [GitHub](https://github.com/AInseven/ComfyUI-fastblend). +- **2023年11月15日** 我们提出 FastBlend,一种强大的视频去闪烁算法。 + - sd-webui 扩展已发布于 [GitHub](https://github.com/Artiprocher/sd-webui-fastblend)。 + - 演示视频已在 Bilibili 上展示,包含三个任务: + - [视频去闪烁](https://www.bilibili.com/video/BV1d94y1W7PE) + - [视频插帧](https://www.bilibili.com/video/BV1Lw411m71p) + - [图像驱动的视频渲染](https://www.bilibili.com/video/BV1RB4y1Z7LF) + - 技术报告已发布于 [arXiv](https://arxiv.org/abs/2311.09265)。 + - 其他用户开发的非官方 ComfyUI 扩展已发布于 [GitHub](https://github.com/AInseven/ComfyUI-fastblend)。 -- **Oct 1, 2023.** We release an early version of this project, namely FastSDXL. A try for building a diffusion engine. - - The source codes are released on [GitHub](https://github.com/Artiprocher/FastSDXL). - - FastSDXL includes a trainable OLSS scheduler for efficiency improvement. - - The original repo of OLSS is [here](https://github.com/alibaba/EasyNLP/tree/master/diffusion/olss_scheduler). - - The technical report (CIKM 2023) is released on [arXiv](https://arxiv.org/abs/2305.14677). - - A demo video is shown on [Bilibili](https://www.bilibili.com/video/BV1w8411y7uj). - - Since OLSS requires additional training, we don't implement it in this project. +- **2023年10月1日** 我们发布了该项目的早期版本,名为 FastSDXL。这是构建一个扩散引擎的初步尝试。 + - 源代码已发布于 [GitHub](https://github.com/Artiprocher/FastSDXL)。 + - FastSDXL 包含一个可训练的 OLSS 调度器,以提高效率。 + - OLSS 的原始仓库位于 [此处](https://github.com/alibaba/EasyNLP/tree/master/diffusion/olss_scheduler)。 + - 技术报告(CIKM 2023)已发布于 [arXiv](https://arxiv.org/abs/2305.14677)。 + - 演示视频已发布于 [Bilibili](https://www.bilibili.com/video/BV1w8411y7uj)。 + - 由于 OLSS 需要额外训练,我们未在本项目中实现它。 -- **Aug 29, 2023.** We propose DiffSynth, a video synthesis framework. - - [Project Page](https://ecnu-cilab.github.io/DiffSynth.github.io/). - - The source codes are released in [EasyNLP](https://github.com/alibaba/EasyNLP/tree/master/diffusion/DiffSynth). - - The technical report (ECML PKDD 2024) is released on [arXiv](https://arxiv.org/abs/2308.03463). +- **2023年8月29日** 我们提出 DiffSynth,一个视频合成框架。 + - [项目页面](https://ecnu-cilab.github.io/DiffSynth.github.io/)。 + - 源代码已发布在 [EasyNLP](https://github.com/alibaba/EasyNLP/tree/master/diffusion/DiffSynth)。 + - 技术报告(ECML PKDD 2024)已发布于 [arXiv](https://arxiv.org/abs/2308.03463)。 From 0b3400bca3474437b12d2d517ce67679249e5308 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:22:48 +0800 Subject: [PATCH 20/51] update readme --- README.md | 2 ++ README_zh.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 3a67e04..7227244 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ modelscope%2FDiffSynth-Studio | Trendshift

+--- + [切换到中文](./README_zh.md) ## Introduction diff --git a/README_zh.md b/README_zh.md index dce90b9..9b028fa 100644 --- a/README_zh.md +++ b/README_zh.md @@ -13,6 +13,8 @@ modelscope%2FDiffSynth-Studio | Trendshift

+--- + [Switch to English](./README.md) ## 简介 From b06066f25b2579134cde73abb204c6c13e3a10e3 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:26:41 +0800 Subject: [PATCH 21/51] update readme --- README_zh.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README_zh.md b/README_zh.md index 9b028fa..48149e6 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,19 +1,14 @@ -

DiffSynth-Studio
+# DiffSynth-Studio +

- +

[![PyPI](https://img.shields.io/pypi/v/DiffSynth)](https://pypi.org/project/DiffSynth/) [![license](https://img.shields.io/github/license/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/blob/master/LICENSE) [![open issues](https://isitmaintained.com/badge/open/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/issues) [![GitHub pull-requests](https://img.shields.io/github/issues-pr/modelscope/DiffSynth-Studio.svg)](https://GitHub.com/modelscope/DiffSynth-Studio/pull/) -[![GitHub latest commit](https://badgen.net/github/last-commit/modelscope/DiffSynth-Studio)](https://GitHub.com/modelscope/DiffSynth-Studio/commit/) - -

-modelscope%2FDiffSynth-Studio | Trendshift -

- ---- +[![GitHub latest commit](https://badgen.net/github/last-commit/modelscope/DiffSynth-Studio)](https://GitHub.com/modelscope/DiffSynth-Studio/commit/) modelscope%2FDiffSynth-Studio | Trendshift

[Switch to English](./README.md) From 11ea986e67cb243573c37e9074f81cfc438ff0f5 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:28:29 +0800 Subject: [PATCH 22/51] update readme --- README_zh.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README_zh.md b/README_zh.md index 48149e6..2d382d3 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,14 +1,16 @@ # DiffSynth-Studio

- +

+modelscope%2FDiffSynth-Studio | Trendshift

+ [![PyPI](https://img.shields.io/pypi/v/DiffSynth)](https://pypi.org/project/DiffSynth/) [![license](https://img.shields.io/github/license/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/blob/master/LICENSE) [![open issues](https://isitmaintained.com/badge/open/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/issues) [![GitHub pull-requests](https://img.shields.io/github/issues-pr/modelscope/DiffSynth-Studio.svg)](https://GitHub.com/modelscope/DiffSynth-Studio/pull/) -[![GitHub latest commit](https://badgen.net/github/last-commit/modelscope/DiffSynth-Studio)](https://GitHub.com/modelscope/DiffSynth-Studio/commit/) modelscope%2FDiffSynth-Studio | Trendshift

+[![GitHub latest commit](https://badgen.net/github/last-commit/modelscope/DiffSynth-Studio)](https://GitHub.com/modelscope/DiffSynth-Studio/commit/) [Switch to English](./README.md) From b9f7d08219ab0f76dccf8ef496497c3cd5182220 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:30:34 +0800 Subject: [PATCH 23/51] update readme --- README_zh.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README_zh.md b/README_zh.md index 2d382d3..beb8bc0 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,10 +1,6 @@ # DiffSynth-Studio -

- -

- -modelscope%2FDiffSynth-Studio | Trendshift

+ modelscope%2FDiffSynth-Studio | Trendshift

[![PyPI](https://img.shields.io/pypi/v/DiffSynth)](https://pypi.org/project/DiffSynth/) [![license](https://img.shields.io/github/license/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/blob/master/LICENSE) From c18b5a0c71a8c68a4d1d08102ddcdc85c65bd543 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:31:44 +0800 Subject: [PATCH 24/51] update readme --- README.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7227244..8522f69 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,12 @@ -

DiffSynth-Studio
-

- -

+# DiffSynth-Studio + + modelscope%2FDiffSynth-Studio | Trendshift

[![PyPI](https://img.shields.io/pypi/v/DiffSynth)](https://pypi.org/project/DiffSynth/) [![license](https://img.shields.io/github/license/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/blob/master/LICENSE) [![open issues](https://isitmaintained.com/badge/open/modelscope/DiffSynth-Studio.svg)](https://github.com/modelscope/DiffSynth-Studio/issues) [![GitHub pull-requests](https://img.shields.io/github/issues-pr/modelscope/DiffSynth-Studio.svg)](https://GitHub.com/modelscope/DiffSynth-Studio/pull/) -[![GitHub latest commit](https://badgen.net/github/last-commit/modelscope/DiffSynth-Studio)](https://GitHub.com/modelscope/DiffSynth-Studio/commit/) - -

-modelscope%2FDiffSynth-Studio | Trendshift -

- ---- +[![GitHub latest commit](https://badgen.net/github/last-commit/modelscope/DiffSynth-Studio)](https://GitHub.com/modelscope/DiffSynth-Studio/commit/) [切换到中文](./README_zh.md) From b3df7e5e21579d5b9e047410ecf33c159f422635 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:43:58 +0800 Subject: [PATCH 25/51] update readme --- examples/flux/README.md | 235 ++++++++++++++++++++++++++-------------- 1 file changed, 152 insertions(+), 83 deletions(-) diff --git a/examples/flux/README.md b/examples/flux/README.md index bc78587..b1451d1 100644 --- a/examples/flux/README.md +++ b/examples/flux/README.md @@ -18,7 +18,7 @@ pip install -e . ## Quick Start -You can quickly load the FLUX.1-dev model and perform inference by running the following code: +You can quickly load the [black-forest-labs/FLUX.1-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-dev ) model and run inference by executing the code below. ```python import torch @@ -41,12 +41,19 @@ image.save("image.jpg") ## Model Overview -**Support for the new framework of the FLUX series models is under active development. Stay tuned!** - -| Model ID | Additional Parameters | Inference | Full Training | Validation After Full Training | LoRA Training | Validation After LoRA Training | -|-|-|-|-|-|-|-| -|[black-forest-labs/FLUX.1-dev](https://modelscope.cn/models/black-forest-labs/FLUX.1-dev)||[code](./model_inference/FLUX.1-dev.py)|[code](./model_training/full/FLUX.1-dev.sh)|[code](./model_training/validate_full/FLUX.1-dev.py)|[code](./model_training/lora/FLUX.1-dev.sh)|[code](./model_training/validate_lora/FLUX.1-dev.py)| -|[black-forest-labs/FLUX.1-Kontext-dev](https://modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev)|`kontext_images`|[code](./model_inference/FLUX.1-Kontext-dev.py)|[code](./model_training/full/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_full/FLUX.1-Kontext-dev.py)|[code](./model_training/lora/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_lora/FLUX.1-Kontext-dev.py)| +|Model ID|Extra Args|Inference|Low VRAM Inference|Full Training|Validation after Full Training|LoRA Training|Validation after LoRA Training| +|-|-|-|-|-|-|-|-| +|[FLUX.1-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-dev )||[code](./model_inference/FLUX.1-dev.py)|[code](./model_inference_low_vram/FLUX.1-dev.py)|[code](./model_training/full/FLUX.1-dev.sh)|[code](./model_training/validate_full/FLUX.1-dev.py)|[code](./model_training/lora/FLUX.1-dev.sh)|[code](./model_training/validate_lora/FLUX.1-dev.py)| +|[FLUX.1-Kontext-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev)|`kontext_images`|[code](./model_inference/FLUX.1-Kontext-dev.py)|[code](./model_inference_low_vram/FLUX.1-Kontext-dev.py)|[code](./model_training/full/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_full/FLUX.1-Kontext-dev.py)|[code](./model_training/lora/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_lora/FLUX.1-Kontext-dev.py)| +|[FLUX.1-dev-Controlnet-Inpainting-Beta](https://www.modelscope.cn/models/alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py)| +|[FLUX.1-dev-Controlnet-Union-alpha](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-Controlnet-Union-alpha)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py)| +|[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| +|[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| +|[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| +|[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| +|[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./model_inference/Step1X-Edit.py)|[code](./model_inference_low_vram/Step1X-Edit.py)|[code](./model_training/full/Step1X-Edit.sh)|[code](./model_training/validate_full/Step1X-Edit.py)|[code](./model_training/lora/Step1X-Edit.sh)|[code](./model_training/validate_lora/Step1X-Edit.py)| +|[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./model_inference/FLEX.2-preview.py)|[code](./model_inference_low_vram/FLEX.2-preview.py)|[code](./model_training/full/FLEX.2-preview.sh)|[code](./model_training/validate_full/FLEX.2-preview.py)|[code](./model_training/lora/FLEX.2-preview.sh)|[code](./model_training/validate_lora/FLEX.2-preview.py)| ## Model Inference @@ -54,11 +61,14 @@ The following sections will help you understand our features and write inference
-Loading Models +Load Model -Models are loaded using `from_pretrained`: +The model is loaded using `from_pretrained`: ```python +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + pipe = FluxImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", @@ -71,21 +81,21 @@ pipe = FluxImagePipeline.from_pretrained( ) ``` -Here, `torch_dtype` and `device` refer to the computation precision and device, respectively. The `model_configs` can be configured in various ways to specify model paths: +Here, `torch_dtype` and `device` set the computation precision and device. The `model_configs` can be used in different ways to specify model paths: -* Download the model from [ModelScope Community](https://modelscope.cn/) and load it. In this case, provide `model_id` and `origin_file_pattern`, for example: +* Download the model from [ModelScope](https://modelscope.cn/ ) and load it. In this case, fill in `model_id` and `origin_file_pattern`, for example: ```python ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors") ``` -* Load the model from a local file path. In this case, provide the `path`, for example: +* Load the model from a local file path. In this case, fill in `path`, for example: ```python ModelConfig(path="models/black-forest-labs/FLUX.1-dev/flux1-dev.safetensors") ``` -For models that consist of multiple files, use a list as follows: +For a single model that loads from multiple files, use a list, for example: ```python ModelConfig(path=[ @@ -95,10 +105,10 @@ ModelConfig(path=[ ]) ``` -The `from_pretrained` method also provides additional parameters to control model loading behavior: +The `from_pretrained` method also provides extra arguments to control model loading behavior: -* `local_model_path`: Path for saving downloaded models. The default is `"./models"`. -* `skip_download`: Whether to skip downloading models. The default is `False`. If your network cannot access [ModelScope Community](https://modelscope.cn/), manually download the required files and set this to `True`. +* `local_model_path`: Path to save downloaded models. Default is `"./models"`. +* `skip_download`: Whether to skip downloading. Default is `False`. If your network cannot access [ModelScope](https://modelscope.cn/ ), download the required files manually and set this to `True`.
@@ -107,7 +117,7 @@ The `from_pretrained` method also provides additional parameters to control mode VRAM Management -DiffSynth-Studio provides fine-grained VRAM management for FLUX models, enabling inference on devices with limited VRAM. You can enable offloading functionality via the following code, which moves certain modules to system memory on devices with limited GPU memory. +DiffSynth-Studio provides fine-grained VRAM management for the FLUX model. This allows the model to run on devices with low VRAM. You can enable the offload feature using the code below. It moves some modules to CPU memory when GPU memory is limited. ```python pipe = FluxImagePipeline.from_pretrained( @@ -123,19 +133,52 @@ pipe = FluxImagePipeline.from_pretrained( pipe.enable_vram_management() ``` -The `enable_vram_management` function provides the following parameters to control VRAM usage: +FP8 quantization is also supported: -* `vram_limit`: VRAM usage limit in GB. By default, it uses the remaining VRAM available on the device. Note that this is not an absolute limit; if the set VRAM is insufficient but more VRAM is actually available, the model will run with minimal VRAM consumption. Setting it to 0 achieves the theoretical minimum VRAM usage. -* `vram_buffer`: VRAM buffer size in GB. The default is 0.5GB. Since some large neural network layers may consume extra VRAM during onload phases, a VRAM buffer is necessary. Ideally, the optimal value should match the VRAM occupied by the largest layer in the model. -* `num_persistent_param_in_dit`: Number of persistent parameters in the DiT model (default: no limit). We plan to remove this parameter in the future, so please avoid relying on it. +```python +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() +``` + +You can use FP8 quantization and offload at the same time: + +```python +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu", offload_dtype=torch.float8_e4m3fn), + ], +) +pipe.enable_vram_management() +``` + +After enabling VRAM management, the framework will automatically decide the VRAM strategy based on available GPU memory. For most FLUX models, inference can run with as little as 8GB of VRAM. The `enable_vram_management` function has the following parameters to manually control the VRAM strategy: + +* `vram_limit`: VRAM usage limit in GB. By default, it uses all free VRAM on the device. Note that this is not an absolute limit. If the set VRAM is not enough but more VRAM is actually available, the model will run with minimal VRAM usage. Setting it to 0 achieves the theoretical minimum VRAM usage. +* `vram_buffer`: VRAM buffer size in GB. Default is 0.5GB. A buffer is needed because larger neural network layers may use more VRAM than expected during loading. The optimal value is the VRAM used by the largest layer in the model. +* `num_persistent_param_in_dit`: Number of parameters in the DiT model that stay in VRAM. Default is no limit. We plan to remove this parameter in the future. Do not rely on it.

+
Inference Acceleration -* TeaCache: Acceleration technique [TeaCache](https://github.com/ali-vilab/TeaCache), please refer to the [sample code](./acceleration/teacache.py). +* TeaCache: Acceleration technique [TeaCache](https://github.com/ali-vilab/TeaCache ). Please refer to the [example code](./acceleration/teacache.py).
@@ -143,75 +186,98 @@ The `enable_vram_management` function provides the following parameters to contr Input Parameters -The pipeline accepts the following input parameters during inference: +The pipeline supports the following input parameters during inference: -* `prompt`: Prompt describing what should appear in the image. -* `negative_prompt`: Negative prompt describing what should **not** appear in the image. Default is `""`. -* `cfg_scale`: Classifier-free guidance scale. Default is 1. It becomes effective when set to a value greater than 1. -* `embedded_guidance`: Embedded guidance parameter for FLUX-dev. Default is 3.5. -* `t5_sequence_length`: Sequence length of T5 text embeddings. Default is 512. -* `input_image`: Input image used for image-to-image generation. This works together with `denoising_strength`. -* `denoising_strength`: Denoising strength, ranging from 0 to 1. Default is 1. When close to 0, the generated image will be similar to the input image; when close to 1, the generated image will differ significantly from the input. Do not set this to a non-1 value if no `input_image` is provided. -* `height`: Height of the generated image. Must be a multiple of 16. -* `width`: Width of the generated image. Must be a multiple of 16. -* `seed`: Random seed. Default is `None`, meaning completely random. -* `rand_device`: Device for generating random Gaussian noise. Default is `"cpu"`. Setting it to `"cuda"` may lead to different results across GPUs. -* `sigma_shift`: Parameter from Rectified Flow theory. Default is 3. A larger value increases the number of steps spent at the beginning of denoising and can improve image quality. However, it may cause inconsistencies between the generation process and training data. +* `prompt`: Text prompt describing what should appear in the image. +* `negative_prompt`: Negative prompt describing what should not appear in the image. Default is `""`. +* `cfg_scale`: Parameter for classifier-free guidance. Default is 1. Takes effect when set to a value greater than 1. +* `embedded_guidance`: Built-in guidance parameter for FLUX-dev. Default is 3.5. +* `t5_sequence_length`: Sequence length of text embeddings from the T5 model. Default is 512. +* `input_image`: Input image used for image-to-image generation. Used together with `denoising_strength`. +* `denoising_strength`: Denoising strength, range from 0 to 1. Default is 1. When close to 0, the output image is similar to the input. When close to 1, the output differs more from the input. Do not set it to values other than 1 if `input_image` is not provided. +* `height`: Image height. Must be a multiple of 16. +* `width`: Image width. Must be a multiple of 16. +* `seed`: Random seed. Default is `None`, meaning fully random. +* `rand_device`: Device for generating random Gaussian noise. Default is `"cpu"`. Setting it to `"cuda"` may lead to different results on different GPUs. +* `sigma_shift`: Parameter from Rectified Flow theory. Default is 3. A larger value means the model spends more steps at the start of denoising. Increasing this can improve image quality, but may cause differences between generated images and training data due to inconsistency with training. * `num_inference_steps`: Number of inference steps. Default is 30. * `kontext_images`: Input images for the Kontext model. * `controlnet_inputs`: Inputs for the ControlNet model. * `ipadapter_images`: Input images for the IP-Adapter model. -* `ipadapter_scale`: Control strength of the IP-Adapter model. +* `ipadapter_scale`: Control strength for the IP-Adapter model. +* `eligen_entity_prompts`: Local prompts for the EliGen model. +* `eligen_entity_masks`: Mask regions for local prompts in the EliGen model. Matches one-to-one with `eligen_entity_prompts`. +* `eligen_enable_on_negative`: Whether to enable EliGen on the negative prompt side. Only works when `cfg_scale > 1`. +* `eligen_enable_inpaint`: Whether to enable EliGen for local inpainting. +* `infinityou_id_image`: Face image for the InfiniteYou model. +* `infinityou_guidance`: Control strength for the InfiniteYou model. +* `flex_inpaint_image`: Image for FLEX model's inpainting. +* `flex_inpaint_mask`: Mask region for FLEX model's inpainting. +* `flex_control_image`: Image for FLEX model's structural control. +* `flex_control_strength`: Strength for FLEX model's structural control. +* `flex_control_stop`: End point for FLEX model's structural control. 1 means enabled throughout, 0.5 means enabled in the first half, 0 means disabled. +* `step1x_reference_image`: Input image for Step1x-Edit model's image editing. +* `lora_encoder_inputs`: Inputs for LoRA encoder. Can be ModelConfig or local path. +* `lora_encoder_scale`: Activation strength for LoRA encoder. Default is 1. Smaller values mean weaker LoRA activation. +* `tea_cache_l1_thresh`: Threshold for TeaCache. Larger values mean faster speed but lower image quality. Note that after enabling TeaCache, inference speed is not uniform, so the remaining time shown in the progress bar will be inaccurate. +* `tiled`: Whether to enable tiled VAE inference. Default is `False`. Setting to `True` reduces VRAM usage during VAE encoding/decoding, with slight error and slightly longer inference time. +* `tile_size`: Tile size during VAE encoding/decoding. Default is 128. Only takes effect when `tiled=True`. +* `tile_stride`: Tile stride during VAE encoding/decoding. Default is 64. Only takes effect when `tiled=True`. Must be less than or equal to `tile_size`. +* `progress_bar_cmd`: Progress bar display. Default is `tqdm.tqdm`. Set to `lambda x:x` to disable the progress bar. + ## Model Training -FLUX series models are trained using a unified script [`./model_training/train.py`](./model_training/train.py). +Training for the FLUX series models is done using a unified script [`./model_training/train.py`](./model_training/train.py).
Script Parameters -The script supports the following parameters: +The script includes the following parameters: * Dataset - * `--dataset_base_path`: Root path to the dataset. - * `--dataset_metadata_path`: Path to the metadata file of the dataset. - * `--max_pixels`: Maximum pixel area, default is 1024*1024. When dynamic resolution is enabled, any image with a resolution larger than this value will be scaled down.。 - * `--height`: Height of images or videos. Leave `height` and `width` empty to enable dynamic resolution. - * `--width`: Width of images or videos. Leave `height` and `width` empty to enable dynamic resolution. - * `--data_file_keys`: Keys in metadata for data files. Comma-separated. + * `--dataset_base_path`: Root path of the dataset. + * `--dataset_metadata_path`: Path to the dataset metadata file. + * `--max_pixels`: Maximum pixel area. Default is 1024*1024. When dynamic resolution is enabled, any image with resolution higher than this will be downscaled. + * `--height`: Height of the image or video. Leave `height` and `width` empty to enable dynamic resolution. + * `--width`: Width of the image or video. Leave `height` and `width` empty to enable dynamic resolution. + * `--data_file_keys`: Data file keys in the metadata. Separate with commas. * `--dataset_repeat`: Number of times the dataset repeats per epoch. -* Models - * `--model_paths`: Paths to load models. JSON format. - * `--model_id_with_origin_paths`: Model IDs with original paths, e.g., black-forest-labs/FLUX.1-dev:flux1-dev.safetensors. Comma-separated. +* Model + * `--model_paths`: Paths to load models. In JSON format. + * `--model_id_with_origin_paths`: Model ID with original paths, e.g., black-forest-labs/FLUX.1-dev:flux1-dev.safetensors. Separate with commas. * Training * `--learning_rate`: Learning rate. - * `--num_epochs`: Number of training epochs. - * `--output_path`: Output path for saving checkpoints. - * `--remove_prefix_in_ckpt`: Remove prefix in checkpoint filenames. + * `--num_epochs`: Number of epochs. + * `--output_path`: Save path. + * `--remove_prefix_in_ckpt`: Remove prefix in checkpoint. * Trainable Modules * `--trainable_models`: Models that can be trained, e.g., dit, vae, text_encoder. - * `--lora_base_model`: Which base model to apply LoRA on. - * `--lora_target_modules`: Which layers to apply LoRA on. + * `--lora_base_model`: Which model to add LoRA to. + * `--lora_target_modules`: Which layers to add LoRA to. * `--lora_rank`: Rank of LoRA. -* Extra Inputs - * `--extra_inputs`: Additional model inputs. Comma-separated. +* Extra Model Inputs + * `--extra_inputs`: Extra model inputs, separated by commas. * VRAM Management - * `--use_gradient_checkpointing`: Whether to use gradient checkpointing. + * `--use_gradient_checkpointing`: Whether to enable gradient checkpointing. * `--use_gradient_checkpointing_offload`: Whether to offload gradient checkpointing to CPU memory. - * `--gradient_accumulation_steps`: Number of steps for gradient accumulation. -* Miscellaneous - * `--align_to_opensource_format`: Whether to align the FLUX DiT LoRA format with the open-source version. Only applicable to LoRA training for FLUX.1-dev and FLUX.1-Kontext-dev. + * `--gradient_accumulation_steps`: Number of gradient accumulation steps. +* Others + * `--align_to_opensource_format`: Whether to align the FLUX DiT LoRA format with the open-source version. Only works for LoRA training. + +In addition, the training framework is built on [`accelerate`](https://huggingface.co/docs/accelerate/index ). Run `accelerate config` before training to set GPU-related parameters. For some training scripts (e.g., full model training), we provide suggested `accelerate` config files. You can find them in the corresponding training scripts.
+
Step 1: Prepare Dataset -The dataset contains a series of files. We recommend organizing your dataset files as follows: +A dataset contains a series of files. We suggest organizing your dataset like this: ``` data/example_image_dataset/ @@ -220,7 +286,7 @@ data/example_image_dataset/ └── image2.jpg ``` -Here, `image1.jpg`, `image2.jpg` are training image data, and `metadata.csv` is the metadata list, for example: +Here, `image1.jpg` and `image2.jpg` are training images, and `metadata.csv` is the metadata list, for example: ``` image,prompt @@ -228,7 +294,7 @@ image1.jpg,"a cat is sleeping" image2.jpg,"a dog is running" ``` -We have built a sample image dataset to help you test more conveniently. You can download this dataset using the following command: +We have built a sample image dataset to help you test. You can download it with the following command: ```shell modelscope download --dataset DiffSynth-Studio/example_image_dataset --local_dir ./data/example_image_dataset @@ -236,26 +302,27 @@ modelscope download --dataset DiffSynth-Studio/example_image_dataset --local_dir The dataset supports multiple image formats: `"jpg", "jpeg", "png", "webp"`. -The image resolution can be controlled via script parameters `--height` and `--width`. When both `--height` and `--width` are left empty, dynamic resolution will be enabled, allowing training with the actual width and height of each image in the dataset. +Image size can be controlled by script arguments `--height` and `--width`. When `--height` and `--width` are left empty, dynamic resolution is enabled. The model will train using each image's actual width and height from the dataset. -**We strongly recommend using fixed-resolution training, as there may be load-balancing issues in multi-GPU training with dynamic resolution.** +**We strongly recommend using fixed resolution for training, because there can be load balancing issues in multi-GPU training.** -When the model requires additional inputs—for instance, `kontext_images` required by the controllable model [`black-forest-labs/FLUX.1-Kontext-dev`](https://modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev)—please add corresponding columns in the dataset, for example: +When the model needs extra inputs, for example, `kontext_images` required by controllable models like [`black-forest-labs/FLUX.1-Kontext-dev`](https://modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev ), add the corresponding column to your dataset, for example: ``` image,prompt,kontext_images image1.jpg,"a cat is sleeping",image1_reference.jpg ``` -If additional inputs include image files, you need to specify the column names to parse using the `--data_file_keys` parameter. You can add more column names accordingly, e.g., `--data_file_keys "image,kontext_images"`. +If an extra input includes image files, you must specify the column name in the `--data_file_keys` argument. Add column names as needed, for example `--data_file_keys "image,kontext_images"`, and also enable `--extra_inputs "kontext_images"`.
+
Step 2: Load Model -Similar to the model loading logic during inference, you can directly configure the model to be loaded using its model ID. For example, during inference we load the model with the following configuration: +Similar to model loading during inference, you can configure which models to load directly using model IDs. For example, during inference we load the model with this setting: ```python model_configs=[ @@ -266,13 +333,13 @@ model_configs=[ ] ``` -Then during training, simply provide the following parameter to load the corresponding model: +Then, during training, use the following parameter to load the same models: ```shell --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors" ``` -If you prefer to load the model from local files, as in the inference example: +If you want to load models from local files, for example, during inference: ```python model_configs=[ @@ -283,7 +350,7 @@ model_configs=[ ] ``` -Then during training, set it up as follows: +Then during training, set it as: ```shell --model_paths '[ @@ -296,23 +363,25 @@ Then during training, set it up as follows:
-
- -Step 3: Configure Trainable Modules - -The training framework supports both full-model training and LoRA-based fine-tuning. Below are some examples: - -* Full training of the DiT module: `--trainable_models dit` -* Training a LoRA model on the DiT module: `--lora_base_model dit --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" --lora_rank 32` - -Additionally, since the training script loads multiple modules (text encoder, DiT, VAE), you need to remove prefixes when saving the model files. For example, when performing full DiT training or LoRA training on the DiT module, please set `--remove_prefix_in_ckpt pipe.dit.` - -
-Step 4: Launch the Training Script +Step 3: Set Trainable Modules -We have written specific training commands for each model. Please refer to the table at the beginning of this document for details. +The training framework supports training base models or LoRA models. Here are some examples: + +* Full training of the DiT part: `--trainable_models dit` +* Training a LoRA model on the DiT part: `--lora_base_model dit --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" --lora_rank 32` + +Also, because the training script loads multiple modules (text encoder, dit, vae), you need to remove prefixes when saving model files. For example, when fully training the DiT part or training a LoRA model on the DiT part, set `--remove_prefix_in_ckpt pipe.dit.` + +
+ + +
+ +Step 4: Start Training + +We have written training commands for each model. Please refer to the table at the beginning of this document.
From 2827b603304867732feebdad636537260280b734 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 22 Jul 2025 20:48:19 +0800 Subject: [PATCH 26/51] update readme --- examples/wanvideo/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/wanvideo/README.md b/examples/wanvideo/README.md index 7b2ebbd..1b78e78 100644 --- a/examples/wanvideo/README.md +++ b/examples/wanvideo/README.md @@ -18,6 +18,8 @@ pip install -e . ## Quick Start +You can quickly load the [Wan-AI/Wan2.1-T2V-1.3B](https://www.modelscope.cn/models/Wan-AI/Wan2.1-T2V-1.3B) model and run inference by executing the code below. + ```python import torch from diffsynth import save_video @@ -78,6 +80,9 @@ The following sections will help you understand our functionalities and write in The model is loaded using `from_pretrained`: ```python +import torch +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig + pipe = WanVideoPipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", @@ -177,11 +182,11 @@ pipe.enable_vram_management() FP8 quantization significantly reduces VRAM usage but does not accelerate computations. Some models may experience issues such as blurry, torn, or distorted outputs due to insufficient precision when using FP8 quantization. Use FP8 quantization with caution. -The `enable_vram_management` function provides the following parameters to control VRAM usage: +After enabling VRAM management, the framework will automatically decide the VRAM strategy based on available GPU memory. The `enable_vram_management` function has the following parameters to manually control the VRAM strategy: -* `vram_limit`: VRAM usage limit (in GB). By default, it uses all available VRAM on the device. Note that this is not an absolute limit; if the specified VRAM is insufficient but more VRAM is actually available, inference will proceed using the minimum required VRAM. -* `vram_buffer`: Size of the VRAM buffer (in GB). Default is 0.5GB. Since certain large neural network layers may consume more VRAM unpredictably during their execution phase, a VRAM buffer is necessary. Ideally, this should match the maximum VRAM consumed by any single layer in the model. -* `num_persistent_param_in_dit`: Number of persistent parameters in DiT models. By default, there is no limit. We plan to remove this parameter in the future, so please avoid relying on it. +* `vram_limit`: VRAM usage limit in GB. By default, it uses all free VRAM on the device. Note that this is not an absolute limit. If the set VRAM is not enough but more VRAM is actually available, the model will run with minimal VRAM usage. Setting it to 0 achieves the theoretical minimum VRAM usage. +* `vram_buffer`: VRAM buffer size in GB. Default is 0.5GB. A buffer is needed because larger neural network layers may use more VRAM than expected during loading. The optimal value is the VRAM used by the largest layer in the model. +* `num_persistent_param_in_dit`: Number of parameters in the DiT model that stay in VRAM. Default is no limit. We plan to remove this parameter in the future. Do not rely on it. From cd1ba7281b4214c91b65cd9c640974e03e8b0fd8 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Wed, 23 Jul 2025 11:13:38 +0800 Subject: [PATCH 27/51] update readme --- README.md | 11 +++++++++-- README_zh.md | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8522f69..aa116b7 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ DiffSynth currently includes two open-source projects: * [DiffSynth-Studio](https://github.com/modelscope/DiffSynth-Studio): Focused on aggressive technical exploration, for academia, providing support for more cutting-edge model capabilities. * [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine): Focused on stable model deployment, for industry, offering higher computing performance and more stable features. +[DiffSynth-Studio](https://github.com/modelscope/DiffSynth-Studio) and [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine) are the core projects behind ModelScope [AIGC zone](https://modelscope.cn/aigc/home), offering powerful AI content generation abilities. Come and try our carefully designed features and start your AI creation journey! + ## Installation Install from source (recommended): @@ -323,10 +325,13 @@ https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-44 - **June 15, 2025** ModelScope's official evaluation framework, [EvalScope](https://github.com/modelscope/evalscope), now supports text-to-image generation evaluation. Try it with the [Best Practices](https://evalscope.readthedocs.io/zh-cn/latest/best_practice/t2i_eval.html) guide. -- **March 31, 2025** We support InfiniteYou, an identity preserving method for FLUX. Please refer to [./examples/InfiniteYou/](./examples/InfiniteYou/) for more details. - - **March 25, 2025** Our new open-source project, [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine), is now open-sourced! Focused on stable model deployment. Geared towards industry. Offers better engineering support, higher computational performance, and more stable functionality. +
+More + +- **March 31, 2025** We support InfiniteYou, an identity preserving method for FLUX. Please refer to [./examples/InfiniteYou/](./examples/InfiniteYou/) for more details. + - **March 13, 2025** We support HunyuanVideo-I2V, the image-to-video generation version of HunyuanVideo open-sourced by Tencent. Please refer to [./examples/HunyuanVideo/](./examples/HunyuanVideo/) for more details. - **February 25, 2025** We support Wan-Video, a collection of SOTA video synthesis models open-sourced by Alibaba. See [./examples/wanvideo/](./examples/wanvideo/). @@ -401,3 +406,5 @@ https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-44 - [Project Page](https://ecnu-cilab.github.io/DiffSynth.github.io/). - The source codes are released in [EasyNLP](https://github.com/alibaba/EasyNLP/tree/master/diffusion/DiffSynth). - The technical report (ECML PKDD 2024) is released on [arXiv](https://arxiv.org/abs/2308.03463). + +
\ No newline at end of file diff --git a/README_zh.md b/README_zh.md index beb8bc0..7c0b569 100644 --- a/README_zh.md +++ b/README_zh.md @@ -18,6 +18,8 @@ DiffSynth 目前包括两个开源项目: * [DiffSynth-Studio](https://github.com/modelscope/DiffSynth-Studio): 聚焦于激进的技术探索,面向学术界,提供更前沿的模型能力支持。 * [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine): 聚焦于稳定的模型部署,面向工业界,提供更高的计算性能与更稳定的功能。 +[DiffSynth-Studio](https://github.com/modelscope/DiffSynth-Studio) 与 [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine) 作为魔搭社区 [AIGC 专区](https://modelscope.cn/aigc/home) 的核心技术支撑,提供了强大的AI生成内容能力。欢迎体验我们精心打造的产品化功能,开启您的AI创作之旅! + ## 安装 从源码安装(推荐): @@ -339,10 +341,13 @@ https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-44 - **2025年6月15日** ModelScope 官方评测框架 [EvalScope](https://github.com/modelscope/evalscope) 现已支持文生图生成评测。请参考[最佳实践](https://evalscope.readthedocs.io/zh-cn/latest/best_practice/t2i_eval.html)指南进行尝试。 -- **2025年3月31日** 我们支持 InfiniteYou,一种用于 FLUX 的人脸特征保留方法。更多细节请参考 [./examples/InfiniteYou/](./examples/InfiniteYou/)。 - - **2025年3月25日** 我们的新开源项目 [DiffSynth-Engine](https://github.com/modelscope/DiffSynth-Engine) 现已开源!专注于稳定的模型部署,面向工业界,提供更好的工程支持、更高的计算性能和更稳定的功能。 +
+更多 + +- **2025年3月31日** 我们支持 InfiniteYou,一种用于 FLUX 的人脸特征保留方法。更多细节请参考 [./examples/InfiniteYou/](./examples/InfiniteYou/)。 + - **2025年3月13日** 我们支持 HunyuanVideo-I2V,即腾讯开源的 HunyuanVideo 的图像到视频生成版本。更多细节请参考 [./examples/HunyuanVideo/](./examples/HunyuanVideo/)。 - **2025年2月25日** 我们支持 Wan-Video,这是阿里巴巴开源的一系列最先进的视频合成模型。详见 [./examples/wanvideo/](./examples/wanvideo/)。 @@ -417,3 +422,5 @@ https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-44 - [项目页面](https://ecnu-cilab.github.io/DiffSynth.github.io/)。 - 源代码已发布在 [EasyNLP](https://github.com/alibaba/EasyNLP/tree/master/diffusion/DiffSynth)。 - 技术报告(ECML PKDD 2024)已发布于 [arXiv](https://arxiv.org/abs/2308.03463)。 + +
From 3aed244c6f119c997679e305c8f02c606ae9460b Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Wed, 23 Jul 2025 11:20:06 +0800 Subject: [PATCH 28/51] update variable --- diffsynth/models/wan_video_dit.py | 6 +++--- diffsynth/pipelines/wan_video_new.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/diffsynth/models/wan_video_dit.py b/diffsynth/models/wan_video_dit.py index b7df3ff..1106669 100644 --- a/diffsynth/models/wan_video_dit.py +++ b/diffsynth/models/wan_video_dit.py @@ -287,14 +287,14 @@ class WanModel(torch.nn.Module): has_ref_conv: bool = False, add_control_adapter: bool = False, in_dim_control_adapter: int = 24, - is_5b: bool = False, + seperated_timestep: bool = False, ): super().__init__() self.dim = dim self.freq_dim = freq_dim self.has_image_input = has_image_input self.patch_size = patch_size - self.is_5b = is_5b + self.seperated_timestep = seperated_timestep self.patch_embedding = nn.Conv3d( in_dim, dim, kernel_size=patch_size, stride=patch_size) @@ -685,7 +685,7 @@ class WanModelStateDictConverter: "num_heads": 24, "num_layers": 30, "eps": 1e-6, - "is_5b": True, + "seperated_timestep": True, } else: config = {} diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 1136403..7ea0e3c 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -237,7 +237,7 @@ class WanVideoPipeline(BasePipeline): WanVideoUnit_InputVideoEmbedder(), WanVideoUnit_PromptEmbedder(), WanVideoUnit_ImageEmbedder(), - WanVideoUnit_ImageEmbedder5B(), + WanVideoUnit_ImageVaeEmbedder(), WanVideoUnit_FunControl(), WanVideoUnit_FunReference(), WanVideoUnit_FunCameraControl(), @@ -737,7 +737,7 @@ class WanVideoUnit_ImageEmbedder(PipelineUnit): ) def process(self, pipe: WanVideoPipeline, input_image, end_image, num_frames, height, width, tiled, tile_size, tile_stride): - if input_image is None or pipe.dit.is_5b: + if input_image is None or pipe.dit.seperated_timestep: return {} pipe.load_models_to_device(self.onload_model_names) image = pipe.preprocess_image(input_image.resize((width, height))).to(pipe.device) @@ -766,7 +766,7 @@ class WanVideoUnit_ImageEmbedder(PipelineUnit): return {"clip_feature": clip_context, "y": y} -class WanVideoUnit_ImageEmbedder5B(PipelineUnit): +class WanVideoUnit_ImageVaeEmbedder(PipelineUnit): def __init__(self): super().__init__( input_params=("input_image", "noise", "num_frames", "height", "width", "tiled", "tile_size", "tile_stride"), @@ -774,7 +774,7 @@ class WanVideoUnit_ImageEmbedder5B(PipelineUnit): ) def process(self, pipe: WanVideoPipeline, input_image, noise, num_frames, height, width, tiled, tile_size, tile_stride): - if input_image is None or not pipe.dit.is_5b: + if input_image is None or not pipe.dit.seperated_timestep: return {} pipe.load_models_to_device(self.onload_model_names) image = pipe.preprocess_image(input_image.resize((width, height))).transpose(0, 1).to(pipe.device) @@ -789,7 +789,7 @@ class WanVideoUnit_ImageEmbedder5B(PipelineUnit): import math seq_len = int(math.ceil(seq_len / pipe.sp_size)) * pipe.sp_size - return {"latents": latents, "mask_5b": mask2[0].unsqueeze(0), "seq_len": seq_len} + return {"latents": latents, "latent_mask_for_timestep": mask2[0].unsqueeze(0), "seq_len": seq_len} @staticmethod def masks_like(tensor, zero=False, generator=None, p=0.2): @@ -1162,8 +1162,8 @@ def model_fn_wan_video( get_sequence_parallel_world_size, get_sp_group) - if dit.is_5b and "mask_5b" in kwargs: - temp_ts = (kwargs["mask_5b"][0][0][:, ::2, ::2] * timestep).flatten() + if dit.seperated_timestep and "latent_mask_for_timestep" in kwargs: + temp_ts = (kwargs["latent_mask_for_timestep"][0][0][:, ::2, ::2] * timestep).flatten() temp_ts= torch.cat([temp_ts, temp_ts.new_ones(kwargs["seq_len"] - temp_ts.size(0)) * timestep]) timestep = temp_ts.unsqueeze(0).flatten() t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep).unflatten(0, (latents.size(0), kwargs["seq_len"]))) From 4ad6bd4e2319f400b6b2d57c6755327515b989fd Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Thu, 24 Jul 2025 18:56:25 +0800 Subject: [PATCH 29/51] rearrange lora loading modules --- diffsynth/lora/flux_lora.py | 50 +++- diffsynth/pipelines/flux_image_new.py | 55 ++-- diffsynth/pipelines/wan_video_new.py | 254 +---------------- diffsynth/utils/__init__.py | 261 ++++++++++++++++++ .../model_inference/FLUX.1-dev-LoRAFusion.py | 36 +-- 5 files changed, 354 insertions(+), 302 deletions(-) create mode 100644 diffsynth/utils/__init__.py diff --git a/diffsynth/lora/flux_lora.py b/diffsynth/lora/flux_lora.py index 45baeaa..cb53b73 100644 --- a/diffsynth/lora/flux_lora.py +++ b/diffsynth/lora/flux_lora.py @@ -1,6 +1,8 @@ import torch, math -from diffsynth.lora import GeneralLoRALoader -from diffsynth.models.lora import FluxLoRAFromCivitai +from . import GeneralLoRALoader +from ..utils import ModelConfig +from ..models.utils import load_state_dict +from typing import Union class FluxLoRALoader(GeneralLoRALoader): @@ -276,3 +278,47 @@ class FluxLoraPatcherStateDictConverter: def from_civitai(self, state_dict): return state_dict + + +class FluxLoRAFuser: + def __init__(self, device="cuda", torch_dtype=torch.bfloat16): + self.device = device + self.torch_dtype = torch_dtype + + def Matrix_Decomposition_lowrank(self, A, k): + U, S, V = torch.svd_lowrank(A.float(), q=k) + S_k = torch.diag(S[:k]) + U_hat = U @ S_k + return U_hat, V.t() + + def LoRA_State_Dicts_Decomposition(self, lora_state_dicts=[], q=4): + lora_1 = lora_state_dicts[0] + state_dict_ = {} + for k,v in lora_1.items(): + if 'lora_A.' in k: + lora_B_name = k.replace('lora_A.', 'lora_B.') + lora_B = lora_1[lora_B_name] + weight = torch.mm(lora_B, v) + for lora_dict in lora_state_dicts[1:]: + lora_A_ = lora_dict[k] + lora_B_ = lora_dict[lora_B_name] + weight_ = torch.mm(lora_B_, lora_A_) + weight += weight_ + new_B, new_A = self.Matrix_Decomposition_lowrank(weight, q) + state_dict_[lora_B_name] = new_B.to(dtype=torch.bfloat16) + state_dict_[k] = new_A.to(dtype=torch.bfloat16) + return state_dict_ + + def __call__(self, lora_configs: list[Union[ModelConfig, str]]): + loras = [] + loader = FluxLoRALoader(torch_dtype=self.torch_dtype, device=self.device) + for lora_config in lora_configs: + if isinstance(lora_config, str): + lora = load_state_dict(lora_config, torch_dtype=self.torch_dtype, device=self.device) + else: + lora_config.download_if_necessary() + lora = load_state_dict(lora_config.path, torch_dtype=self.torch_dtype, device=self.device) + lora = loader.convert_state_dict(lora) + loras.append(lora) + lora = self.LoRA_State_Dicts_Decomposition(loras) + return lora diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index 6525dd4..2abd16c 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -22,8 +22,8 @@ from ..models.flux_value_control import MultiValueEncoder from ..models.flux_infiniteyou import InfiniteYouImageProjector from ..models.flux_lora_encoder import FluxLoRAEncoder, LoRALayerBlock from ..models.tiler import FastTileWorker -from .wan_video_new import BasePipeline, ModelConfig, PipelineUnitRunner, PipelineUnit -from ..lora.flux_lora import FluxLoRALoader, FluxLoraPatcher +from ..utils import BasePipeline, ModelConfig, PipelineUnitRunner, PipelineUnit +from ..lora.flux_lora import FluxLoRALoader, FluxLoraPatcher, FluxLoRAFuser from ..models.flux_dit import RMSNorm from ..vram_management import gradient_checkpoint_forward, enable_vram_management, AutoWrappedModule, AutoWrappedLinear @@ -125,18 +125,20 @@ class FluxImagePipeline(BasePipeline): def load_lora( self, module: torch.nn.Module, - lora_config: Union[ModelConfig, str], + lora_config: Union[ModelConfig, str] = None, alpha=1, hotload=False, - local_model_path="./models", - skip_download=False + state_dict=None, ): - if isinstance(lora_config, str): - lora_config = ModelConfig(path=lora_config) + if state_dict is None: + if isinstance(lora_config, str): + lora = load_state_dict(lora_config, torch_dtype=self.torch_dtype, device=self.device) + else: + lora_config.download_if_necessary() + lora = load_state_dict(lora_config.path, torch_dtype=self.torch_dtype, device=self.device) else: - lora_config.download_if_necessary(local_model_path, skip_download=skip_download) + lora = state_dict loader = FluxLoRALoader(torch_dtype=self.torch_dtype, device=self.device) - lora = load_state_dict(lora_config.path, torch_dtype=self.torch_dtype, device=self.device) lora = loader.convert_state_dict(lora) if hotload: for name, module in module.named_modules(): @@ -150,19 +152,21 @@ class FluxImagePipeline(BasePipeline): loader.load(module, lora, alpha=alpha) - def enable_lora_patcher(self): - if not (hasattr(self, "vram_management_enabled") and self.vram_management_enabled): - print("Please enable VRAM management using `enable_vram_management()` before `enable_lora_patcher()`.") - return - if self.lora_patcher is None: - print("Please load lora patcher models before `enable_lora_patcher()`.") - return - for name, module in self.dit.named_modules(): - if isinstance(module, AutoWrappedLinear): - merger_name = name.replace(".", "___") - if merger_name in self.lora_patcher.model_dict: - module.lora_merger = self.lora_patcher.model_dict[merger_name] - + def load_loras( + self, + module: torch.nn.Module, + lora_configs: list[Union[ModelConfig, str]], + alpha=1, + hotload=False, + extra_fused_lora=False, + ): + for lora_config in lora_configs: + self.load_lora(module, lora_config, hotload=hotload, alpha=alpha) + if extra_fused_lora: + lora_fuser = FluxLoRAFuser(device="cuda", torch_dtype=torch.bfloat16) + fused_lora = lora_fuser(lora_configs) + self.load_lora(module, state_dict=fused_lora, hotload=hotload, alpha=alpha) + def clear_lora(self): for name, module in self.named_modules(): @@ -365,16 +369,11 @@ class FluxImagePipeline(BasePipeline): torch_dtype: torch.dtype = torch.bfloat16, device: Union[str, torch.device] = "cuda", model_configs: list[ModelConfig] = [], - tokenizer_config: ModelConfig = ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="google/*"), - local_model_path: str = "./models", - skip_download: bool = False, - redirect_common_files: bool = True, - use_usp=False, ): # Download and load models model_manager = ModelManager() for model_config in model_configs: - model_config.download_if_necessary(local_model_path, skip_download=skip_download) + model_config.download_if_necessary() model_manager.load_model( model_config.path, device=model_config.offload_device or device, diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 6902011..21b8ba0 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -12,6 +12,7 @@ from tqdm import tqdm from typing import Optional from typing_extensions import Literal +from ..utils import BasePipeline, ModelConfig, PipelineUnit, PipelineUnitRunner from ..models import ModelManager, load_state_dict from ..models.wan_video_dit import WanModel, RMSNorm, sinusoidal_embedding_1d from ..models.wan_video_text_encoder import WanTextEncoder, T5RelativeEmbedding, T5LayerNorm @@ -26,196 +27,6 @@ from ..lora import GeneralLoRALoader -class BasePipeline(torch.nn.Module): - - def __init__( - self, - device="cuda", torch_dtype=torch.float16, - height_division_factor=64, width_division_factor=64, - time_division_factor=None, time_division_remainder=None, - ): - super().__init__() - # The device and torch_dtype is used for the storage of intermediate variables, not models. - self.device = device - self.torch_dtype = torch_dtype - # The following parameters are used for shape check. - self.height_division_factor = height_division_factor - self.width_division_factor = width_division_factor - self.time_division_factor = time_division_factor - self.time_division_remainder = time_division_remainder - self.vram_management_enabled = False - - - def to(self, *args, **kwargs): - device, dtype, non_blocking, convert_to_format = torch._C._nn._parse_to(*args, **kwargs) - if device is not None: - self.device = device - if dtype is not None: - self.torch_dtype = dtype - super().to(*args, **kwargs) - return self - - - def check_resize_height_width(self, height, width, num_frames=None): - # Shape check - if height % self.height_division_factor != 0: - height = (height + self.height_division_factor - 1) // self.height_division_factor * self.height_division_factor - print(f"height % {self.height_division_factor} != 0. We round it up to {height}.") - if width % self.width_division_factor != 0: - width = (width + self.width_division_factor - 1) // self.width_division_factor * self.width_division_factor - print(f"width % {self.width_division_factor} != 0. We round it up to {width}.") - if num_frames is None: - return height, width - else: - if num_frames % self.time_division_factor != self.time_division_remainder: - num_frames = (num_frames + self.time_division_factor - 1) // self.time_division_factor * self.time_division_factor + self.time_division_remainder - print(f"num_frames % {self.time_division_factor} != {self.time_division_remainder}. We round it up to {num_frames}.") - return height, width, num_frames - - - def preprocess_image(self, image, torch_dtype=None, device=None, pattern="B C H W", min_value=-1, max_value=1): - # Transform a PIL.Image to torch.Tensor - image = torch.Tensor(np.array(image, dtype=np.float32)) - image = image.to(dtype=torch_dtype or self.torch_dtype, device=device or self.device) - image = image * ((max_value - min_value) / 255) + min_value - image = repeat(image, f"H W C -> {pattern}", **({"B": 1} if "B" in pattern else {})) - return image - - - def preprocess_video(self, video, torch_dtype=None, device=None, pattern="B C T H W", min_value=-1, max_value=1): - # Transform a list of PIL.Image to torch.Tensor - video = [self.preprocess_image(image, torch_dtype=torch_dtype, device=device, min_value=min_value, max_value=max_value) for image in video] - video = torch.stack(video, dim=pattern.index("T") // 2) - return video - - - def vae_output_to_image(self, vae_output, pattern="B C H W", min_value=-1, max_value=1): - # Transform a torch.Tensor to PIL.Image - if pattern != "H W C": - vae_output = reduce(vae_output, f"{pattern} -> H W C", reduction="mean") - image = ((vae_output - min_value) * (255 / (max_value - min_value))).clip(0, 255) - image = image.to(device="cpu", dtype=torch.uint8) - image = Image.fromarray(image.numpy()) - return image - - - def vae_output_to_video(self, vae_output, pattern="B C T H W", min_value=-1, max_value=1): - # Transform a torch.Tensor to list of PIL.Image - if pattern != "T H W C": - vae_output = reduce(vae_output, f"{pattern} -> T H W C", reduction="mean") - video = [self.vae_output_to_image(image, pattern="H W C", min_value=min_value, max_value=max_value) for image in vae_output] - return video - - - def load_models_to_device(self, model_names=[]): - if self.vram_management_enabled: - # offload models - for name, model in self.named_children(): - if name not in model_names: - if hasattr(model, "vram_management_enabled") and model.vram_management_enabled: - for module in model.modules(): - if hasattr(module, "offload"): - module.offload() - else: - model.cpu() - torch.cuda.empty_cache() - # onload models - for name, model in self.named_children(): - if name in model_names: - if hasattr(model, "vram_management_enabled") and model.vram_management_enabled: - for module in model.modules(): - if hasattr(module, "onload"): - module.onload() - else: - model.to(self.device) - - - def generate_noise(self, shape, seed=None, rand_device="cpu", rand_torch_dtype=torch.float32, device=None, torch_dtype=None): - # Initialize Gaussian noise - generator = None if seed is None else torch.Generator(rand_device).manual_seed(seed) - noise = torch.randn(shape, generator=generator, device=rand_device, dtype=rand_torch_dtype) - noise = noise.to(dtype=torch_dtype or self.torch_dtype, device=device or self.device) - return noise - - - def enable_cpu_offload(self): - warnings.warn("`enable_cpu_offload` will be deprecated. Please use `enable_vram_management`.") - self.vram_management_enabled = True - - - def get_vram(self): - return torch.cuda.mem_get_info(self.device)[1] / (1024 ** 3) - - - def freeze_except(self, model_names): - for name, model in self.named_children(): - if name in model_names: - model.train() - model.requires_grad_(True) - else: - model.eval() - model.requires_grad_(False) - - -@dataclass -class ModelConfig: - path: Union[str, list[str]] = None - model_id: str = None - origin_file_pattern: Union[str, list[str]] = None - download_resource: str = "ModelScope" - offload_device: Optional[Union[str, torch.device]] = None - offload_dtype: Optional[torch.dtype] = None - skip_download: bool = False - - def download_if_necessary(self, local_model_path="./models", skip_download=False, use_usp=False): - if self.path is None: - # Check model_id and origin_file_pattern - if self.model_id is None: - raise ValueError(f"""No valid model files. Please use `ModelConfig(path="xxx")` or `ModelConfig(model_id="xxx/yyy", origin_file_pattern="zzz")`.""") - - # Skip if not in rank 0 - if use_usp: - import torch.distributed as dist - skip_download = dist.get_rank() != 0 - - # Check whether the origin path is a folder - if self.origin_file_pattern is None or self.origin_file_pattern == "": - self.origin_file_pattern = "" - allow_file_pattern = None - is_folder = True - elif isinstance(self.origin_file_pattern, str) and self.origin_file_pattern.endswith("/"): - allow_file_pattern = self.origin_file_pattern + "*" - is_folder = True - else: - allow_file_pattern = self.origin_file_pattern - is_folder = False - - # Download - skip_download = skip_download or self.skip_download - if not skip_download: - downloaded_files = glob.glob(self.origin_file_pattern, root_dir=os.path.join(local_model_path, self.model_id)) - snapshot_download( - self.model_id, - local_dir=os.path.join(local_model_path, self.model_id), - allow_file_pattern=allow_file_pattern, - ignore_file_pattern=downloaded_files, - local_files_only=False - ) - - # Let rank 1, 2, ... wait for rank 0 - if use_usp: - import torch.distributed as dist - dist.barrier(device_ids=[dist.get_rank()]) - - # Return downloaded files - if is_folder: - self.path = os.path.join(local_model_path, self.model_id, self.origin_file_pattern) - else: - self.path = glob.glob(os.path.join(local_model_path, self.model_id, self.origin_file_pattern)) - if isinstance(self.path, list) and len(self.path) == 1: - self.path = self.path[0] - - class WanVideoPipeline(BasePipeline): def __init__(self, device="cuda", torch_dtype=torch.bfloat16, tokenizer_path=None): @@ -438,8 +249,6 @@ class WanVideoPipeline(BasePipeline): device: Union[str, torch.device] = "cuda", model_configs: list[ModelConfig] = [], tokenizer_config: ModelConfig = ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="google/*"), - local_model_path: str = "./models", - skip_download: bool = False, redirect_common_files: bool = True, use_usp=False, ): @@ -464,7 +273,7 @@ class WanVideoPipeline(BasePipeline): # Download and load models model_manager = ModelManager() for model_config in model_configs: - model_config.download_if_necessary(local_model_path, skip_download=skip_download, use_usp=use_usp) + model_config.download_if_necessary(use_usp=use_usp) model_manager.load_model( model_config.path, device=model_config.offload_device or device, @@ -480,7 +289,7 @@ class WanVideoPipeline(BasePipeline): pipe.vace = model_manager.fetch_model("wan_video_vace") # Initialize tokenizer - tokenizer_config.download_if_necessary(local_model_path, skip_download=skip_download) + tokenizer_config.download_if_necessary(use_usp=use_usp) pipe.prompter.fetch_models(pipe.text_encoder) pipe.prompter.fetch_tokenizer(tokenizer_config.path) @@ -606,63 +415,6 @@ class WanVideoPipeline(BasePipeline): -class PipelineUnit: - def __init__( - self, - seperate_cfg: bool = False, - take_over: bool = False, - input_params: tuple[str] = None, - input_params_posi: dict[str, str] = None, - input_params_nega: dict[str, str] = None, - onload_model_names: tuple[str] = None - ): - self.seperate_cfg = seperate_cfg - self.take_over = take_over - self.input_params = input_params - self.input_params_posi = input_params_posi - self.input_params_nega = input_params_nega - self.onload_model_names = onload_model_names - - - def process(self, pipe: WanVideoPipeline, inputs: dict, positive=True, **kwargs) -> dict: - raise NotImplementedError("`process` is not implemented.") - - - -class PipelineUnitRunner: - def __init__(self): - pass - - def __call__(self, unit: PipelineUnit, pipe: WanVideoPipeline, inputs_shared: dict, inputs_posi: dict, inputs_nega: dict) -> tuple[dict, dict]: - if unit.take_over: - # Let the pipeline unit take over this function. - inputs_shared, inputs_posi, inputs_nega = unit.process(pipe, inputs_shared=inputs_shared, inputs_posi=inputs_posi, inputs_nega=inputs_nega) - elif unit.seperate_cfg: - # Positive side - processor_inputs = {name: inputs_posi.get(name_) for name, name_ in unit.input_params_posi.items()} - if unit.input_params is not None: - for name in unit.input_params: - processor_inputs[name] = inputs_shared.get(name) - processor_outputs = unit.process(pipe, **processor_inputs) - inputs_posi.update(processor_outputs) - # Negative side - if inputs_shared["cfg_scale"] != 1: - processor_inputs = {name: inputs_nega.get(name_) for name, name_ in unit.input_params_nega.items()} - if unit.input_params is not None: - for name in unit.input_params: - processor_inputs[name] = inputs_shared.get(name) - processor_outputs = unit.process(pipe, **processor_inputs) - inputs_nega.update(processor_outputs) - else: - inputs_nega.update(processor_outputs) - else: - processor_inputs = {name: inputs_shared.get(name) for name in unit.input_params} - processor_outputs = unit.process(pipe, **processor_inputs) - inputs_shared.update(processor_outputs) - return inputs_shared, inputs_posi, inputs_nega - - - class WanVideoUnit_ShapeChecker(PipelineUnit): def __init__(self): super().__init__(input_params=("height", "width", "num_frames")) diff --git a/diffsynth/utils/__init__.py b/diffsynth/utils/__init__.py new file mode 100644 index 0000000..58733c1 --- /dev/null +++ b/diffsynth/utils/__init__.py @@ -0,0 +1,261 @@ +import torch, warnings, glob, os +import numpy as np +from PIL import Image +from einops import repeat, reduce +from typing import Optional, Union +from dataclasses import dataclass +from modelscope import snapshot_download +import numpy as np +from PIL import Image +from typing import Optional + + +class BasePipeline(torch.nn.Module): + + def __init__( + self, + device="cuda", torch_dtype=torch.float16, + height_division_factor=64, width_division_factor=64, + time_division_factor=None, time_division_remainder=None, + ): + super().__init__() + # The device and torch_dtype is used for the storage of intermediate variables, not models. + self.device = device + self.torch_dtype = torch_dtype + # The following parameters are used for shape check. + self.height_division_factor = height_division_factor + self.width_division_factor = width_division_factor + self.time_division_factor = time_division_factor + self.time_division_remainder = time_division_remainder + self.vram_management_enabled = False + + + def to(self, *args, **kwargs): + device, dtype, non_blocking, convert_to_format = torch._C._nn._parse_to(*args, **kwargs) + if device is not None: + self.device = device + if dtype is not None: + self.torch_dtype = dtype + super().to(*args, **kwargs) + return self + + + def check_resize_height_width(self, height, width, num_frames=None): + # Shape check + if height % self.height_division_factor != 0: + height = (height + self.height_division_factor - 1) // self.height_division_factor * self.height_division_factor + print(f"height % {self.height_division_factor} != 0. We round it up to {height}.") + if width % self.width_division_factor != 0: + width = (width + self.width_division_factor - 1) // self.width_division_factor * self.width_division_factor + print(f"width % {self.width_division_factor} != 0. We round it up to {width}.") + if num_frames is None: + return height, width + else: + if num_frames % self.time_division_factor != self.time_division_remainder: + num_frames = (num_frames + self.time_division_factor - 1) // self.time_division_factor * self.time_division_factor + self.time_division_remainder + print(f"num_frames % {self.time_division_factor} != {self.time_division_remainder}. We round it up to {num_frames}.") + return height, width, num_frames + + + def preprocess_image(self, image, torch_dtype=None, device=None, pattern="B C H W", min_value=-1, max_value=1): + # Transform a PIL.Image to torch.Tensor + image = torch.Tensor(np.array(image, dtype=np.float32)) + image = image.to(dtype=torch_dtype or self.torch_dtype, device=device or self.device) + image = image * ((max_value - min_value) / 255) + min_value + image = repeat(image, f"H W C -> {pattern}", **({"B": 1} if "B" in pattern else {})) + return image + + + def preprocess_video(self, video, torch_dtype=None, device=None, pattern="B C T H W", min_value=-1, max_value=1): + # Transform a list of PIL.Image to torch.Tensor + video = [self.preprocess_image(image, torch_dtype=torch_dtype, device=device, min_value=min_value, max_value=max_value) for image in video] + video = torch.stack(video, dim=pattern.index("T") // 2) + return video + + + def vae_output_to_image(self, vae_output, pattern="B C H W", min_value=-1, max_value=1): + # Transform a torch.Tensor to PIL.Image + if pattern != "H W C": + vae_output = reduce(vae_output, f"{pattern} -> H W C", reduction="mean") + image = ((vae_output - min_value) * (255 / (max_value - min_value))).clip(0, 255) + image = image.to(device="cpu", dtype=torch.uint8) + image = Image.fromarray(image.numpy()) + return image + + + def vae_output_to_video(self, vae_output, pattern="B C T H W", min_value=-1, max_value=1): + # Transform a torch.Tensor to list of PIL.Image + if pattern != "T H W C": + vae_output = reduce(vae_output, f"{pattern} -> T H W C", reduction="mean") + video = [self.vae_output_to_image(image, pattern="H W C", min_value=min_value, max_value=max_value) for image in vae_output] + return video + + + def load_models_to_device(self, model_names=[]): + if self.vram_management_enabled: + # offload models + for name, model in self.named_children(): + if name not in model_names: + if hasattr(model, "vram_management_enabled") and model.vram_management_enabled: + for module in model.modules(): + if hasattr(module, "offload"): + module.offload() + else: + model.cpu() + torch.cuda.empty_cache() + # onload models + for name, model in self.named_children(): + if name in model_names: + if hasattr(model, "vram_management_enabled") and model.vram_management_enabled: + for module in model.modules(): + if hasattr(module, "onload"): + module.onload() + else: + model.to(self.device) + + + def generate_noise(self, shape, seed=None, rand_device="cpu", rand_torch_dtype=torch.float32, device=None, torch_dtype=None): + # Initialize Gaussian noise + generator = None if seed is None else torch.Generator(rand_device).manual_seed(seed) + noise = torch.randn(shape, generator=generator, device=rand_device, dtype=rand_torch_dtype) + noise = noise.to(dtype=torch_dtype or self.torch_dtype, device=device or self.device) + return noise + + + def enable_cpu_offload(self): + warnings.warn("`enable_cpu_offload` will be deprecated. Please use `enable_vram_management`.") + self.vram_management_enabled = True + + + def get_vram(self): + return torch.cuda.mem_get_info(self.device)[1] / (1024 ** 3) + + + def freeze_except(self, model_names): + for name, model in self.named_children(): + if name in model_names: + model.train() + model.requires_grad_(True) + else: + model.eval() + model.requires_grad_(False) + + +@dataclass +class ModelConfig: + path: Union[str, list[str]] = None + model_id: str = None + origin_file_pattern: Union[str, list[str]] = None + download_resource: str = "ModelScope" + offload_device: Optional[Union[str, torch.device]] = None + offload_dtype: Optional[torch.dtype] = None + local_model_path: str = None + skip_download: bool = False + + def download_if_necessary(self, use_usp=False): + if self.path is None: + # Check model_id and origin_file_pattern + if self.model_id is None: + raise ValueError(f"""No valid model files. Please use `ModelConfig(path="xxx")` or `ModelConfig(model_id="xxx/yyy", origin_file_pattern="zzz")`.""") + + # Skip if not in rank 0 + if use_usp: + import torch.distributed as dist + skip_download = self.skip_download or dist.get_rank() != 0 + else: + skip_download = self.skip_download + + # Check whether the origin path is a folder + if self.origin_file_pattern is None or self.origin_file_pattern == "": + self.origin_file_pattern = "" + allow_file_pattern = None + is_folder = True + elif isinstance(self.origin_file_pattern, str) and self.origin_file_pattern.endswith("/"): + allow_file_pattern = self.origin_file_pattern + "*" + is_folder = True + else: + allow_file_pattern = self.origin_file_pattern + is_folder = False + + # Download + if not skip_download: + if self.local_model_path is None: + self.local_model_path = "./models" + downloaded_files = glob.glob(self.origin_file_pattern, root_dir=os.path.join(self.local_model_path, self.model_id)) + snapshot_download( + self.model_id, + local_dir=os.path.join(self.local_model_path, self.model_id), + allow_file_pattern=allow_file_pattern, + ignore_file_pattern=downloaded_files, + local_files_only=False + ) + + # Let rank 1, 2, ... wait for rank 0 + if use_usp: + import torch.distributed as dist + dist.barrier(device_ids=[dist.get_rank()]) + + # Return downloaded files + if is_folder: + self.path = os.path.join(self.local_model_path, self.model_id, self.origin_file_pattern) + else: + self.path = glob.glob(os.path.join(self.local_model_path, self.model_id, self.origin_file_pattern)) + if isinstance(self.path, list) and len(self.path) == 1: + self.path = self.path[0] + + + +class PipelineUnit: + def __init__( + self, + seperate_cfg: bool = False, + take_over: bool = False, + input_params: tuple[str] = None, + input_params_posi: dict[str, str] = None, + input_params_nega: dict[str, str] = None, + onload_model_names: tuple[str] = None + ): + self.seperate_cfg = seperate_cfg + self.take_over = take_over + self.input_params = input_params + self.input_params_posi = input_params_posi + self.input_params_nega = input_params_nega + self.onload_model_names = onload_model_names + + + def process(self, pipe: BasePipeline, inputs: dict, positive=True, **kwargs) -> dict: + raise NotImplementedError("`process` is not implemented.") + + + +class PipelineUnitRunner: + def __init__(self): + pass + + def __call__(self, unit: PipelineUnit, pipe: BasePipeline, inputs_shared: dict, inputs_posi: dict, inputs_nega: dict) -> tuple[dict, dict]: + if unit.take_over: + # Let the pipeline unit take over this function. + inputs_shared, inputs_posi, inputs_nega = unit.process(pipe, inputs_shared=inputs_shared, inputs_posi=inputs_posi, inputs_nega=inputs_nega) + elif unit.seperate_cfg: + # Positive side + processor_inputs = {name: inputs_posi.get(name_) for name, name_ in unit.input_params_posi.items()} + if unit.input_params is not None: + for name in unit.input_params: + processor_inputs[name] = inputs_shared.get(name) + processor_outputs = unit.process(pipe, **processor_inputs) + inputs_posi.update(processor_outputs) + # Negative side + if inputs_shared["cfg_scale"] != 1: + processor_inputs = {name: inputs_nega.get(name_) for name, name_ in unit.input_params_nega.items()} + if unit.input_params is not None: + for name in unit.input_params: + processor_inputs[name] = inputs_shared.get(name) + processor_outputs = unit.process(pipe, **processor_inputs) + inputs_nega.update(processor_outputs) + else: + inputs_nega.update(processor_outputs) + else: + processor_inputs = {name: inputs_shared.get(name) for name in unit.input_params} + processor_outputs = unit.process(pipe, **processor_inputs) + inputs_shared.update(processor_outputs) + return inputs_shared, inputs_posi, inputs_nega diff --git a/examples/flux/model_inference/FLUX.1-dev-LoRAFusion.py b/examples/flux/model_inference/FLUX.1-dev-LoRAFusion.py index 68116d0..69b20db 100644 --- a/examples/flux/model_inference/FLUX.1-dev-LoRAFusion.py +++ b/examples/flux/model_inference/FLUX.1-dev-LoRAFusion.py @@ -1,7 +1,7 @@ import torch from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig - + pipe = FluxImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", @@ -10,26 +10,20 @@ pipe = FluxImagePipeline.from_pretrained( ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), - ModelConfig(model_id="DiffSynth-Studio/FLUX.1-dev-LoRAFusion", origin_file_pattern="model.safetensors") + ModelConfig(model_id="DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev", origin_file_pattern="model.safetensors"), ], ) -pipe.enable_vram_management() -pipe.enable_lora_patcher() -pipe.load_lora( - pipe.dit, - ModelConfig(model_id="yangyufeng/fgao", origin_file_pattern="30.safetensors"), - hotload=True -) -pipe.load_lora( - pipe.dit, - ModelConfig(model_id="bobooblue/LoRA-bling-mai", origin_file_pattern="10.safetensors"), - hotload=True -) -pipe.load_lora( - pipe.dit, - ModelConfig(model_id="JIETANGAB/E", origin_file_pattern="17.safetensors"), - hotload=True -) +pipe.enable_lora_magic() -image = pipe(prompt="This is a digital painting in a soft, ethereal style. a beautiful Asian girl Shine like a diamond. Everywhere is shining with bling bling luster.The background is a textured blue with visible brushstrokes, giving the image an impressionistic style reminiscent of Vincent van Gogh's work", seed=0) -image.save("flux.jpg") +pipe.load_lora( + pipe.dit, + ModelConfig(model_id="cancel13/cxsk", origin_file_pattern="30.safetensors"), + hotload=True, +) +pipe.load_lora( + pipe.dit, + ModelConfig(model_id="DiffSynth-Studio/ArtAug-lora-FLUX.1dev-v1", origin_file_pattern="merged_lora.safetensors"), + hotload=True, +) +image = pipe(prompt="a cat", seed=0) +image.save("image_fused.jpg") From f3d2470e84059e56dce24c5394d1e3788fb898a4 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Thu, 24 Jul 2025 19:02:08 +0800 Subject: [PATCH 30/51] update README --- examples/flux/README.md | 2 +- examples/flux/README_zh.md | 2 +- examples/wanvideo/README.md | 7 +++++-- examples/wanvideo/README_zh.md | 7 +++++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/flux/README.md b/examples/flux/README.md index b1451d1..32cce4a 100644 --- a/examples/flux/README.md +++ b/examples/flux/README.md @@ -105,7 +105,7 @@ ModelConfig(path=[ ]) ``` -The `from_pretrained` method also provides extra arguments to control model loading behavior: +The `ModelConfig` method also provides extra arguments to control model loading behavior: * `local_model_path`: Path to save downloaded models. Default is `"./models"`. * `skip_download`: Whether to skip downloading. Default is `False`. If your network cannot access [ModelScope](https://modelscope.cn/ ), download the required files manually and set this to `True`. diff --git a/examples/flux/README_zh.md b/examples/flux/README_zh.md index aeade8f..28f1e10 100644 --- a/examples/flux/README_zh.md +++ b/examples/flux/README_zh.md @@ -105,7 +105,7 @@ ModelConfig(path=[ ]) ``` -`from_pretrained` 还提供了额外的参数用于控制模型加载时的行为: +`ModelConfig` 还提供了额外的参数用于控制模型加载时的行为: * `local_model_path`: 用于保存下载模型的路径,默认值为 `"./models"`。 * `skip_download`: 是否跳过下载,默认值为 `False`。当您的网络无法访问[魔搭社区](https://modelscope.cn/)时,请手动下载必要的文件,并将其设置为 `True`。 diff --git a/examples/wanvideo/README.md b/examples/wanvideo/README.md index 1b78e78..2c811f5 100644 --- a/examples/wanvideo/README.md +++ b/examples/wanvideo/README.md @@ -121,11 +121,14 @@ ModelConfig(path=[ ]) ``` -The `from_pretrained` function also provides additional parameters to control the behavior during model loading: +The `ModelConfig` function provides additional parameters to control the behavior during model loading: -* `tokenizer_config`: Path to the tokenizer of the Wan model. Default value is `ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="google/*")`. * `local_model_path`: Path where downloaded models are saved. Default value is `"./models"`. * `skip_download`: Whether to skip downloading models. Default value is `False`. When your network cannot access [ModelScope](https://modelscope.cn/), manually download the necessary files and set this to `True`. + +The `from_pretrained` function provides additional parameters to control the behavior during model loading: + +* `tokenizer_config`: Path to the tokenizer of the Wan model. Default value is `ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="google/*")`. * `redirect_common_files`: Whether to redirect duplicate model files. Default value is `True`. Since the Wan series models include multiple base models, some modules like text encoder are shared across these models. To avoid redundant downloads, we redirect the model paths. * `use_usp`: Whether to enable Unified Sequence Parallel. Default value is `False`. Used for multi-GPU parallel inference. diff --git a/examples/wanvideo/README_zh.md b/examples/wanvideo/README_zh.md index d9cd43b..517cd9e 100644 --- a/examples/wanvideo/README_zh.md +++ b/examples/wanvideo/README_zh.md @@ -120,11 +120,14 @@ ModelConfig(path=[ ]) ``` -`from_pretrained` 还提供了额外的参数用于控制模型加载时的行为: +`ModelConfig` 提供了额外的参数用于控制模型加载时的行为: -* `tokenizer_config`: Wan 模型的 tokenizer 路径,默认值为 `ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="google/*")`。 * `local_model_path`: 用于保存下载模型的路径,默认值为 `"./models"`。 * `skip_download`: 是否跳过下载,默认值为 `False`。当您的网络无法访问[魔搭社区](https://modelscope.cn/)时,请手动下载必要的文件,并将其设置为 `True`。 + +`from_pretrained` 提供了额外的参数用于控制模型加载时的行为: + +* `tokenizer_config`: Wan 模型的 tokenizer 路径,默认值为 `ModelConfig(model_id="Wan-AI/Wan2.1-T2V-1.3B", origin_file_pattern="google/*")`。 * `redirect_common_files`: 是否重定向重复模型文件,默认值为 `True`。由于 Wan 系列模型包括多个基础模型,每个基础模型的 text encoder 等模块都是相同的,为避免重复下载,我们会对模型路径进行重定向。 * `use_usp`: 是否启用 Unified Sequence Parallel,默认值为 `False`。用于多 GPU 并行推理。 From c98e31fee3df9e5790d59186aa6f9242c8eb48db Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Thu, 24 Jul 2025 19:10:06 +0800 Subject: [PATCH 31/51] update README --- README.md | 1 + README_zh.md | 1 + examples/flux/README.md | 1 + examples/flux/README_zh.md | 1 + .../{FLUX.1-dev-LoRAFusion.py => FLUX.1-dev-LoRA-Fusion.py} | 0 5 files changed, 4 insertions(+) rename examples/flux/model_inference/{FLUX.1-dev-LoRAFusion.py => FLUX.1-dev-LoRA-Fusion.py} (100%) diff --git a/README.md b/README.md index aa116b7..11403a5 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ image.save("image.jpg") |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| |[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./examples/flux/model_inference/FLUX.1-dev-EliGen.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| +|[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| diff --git a/README_zh.md b/README_zh.md index 7c0b569..650d2ec 100644 --- a/README_zh.md +++ b/README_zh.md @@ -100,6 +100,7 @@ image.save("image.jpg") |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| |[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./examples/flux/model_inference/FLUX.1-dev-EliGen.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| +|[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| diff --git a/examples/flux/README.md b/examples/flux/README.md index 32cce4a..a66e2bc 100644 --- a/examples/flux/README.md +++ b/examples/flux/README.md @@ -52,6 +52,7 @@ image.save("image.jpg") |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| |[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| +|[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./model_inference/Step1X-Edit.py)|[code](./model_inference_low_vram/Step1X-Edit.py)|[code](./model_training/full/Step1X-Edit.sh)|[code](./model_training/validate_full/Step1X-Edit.py)|[code](./model_training/lora/Step1X-Edit.sh)|[code](./model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./model_inference/FLEX.2-preview.py)|[code](./model_inference_low_vram/FLEX.2-preview.py)|[code](./model_training/full/FLEX.2-preview.sh)|[code](./model_training/validate_full/FLEX.2-preview.py)|[code](./model_training/lora/FLEX.2-preview.sh)|[code](./model_training/validate_lora/FLEX.2-preview.py)| diff --git a/examples/flux/README_zh.md b/examples/flux/README_zh.md index 28f1e10..3d3dc35 100644 --- a/examples/flux/README_zh.md +++ b/examples/flux/README_zh.md @@ -52,6 +52,7 @@ image.save("image.jpg") |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| |[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| +|[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./model_inference/Step1X-Edit.py)|[code](./model_inference_low_vram/Step1X-Edit.py)|[code](./model_training/full/Step1X-Edit.sh)|[code](./model_training/validate_full/Step1X-Edit.py)|[code](./model_training/lora/Step1X-Edit.sh)|[code](./model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./model_inference/FLEX.2-preview.py)|[code](./model_inference_low_vram/FLEX.2-preview.py)|[code](./model_training/full/FLEX.2-preview.sh)|[code](./model_training/validate_full/FLEX.2-preview.py)|[code](./model_training/lora/FLEX.2-preview.sh)|[code](./model_training/validate_lora/FLEX.2-preview.py)| diff --git a/examples/flux/model_inference/FLUX.1-dev-LoRAFusion.py b/examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py similarity index 100% rename from examples/flux/model_inference/FLUX.1-dev-LoRAFusion.py rename to examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py From 9015d08927331a7b5b559ed17412558279690c33 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Fri, 25 Jul 2025 17:09:53 +0800 Subject: [PATCH 32/51] support wan2.2 A14B I2V&T2V --- diffsynth/configs/model_config.py | 2 + diffsynth/models/wan_video_dit.py | 19 ++++ diffsynth/pipelines/wan_video_new.py | 96 ++++++++++++++++++- .../model_inference/Wan2.2-I2V-A14B.py | 32 +++++++ .../model_inference/Wan2.2-T2V-A14B.py | 27 ++++++ .../model_inference/Wan2.2-TI2V-5B.py | 8 +- 6 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py create mode 100644 examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py diff --git a/diffsynth/configs/model_config.py b/diffsynth/configs/model_config.py index 0903f79..6448fbf 100644 --- a/diffsynth/configs/model_config.py +++ b/diffsynth/configs/model_config.py @@ -141,6 +141,8 @@ model_loader_configs = [ (None, "ac6a5aa74f4a0aab6f64eb9a72f19901", ["wan_video_dit"], [WanModel], "civitai"), (None, "b61c605c2adbd23124d152ed28e049ae", ["wan_video_dit"], [WanModel], "civitai"), (None, "1f5ab7703c6fc803fdded85ff040c316", ["wan_video_dit"], [WanModel], "civitai"), + (None, "aafcfd9672c3a2456dc46e1cb6e52c70", ["wan_video_dit"], [WanModel], "civitai"), + (None, "5b013604280dd715f8457c6ed6d6a626", ["wan_video_dit"], [WanModel], "civitai"), (None, "a61453409b67cd3246cf0c3bebad47ba", ["wan_video_dit", "wan_video_vace"], [WanModel, VaceWanModel], "civitai"), (None, "7a513e1f257a861512b1afd387a8ecd9", ["wan_video_dit", "wan_video_vace"], [WanModel, VaceWanModel], "civitai"), (None, "cb104773c6c2cb6df4f9529ad5c60d0b", ["wan_video_dit"], [WanModel], "diffusers"), diff --git a/diffsynth/models/wan_video_dit.py b/diffsynth/models/wan_video_dit.py index 1106669..3262057 100644 --- a/diffsynth/models/wan_video_dit.py +++ b/diffsynth/models/wan_video_dit.py @@ -352,6 +352,7 @@ class WanModel(torch.nn.Module): context: torch.Tensor, clip_feature: Optional[torch.Tensor] = None, y: Optional[torch.Tensor] = None, + fused_y: Optional[torch.Tensor] = None, use_gradient_checkpointing: bool = False, use_gradient_checkpointing_offload: bool = False, **kwargs, @@ -365,6 +366,8 @@ class WanModel(torch.nn.Module): x = torch.cat([x, y], dim=1) # (b, c_x + c_y, f, h, w) clip_embdding = self.img_emb(clip_feature) context = torch.cat([clip_embdding, context], dim=1) + if fused_y is not None: + x = torch.cat([x, fused_y], dim=1) # (b, c_x + c_y + c_fused_y, f, h, w) x, (f, h, w) = self.patchify(x) @@ -673,6 +676,7 @@ class WanModelStateDictConverter: "in_dim_control_adapter": 24, } elif hash_state_dict_keys(state_dict) == "1f5ab7703c6fc803fdded85ff040c316": + # Wan-AI/Wan2.2-TI2V-5B config = { "has_image_input": False, "patch_size": [1, 2, 2], @@ -687,6 +691,21 @@ class WanModelStateDictConverter: "eps": 1e-6, "seperated_timestep": True, } + elif hash_state_dict_keys(state_dict) == "5b013604280dd715f8457c6ed6d6a626": + # Wan-AI/Wan2.2-I2V-A14B + config = { + "has_image_input": False, + "patch_size": [1, 2, 2], + "in_dim": 36, + "dim": 5120, + "ffn_dim": 13824, + "freq_dim": 256, + "text_dim": 4096, + "out_dim": 16, + "num_heads": 40, + "num_layers": 40, + "eps": 1e-6, + } else: config = {} return state_dict, config diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 7ea0e3c..d108ad4 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -226,10 +226,11 @@ class WanVideoPipeline(BasePipeline): self.text_encoder: WanTextEncoder = None self.image_encoder: WanImageEncoder = None self.dit: WanModel = None + self.dit2: WanModel = None self.vae: WanVideoVAE = None self.motion_controller: WanMotionControllerModel = None self.vace: VaceWanModel = None - self.in_iteration_models = ("dit", "motion_controller", "vace") + self.in_iteration_models = ("dit", "dit2", "motion_controller", "vace") self.unit_runner = PipelineUnitRunner() self.units = [ WanVideoUnit_ShapeChecker(), @@ -238,6 +239,7 @@ class WanVideoPipeline(BasePipeline): WanVideoUnit_PromptEmbedder(), WanVideoUnit_ImageEmbedder(), WanVideoUnit_ImageVaeEmbedder(), + WanVideoUnit_ImageEmbedderNoClip(), WanVideoUnit_FunControl(), WanVideoUnit_FunReference(), WanVideoUnit_FunCameraControl(), @@ -329,6 +331,37 @@ class WanVideoPipeline(BasePipeline): ), vram_limit=vram_limit, ) + if self.dit2 is not None: + dtype = next(iter(self.dit2.parameters())).dtype + device = "cpu" if vram_limit is not None else self.device + enable_vram_management( + self.dit2, + module_map = { + torch.nn.Linear: AutoWrappedLinear, + torch.nn.Conv3d: AutoWrappedModule, + torch.nn.LayerNorm: WanAutoCastLayerNorm, + RMSNorm: AutoWrappedModule, + torch.nn.Conv2d: AutoWrappedModule, + }, + module_config = dict( + offload_dtype=dtype, + offload_device="cpu", + onload_dtype=dtype, + onload_device=device, + computation_dtype=self.torch_dtype, + computation_device=self.device, + ), + max_num_param=num_persistent_param_in_dit, + overflow_module_config = dict( + offload_dtype=dtype, + offload_device="cpu", + onload_dtype=dtype, + onload_device="cpu", + computation_dtype=self.torch_dtype, + computation_device=self.device, + ), + vram_limit=vram_limit, + ) if self.vae is not None: dtype = next(iter(self.vae.parameters())).dtype enable_vram_management( @@ -427,6 +460,10 @@ class WanVideoPipeline(BasePipeline): for block in self.dit.blocks: block.self_attn.forward = types.MethodType(usp_attn_forward, block.self_attn) self.dit.forward = types.MethodType(usp_dit_forward, self.dit) + if self.dit2 is not None: + for block in self.dit2.blocks: + block.self_attn.forward = types.MethodType(usp_attn_forward, block.self_attn) + self.dit2.forward = types.MethodType(usp_dit_forward, self.dit2) self.sp_size = get_sequence_parallel_world_size() self.use_unified_sequence_parallel = True @@ -473,6 +510,9 @@ class WanVideoPipeline(BasePipeline): # Load models pipe.text_encoder = model_manager.fetch_model("wan_video_text_encoder") pipe.dit = model_manager.fetch_model("wan_video_dit") + num_dits = len([model_name for model_name in model_manager.model_name if model_name == "wan_video_dit"]) + if num_dits == 2: + pipe.dit2 = [model for model, model_name in zip(model_manager.model, model_manager.model_name) if model_name == "wan_video_dit"][-1] pipe.vae = model_manager.fetch_model("wan_video_vae") pipe.image_encoder = model_manager.fetch_model("wan_video_image_encoder") pipe.motion_controller = model_manager.fetch_model("wan_video_motion_controller") @@ -523,6 +563,8 @@ class WanVideoPipeline(BasePipeline): # Classifier-free guidance cfg_scale: Optional[float] = 5.0, cfg_merge: Optional[bool] = False, + # Boundary + boundary: Optional[float] = 0.875, # Scheduler num_inference_steps: Optional[int] = 50, sigma_shift: Optional[float] = 5.0, @@ -575,8 +617,12 @@ class WanVideoPipeline(BasePipeline): self.load_models_to_device(self.in_iteration_models) models = {name: getattr(self, name) for name in self.in_iteration_models} for progress_id, timestep in enumerate(progress_bar_cmd(self.scheduler.timesteps)): + # switch high_noise DiT to low_noise DiT + if models.get("dit2") is not None and timestep.item() < boundary * self.scheduler.num_train_timesteps: + print("switching to low noise DiT") + self.load_models_to_device(["dit2", "motion_controller", "vace"]) + models["dit"] = models.pop("dit2") timestep = timestep.unsqueeze(0).to(dtype=self.torch_dtype, device=self.device) - # Inference noise_pred_posi = self.model_fn(**models, **inputs_shared, **inputs_posi, timestep=timestep) if cfg_scale != 1.0: @@ -737,7 +783,7 @@ class WanVideoUnit_ImageEmbedder(PipelineUnit): ) def process(self, pipe: WanVideoPipeline, input_image, end_image, num_frames, height, width, tiled, tile_size, tile_stride): - if input_image is None or pipe.dit.seperated_timestep: + if input_image is None or pipe.image_encoder is None: return {} pipe.load_models_to_device(self.onload_model_names) image = pipe.preprocess_image(input_image.resize((width, height))).to(pipe.device) @@ -767,6 +813,9 @@ class WanVideoUnit_ImageEmbedder(PipelineUnit): class WanVideoUnit_ImageVaeEmbedder(PipelineUnit): + """ + Encode input image to latents using VAE. This unit is for Wan-AI/Wan2.2-TI2V-5B. + """ def __init__(self): super().__init__( input_params=("input_image", "noise", "num_frames", "height", "width", "tiled", "tile_size", "tile_stride"), @@ -815,6 +864,42 @@ class WanVideoUnit_ImageVaeEmbedder(PipelineUnit): return out1, out2 +class WanVideoUnit_ImageEmbedderNoClip(PipelineUnit): + """ + Encode input image to fused_y using only VAE. This unit is for Wan-AI/Wan2.2-I2V-A14B. + """ + def __init__(self): + super().__init__( + input_params=("input_image", "end_image", "num_frames", "height", "width", "tiled", "tile_size", "tile_stride"), + onload_model_names=("vae") + ) + + def process(self, pipe: WanVideoPipeline, input_image, end_image, num_frames, height, width, tiled, tile_size, tile_stride): + if input_image is None or pipe.image_encoder is not None or pipe.dit.seperated_timestep: + return {} + pipe.load_models_to_device(self.onload_model_names) + image = pipe.preprocess_image(input_image.resize((width, height))).to(pipe.device) + msk = torch.ones(1, num_frames, height//8, width//8, device=pipe.device) + msk[:, 1:] = 0 + if end_image is not None: + end_image = pipe.preprocess_image(end_image.resize((width, height))).to(pipe.device) + vae_input = torch.concat([image.transpose(0,1), torch.zeros(3, num_frames-2, height, width).to(image.device), end_image.transpose(0,1)],dim=1) + msk[:, -1:] = 1 + else: + vae_input = torch.concat([image.transpose(0, 1), torch.zeros(3, num_frames-1, height, width).to(image.device)], dim=1) + + msk = torch.concat([torch.repeat_interleave(msk[:, 0:1], repeats=4, dim=1), msk[:, 1:]], dim=1) + msk = msk.view(1, msk.shape[1] // 4, 4, height//8, width//8) + msk = msk.transpose(1, 2)[0] + + y = pipe.vae.encode([vae_input.to(dtype=pipe.torch_dtype, device=pipe.device)], device=pipe.device, tiled=tiled, tile_size=tile_size, tile_stride=tile_stride)[0] + y = y.to(dtype=pipe.torch_dtype, device=pipe.device) + y = torch.concat([msk, y]) + y = y.unsqueeze(0) + y = y.to(dtype=pipe.torch_dtype, device=pipe.device) + return {"fused_y": y} + + class WanVideoUnit_FunControl(PipelineUnit): def __init__(self): super().__init__( @@ -1116,6 +1201,7 @@ def model_fn_wan_video( context: torch.Tensor = None, clip_feature: Optional[torch.Tensor] = None, y: Optional[torch.Tensor] = None, + fused_y: Optional[torch.Tensor] = None, reference_latents = None, vace_context = None, vace_scale = 1.0, @@ -1181,11 +1267,13 @@ def model_fn_wan_video( x = torch.concat([x] * context.shape[0], dim=0) if timestep.shape[0] != context.shape[0]: timestep = torch.concat([timestep] * context.shape[0], dim=0) - + if dit.has_image_input: x = torch.cat([x, y], dim=1) # (b, c_x + c_y, f, h, w) clip_embdding = dit.img_emb(clip_feature) context = torch.cat([clip_embdding, context], dim=1) + if fused_y is not None: + x = torch.cat([x, fused_y], dim=1) # (b, c_x + c_y + c_fused_y, f, h, w) # Add camera control x, (f, h, w) = dit.patchify(x, control_camera_latents_input) diff --git a/examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py b/examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py new file mode 100644 index 0000000..9782a2c --- /dev/null +++ b/examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py @@ -0,0 +1,32 @@ +import torch +from PIL import Image +from diffsynth import save_video +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig +from modelscope import dataset_snapshot_download + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="high_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="low_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="Wan2.1_VAE.pth", offload_device="cpu"), + ], +) +pipe.enable_vram_management() + +dataset_snapshot_download( + dataset_id="DiffSynth-Studio/examples_in_diffsynth", + local_dir="./", + allow_file_pattern=["data/examples/wan/cat_fightning.jpg"] +) +input_image = Image.open("data/examples/wan/cat_fightning.jpg").resize((832, 480)) +# Text-to-video +video = pipe( + prompt="Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage.", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + seed=0, tiled=True, + input_image=input_image, +) +save_video(video, "video1.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py b/examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py new file mode 100644 index 0000000..de9ae5f --- /dev/null +++ b/examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py @@ -0,0 +1,27 @@ +import torch +from diffsynth import save_video +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig +from modelscope import snapshot_download + +snapshot_download("Wan-AI/Wan2.2-T2V-A14B", local_dir="models/Wan-AI/Wan2.2-T2V-A14B") + + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="high_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="low_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="Wan2.1_VAE.pth", offload_device="cpu"), + ], +) +pipe.enable_vram_management() + +# Text-to-video +video = pipe( + prompt="Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage.", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + seed=0, tiled=True, +) +save_video(video, "video1.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py index b737b47..f41a941 100644 --- a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py +++ b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py @@ -1,17 +1,15 @@ import torch from PIL import Image -from diffsynth import save_video, VideoData +from diffsynth import save_video from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig -from modelscope import snapshot_download -from diffsynth.models.utils import load_state_dict, hash_state_dict_keys from modelscope import dataset_snapshot_download pipe = WanVideoPipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", model_configs=[ - ModelConfig(model_id="Wan-AI/Wan2.1-T2V-14B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), - ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="model_shards/model-*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="diffusion_pytorch_model*.safetensors", offload_device="cpu"), ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="Wan2.2_VAE.safetensors", offload_device="cpu"), ], ) From bba44173d2a838c0c5d7df1a33729aedc1ac5839 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Fri, 25 Jul 2025 17:24:42 +0800 Subject: [PATCH 33/51] minor fix --- diffsynth/configs/model_config.py | 1 - diffsynth/pipelines/wan_video_new.py | 1 - 2 files changed, 2 deletions(-) diff --git a/diffsynth/configs/model_config.py b/diffsynth/configs/model_config.py index 6448fbf..6665599 100644 --- a/diffsynth/configs/model_config.py +++ b/diffsynth/configs/model_config.py @@ -141,7 +141,6 @@ model_loader_configs = [ (None, "ac6a5aa74f4a0aab6f64eb9a72f19901", ["wan_video_dit"], [WanModel], "civitai"), (None, "b61c605c2adbd23124d152ed28e049ae", ["wan_video_dit"], [WanModel], "civitai"), (None, "1f5ab7703c6fc803fdded85ff040c316", ["wan_video_dit"], [WanModel], "civitai"), - (None, "aafcfd9672c3a2456dc46e1cb6e52c70", ["wan_video_dit"], [WanModel], "civitai"), (None, "5b013604280dd715f8457c6ed6d6a626", ["wan_video_dit"], [WanModel], "civitai"), (None, "a61453409b67cd3246cf0c3bebad47ba", ["wan_video_dit", "wan_video_vace"], [WanModel, VaceWanModel], "civitai"), (None, "7a513e1f257a861512b1afd387a8ecd9", ["wan_video_dit", "wan_video_vace"], [WanModel, VaceWanModel], "civitai"), diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index d108ad4..f27382d 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -619,7 +619,6 @@ class WanVideoPipeline(BasePipeline): for progress_id, timestep in enumerate(progress_bar_cmd(self.scheduler.timesteps)): # switch high_noise DiT to low_noise DiT if models.get("dit2") is not None and timestep.item() < boundary * self.scheduler.num_train_timesteps: - print("switching to low noise DiT") self.load_models_to_device(["dit2", "motion_controller", "vace"]) models["dit"] = models.pop("dit2") timestep = timestep.unsqueeze(0).to(dtype=self.torch_dtype, device=self.device) From 5f68727ad31c07ea3ce45f6dab2787854a711a19 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Mon, 28 Jul 2025 11:00:54 +0800 Subject: [PATCH 34/51] refine code --- diffsynth/models/model_manager.py | 21 ++- diffsynth/models/wan_video_dit.py | 13 +- diffsynth/pipelines/wan_video_new.py | 155 +++++++++--------- diffsynth/trainers/utils.py | 2 + examples/wanvideo/README.md | 3 + examples/wanvideo/README_zh.md | 3 + .../model_inference/Wan2.2-I2V-A14B.py | 2 +- .../model_inference/Wan2.2-T2V-A14B.py | 3 - .../model_inference/Wan2.2-TI2V-5B.py | 2 +- .../model_training/full/Wan2.2-I2V-A14B.sh | 35 ++++ .../model_training/full/Wan2.2-T2V-A14B.sh | 31 ++++ .../model_training/full/Wan2.2-TI2V-5B.sh | 14 ++ .../model_training/lora/Wan2.2-I2V-A14B.sh | 37 +++++ .../model_training/lora/Wan2.2-T2V-A14B.sh | 36 ++++ .../model_training/lora/Wan2.2-TI2V-5B.sh | 16 ++ examples/wanvideo/model_training/train.py | 8 + .../validate_full/Wan2.2-I2V-A14B.py | 33 ++++ .../validate_full/Wan2.2-T2V-A14B.py | 28 ++++ .../validate_full/Wan2.2-TI2V-5B.py | 30 ++++ .../validate_lora/Wan2.2-I2V-A14B.py | 31 ++++ .../validate_lora/Wan2.2-T2V-A14B.py | 28 ++++ .../validate_lora/Wan2.2-TI2V-5B.py | 29 ++++ 22 files changed, 474 insertions(+), 86 deletions(-) create mode 100644 examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh create mode 100644 examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh create mode 100644 examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh create mode 100644 examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh create mode 100644 examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh create mode 100644 examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh create mode 100644 examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py create mode 100644 examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py create mode 100644 examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py create mode 100644 examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py create mode 100644 examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py create mode 100644 examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py diff --git a/diffsynth/models/model_manager.py b/diffsynth/models/model_manager.py index 7ae3c50..d46eedf 100644 --- a/diffsynth/models/model_manager.py +++ b/diffsynth/models/model_manager.py @@ -426,7 +426,7 @@ class ModelManager: self.load_model(file_path, model_names, device=device, torch_dtype=torch_dtype) - def fetch_model(self, model_name, file_path=None, require_model_path=False): + def fetch_model(self, model_name, file_path=None, require_model_path=False, index=None): fetched_models = [] fetched_model_paths = [] for model, model_path, model_name_ in zip(self.model, self.model_path, self.model_name): @@ -440,12 +440,25 @@ class ModelManager: return None if len(fetched_models) == 1: print(f"Using {model_name} from {fetched_model_paths[0]}.") + model = fetched_models[0] + path = fetched_model_paths[0] else: - print(f"More than one {model_name} models are loaded in model manager: {fetched_model_paths}. Using {model_name} from {fetched_model_paths[0]}.") + if index is None: + model = fetched_models[0] + path = fetched_model_paths[0] + print(f"More than one {model_name} models are loaded in model manager: {fetched_model_paths}. Using {model_name} from {fetched_model_paths[0]}.") + elif isinstance(index, int): + model = fetched_models[:index] + path = fetched_model_paths[:index] + print(f"More than one {model_name} models are loaded in model manager: {fetched_model_paths}. Using {model_name} from {fetched_model_paths[:index]}.") + else: + model = fetched_models + path = fetched_model_paths + print(f"More than one {model_name} models are loaded in model manager: {fetched_model_paths}. Using {model_name} from {fetched_model_paths}.") if require_model_path: - return fetched_models[0], fetched_model_paths[0] + return model, path else: - return fetched_models[0] + return model def to(self, device): diff --git a/diffsynth/models/wan_video_dit.py b/diffsynth/models/wan_video_dit.py index 3262057..ea473b0 100644 --- a/diffsynth/models/wan_video_dit.py +++ b/diffsynth/models/wan_video_dit.py @@ -288,6 +288,9 @@ class WanModel(torch.nn.Module): add_control_adapter: bool = False, in_dim_control_adapter: int = 24, seperated_timestep: bool = False, + require_vae_embedding: bool = True, + require_clip_embedding: bool = True, + fuse_vae_embedding_in_latents: bool = False, ): super().__init__() self.dim = dim @@ -295,6 +298,9 @@ class WanModel(torch.nn.Module): self.has_image_input = has_image_input self.patch_size = patch_size self.seperated_timestep = seperated_timestep + self.require_vae_embedding = require_vae_embedding + self.require_clip_embedding = require_clip_embedding + self.fuse_vae_embedding_in_latents = fuse_vae_embedding_in_latents self.patch_embedding = nn.Conv3d( in_dim, dim, kernel_size=patch_size, stride=patch_size) @@ -352,7 +358,6 @@ class WanModel(torch.nn.Module): context: torch.Tensor, clip_feature: Optional[torch.Tensor] = None, y: Optional[torch.Tensor] = None, - fused_y: Optional[torch.Tensor] = None, use_gradient_checkpointing: bool = False, use_gradient_checkpointing_offload: bool = False, **kwargs, @@ -366,8 +371,6 @@ class WanModel(torch.nn.Module): x = torch.cat([x, y], dim=1) # (b, c_x + c_y, f, h, w) clip_embdding = self.img_emb(clip_feature) context = torch.cat([clip_embdding, context], dim=1) - if fused_y is not None: - x = torch.cat([x, fused_y], dim=1) # (b, c_x + c_y + c_fused_y, f, h, w) x, (f, h, w) = self.patchify(x) @@ -690,6 +693,9 @@ class WanModelStateDictConverter: "num_layers": 30, "eps": 1e-6, "seperated_timestep": True, + "require_clip_embedding": False, + "require_vae_embedding": False, + "fuse_vae_embedding_in_latents": True, } elif hash_state_dict_keys(state_dict) == "5b013604280dd715f8457c6ed6d6a626": # Wan-AI/Wan2.2-I2V-A14B @@ -705,6 +711,7 @@ class WanModelStateDictConverter: "num_heads": 40, "num_layers": 40, "eps": 1e-6, + "require_clip_embedding": False, } else: config = {} diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index f27382d..9963aa1 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -230,16 +230,17 @@ class WanVideoPipeline(BasePipeline): self.vae: WanVideoVAE = None self.motion_controller: WanMotionControllerModel = None self.vace: VaceWanModel = None - self.in_iteration_models = ("dit", "dit2", "motion_controller", "vace") + self.in_iteration_models = ("dit", "motion_controller", "vace") + self.in_iteration_models_2 = ("dit2", "motion_controller", "vace") self.unit_runner = PipelineUnitRunner() self.units = [ WanVideoUnit_ShapeChecker(), WanVideoUnit_NoiseInitializer(), WanVideoUnit_InputVideoEmbedder(), WanVideoUnit_PromptEmbedder(), - WanVideoUnit_ImageEmbedder(), - WanVideoUnit_ImageVaeEmbedder(), - WanVideoUnit_ImageEmbedderNoClip(), + WanVideoUnit_ImageEmbedderVAE(), + WanVideoUnit_ImageEmbedderCLIP(), + WanVideoUnit_ImageEmbedderFused(), WanVideoUnit_FunControl(), WanVideoUnit_FunReference(), WanVideoUnit_FunCameraControl(), @@ -259,7 +260,9 @@ class WanVideoPipeline(BasePipeline): def training_loss(self, **inputs): - timestep_id = torch.randint(0, self.scheduler.num_train_timesteps, (1,)) + max_timestep_boundary = int(inputs.get("max_timestep_boundary", 1) * self.scheduler.num_train_timesteps) + min_timestep_boundary = int(inputs.get("min_timestep_boundary", 0) * self.scheduler.num_train_timesteps) + timestep_id = torch.randint(min_timestep_boundary, max_timestep_boundary, (1,)) timestep = self.scheduler.timesteps[timestep_id].to(dtype=self.torch_dtype, device=self.device) inputs["latents"] = self.scheduler.add_noise(inputs["input_latents"], inputs["noise"], timestep) @@ -517,6 +520,11 @@ class WanVideoPipeline(BasePipeline): pipe.image_encoder = model_manager.fetch_model("wan_video_image_encoder") pipe.motion_controller = model_manager.fetch_model("wan_video_motion_controller") pipe.vace = model_manager.fetch_model("wan_video_vace") + + # Size division factor + if pipe.vae is not None: + pipe.height_division_factor = pipe.vae.upsampling_factor * 2 + pipe.width_division_factor = pipe.vae.upsampling_factor * 2 # Initialize tokenizer tokenizer_config.download_if_necessary(local_model_path, skip_download=skip_download) @@ -564,7 +572,7 @@ class WanVideoPipeline(BasePipeline): cfg_scale: Optional[float] = 5.0, cfg_merge: Optional[bool] = False, # Boundary - boundary: Optional[float] = 0.875, + switch_DiT_boundary: Optional[float] = 0.875, # Scheduler num_inference_steps: Optional[int] = 50, sigma_shift: Optional[float] = 5.0, @@ -617,11 +625,14 @@ class WanVideoPipeline(BasePipeline): self.load_models_to_device(self.in_iteration_models) models = {name: getattr(self, name) for name in self.in_iteration_models} for progress_id, timestep in enumerate(progress_bar_cmd(self.scheduler.timesteps)): - # switch high_noise DiT to low_noise DiT - if models.get("dit2") is not None and timestep.item() < boundary * self.scheduler.num_train_timesteps: - self.load_models_to_device(["dit2", "motion_controller", "vace"]) - models["dit"] = models.pop("dit2") + # Switch DiT if necessary + if timestep.item() < switch_DiT_boundary * self.scheduler.num_train_timesteps and self.dit2 is not None and not models["dit"] is self.dit2: + self.load_models_to_device(self.in_iteration_models_2) + models["dit"] = self.dit2 + + # Timestep timestep = timestep.unsqueeze(0).to(dtype=self.torch_dtype, device=self.device) + # Inference noise_pred_posi = self.model_fn(**models, **inputs_shared, **inputs_posi, timestep=timestep) if cfg_scale != 1.0: @@ -775,6 +786,9 @@ class WanVideoUnit_PromptEmbedder(PipelineUnit): class WanVideoUnit_ImageEmbedder(PipelineUnit): + """ + Deprecated + """ def __init__(self): super().__init__( input_params=("input_image", "end_image", "num_frames", "height", "width", "tiled", "tile_size", "tile_stride"), @@ -811,70 +825,38 @@ class WanVideoUnit_ImageEmbedder(PipelineUnit): return {"clip_feature": clip_context, "y": y} -class WanVideoUnit_ImageVaeEmbedder(PipelineUnit): - """ - Encode input image to latents using VAE. This unit is for Wan-AI/Wan2.2-TI2V-5B. - """ + +class WanVideoUnit_ImageEmbedderCLIP(PipelineUnit): def __init__(self): super().__init__( - input_params=("input_image", "noise", "num_frames", "height", "width", "tiled", "tile_size", "tile_stride"), - onload_model_names=("vae") + input_params=("input_image", "end_image", "height", "width"), + onload_model_names=("image_encoder",) ) - def process(self, pipe: WanVideoPipeline, input_image, noise, num_frames, height, width, tiled, tile_size, tile_stride): - if input_image is None or not pipe.dit.seperated_timestep: + def process(self, pipe: WanVideoPipeline, input_image, end_image, height, width): + if input_image is None or pipe.image_encoder is None or not pipe.dit.require_clip_embedding: return {} pipe.load_models_to_device(self.onload_model_names) - image = pipe.preprocess_image(input_image.resize((width, height))).transpose(0, 1).to(pipe.device) - z = pipe.vae.encode([image.to(dtype=pipe.torch_dtype, device=pipe.device)], device=pipe.device, tiled=tiled, tile_size=tile_size, tile_stride=tile_stride)[0] + image = pipe.preprocess_image(input_image.resize((width, height))).to(pipe.device) + clip_context = pipe.image_encoder.encode_image([image]) + if end_image is not None: + end_image = pipe.preprocess_image(end_image.resize((width, height))).to(pipe.device) + if pipe.dit.has_image_pos_emb: + clip_context = torch.concat([clip_context, pipe.image_encoder.encode_image([end_image])], dim=1) + clip_context = clip_context.to(dtype=pipe.torch_dtype, device=pipe.device) + return {"clip_feature": clip_context} + - _, mask2 = self.masks_like([noise.squeeze(0)], zero=True) - latents = (1. - mask2[0]) * z + mask2[0] * noise.squeeze(0) - latents = latents.unsqueeze(0) - seq_len = ((num_frames - 1) // 4 + 1) * (height // pipe.vae.upsampling_factor) * (width // pipe.vae.upsampling_factor) // (2 * 2) - if hasattr(pipe, "use_unified_sequence_parallel") and pipe.use_unified_sequence_parallel: - import math - seq_len = int(math.ceil(seq_len / pipe.sp_size)) * pipe.sp_size - - return {"latents": latents, "latent_mask_for_timestep": mask2[0].unsqueeze(0), "seq_len": seq_len} - - @staticmethod - def masks_like(tensor, zero=False, generator=None, p=0.2): - assert isinstance(tensor, list) - out1 = [torch.ones(u.shape, dtype=u.dtype, device=u.device) for u in tensor] - out2 = [torch.ones(u.shape, dtype=u.dtype, device=u.device) for u in tensor] - - if zero: - if generator is not None: - for u, v in zip(out1, out2): - random_num = torch.rand(1, generator=generator, device=generator.device).item() - if random_num < p: - u[:, 0] = torch.normal(mean=-3.5, std=0.5, size=(1,), device=u.device, generator=generator).expand_as(u[:, 0]).exp() - v[:, 0] = torch.zeros_like(v[:, 0]) - else: - u[:, 0] = u[:, 0] - v[:, 0] = v[:, 0] - else: - for u, v in zip(out1, out2): - u[:, 0] = torch.zeros_like(u[:, 0]) - v[:, 0] = torch.zeros_like(v[:, 0]) - - return out1, out2 - - -class WanVideoUnit_ImageEmbedderNoClip(PipelineUnit): - """ - Encode input image to fused_y using only VAE. This unit is for Wan-AI/Wan2.2-I2V-A14B. - """ +class WanVideoUnit_ImageEmbedderVAE(PipelineUnit): def __init__(self): super().__init__( input_params=("input_image", "end_image", "num_frames", "height", "width", "tiled", "tile_size", "tile_stride"), - onload_model_names=("vae") + onload_model_names=("vae",) ) def process(self, pipe: WanVideoPipeline, input_image, end_image, num_frames, height, width, tiled, tile_size, tile_stride): - if input_image is None or pipe.image_encoder is not None or pipe.dit.seperated_timestep: + if input_image is None or not pipe.dit.require_vae_embedding: return {} pipe.load_models_to_device(self.onload_model_names) image = pipe.preprocess_image(input_image.resize((width, height))).to(pipe.device) @@ -896,14 +878,36 @@ class WanVideoUnit_ImageEmbedderNoClip(PipelineUnit): y = torch.concat([msk, y]) y = y.unsqueeze(0) y = y.to(dtype=pipe.torch_dtype, device=pipe.device) - return {"fused_y": y} + return {"y": y} + + + +class WanVideoUnit_ImageEmbedderFused(PipelineUnit): + """ + Encode input image to latents using VAE. This unit is for Wan-AI/Wan2.2-TI2V-5B. + """ + def __init__(self): + super().__init__( + input_params=("input_image", "latents", "height", "width", "tiled", "tile_size", "tile_stride"), + onload_model_names=("vae",) + ) + + def process(self, pipe: WanVideoPipeline, input_image, latents, height, width, tiled, tile_size, tile_stride): + if input_image is None or not pipe.dit.fuse_vae_embedding_in_latents: + return {} + pipe.load_models_to_device(self.onload_model_names) + image = pipe.preprocess_image(input_image.resize((width, height))).transpose(0, 1) + z = pipe.vae.encode([image], device=pipe.device, tiled=tiled, tile_size=tile_size, tile_stride=tile_stride) + latents[:, :, 0: 1] = z + return {"latents": latents, "fuse_vae_embedding_in_latents": True} + class WanVideoUnit_FunControl(PipelineUnit): def __init__(self): super().__init__( input_params=("control_video", "num_frames", "height", "width", "tiled", "tile_size", "tile_stride", "clip_feature", "y"), - onload_model_names=("vae") + onload_model_names=("vae",) ) def process(self, pipe: WanVideoPipeline, control_video, num_frames, height, width, tiled, tile_size, tile_stride, clip_feature, y): @@ -927,7 +931,7 @@ class WanVideoUnit_FunReference(PipelineUnit): def __init__(self): super().__init__( input_params=("reference_image", "height", "width", "reference_image"), - onload_model_names=("vae") + onload_model_names=("vae",) ) def process(self, pipe: WanVideoPipeline, reference_image, height, width): @@ -1200,7 +1204,6 @@ def model_fn_wan_video( context: torch.Tensor = None, clip_feature: Optional[torch.Tensor] = None, y: Optional[torch.Tensor] = None, - fused_y: Optional[torch.Tensor] = None, reference_latents = None, vace_context = None, vace_scale = 1.0, @@ -1213,6 +1216,7 @@ def model_fn_wan_video( use_gradient_checkpointing: bool = False, use_gradient_checkpointing_offload: bool = False, control_camera_latents_input = None, + fuse_vae_embedding_in_latents: bool = False, **kwargs, ): if sliding_window_size is not None and sliding_window_stride is not None: @@ -1247,15 +1251,19 @@ def model_fn_wan_video( get_sequence_parallel_world_size, get_sp_group) - if dit.seperated_timestep and "latent_mask_for_timestep" in kwargs: - temp_ts = (kwargs["latent_mask_for_timestep"][0][0][:, ::2, ::2] * timestep).flatten() - temp_ts= torch.cat([temp_ts, temp_ts.new_ones(kwargs["seq_len"] - temp_ts.size(0)) * timestep]) - timestep = temp_ts.unsqueeze(0).flatten() - t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep).unflatten(0, (latents.size(0), kwargs["seq_len"]))) + # Timestep + if dit.seperated_timestep and fuse_vae_embedding_in_latents: + timestep = torch.concat([ + torch.zeros((1, latents.shape[3] * latents.shape[4] // 4), dtype=latents.dtype, device=latents.device), + torch.ones((latents.shape[2] - 1, latents.shape[3] * latents.shape[4] // 4), dtype=latents.dtype, device=latents.device) * timestep + ]).flatten() + t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep).unsqueeze(0)) t_mod = dit.time_projection(t).unflatten(2, (6, dit.dim)) else: t = dit.time_embedding(sinusoidal_embedding_1d(dit.freq_dim, timestep)) t_mod = dit.time_projection(t).unflatten(1, (6, dit.dim)) + + # Motion Controller if motion_bucket_id is not None and motion_controller is not None: t_mod = t_mod + motion_controller(motion_bucket_id).unflatten(1, (6, dit.dim)) context = dit.text_embedding(context) @@ -1267,16 +1275,15 @@ def model_fn_wan_video( if timestep.shape[0] != context.shape[0]: timestep = torch.concat([timestep] * context.shape[0], dim=0) - if dit.has_image_input: - x = torch.cat([x, y], dim=1) # (b, c_x + c_y, f, h, w) + # Image Embedding + if y is not None and dit.require_vae_embedding: + x = torch.cat([x, y], dim=1) + if clip_feature is not None and dit.require_clip_embedding: clip_embdding = dit.img_emb(clip_feature) context = torch.cat([clip_embdding, context], dim=1) - if fused_y is not None: - x = torch.cat([x, fused_y], dim=1) # (b, c_x + c_y + c_fused_y, f, h, w) # Add camera control x, (f, h, w) = dit.patchify(x, control_camera_latents_input) - # Reference image if reference_latents is not None: diff --git a/diffsynth/trainers/utils.py b/diffsynth/trainers/utils.py index b171857..b27fc34 100644 --- a/diffsynth/trainers/utils.py +++ b/diffsynth/trainers/utils.py @@ -434,6 +434,8 @@ def wan_parser(): parser.add_argument("--extra_inputs", default=None, help="Additional model inputs, comma-separated.") parser.add_argument("--use_gradient_checkpointing_offload", default=False, action="store_true", help="Whether to offload gradient checkpointing to CPU memory.") parser.add_argument("--gradient_accumulation_steps", type=int, default=1, help="Gradient accumulation steps.") + parser.add_argument("--max_timestep_boundary", type=float, default=1.0, help="Max timestep boundary (for mixed models, e.g., Wan-AI/Wan2.2-I2V-A14B).") + parser.add_argument("--min_timestep_boundary", type=float, default=0.0, help="Min timestep boundary (for mixed models, e.g., Wan-AI/Wan2.2-I2V-A14B).") return parser diff --git a/examples/wanvideo/README.md b/examples/wanvideo/README.md index 7b2ebbd..c75e518 100644 --- a/examples/wanvideo/README.md +++ b/examples/wanvideo/README.md @@ -65,6 +65,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) |[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./model_inference/Wan2.1-VACE-1.3B.py)|[code](./model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./model_training/validate_lora/Wan2.1-VACE-1.3B.py)| |[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./model_inference/Wan2.1-VACE-14B.py)|[code](./model_training/full/Wan2.1-VACE-14B.sh)|[code](./model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./model_training/lora/Wan2.1-VACE-14B.sh)|[code](./model_training/validate_lora/Wan2.1-VACE-14B.py)| |[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./model_inference/Wan2.2-I2V-A14B.py)|[code](./model_training/full/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./model_inference/Wan2.2-T2V-A14B.py)|[code](./model_training/full/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./model_inference/Wan2.2-TI2V-5B.py)|[code](./model_training/full/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_lora/Wan2.2-TI2V-5B.py)| ## Model Inference diff --git a/examples/wanvideo/README_zh.md b/examples/wanvideo/README_zh.md index 860ff83..822c8e6 100644 --- a/examples/wanvideo/README_zh.md +++ b/examples/wanvideo/README_zh.md @@ -65,6 +65,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) |[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./model_inference/Wan2.1-VACE-1.3B.py)|[code](./model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./model_training/validate_lora/Wan2.1-VACE-1.3B.py)| |[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./model_inference/Wan2.1-VACE-14B.py)|[code](./model_training/full/Wan2.1-VACE-14B.sh)|[code](./model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./model_training/lora/Wan2.1-VACE-14B.sh)|[code](./model_training/validate_lora/Wan2.1-VACE-14B.py)| |[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./model_inference/Wan2.2-I2V-A14B.py)|[code](./model_training/full/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./model_inference/Wan2.2-T2V-A14B.py)|[code](./model_training/full/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./model_inference/Wan2.2-TI2V-5B.py)|[code](./model_training/full/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_lora/Wan2.2-TI2V-5B.py)| ## 模型推理 diff --git a/examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py b/examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py index 9782a2c..0c1be54 100644 --- a/examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py +++ b/examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py @@ -22,7 +22,7 @@ dataset_snapshot_download( allow_file_pattern=["data/examples/wan/cat_fightning.jpg"] ) input_image = Image.open("data/examples/wan/cat_fightning.jpg").resize((832, 480)) -# Text-to-video + video = pipe( prompt="Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage.", negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", diff --git a/examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py b/examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py index de9ae5f..27b10d0 100644 --- a/examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py +++ b/examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py @@ -1,9 +1,6 @@ import torch from diffsynth import save_video from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig -from modelscope import snapshot_download - -snapshot_download("Wan-AI/Wan2.2-T2V-A14B", local_dir="models/Wan-AI/Wan2.2-T2V-A14B") pipe = WanVideoPipeline.from_pretrained( diff --git a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py index f41a941..50d81c2 100644 --- a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py +++ b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py @@ -10,7 +10,7 @@ pipe = WanVideoPipeline.from_pretrained( model_configs=[ ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="diffusion_pytorch_model*.safetensors", offload_device="cpu"), - ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="Wan2.2_VAE.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="Wan2.2_VAE.pth", offload_device="cpu"), ], ) pipe.enable_vram_management() diff --git a/examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh b/examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh new file mode 100644 index 0000000..2f531e7 --- /dev/null +++ b/examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh @@ -0,0 +1,35 @@ +accelerate launch --config_file examples/wanvideo/model_training/full/accelerate_config_14B.yaml examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-I2V-A14B:high_noise_model/diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-I2V-A14B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-I2V-A14B:Wan2.1_VAE.pth" \ + --learning_rate 1e-5 \ + --num_epochs 2 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-I2V-A14B_high_noise_full" \ + --trainable_models "dit" \ + --extra_inputs "input_image" \ + --use_gradient_checkpointing_offload \ + --max_timestep_boundary 1 \ + --min_timestep_boundary 0.875 + +accelerate launch --config_file examples/wanvideo/model_training/full/accelerate_config_14B.yaml examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-I2V-A14B:low_noise_model/diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-I2V-A14B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-I2V-A14B:Wan2.1_VAE.pth" \ + --learning_rate 1e-5 \ + --num_epochs 2 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-I2V-A14B_low_noise_full" \ + --trainable_models "dit" \ + --extra_inputs "input_image" \ + --use_gradient_checkpointing_offload \ + --max_timestep_boundary 0.875 \ + --min_timestep_boundary 0 diff --git a/examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh b/examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh new file mode 100644 index 0000000..f634117 --- /dev/null +++ b/examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh @@ -0,0 +1,31 @@ +accelerate launch --config_file examples/wanvideo/model_training/full/accelerate_config_14B.yaml examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-T2V-A14B:high_noise_model/diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-T2V-A14B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-T2V-A14B:Wan2.1_VAE.pth" \ + --learning_rate 1e-5 \ + --num_epochs 2 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-T2V-A14B_high_noise_full" \ + --trainable_models "dit" \ + --max_timestep_boundary 1 \ + --min_timestep_boundary 0.875 + +accelerate launch --config_file examples/wanvideo/model_training/full/accelerate_config_14B.yaml examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-T2V-A14B:low_noise_model/diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-T2V-A14B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-T2V-A14B:Wan2.1_VAE.pth" \ + --learning_rate 1e-5 \ + --num_epochs 2 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-T2V-A14B_low_noise_full" \ + --trainable_models "dit" \ + --max_timestep_boundary 0.875 \ + --min_timestep_boundary 0 diff --git a/examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh b/examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh new file mode 100644 index 0000000..def9f89 --- /dev/null +++ b/examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh @@ -0,0 +1,14 @@ +accelerate launch examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-TI2V-5B:diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-TI2V-5B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-TI2V-5B:Wan2.2_VAE.pth" \ + --learning_rate 1e-5 \ + --num_epochs 2 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-TI2V-5B_full" \ + --trainable_models "dit" \ + --extra_inputs "input_image" \ No newline at end of file diff --git a/examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh b/examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh new file mode 100644 index 0000000..4201b47 --- /dev/null +++ b/examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh @@ -0,0 +1,37 @@ +accelerate launch examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-I2V-A14B:high_noise_model/diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-I2V-A14B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-I2V-A14B:Wan2.1_VAE.pth" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-I2V-A14B_high_noise_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "q,k,v,o,ffn.0,ffn.2" \ + --lora_rank 32 \ + --extra_inputs "input_image" \ + --max_timestep_boundary 1 \ + --min_timestep_boundary 0.875 + +accelerate launch examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-I2V-A14B:low_noise_model/diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-I2V-A14B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-I2V-A14B:Wan2.1_VAE.pth" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-I2V-A14B_low_noise_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "q,k,v,o,ffn.0,ffn.2" \ + --lora_rank 32 \ + --extra_inputs "input_image" \ + --max_timestep_boundary 0.875 \ + --min_timestep_boundary 0 diff --git a/examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh b/examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh new file mode 100644 index 0000000..737896c --- /dev/null +++ b/examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh @@ -0,0 +1,36 @@ +accelerate launch examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-T2V-A14B:high_noise_model/diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-T2V-A14B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-T2V-A14B:Wan2.1_VAE.pth" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-T2V-A14B_high_noise_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "q,k,v,o,ffn.0,ffn.2" \ + --lora_rank 32 \ + --max_timestep_boundary 1 \ + --min_timestep_boundary 0.875 + + +accelerate launch examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-T2V-A14B:low_noise_model/diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-T2V-A14B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-T2V-A14B:Wan2.1_VAE.pth" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-T2V-A14B_low_noise_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "q,k,v,o,ffn.0,ffn.2" \ + --lora_rank 32 \ + --max_timestep_boundary 0.875 \ + --min_timestep_boundary 0 diff --git a/examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh b/examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh new file mode 100644 index 0000000..6a33b57 --- /dev/null +++ b/examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh @@ -0,0 +1,16 @@ +accelerate launch examples/wanvideo/model_training/train.py \ + --dataset_base_path data/example_video_dataset \ + --dataset_metadata_path data/example_video_dataset/metadata.csv \ + --height 480 \ + --width 832 \ + --num_frames 49 \ + --dataset_repeat 100 \ + --model_id_with_origin_paths "Wan-AI/Wan2.2-TI2V-5B:diffusion_pytorch_model*.safetensors,Wan-AI/Wan2.2-TI2V-5B:models_t5_umt5-xxl-enc-bf16.pth,Wan-AI/Wan2.2-TI2V-5B:Wan2.2_VAE.pth" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/Wan2.2-TI2V-5B_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "q,k,v,o,ffn.0,ffn.2" \ + --lora_rank 32 \ + --extra_inputs "input_image" \ No newline at end of file diff --git a/examples/wanvideo/model_training/train.py b/examples/wanvideo/model_training/train.py index 877c5b8..93ec0bd 100644 --- a/examples/wanvideo/model_training/train.py +++ b/examples/wanvideo/model_training/train.py @@ -14,6 +14,8 @@ class WanTrainingModule(DiffusionTrainingModule): use_gradient_checkpointing=True, use_gradient_checkpointing_offload=False, extra_inputs=None, + max_timestep_boundary=1.0, + min_timestep_boundary=0.0, ): super().__init__() # Load models @@ -45,6 +47,8 @@ class WanTrainingModule(DiffusionTrainingModule): self.use_gradient_checkpointing = use_gradient_checkpointing self.use_gradient_checkpointing_offload = use_gradient_checkpointing_offload self.extra_inputs = extra_inputs.split(",") if extra_inputs is not None else [] + self.max_timestep_boundary = max_timestep_boundary + self.min_timestep_boundary = min_timestep_boundary def forward_preprocess(self, data): @@ -69,6 +73,8 @@ class WanTrainingModule(DiffusionTrainingModule): "use_gradient_checkpointing_offload": self.use_gradient_checkpointing_offload, "cfg_merge": False, "vace_scale": 1, + "max_timestep_boundary": self.max_timestep_boundary, + "min_timestep_boundary": self.min_timestep_boundary, } # Extra inputs @@ -106,6 +112,8 @@ if __name__ == "__main__": lora_rank=args.lora_rank, use_gradient_checkpointing_offload=args.use_gradient_checkpointing_offload, extra_inputs=args.extra_inputs, + max_timestep_boundary=args.max_timestep_boundary, + min_timestep_boundary=args.min_timestep_boundary, ) model_logger = ModelLogger( args.output_path, diff --git a/examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py b/examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py new file mode 100644 index 0000000..ddcdf5c --- /dev/null +++ b/examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py @@ -0,0 +1,33 @@ +import torch +from PIL import Image +from diffsynth import save_video, VideoData, load_state_dict +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig +from modelscope import dataset_snapshot_download + + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="high_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="low_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="Wan2.1_VAE.pth", offload_device="cpu"), + ], +) +state_dict = load_state_dict("models/train/Wan2.2-I2V-A14B_high_noise_full/epoch-1.safetensors") +pipe.dit.load_state_dict(state_dict) +state_dict = load_state_dict("models/train/Wan2.2-I2V-A14B_low_noise_full/epoch-1.safetensors") +pipe.dit2.load_state_dict(state_dict) +pipe.enable_vram_management() + +input_image = VideoData("data/example_video_dataset/video1.mp4", height=480, width=832)[0] + +video = pipe( + prompt="from sunset to night, a small town, light, house, river", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + input_image=input_image, + num_frames=49, + seed=1, tiled=False, +) +save_video(video, "video_Wan2.2-I2V-A14B.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py b/examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py new file mode 100644 index 0000000..be0e000 --- /dev/null +++ b/examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py @@ -0,0 +1,28 @@ +import torch +from PIL import Image +from diffsynth import save_video, VideoData, load_state_dict +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig + + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="high_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="low_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="Wan2.1_VAE.pth", offload_device="cpu"), + ], +) +state_dict = load_state_dict("models/train/Wan2.2-T2V-A14B_high_noise_full/epoch-1.safetensors") +pipe.dit.load_state_dict(state_dict) +state_dict = load_state_dict("models/train/Wan2.2-T2V-A14B_low_noise_full/epoch-1.safetensors") +pipe.dit2.load_state_dict(state_dict) +pipe.enable_vram_management() + +video = pipe( + prompt="from sunset to night, a small town, light, house, river", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + seed=1, tiled=True +) +save_video(video, "video_Wan2.2-T2V-A14B.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py new file mode 100644 index 0000000..0f0ea5d --- /dev/null +++ b/examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py @@ -0,0 +1,30 @@ +import torch +from PIL import Image +from diffsynth import save_video, VideoData, load_state_dict +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig +from modelscope import dataset_snapshot_download + + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="Wan2.2_VAE.pth", offload_device="cpu"), + ], +) +state_dict = load_state_dict("models/train/Wan2.2-TI2V-5B_full/epoch-1.safetensors") +pipe.dit.load_state_dict(state_dict) +pipe.enable_vram_management() + +input_image = VideoData("data/example_video_dataset/video1.mp4", height=480, width=832)[0] + +video = pipe( + prompt="from sunset to night, a small town, light, house, river", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + input_image=input_image, + num_frames=49, + seed=1, tiled=False, +) +save_video(video, "video_Wan2.2-TI2V-5B.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py b/examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py new file mode 100644 index 0000000..4a6bd9c --- /dev/null +++ b/examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py @@ -0,0 +1,31 @@ +import torch +from PIL import Image +from diffsynth import save_video, VideoData +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig +from modelscope import dataset_snapshot_download + + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="high_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="low_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-I2V-A14B", origin_file_pattern="Wan2.1_VAE.pth", offload_device="cpu"), + ], +) +pipe.load_lora(pipe.dit, "models/train/Wan2.2-I2V-A14B_high_noise_lora/epoch-4.safetensors", alpha=1) +pipe.load_lora(pipe.dit2, "models/train/Wan2.2-I2V-A14B_low_noise_lora/epoch-4.safetensors", alpha=1) +pipe.enable_vram_management() + +input_image = VideoData("data/example_video_dataset/video1.mp4", height=480, width=832)[0] + +video = pipe( + prompt="from sunset to night, a small town, light, house, river", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + input_image=input_image, + num_frames=49, + seed=1, tiled=False, +) +save_video(video, "video_Wan2.2-I2V-A14B.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py b/examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py new file mode 100644 index 0000000..ab43927 --- /dev/null +++ b/examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py @@ -0,0 +1,28 @@ +import torch +from PIL import Image +from diffsynth import save_video, VideoData +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig +from modelscope import dataset_snapshot_download + + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="high_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="low_noise_model/diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-T2V-A14B", origin_file_pattern="Wan2.1_VAE.pth", offload_device="cpu"), + ], +) +pipe.load_lora(pipe.dit, "models/train/Wan2.2-T2V-A14B_high_noise_lora/epoch-4.safetensors", alpha=1) +pipe.load_lora(pipe.dit2, "models/train/Wan2.2-T2V-A14B_low_noise_lora/epoch-4.safetensors", alpha=1) +pipe.enable_vram_management() + +video = pipe( + prompt="from sunset to night, a small town, light, house, river", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + num_frames=49, + seed=1, tiled=True +) +save_video(video, "video_Wan2.2-T2V-A14B.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py new file mode 100644 index 0000000..d5a9229 --- /dev/null +++ b/examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py @@ -0,0 +1,29 @@ +import torch +from PIL import Image +from diffsynth import save_video, VideoData, load_state_dict +from diffsynth.pipelines.wan_video_new import WanVideoPipeline, ModelConfig +from modelscope import dataset_snapshot_download + + +pipe = WanVideoPipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="models_t5_umt5-xxl-enc-bf16.pth", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="diffusion_pytorch_model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="Wan-AI/Wan2.2-TI2V-5B", origin_file_pattern="Wan2.2_VAE.pth", offload_device="cpu"), + ], +) +pipe.load_lora(pipe.dit, "models/train/Wan2.2-TI2V-5B_lora/epoch-4.safetensors", alpha=1) +pipe.enable_vram_management() + +input_image = VideoData("data/example_video_dataset/video1.mp4", height=480, width=832)[0] + +video = pipe( + prompt="from sunset to night, a small town, light, house, river", + negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", + input_image=input_image, + num_frames=49, + seed=1, tiled=False, +) +save_video(video, "video_Wan2.2-TI2V-5B.mp4", fps=15, quality=5) From b8f05bb3425861ced8bc3777f7a2b998f5f82122 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Mon, 28 Jul 2025 11:09:33 +0800 Subject: [PATCH 35/51] tmp commit --- diffsynth/configs/model_config.py | 2 ++ diffsynth/models/flux_dit.py | 5 ++++- .../model_inference/Nexus-Gen-Generation.py | 21 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 examples/flux/model_inference/Nexus-Gen-Generation.py diff --git a/diffsynth/configs/model_config.py b/diffsynth/configs/model_config.py index b60c200..9fa652d 100644 --- a/diffsynth/configs/model_config.py +++ b/diffsynth/configs/model_config.py @@ -69,6 +69,7 @@ from ..models.flux_value_control import SingleValueEncoder from ..lora.flux_lora import FluxLoraPatcher from ..models.flux_lora_encoder import FluxLoRAEncoder +from ..models.nexus_gen_projector import NexusGenAdapter model_loader_configs = [ # These configs are provided for detecting model type automatically. @@ -152,6 +153,7 @@ model_loader_configs = [ (None, "d30fb9e02b1dbf4e509142f05cf7dd50", ["flux_dit", "step1x_connector"], [FluxDiT, Qwen2Connector], "civitai"), (None, "30143afb2dea73d1ac580e0787628f8c", ["flux_lora_patcher"], [FluxLoraPatcher], "civitai"), (None, "77c2e4dd2440269eb33bfaa0d004f6ab", ["flux_lora_encoder"], [FluxLoRAEncoder], "civitai"), + (None, "3e6c61b0f9471135fc9c6d6a98e98b6d", ["flux_dit", "nexus-gen_adapter"], [FluxDiT, NexusGenAdapter], "civitai"), ] huggingface_model_loader_configs = [ # These configs are provided for detecting model type automatically. diff --git a/diffsynth/models/flux_dit.py b/diffsynth/models/flux_dit.py index ea5ce21..3dd728d 100644 --- a/diffsynth/models/flux_dit.py +++ b/diffsynth/models/flux_dit.py @@ -2,7 +2,7 @@ import torch from .sd3_dit import TimestepEmbeddings, AdaLayerNorm, RMSNorm from einops import rearrange from .tiler import TileWorker -from .utils import init_weights_on_device +from .utils import init_weights_on_device, hash_state_dict_keys def interact_with_ipadapter(hidden_states, q, ip_k, ip_v, scale=1.0): batch_size, num_tokens = hidden_states.shape[0:2] @@ -662,6 +662,9 @@ class FluxDiTStateDictConverter: return state_dict_ def from_civitai(self, state_dict): + if hash_state_dict_keys(state_dict, with_shape=True) == "3e6c61b0f9471135fc9c6d6a98e98b6d": + dit_state_dict = {key.replace("pipe.dit.", ""): value for key, value in state_dict.items() if not key.startswith('adapter.')} + return dit_state_dict rename_dict = { "time_in.in_layer.bias": "time_embedder.timestep_embedder.0.bias", "time_in.in_layer.weight": "time_embedder.timestep_embedder.0.weight", diff --git a/examples/flux/model_inference/Nexus-Gen-Generation.py b/examples/flux/model_inference/Nexus-Gen-Generation.py new file mode 100644 index 0000000..102b7ef --- /dev/null +++ b/examples/flux/model_inference/Nexus-Gen-Generation.py @@ -0,0 +1,21 @@ +import importlib +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + +if importlib.util.find_spec("transformers") is None: + raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") +else: + import transformers + assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==0.49.0, please install it with `pip install transformers==0.49.0`." + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="generation_decoder.bin"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) From 05dba91f79f4b4195100b2a61e407c263ae21265 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Mon, 28 Jul 2025 13:38:01 +0800 Subject: [PATCH 36/51] fix wan2.2 5B --- diffsynth/pipelines/wan_video_new.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 9963aa1..1fa930d 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -646,6 +646,8 @@ class WanVideoPipeline(BasePipeline): # Scheduler inputs_shared["latents"] = self.scheduler.step(noise_pred, self.scheduler.timesteps[progress_id], inputs_shared["latents"]) + if "first_frame_latents" in inputs_shared: + inputs_shared["latents"][:, :, 0:1] = inputs_shared["first_frame_latents"] # VACE (TODO: remove it) if vace_reference_image is not None: @@ -899,7 +901,7 @@ class WanVideoUnit_ImageEmbedderFused(PipelineUnit): image = pipe.preprocess_image(input_image.resize((width, height))).transpose(0, 1) z = pipe.vae.encode([image], device=pipe.device, tiled=tiled, tile_size=tile_size, tile_stride=tile_stride) latents[:, :, 0: 1] = z - return {"latents": latents, "fuse_vae_embedding_in_latents": True} + return {"latents": latents, "fuse_vae_embedding_in_latents": True, "first_frame_latents": z} From 729c512c6656ed608de85fc1b671d09855c21590 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Mon, 28 Jul 2025 15:18:47 +0800 Subject: [PATCH 37/51] bugfix --- diffsynth/pipelines/wan_video_new.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 8b47508..4f62a74 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -706,7 +706,8 @@ class WanVideoUnit_FunReference(PipelineUnit): class WanVideoUnit_FunCameraControl(PipelineUnit): def __init__(self): super().__init__( - input_params=("height", "width", "num_frames", "camera_control_direction", "camera_control_speed", "camera_control_origin", "latents", "input_image") + input_params=("height", "width", "num_frames", "camera_control_direction", "camera_control_speed", "camera_control_origin", "latents", "input_image"), + onload_model_names=("vae",) ) def process(self, pipe: WanVideoPipeline, height, width, num_frames, camera_control_direction, camera_control_speed, camera_control_origin, latents, input_image): @@ -729,6 +730,7 @@ class WanVideoUnit_FunCameraControl(PipelineUnit): input_image = input_image.resize((width, height)) input_latents = pipe.preprocess_video([input_image]) + pipe.load_models_to_device(self.onload_model_names) input_latents = pipe.vae.encode(input_latents, device=pipe.device) y = torch.zeros_like(latents).to(pipe.device) y[:, :, :1] = input_latents From 2861ec4d9f9bbec6eacd93984d85b75a3da5be17 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Mon, 28 Jul 2025 16:18:38 +0800 Subject: [PATCH 38/51] tmp commit for nexus-gen edit --- diffsynth/configs/model_config.py | 7 +- diffsynth/models/flux_dit.py | 4 +- diffsynth/models/nexus_gen.py | 100 ++ diffsynth/models/nexus_gen_ar_model.py | 1143 +++++++++++++++++ diffsynth/models/nexus_gen_projector.py | 359 ++++++ diffsynth/pipelines/flux_image_new.py | 66 + .../flux/model_inference/Nexus-Gen-Editing.py | 34 + .../model_inference/Nexus-Gen-Generation.py | 14 +- 8 files changed, 1721 insertions(+), 6 deletions(-) create mode 100644 diffsynth/models/nexus_gen.py create mode 100644 diffsynth/models/nexus_gen_ar_model.py create mode 100644 diffsynth/models/nexus_gen_projector.py create mode 100644 examples/flux/model_inference/Nexus-Gen-Editing.py diff --git a/diffsynth/configs/model_config.py b/diffsynth/configs/model_config.py index 9fa652d..d6517ba 100644 --- a/diffsynth/configs/model_config.py +++ b/diffsynth/configs/model_config.py @@ -69,7 +69,8 @@ from ..models.flux_value_control import SingleValueEncoder from ..lora.flux_lora import FluxLoraPatcher from ..models.flux_lora_encoder import FluxLoRAEncoder -from ..models.nexus_gen_projector import NexusGenAdapter +from ..models.nexus_gen_projector import NexusGenAdapter, NexusGenImageEmbeddingMerger +from ..models.nexus_gen import NexusGenAutoregressiveModel model_loader_configs = [ # These configs are provided for detecting model type automatically. @@ -153,7 +154,9 @@ model_loader_configs = [ (None, "d30fb9e02b1dbf4e509142f05cf7dd50", ["flux_dit", "step1x_connector"], [FluxDiT, Qwen2Connector], "civitai"), (None, "30143afb2dea73d1ac580e0787628f8c", ["flux_lora_patcher"], [FluxLoraPatcher], "civitai"), (None, "77c2e4dd2440269eb33bfaa0d004f6ab", ["flux_lora_encoder"], [FluxLoRAEncoder], "civitai"), - (None, "3e6c61b0f9471135fc9c6d6a98e98b6d", ["flux_dit", "nexus-gen_adapter"], [FluxDiT, NexusGenAdapter], "civitai"), + (None, "3e6c61b0f9471135fc9c6d6a98e98b6d", ["flux_dit", "nexus_gen_generation_adapter"], [FluxDiT, NexusGenAdapter], "civitai"), + (None, "63c969fd37cce769a90aa781fbff5f81", ["flux_dit", "nexus_gen_editing_adapter"], [FluxDiT, NexusGenImageEmbeddingMerger], "civitai"), + (None, "2bd19e845116e4f875a0a048e27fc219", ["nexus_gen_llm"], [NexusGenAutoregressiveModel], "civitai"), ] huggingface_model_loader_configs = [ # These configs are provided for detecting model type automatically. diff --git a/diffsynth/models/flux_dit.py b/diffsynth/models/flux_dit.py index 3dd728d..0ec9b07 100644 --- a/diffsynth/models/flux_dit.py +++ b/diffsynth/models/flux_dit.py @@ -662,8 +662,8 @@ class FluxDiTStateDictConverter: return state_dict_ def from_civitai(self, state_dict): - if hash_state_dict_keys(state_dict, with_shape=True) == "3e6c61b0f9471135fc9c6d6a98e98b6d": - dit_state_dict = {key.replace("pipe.dit.", ""): value for key, value in state_dict.items() if not key.startswith('adapter.')} + if hash_state_dict_keys(state_dict, with_shape=True) in ["3e6c61b0f9471135fc9c6d6a98e98b6d", "63c969fd37cce769a90aa781fbff5f81"]: + dit_state_dict = {key.replace("pipe.dit.", ""): value for key, value in state_dict.items() if key.startswith('pipe.dit.')} return dit_state_dict rename_dict = { "time_in.in_layer.bias": "time_embedder.timestep_embedder.0.bias", diff --git a/diffsynth/models/nexus_gen.py b/diffsynth/models/nexus_gen.py new file mode 100644 index 0000000..f7a771e --- /dev/null +++ b/diffsynth/models/nexus_gen.py @@ -0,0 +1,100 @@ +import torch +from PIL import Image +from qwen_vl_utils import smart_resize +from transformers import AutoConfig +from .nexus_gen_ar_model import Qwen2_5_VLForConditionalGeneration, Qwen2_5_VLProcessor + + +class NexusGenAutoregressiveModel(torch.nn.Module): + def __init__(self, model_path="models/DiffSynth-Studio/Nexus-GenV2", max_length=1024, max_pixels=262640, dtype=torch.bfloat16, device="cuda"): + super(NexusGenAutoregressiveModel, self).__init__() + self.max_length = max_length + self.max_pixels = max_pixels + model_config = AutoConfig.from_pretrained(model_path) + self.model = Qwen2_5_VLForConditionalGeneration(model_config) + self.processor = Qwen2_5_VLProcessor.from_pretrained(model_path) + + + @staticmethod + def state_dict_converter(): + return NexusGenAutoregressiveModelStateDictConverter() + + def bound_image(self, image, max_pixels=262640): + resized_height, resized_width = smart_resize( + image.height, + image.width, + max_pixels=max_pixels, + ) + return image.resize((resized_width, resized_height)) + + def get_editing_msg(self, instruction): + if '' not in instruction: + instruction = ' ' + instruction + messages = [{"role":"user", "content":instruction}, {"role":"assistant", "content":"Here is the image: "}] + return messages + + def get_generation_msg(self, instruction): + messages = [{"role":"user", "content":instruction}, {"role":"assistant", "content":"Here is an image based on the description: "}] + return messages + + def forward(self, instruction, ref_image=None, num_img_tokens=81): + """ + Generate target embeddings for the given instruction and reference image. + """ + if ref_image is not None: + messages = self.get_editing_msg(instruction) + images = [self.bound_image(ref_image)] + [Image.new(mode='RGB', size=(252, 252), color=(255, 255, 255))] + output_image_embeddings = self.get_target_embeddings(images, messages, self.processor, self.model, num_img_tokens) + else: + messages = self.get_generation_msg(instruction) + images = [Image.new(mode='RGB', size=(252, 252), color=(255, 255, 255))] + output_image_embeddings = self.get_target_embeddings(images, messages, self.processor, self.model, num_img_tokens) + + return output_image_embeddings + + def get_target_embeddings(self, images, messages, processor, model, num_img_tokens=81): + text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=False) + text = text.replace('', '<|vision_start|><|image_pad|><|vision_end|>') + inputs = processor( + text=[text], + images=images, + padding=True, + return_tensors="pt", + ) + inputs = inputs.to(model.device) + + input_embeds = model.model.embed_tokens(inputs['input_ids']) + image_embeds = model.visual(inputs['pixel_values'], grid_thw=inputs['image_grid_thw']) + ground_truth_image_embeds = image_embeds[-num_img_tokens:] + input_image_embeds = image_embeds[:-num_img_tokens] + + image_mask = inputs['input_ids'] == model.config.image_token_id + indices = image_mask.cumsum(dim=1) + input_image_mask = torch.logical_and(indices <= (image_embeds.shape[0] - ground_truth_image_embeds.shape[0]), image_mask) + gt_image_mask = torch.logical_and(image_mask, ~input_image_mask) + input_image_mask = input_image_mask.unsqueeze(-1).expand_as(input_embeds) + input_embeds = input_embeds.masked_scatter(input_image_mask, input_image_embeds) + + image_prefill_embeds = model.image_prefill_embeds( + torch.arange(81, device=model.device).long() + ) + input_embeds = input_embeds.masked_scatter(gt_image_mask.unsqueeze(-1).expand_as(input_embeds), image_prefill_embeds) + + position_ids, _ = model.get_rope_index(inputs['input_ids'], + inputs['image_grid_thw'], + attention_mask=inputs['attention_mask']) + position_ids = position_ids.contiguous() + outputs = model(inputs_embeds=input_embeds, position_ids=position_ids, attention_mask=inputs['attention_mask'], return_dict=True) + output_image_embeddings = outputs.image_embeddings[:, :-1, :] + output_image_embeddings = output_image_embeddings[gt_image_mask[:, 1:]] + return output_image_embeddings, input_image_embeds, inputs['image_grid_thw'] + + +class NexusGenAutoregressiveModelStateDictConverter: + def __init__(self): + pass + + def from_civitai(self, state_dict): + state_dict = {"model." + key: value for key, value in state_dict.items()} + return state_dict + \ No newline at end of file diff --git a/diffsynth/models/nexus_gen_ar_model.py b/diffsynth/models/nexus_gen_ar_model.py new file mode 100644 index 0000000..d5a2973 --- /dev/null +++ b/diffsynth/models/nexus_gen_ar_model.py @@ -0,0 +1,1143 @@ +import os +import re +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Tuple, Union + +import torch +import torch.nn as nn +from torch.nn import CrossEntropyLoss + +from transformers.cache_utils import Cache +from transformers.generation import GenerationMixin, LogitsProcessorList, StoppingCriteriaList, GenerationConfig, GenerateDecoderOnlyOutput, GenerateEncoderDecoderOutput +from transformers.utils import add_start_docstrings_to_model_forward, logging, replace_return_docstrings +from transformers.modeling_outputs import ModelOutput +from transformers.models.qwen2_5_vl.configuration_qwen2_5_vl import Qwen2_5_VLConfig +from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import ( + Qwen2_5_VisionTransformerPretrainedModel, + Qwen2_5_VLModel, + Qwen2_5_VLPreTrainedModel, + QWEN2_5_VL_INPUTS_DOCSTRING, + ) + +from transformers.feature_extraction_utils import BatchFeature +from transformers.image_utils import ImageInput, VideoInput +from transformers.processing_utils import ProcessingKwargs, ProcessorMixin, Unpack, VideosKwargs +from transformers.tokenization_utils_base import PreTokenizedInput, TextInput + +GenerateNonBeamOutput = Union[GenerateDecoderOnlyOutput, GenerateEncoderDecoderOutput] + +logger = logging.get_logger(__name__) + +_CONFIG_FOR_DOC = "Qwen2_5_VLConfig" + + +@dataclass +class Qwen2_5_VLCausalLMOutputWithPast(ModelOutput): + """ + Base class for Qwen2_5_VL causal language model (or autoregressive) outputs. + + Args: + loss (`torch.FloatTensor` of shape `(1,)`, *optional*, returned when `labels` is provided): + Language modeling loss (for next-token prediction). + logits (`torch.FloatTensor` of shape `(batch_size, sequence_length, config.vocab_size)`): + Prediction scores of the language modeling head (scores for each vocabulary token before SoftMax). + past_key_values (`tuple(tuple(torch.FloatTensor))`, *optional*, returned when `use_cache=True` is passed or when `config.use_cache=True`): + Tuple of `tuple(torch.FloatTensor)` of length `config.n_layers`, with each tuple having 2 tensors of shape + `(batch_size, num_heads, sequence_length, embed_size_per_head)`) + + Contains pre-computed hidden-states (key and values in the self-attention blocks) that can be used (see + `past_key_values` input) to speed up sequential decoding. + hidden_states (`tuple(torch.FloatTensor)`, *optional*, returned when `output_hidden_states=True` is passed or when `config.output_hidden_states=True`): + Tuple of `torch.FloatTensor` (one for the output of the embeddings, if the model has an embedding layer, + + one for the output of each layer) of shape `(batch_size, sequence_length, hidden_size)`. + + Hidden-states of the model at the output of each layer plus the optional initial embedding outputs. + attentions (`tuple(torch.FloatTensor)`, *optional*, returned when `output_attentions=True` is passed or when `config.output_attentions=True`): + Tuple of `torch.FloatTensor` (one for each layer) of shape `(batch_size, num_heads, sequence_length, + sequence_length)`. + + Attentions weights after the attention softmax, used to compute the weighted average in the self-attention + heads. + rope_deltas (`torch.LongTensor` of shape `(batch_size, )`, *optional*): + The rope index difference between sequence length and multimodal rope. + """ + + loss: Optional[torch.FloatTensor] = None + logits: torch.FloatTensor = None + image_embeddings: torch.FloatTensor = None + past_key_values: Optional[List[torch.FloatTensor]] = None + hidden_states: Optional[Tuple[torch.FloatTensor]] = None + attentions: Optional[Tuple[torch.FloatTensor]] = None + rope_deltas: Optional[torch.LongTensor] = None + + +class Qwen2_5_VLForConditionalGeneration(Qwen2_5_VLPreTrainedModel, GenerationMixin): + _tied_weights_keys = ["lm_head.weight"] + config_class = Qwen2_5_VLConfig + _no_split_modules = ["Qwen2_5_VLDecoderLayer", "Qwen2_5_VLVisionBlock"] + + def __init__(self, config): + super().__init__(config) + self.visual = Qwen2_5_VisionTransformerPretrainedModel._from_config(config.vision_config) + self.model = Qwen2_5_VLModel(config) + self.vocab_size = config.vocab_size + self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) + self.vision_head = nn.Linear(config.hidden_size, config.hidden_size, bias=False) + self.rope_deltas = None # cache rope_deltas here + self.image_prefill_embeds = nn.Embedding(81, config.hidden_size) + + # Initialize weights and apply final processing + self.post_init() + + def get_input_embeddings(self): + return self.model.embed_tokens + + def set_input_embeddings(self, value): + self.model.embed_tokens = value + + def get_output_embeddings(self): + return self.lm_head + + def set_output_embeddings(self, new_embeddings): + self.lm_head = new_embeddings + + def set_decoder(self, decoder): + self.model = decoder + + def get_decoder(self): + return self.model + + def get_rope_index( + self, + input_ids: Optional[torch.LongTensor] = None, + image_grid_thw: Optional[torch.LongTensor] = None, + video_grid_thw: Optional[torch.LongTensor] = None, + second_per_grid_ts: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Calculate the 3D rope index based on image and video's temporal, height and width in LLM. + + Explanation: + Each embedding sequence contains vision embedding and text embedding or just contains text embedding. + + For pure text embedding sequence, the rotary position embedding has no difference with modern LLMs. + Examples: + input_ids: [T T T T T], here T is for text. + temporal position_ids: [0, 1, 2, 3, 4] + height position_ids: [0, 1, 2, 3, 4] + width position_ids: [0, 1, 2, 3, 4] + + For vision and text embedding sequence, we calculate 3D rotary position embedding for vision part + and 1D rotary position embedding for text part. + Examples: + Temporal (Time): 3 patches, representing different segments of the video in time. + Height: 2 patches, dividing each frame vertically. + Width: 2 patches, dividing each frame horizontally. + We also have some important parameters: + fps (Frames Per Second): The video's frame rate, set to 1. This means one frame is processed each second. + tokens_per_second: This is a crucial parameter. It dictates how many "time-steps" or "temporal tokens" are conceptually packed into a one-second interval of the video. In this case, we have 25 tokens per second. So each second of the video will be represented with 25 separate time points. It essentially defines the temporal granularity. + temporal_patch_size: The number of frames that compose one temporal patch. Here, it's 2 frames. + interval: The step size for the temporal position IDs, calculated as tokens_per_second * temporal_patch_size / fps. In this case, 25 * 2 / 1 = 50. This means that each temporal patch will be have a difference of 50 in the temporal position IDs. + input_ids: [V V V V V V V V V V V V T T T T T], here V is for vision. + vision temporal position_ids: [0, 0, 0, 0, 50, 50, 50, 50, 100, 100, 100, 100] + vision height position_ids: [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] + vision width position_ids: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + text temporal position_ids: [101, 102, 103, 104, 105] + text height position_ids: [101, 102, 103, 104, 105] + text width position_ids: [101, 102, 103, 104, 105] + Here we calculate the text start position_ids as the max vision position_ids plus 1. + + Args: + input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): + Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide + it. + image_grid_thw (`torch.LongTensor` of shape `(num_images, 3)`, *optional*): + The temporal, height and width of feature shape of each image in LLM. + video_grid_thw (`torch.LongTensor` of shape `(num_videos, 3)`, *optional*): + The temporal, height and width of feature shape of each video in LLM. + second_per_grid_ts (`torch.Tensor` of shape `(num_videos)`, *optional*): + The time interval (in seconds) for each grid along the temporal dimension in the 3D position IDs. + attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`: + + - 1 for tokens that are **not masked**, + - 0 for tokens that are **masked**. + + Returns: + position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`) + mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`) + """ + spatial_merge_size = self.config.vision_config.spatial_merge_size + image_token_id = self.config.image_token_id + video_token_id = self.config.video_token_id + vision_start_token_id = self.config.vision_start_token_id + mrope_position_deltas = [] + if input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None): + total_input_ids = input_ids + if attention_mask is None: + attention_mask = torch.ones_like(total_input_ids) + position_ids = torch.ones( + 3, + input_ids.shape[0], + input_ids.shape[1], + dtype=input_ids.dtype, + device=input_ids.device, + ) + image_index, video_index = 0, 0 + attention_mask = attention_mask.to(total_input_ids.device) + for i, input_ids in enumerate(total_input_ids): + input_ids = input_ids[attention_mask[i] == 1] + image_nums, video_nums = 0, 0 + vision_start_indices = torch.argwhere(input_ids == vision_start_token_id).squeeze(1) + vision_tokens = input_ids[vision_start_indices + 1] + image_nums = (vision_tokens == image_token_id).sum() + video_nums = (vision_tokens == video_token_id).sum() + input_tokens = input_ids.tolist() + llm_pos_ids_list: list = [] + st = 0 + remain_images, remain_videos = image_nums, video_nums + for _ in range(image_nums + video_nums): + if image_token_id in input_tokens and remain_images > 0: + ed_image = input_tokens.index(image_token_id, st) + else: + ed_image = len(input_tokens) + 1 + if video_token_id in input_tokens and remain_videos > 0: + ed_video = input_tokens.index(video_token_id, st) + else: + ed_video = len(input_tokens) + 1 + if ed_image < ed_video: + t, h, w = ( + image_grid_thw[image_index][0], + image_grid_thw[image_index][1], + image_grid_thw[image_index][2], + ) + second_per_grid_t = 0 + image_index += 1 + remain_images -= 1 + ed = ed_image + + else: + t, h, w = ( + video_grid_thw[video_index][0], + video_grid_thw[video_index][1], + video_grid_thw[video_index][2], + ) + if second_per_grid_ts is not None: + second_per_grid_t = second_per_grid_ts[video_index] + else: + second_per_grid_t = 1.0 + video_index += 1 + remain_videos -= 1 + ed = ed_video + llm_grid_t, llm_grid_h, llm_grid_w = ( + t.item(), + h.item() // spatial_merge_size, + w.item() // spatial_merge_size, + ) + text_len = ed - st + + st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 + llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + + range_tensor = torch.arange(llm_grid_t).view(-1, 1) + expanded_range = range_tensor.expand(-1, llm_grid_h * llm_grid_w) + + time_tensor = expanded_range * second_per_grid_t * self.config.vision_config.tokens_per_second + + time_tensor_long = time_tensor.long() + t_index = time_tensor_long.flatten() + + h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten() + w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten() + llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx) + st = ed + llm_grid_t * llm_grid_h * llm_grid_w + + if st < len(input_tokens): + st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0 + text_len = len(input_tokens) - st + llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx) + + llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1) + position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(position_ids.device) + mrope_position_deltas.append(llm_positions.max() + 1 - len(total_input_ids[i])) + mrope_position_deltas = torch.tensor(mrope_position_deltas, device=input_ids.device).unsqueeze(1) + return position_ids, mrope_position_deltas + else: + if attention_mask is not None: + position_ids = attention_mask.long().cumsum(-1) - 1 + position_ids.masked_fill_(attention_mask == 0, 1) + position_ids = position_ids.unsqueeze(0).expand(3, -1, -1).to(attention_mask.device) + max_position_ids = position_ids.max(0, keepdim=False)[0].max(-1, keepdim=True)[0] + mrope_position_deltas = max_position_ids + 1 - attention_mask.shape[-1] + else: + position_ids = ( + torch.arange(input_ids.shape[1], device=input_ids.device) + .view(1, 1, -1) + .expand(3, input_ids.shape[0], -1) + ) + mrope_position_deltas = torch.zeros( + [input_ids.shape[0], 1], + device=input_ids.device, + dtype=input_ids.dtype, + ) + + return position_ids, mrope_position_deltas + + @add_start_docstrings_to_model_forward(QWEN2_5_VL_INPUTS_DOCSTRING) + @replace_return_docstrings(output_type=Qwen2_5_VLCausalLMOutputWithPast, config_class=_CONFIG_FOR_DOC) + 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, + labels: Optional[torch.LongTensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + pixel_values: Optional[torch.Tensor] = None, + pixel_values_videos: Optional[torch.FloatTensor] = None, + image_grid_thw: Optional[torch.LongTensor] = None, + video_grid_thw: Optional[torch.LongTensor] = None, + rope_deltas: Optional[torch.LongTensor] = None, + cache_position: Optional[torch.LongTensor] = None, + second_per_grid_ts: Optional[torch.Tensor] = None, + image_embeddings: Optional[torch.Tensor] = None, + token_loss_weight: Optional[float] = 0.1, + img_loss_weight: Optional[float] = 1.0, + ) -> Union[Tuple, Qwen2_5_VLCausalLMOutputWithPast]: + r""" + labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*): + Labels for computing the masked language modeling loss. Indices should either be in `[0, ..., + config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored + (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`. + + Returns: + + Example: + + ```python + >>> from PIL import Image + >>> import requests + >>> from transformers import AutoProcessor, Qwen2_5_VLForConditionalGeneration + + >>> model = Qwen2_5_VLForConditionalGeneration.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct") + >>> processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-7B-Instruct") + + >>> messages = [ + { + "role": "user", + "content": [ + {"type": "image"}, + {"type": "text", "text": "What is shown in this image?"}, + ], + }, + ] + >>> url = "https://www.ilankelman.org/stopsigns/australia.jpg" + >>> image = Image.open(requests.get(url, stream=True).raw) + + >>> text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + >>> inputs = processor(text=[text], images=[image], vision_infos=[vision_infos]) + + >>> # Generate + >>> generate_ids = model.generate(inputs.input_ids, max_length=30) + >>> tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0] + "The image shows a street scene with a red stop sign in the foreground. In the background, there is a large red gate with Chinese characters ..." + ```""" + + 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 + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + if inputs_embeds is None: + # test feature + inputs_embeds = self.model.embed_tokens(input_ids) + # for image encoding and training + if pixel_values is not None: + pixel_values = pixel_values.type(self.visual.dtype) + image_embeds = self.visual(pixel_values, grid_thw=image_grid_thw) + n_image_tokens = (input_ids == self.config.image_token_id).sum().item() + n_image_features = image_embeds.shape[0] + if n_image_tokens != n_image_features: + raise ValueError( + f"Image features and image tokens do not match: tokens: {n_image_tokens}, features {n_image_features}" + ) + + mask = input_ids == self.config.image_token_id + mask_unsqueezed = mask.unsqueeze(-1) + mask_expanded = mask_unsqueezed.expand_as(inputs_embeds) + image_mask = mask_expanded.to(inputs_embeds.device) + + image_embeds = image_embeds.to(inputs_embeds.device, inputs_embeds.dtype) + inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds) + + if pixel_values_videos is not None: + pixel_values_videos = pixel_values_videos.type(self.visual.dtype) + video_embeds = self.visual(pixel_values_videos, grid_thw=video_grid_thw) + n_video_tokens = (input_ids == self.config.video_token_id).sum().item() + n_video_features = video_embeds.shape[0] + if n_video_tokens != n_video_features: + raise ValueError( + f"Video features and video tokens do not match: tokens: {n_video_tokens}, features {n_video_features}" + ) + + mask = input_ids == self.config.video_token_id + mask_unsqueezed = mask.unsqueeze(-1) + mask_expanded = mask_unsqueezed.expand_as(inputs_embeds) + video_mask = mask_expanded.to(inputs_embeds.device) + + video_embeds = video_embeds.to(inputs_embeds.device, inputs_embeds.dtype) + inputs_embeds = inputs_embeds.masked_scatter(video_mask, video_embeds) + + if attention_mask is not None: + attention_mask = attention_mask.to(inputs_embeds.device) + + # if we get 4D attention mask we cannot calculate rope deltas anymore. TODO @raushan fixme + if position_ids is None and (attention_mask is None or attention_mask.ndim == 2): + # calculate RoPE index once per generation in the pre-fill stage only + if ( + (cache_position is not None and cache_position[0] == 0) + or self.rope_deltas is None + or (past_key_values is None or past_key_values.get_seq_length() == 0) + ): + position_ids, rope_deltas = self.get_rope_index( + input_ids, + image_grid_thw, + video_grid_thw, + second_per_grid_ts, + attention_mask, + ) + self.rope_deltas = rope_deltas + # then use the prev pre-calculated rope-deltas to get the correct position ids + else: + batch_size, seq_length, _ = inputs_embeds.shape + delta = ( + (cache_position[0] + self.rope_deltas).to(inputs_embeds.device) + if cache_position is not None + else 0 + ) + position_ids = torch.arange(seq_length, device=inputs_embeds.device) + position_ids = position_ids.view(1, -1).expand(batch_size, -1) + if cache_position is not None: # otherwise `deltas` is an int `0` + delta = delta.repeat_interleave(batch_size // delta.shape[0], dim=0) + position_ids = position_ids.add(delta) + position_ids = position_ids.unsqueeze(0).expand(3, -1, -1) + # position_ids [3, B, L] + + outputs = self.model( + input_ids=None, + position_ids=position_ids, + attention_mask=attention_mask, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + cache_position=cache_position, + ) + + hidden_states = outputs[0] + logits = self.lm_head(hidden_states) + image_embeds = self.vision_head(hidden_states) + + loss = None + if labels is not None: + # Upcast to float if we need to compute the loss to avoid potential precision issues + # prepare labels for logits + logits_labels = labels.clone().detach() + image_tokens = (labels == self.config.image_token_id) + logits_labels[image_tokens] = -100 + + logits = logits.float() + # Shift so that tokens < n predict n + shift_logits = logits[..., :-1, :].contiguous() + shift_labels = logits_labels[..., 1:].contiguous() + # Flatten the tokens + loss_fct = CrossEntropyLoss() + shift_logits = shift_logits.view(-1, self.config.vocab_size) + shift_labels = shift_labels.view(-1) + # Enable model parallelism + shift_labels = shift_labels.to(shift_logits.device) + loss = loss_fct(shift_logits, shift_labels) * token_loss_weight + + shift_image_tokens_2d = (labels[..., 1:].contiguous() == self.config.image_token_id) # (B, L-1) + shifted_image_embeds = image_embeds[:, :-1, :].contiguous() # (B, L-1, D) + masked_image_embeds = shifted_image_embeds[shift_image_tokens_2d] # (num_image_tokens, D) + + mse_loss_fct = nn.MSELoss() + mse_loss_fct = mse_loss_fct.to(shift_logits.device) + if image_embeddings is None: + image_embeddings = torch.zeros_like(masked_image_embeds) + img_loss = mse_loss_fct(masked_image_embeds, image_embeddings) + + cos_sim = torch.cosine_similarity( + masked_image_embeds, + image_embeddings, + dim=-1 + ) + cos_loss = (1 - cos_sim).mean() + img_loss = 0.5 * img_loss + 0.5 * cos_loss + # fix nan for empty image tokens + if image_embeddings.size(0) == 0: + img_loss = img_loss.nan_to_num(0.0) + # combine the loss + loss = loss + img_loss_weight * img_loss + + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return Qwen2_5_VLCausalLMOutputWithPast( + loss=loss, + logits=logits, + image_embeddings=image_embeds, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + rope_deltas=self.rope_deltas, + ) + + + + def _sample( + self, + input_ids: torch.LongTensor, + logits_processor: LogitsProcessorList, + stopping_criteria: StoppingCriteriaList, + generation_config: GenerationConfig, + synced_gpus: bool, + streamer: Optional["BaseStreamer"], + **model_kwargs, + ) -> Union[GenerateNonBeamOutput, torch.LongTensor]: + r""" + Generates sequences of token ids for models with a language modeling head using **multinomial sampling** and + can be used for text-decoder, text-to-text, speech-to-text, and vision-to-text models. + + Parameters: + input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): + The sequence used as a prompt for the generation. + logits_processor (`LogitsProcessorList`): + An instance of [`LogitsProcessorList`]. List of instances of class derived from [`LogitsProcessor`] + used to modify the prediction scores of the language modeling head applied at each generation step. + stopping_criteria (`StoppingCriteriaList`): + An instance of [`StoppingCriteriaList`]. List of instances of class derived from [`StoppingCriteria`] + used to tell if the generation loop should stop. + generation_config ([`~generation.GenerationConfig`]): + The generation configuration to be used as parametrization of the decoding method. + synced_gpus (`bool`): + Whether to continue running the while loop until max_length (needed to avoid deadlocking with + `FullyShardedDataParallel` and DeepSpeed ZeRO Stage 3). + streamer (`BaseStreamer`, *optional*): + Streamer object that will be used to stream the generated sequences. Generated tokens are passed + through `streamer.put(token_ids)` and the streamer is responsible for any further processing. + model_kwargs: + Additional model specific kwargs will be forwarded to the `forward` function of the model. If model is + an encoder-decoder model the kwargs should include `encoder_outputs`. + + Return: + [`~generation.GenerateDecoderOnlyOutput`], [`~generation.GenerateEncoderDecoderOutput`] or `torch.LongTensor`: + A `torch.LongTensor` containing the generated tokens (default behaviour) or a + [`~generation.GenerateDecoderOnlyOutput`] if `model.config.is_encoder_decoder=False` and + `return_dict_in_generate=True` or a [`~generation.GenerateEncoderDecoderOutput`] if + `model.config.is_encoder_decoder=True`. + """ + # init values + pad_token_id = generation_config._pad_token_tensor + output_attentions = generation_config.output_attentions + output_hidden_states = generation_config.output_hidden_states + output_scores = generation_config.output_scores + output_logits = generation_config.output_logits + return_dict_in_generate = generation_config.return_dict_in_generate + max_length = generation_config.max_length + has_eos_stopping_criteria = any(hasattr(criteria, "eos_token_id") for criteria in stopping_criteria) + do_sample = generation_config.do_sample + + # init attention / hidden states / scores tuples + scores = () if (return_dict_in_generate and output_scores) else None + raw_logits = () if (return_dict_in_generate and output_logits) else None + decoder_attentions = () if (return_dict_in_generate and output_attentions) else None + cross_attentions = () if (return_dict_in_generate and output_attentions) else None + decoder_hidden_states = () if (return_dict_in_generate and output_hidden_states) else None + + # if model is an encoder-decoder, retrieve encoder attention weights and hidden states + if return_dict_in_generate and self.config.is_encoder_decoder: + encoder_attentions = model_kwargs["encoder_outputs"].get("attentions") if output_attentions else None + encoder_hidden_states = ( + model_kwargs["encoder_outputs"].get("hidden_states") if output_hidden_states else None + ) + + # keep track of which sequences are already finished + batch_size, cur_len = input_ids.shape + this_peer_finished = False + unfinished_sequences = torch.ones(batch_size, dtype=torch.long, device=input_ids.device) + model_kwargs = self._get_initial_cache_position(input_ids, model_kwargs) + + model_forward = self.__call__ + if isinstance(model_kwargs.get("past_key_values"), Cache): + is_compileable = model_kwargs["past_key_values"].is_compileable and self._supports_static_cache + is_compileable = is_compileable and not self.generation_config.disable_compile + if is_compileable and ( + self.device.type == "cuda" or generation_config.compile_config._compile_all_devices + ): + os.environ["TOKENIZERS_PARALLELISM"] = "0" + model_forward = self.get_compiled_call(generation_config.compile_config) + + is_prefill = True + is_sampling_img = input_ids[:, -1] == self.config.vision_start_token_id + generation_image_grid_thw = model_kwargs.pop("generation_image_grid_thw", self.get_default_image_grid_thw()) + num_img_tokens = self.get_num_image_tokens(generation_image_grid_thw) + output_image_embeddings = [] + while self._has_unfinished_sequences( + this_peer_finished, synced_gpus, device=input_ids.device, cur_len=cur_len, max_length=max_length + ): + # prepare model inputs + model_inputs = self.prepare_inputs_for_generation(input_ids, **model_kwargs) + + # prepare prefilled embeds + model_inputs.update(self.prepare_prefilled_image_embeds(len(output_image_embeddings), num_img_tokens, is_sampling_img, **model_kwargs)) + + # parse position_ids from model_kwargs + model_inputs.update(self.prepare_image_position_ids(input_ids, generation_image_grid_thw, is_sampling_img, **model_kwargs)) + + # prepare variable output controls (note: some models won't accept all output controls) + model_inputs.update({"output_attentions": output_attentions} if output_attentions else {}) + model_inputs.update({"output_hidden_states": output_hidden_states} if output_hidden_states else {}) + + if is_prefill: + outputs = self(**model_inputs, return_dict=True) + is_prefill = False + else: + outputs = model_forward(**model_inputs, return_dict=True) + + # synced_gpus: don't waste resources running the code we don't need; kwargs must be updated before skipping + model_kwargs = self._update_model_kwargs_for_generation( + outputs, + model_kwargs, + is_encoder_decoder=self.config.is_encoder_decoder, + ) + # TODO: support batch image sampling + if bool(is_sampling_img) and len(output_image_embeddings) < num_img_tokens: + output_image_embeddings.append(outputs.image_embeddings[:, -1, :].unsqueeze(1)) + + if synced_gpus and this_peer_finished: + continue + # Clone is needed to avoid keeping a hanging ref to outputs.logits which may be very large for first iteration + # (the clone itself is always small) + next_token_logits = outputs.logits[:, -1, :].clone().float() + next_token_logits = next_token_logits.to(input_ids.device) + + # do not sample token + next_token_logits[:, self.config.vision_end_token_id] = -float('inf') + # pre-process distribution + next_token_scores = logits_processor(input_ids, next_token_logits) + # Store scores, attentions and hidden_states when required + if return_dict_in_generate: + if output_scores: + scores += (next_token_scores,) + if output_logits: + raw_logits += (next_token_logits,) + if output_attentions: + decoder_attentions += ( + (outputs.decoder_attentions,) if self.config.is_encoder_decoder else (outputs.attentions,) + ) + if self.config.is_encoder_decoder: + cross_attentions += (outputs.cross_attentions,) + + if output_hidden_states: + decoder_hidden_states += ( + (outputs.decoder_hidden_states,) + if self.config.is_encoder_decoder + else (outputs.hidden_states,) + ) + + # token selection + if do_sample: + probs = nn.functional.softmax(next_token_scores, dim=-1) + # TODO (joao): this OP throws "skipping cudagraphs due to ['incompatible ops']", find solution + next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1) + # while not bool(is_sampling_img) and torch.any(next_tokens == self.config.vision_end_token_id): + # probs[:, self.config.vision_end_token_id] = 0 + # next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1) + else: + next_tokens = torch.argmax(next_token_scores, dim=-1) + + # finished sentences should have their next token be a padding token + if has_eos_stopping_criteria: + next_tokens = next_tokens * unfinished_sequences + pad_token_id * (1 - unfinished_sequences) + + #TODO: support batch image sample + if num_img_tokens is not None: + cur_img_tokens = (input_ids == self.config.vision_start_token_id).flip(dims=[1]).float().argmax(dim=1) + # check whether is sampling images + is_end_img = torch.logical_and(cur_img_tokens == num_img_tokens, is_sampling_img) + is_sampling_img = torch.logical_and(is_sampling_img, cur_img_tokens < num_img_tokens) + next_tokens[is_sampling_img] = self.config.image_token_id + # check whether to end sampling images + next_tokens[is_end_img] = self.config.vision_end_token_id + else: + # check whether to end sampling images + is_sampling_img = torch.logical_and(is_sampling_img, (next_tokens != self.config.vision_end_token_id)) + # replace the next token with the image token if is sampling image + next_tokens[is_sampling_img] = self.config.image_token_id + # check whether to start sampling images + is_sampling_img = torch.logical_or(is_sampling_img, (next_tokens == self.config.vision_start_token_id)) + + # update generated ids, model inputs, and length for next step + input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1) + + if streamer is not None: + streamer.put(next_tokens.cpu()) + + unfinished_sequences = unfinished_sequences & ~stopping_criteria(input_ids, scores) + this_peer_finished = unfinished_sequences.max() == 0 + cur_len += 1 + + # This is needed to properly delete outputs.logits which may be very large for first iteration + # Otherwise a reference to outputs is kept which keeps the logits alive in the next iteration + del outputs + + if streamer is not None: + streamer.end() + + # output the image embeddings + output_image_embeddings = torch.cat(output_image_embeddings, dim=1) if len(output_image_embeddings) > 0 else None + + if return_dict_in_generate: + return GenerateDecoderOnlyAll2AllOutput( + sequences=input_ids, + scores=scores, + logits=raw_logits, + attentions=decoder_attentions, + hidden_states=decoder_hidden_states, + past_key_values=model_kwargs.get("past_key_values"), + output_image_embeddings=output_image_embeddings, + ) + else: + return input_ids + + + def prepare_prefilled_image_embeds(self, cur_image_tokens, num_img_tokens, is_sampling_img, **model_kwargs): + if cur_image_tokens == 0 or cur_image_tokens > num_img_tokens or not bool(is_sampling_img): + return {} + # TODO: support batch image sample + image_idx = torch.tensor([cur_image_tokens-1]).to(self.device).long().unsqueeze(0) + inputs_embeds = self.image_prefill_embeds(image_idx) + return {"inputs_embeds": inputs_embeds} + + + def get_default_image_grid_thw(self,): + return torch.tensor([[1, 18, 18]]).to(self.device) + + + def get_num_image_tokens(self, image_grid_thw): + return int(torch.prod(image_grid_thw, dim=1).sum() // 4) + + + def _validate_model_kwargs(self, model_kwargs: Dict[str, Any]): + num_img_tokens = model_kwargs.pop("generation_image_grid_thw", None) + super()._validate_model_kwargs(model_kwargs) + model_kwargs["generation_image_grid_thw"] = num_img_tokens + + def prepare_image_position_ids(self, input_ids, generation_image_grid_thw, is_sampling_img, **model_kwargs): + # Overwritten -- prepare position_ids for image tokens + cur_img_tokens = int((input_ids == self.config.vision_start_token_id).flip(dims=[1]).float().argmax(dim=1)) + # TODO: support batch image sample + if cur_img_tokens > 0 and bool(is_sampling_img): + image_grid_thw = generation_image_grid_thw + if model_kwargs.get('image_grid_thw') is not None: + image_grid_thw = torch.cat([model_kwargs.get('image_grid_thw'), image_grid_thw]) + remaining_img_tokens = self.get_num_image_tokens(generation_image_grid_thw) - cur_img_tokens + padding_ids = input_ids.new_full((1, remaining_img_tokens), fill_value=self.config.image_token_id) + padded_ids = torch.cat([input_ids, padding_ids], dim=1) + position_ids, _ = self.get_rope_index(padded_ids, image_grid_thw, None, None) + if model_kwargs.get("use_cache", True): + position_ids = position_ids[:, :, input_ids.shape[1] - 1].unsqueeze(-1) + else: + position_ids = position_ids[:, :, :input_ids.shape[1]] + return {"position_ids": position_ids} + return {} + + def prepare_inputs_for_generation( + self, + input_ids, + past_key_values=None, + attention_mask=None, + inputs_embeds=None, + cache_position=None, + position_ids=None, + use_cache=True, + pixel_values=None, + pixel_values_videos=None, + image_grid_thw=None, + video_grid_thw=None, + second_per_grid_ts=None, + image_embeddings=None, + **kwargs, + ): + # Overwritten -- in specific circumstances we don't want to forward image inputs to the model + + model_inputs = super().prepare_inputs_for_generation( + input_ids, + past_key_values=past_key_values, + attention_mask=attention_mask, + inputs_embeds=inputs_embeds, + cache_position=cache_position, + position_ids=position_ids, + pixel_values=pixel_values, + pixel_values_videos=pixel_values_videos, + image_grid_thw=image_grid_thw, + video_grid_thw=video_grid_thw, + second_per_grid_ts=second_per_grid_ts, + use_cache=use_cache, + **kwargs, + ) + + # Qwen2-5-VL position_ids are prepared with rope_deltas in forward + model_inputs["position_ids"] = None + + if cache_position[0] != 0: + model_inputs["pixel_values"] = None + model_inputs["pixel_values_videos"] = None + return model_inputs + + def _get_image_nums_and_video_nums( + self, + input_ids: Optional[torch.LongTensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Get the number of images and videos for each sample to calculate the separation length of the sample tensor. + These parameters are not passed through the processor to avoid unpredictable impacts from interface modifications. + + Args: + input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`): + Indices of input sequence tokens in the vocabulary. + + Returns: + image_nums (`torch.LongTensor` of shape `(batch_size, num_images_sample)`) + video_nums (`torch.LongTensor` of shape `(batch_size, num_videos_sample)`) + """ + image_token_id = self.config.image_token_id + video_token_id = self.config.video_token_id + vision_start_token_id = self.config.vision_start_token_id + + vision_start_mask = input_ids == vision_start_token_id + vision_first_mask = torch.roll(vision_start_mask, shifts=1, dims=1) + image_mask = input_ids == image_token_id + video_mask = input_ids == video_token_id + image_nums = torch.sum(vision_first_mask & image_mask, dim=1) + video_nums = torch.sum(vision_first_mask & video_mask, dim=1) + + return image_nums, video_nums + + def _expand_inputs_for_generation( + self, + expand_size: int = 1, + is_encoder_decoder: bool = False, + input_ids: Optional[torch.LongTensor] = None, + **model_kwargs, + ) -> Tuple[torch.LongTensor, Dict[str, Any]]: + # Overwritten -- Support for expanding tensors without a batch size dimension + # e.g., pixel_values, image_grid_thw, pixel_values_videos, video_grid_thw, second_per_grid_t + # pixel_values.shape[0] is sum(seqlen_images for samples) + # image_grid_thw.shape[0] is sum(num_images for samples) + + if expand_size == 1: + return input_ids, model_kwargs + + visual_keys = ["pixel_values", "image_grid_thw", "pixel_values_videos", "video_grid_thw", "second_per_grid_ts"] + + def _expand_dict_for_generation_visual(dict_to_expand): + image_grid_thw = model_kwargs.get("image_grid_thw", None) + video_grid_thw = model_kwargs.get("video_grid_thw", None) + image_nums, video_nums = self._get_image_nums_and_video_nums(input_ids) + + def _repeat_interleave_samples(x, lengths, repeat_times): + samples = torch.split(x, lengths) + repeat_args = [repeat_times] + [1] * (x.dim() - 1) + result = torch.cat([sample.repeat(*repeat_args) for sample in samples], dim=0) + return result + + for key in dict_to_expand: + if key == "pixel_values": + # split images into samples + samples = torch.split(image_grid_thw, list(image_nums)) + # compute the sequence length of images for each sample + lengths = [torch.prod(sample, dim=1).sum() for sample in samples] + dict_to_expand[key] = _repeat_interleave_samples( + dict_to_expand[key], lengths=lengths, repeat_times=expand_size + ) + elif key == "image_grid_thw": + # get the num of images for each sample + lengths = list(image_nums) + dict_to_expand[key] = _repeat_interleave_samples( + dict_to_expand[key], lengths=lengths, repeat_times=expand_size + ) + elif key == "pixel_values_videos": + samples = torch.split(video_grid_thw, list(video_nums)) + lengths = [torch.prod(sample, dim=1).sum() for sample in samples] + dict_to_expand[key] = _repeat_interleave_samples( + dict_to_expand[key], lengths=lengths, repeat_times=expand_size + ) + elif key == "video_grid_thw": + lengths = list(video_nums) + dict_to_expand[key] = _repeat_interleave_samples( + dict_to_expand[key], lengths=lengths, repeat_times=expand_size + ) + elif key == "second_per_grid_ts": + if not isinstance(dict_to_expand[key], list): + raise TypeError( + f"Expected value for key '{key}' to be a list, but got {type(dict_to_expand[key])} instead." + ) + tensor = torch.tensor(dict_to_expand[key]) + lengths = list(video_nums) + tensor = _repeat_interleave_samples(tensor, lengths=lengths, repeat_times=expand_size) + dict_to_expand[key] = tensor.tolist() + return dict_to_expand + + def _expand_dict_for_generation(dict_to_expand): + for key in dict_to_expand: + if ( + key != "cache_position" + and dict_to_expand[key] is not None + and isinstance(dict_to_expand[key], torch.Tensor) + and key not in visual_keys + ): + dict_to_expand[key] = dict_to_expand[key].repeat_interleave(expand_size, dim=0) + return dict_to_expand + + # input_ids is required for expanding visual inputs + # If input_ids is unavailable, visual inputs will not be used; therefore, there is no need to expand visual inputs. + if input_ids is not None and input_ids.numel() != 0: + model_kwargs = _expand_dict_for_generation_visual(model_kwargs) + + if input_ids is not None: + input_ids = input_ids.repeat_interleave(expand_size, dim=0) + + model_kwargs = _expand_dict_for_generation(model_kwargs) + + if is_encoder_decoder: + if model_kwargs.get("encoder_outputs") is None: + raise ValueError("If `is_encoder_decoder` is True, make sure that `encoder_outputs` is defined.") + model_kwargs["encoder_outputs"] = _expand_dict_for_generation(model_kwargs["encoder_outputs"]) + + return input_ids, model_kwargs + + +__all__ = ["Qwen2_5_VLForConditionalGeneration", "Qwen2_5_VLModel", "Qwen2_5_VLPreTrainedModel"] + + + +class Qwen2_5_VLVideosProcessorKwargs(VideosKwargs, total=False): + fps: Union[List[float], float] + + +class Qwen2_5_VLProcessorKwargs(ProcessingKwargs, total=False): + videos_kwargs: Qwen2_5_VLVideosProcessorKwargs + _defaults = { + "text_kwargs": { + "padding": False, + }, + "videos_kwargs": {"fps": 2.0}, + } + + +class Qwen2_5_VLProcessor(ProcessorMixin): + r""" + Constructs a Qwen2.5-VL processor which wraps a Qwen2.5-VL image processor and a Qwen2 tokenizer into a single processor. + [`Qwen2_5_VLProcessor`] offers all the functionalities of [`Qwen2VLImageProcessor`] and [`Qwen2TokenizerFast`]. See the + [`~Qwen2_5_VLProcessor.__call__`] and [`~Qwen2_5_VLProcessor.decode`] for more information. + Args: + image_processor ([`Qwen2VLImageProcessor`], *optional*): + The image processor is a required input. + tokenizer ([`Qwen2TokenizerFast`], *optional*): + The tokenizer is a required input. + chat_template (`str`, *optional*): A Jinja template which will be used to convert lists of messages + in a chat into a tokenizable string. + """ + + attributes = ["image_processor", "tokenizer"] + valid_kwargs = ["chat_template"] + + image_processor_class = "AutoImageProcessor" + tokenizer_class = ("Qwen2Tokenizer", "Qwen2TokenizerFast") + + def __init__(self, image_processor=None, tokenizer=None, chat_template=None, **kwargs): + self.image_token = "<|image_pad|>" if not hasattr(tokenizer, "image_token") else tokenizer.image_token + self.video_token = "<|video_pad|>" if not hasattr(tokenizer, "video_token") else tokenizer.video_token + super().__init__(image_processor, tokenizer, chat_template=chat_template) + + def __call__( + self, + images: ImageInput = None, + text: Union[TextInput, PreTokenizedInput, List[TextInput], List[PreTokenizedInput]] = None, + videos: VideoInput = None, + **kwargs: Unpack[Qwen2_5_VLProcessorKwargs], + ) -> BatchFeature: + """ + Main method to prepare for the model one or several sequences(s) and image(s). This method forwards the `text` + and `kwargs` arguments to Qwen2TokenizerFast's [`~Qwen2TokenizerFast.__call__`] if `text` is not `None` to encode + the text. To prepare the vision inputs, this method forwards the `vision_infos` and `kwrags` arguments to + Qwen2VLImageProcessor's [`~Qwen2VLImageProcessor.__call__`] if `vision_infos` is not `None`. + + Args: + images (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `List[PIL.Image.Image]`, `List[np.ndarray]`, `List[torch.Tensor]`): + The image or batch of images to be prepared. Each image can be a PIL image, NumPy array or PyTorch + tensor. Both channels-first and channels-last formats are supported. + text (`str`, `List[str]`, `List[List[str]]`): + The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings + (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set + `is_split_into_words=True` (to lift the ambiguity with a batch of sequences). + videos (`np.ndarray`, `torch.Tensor`, `List[np.ndarray]`, `List[torch.Tensor]`): + The image or batch of videos to be prepared. Each video can be a 4D NumPy array or PyTorch + tensor, or a nested list of 3D frames. Both channels-first and channels-last formats are supported. + return_tensors (`str` or [`~utils.TensorType`], *optional*): + If set, will return tensors of a particular framework. Acceptable values are: + - `'tf'`: Return TensorFlow `tf.constant` objects. + - `'pt'`: Return PyTorch `torch.Tensor` objects. + - `'np'`: Return NumPy `np.ndarray` objects. + - `'jax'`: Return JAX `jnp.ndarray` objects. + + Returns: + [`BatchFeature`]: A [`BatchFeature`] with the following fields: + + - **input_ids** -- List of token ids to be fed to a model. Returned when `text` is not `None`. + - **attention_mask** -- List of indices specifying which tokens should be attended to by the model (when + `return_attention_mask=True` or if *"attention_mask"* is in `self.model_input_names` and if `text` is not + `None`). + - **pixel_values** -- Pixel values to be fed to a model. Returned when `images` is not `None`. + - **pixel_values_videos** -- Pixel values of videos to be fed to a model. Returned when `videos` is not `None`. + - **image_grid_thw** -- List of image 3D grid in LLM. Returned when `images` is not `None`. + - **video_grid_thw** -- List of video 3D grid in LLM. Returned when `videos` is not `None`. + - **second_per_grid_ts** -- List of video seconds per time grid. Returned when `videos` is not `None`. + """ + output_kwargs = self._merge_kwargs( + Qwen2_5_VLProcessorKwargs, + tokenizer_init_kwargs=self.tokenizer.init_kwargs, + **kwargs, + ) + if images is not None: + image_inputs = self.image_processor(images=images, videos=None, **output_kwargs["images_kwargs"]) + image_grid_thw = image_inputs["image_grid_thw"] + else: + image_inputs = {} + image_grid_thw = None + + if videos is not None: + videos_inputs = self.image_processor(images=None, videos=videos, **output_kwargs["images_kwargs"]) + video_grid_thw = videos_inputs["video_grid_thw"] + + fps = output_kwargs["videos_kwargs"].pop("fps", 2.0) + if isinstance(fps, (int, float)): + second_per_grid_ts = [self.image_processor.temporal_patch_size / fps] * len(video_grid_thw) + elif hasattr(fps, "__len__") and len(fps) == len(video_grid_thw): + second_per_grid_ts = [self.image_processor.temporal_patch_size / tmp for tmp in fps] + else: + raise ValueError( + f"The length of fps ({len(fps) if hasattr(fps, '__len__') else fps}) must be equal to the length of video_grid_thw ({len(video_grid_thw)}) or fps should be a single number." + ) + videos_inputs.update({"second_per_grid_ts": second_per_grid_ts}) + + else: + videos_inputs = {} + video_grid_thw = None + + if not isinstance(text, list): + text = [text] + + if image_grid_thw is not None: + merge_length = self.image_processor.merge_size**2 + index = 0 + for i in range(len(text)): + while self.image_token in text[i]: + text[i] = text[i].replace( + self.image_token, + "<|placeholder|>" * (image_grid_thw[index].prod() // merge_length), + 1, + ) + index += 1 + text[i] = text[i].replace("<|placeholder|>", self.image_token) + + if video_grid_thw is not None: + merge_length = self.image_processor.merge_size**2 + index = 0 + for i in range(len(text)): + while self.video_token in text[i]: + text[i] = text[i].replace( + self.video_token, + "<|placeholder|>" * (video_grid_thw[index].prod() // merge_length), + 1, + ) + index += 1 + text[i] = text[i].replace("<|placeholder|>", self.video_token) + + text_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"]) + + return BatchFeature(data={**text_inputs, **image_inputs, **videos_inputs}) + + def batch_decode(self, *args, **kwargs): + """ + This method forwards all its arguments to Qwen2TokenizerFast's [`~PreTrainedTokenizer.batch_decode`]. Please + refer to the docstring of this method for more information. + """ + return self.tokenizer.batch_decode(*args, **kwargs) + + def batch_decode_all2all(self, *args, **kwargs): + """ + This method forwards all its arguments to Qwen2TokenizerFast's [`~PreTrainedTokenizer.batch_decode`]. Please + refer to the docstring of this method for more information. + """ + decoded = self.tokenizer.batch_decode(*args, **kwargs) + pattern = r'<\|vision_start\|>.*?<\|vision_end\|>' + decoded_with_image_tag = [re.sub(pattern, '', d, flags=re.DOTALL) for d in decoded] + decoded_with_image_tag = [re.sub(r'<\|im_end\|>', '', d) for d in decoded_with_image_tag] + return decoded_with_image_tag + + def decode(self, *args, **kwargs): + """ + This method forwards all its arguments to Qwen2TokenizerFast's [`~PreTrainedTokenizer.decode`]. Please refer to + the docstring of this method for more information. + """ + return self.tokenizer.decode(*args, **kwargs) + + def post_process_image_text_to_text( + self, generated_outputs, skip_special_tokens=True, clean_up_tokenization_spaces=False, **kwargs + ): + """ + Post-process the output of the model to decode the text. + + Args: + generated_outputs (`torch.Tensor` or `np.ndarray`): + The output of the model `generate` function. The output is expected to be a tensor of shape `(batch_size, sequence_length)` + or `(sequence_length,)`. + skip_special_tokens (`bool`, *optional*, defaults to `True`): + Whether or not to remove special tokens in the output. Argument passed to the tokenizer's `batch_decode` method. + Clean_up_tokenization_spaces (`bool`, *optional*, defaults to `False`): + Whether or not to clean up the tokenization spaces. Argument passed to the tokenizer's `batch_decode` method. + **kwargs: + Additional arguments to be passed to the tokenizer's `batch_decode method`. + + Returns: + `List[str]`: The decoded text. + """ + return self.tokenizer.batch_decode( + generated_outputs, + skip_special_tokens=skip_special_tokens, + clean_up_tokenization_spaces=clean_up_tokenization_spaces, + **kwargs, + ) + + @property + def model_input_names(self): + tokenizer_input_names = self.tokenizer.model_input_names + image_processor_input_names = self.image_processor.model_input_names + names_from_processor = list(dict.fromkeys(tokenizer_input_names + image_processor_input_names)) + return names_from_processor + ["second_per_grid_ts"] + + +__all__ = ["Qwen2_5_VLProcessor"] diff --git a/diffsynth/models/nexus_gen_projector.py b/diffsynth/models/nexus_gen_projector.py new file mode 100644 index 0000000..b35ff3f --- /dev/null +++ b/diffsynth/models/nexus_gen_projector.py @@ -0,0 +1,359 @@ +import math +import torch +import torch.nn as nn +from typing import Optional, Tuple +from transformers.activations import ACT2FN +from transformers.modeling_rope_utils import _compute_default_rope_parameters +from transformers import AutoConfig + +def rotate_half(x): + """Rotates half the hidden dims of the input.""" + x1 = x[..., : x.shape[-1] // 2] + x2 = x[..., x.shape[-1] // 2 :] + return torch.cat((-x2, x1), dim=-1) + + +def apply_multimodal_rotary_pos_emb(q, k, cos, sin, mrope_section, unsqueeze_dim=1): + mrope_section = mrope_section * 2 + cos = torch.cat([m[i % 3] for i, m in enumerate(cos.split(mrope_section, dim=-1))], dim=-1).unsqueeze( + unsqueeze_dim + ) + sin = torch.cat([m[i % 3] for i, m in enumerate(sin.split(mrope_section, dim=-1))], dim=-1).unsqueeze( + unsqueeze_dim + ) + + q_embed = (q * cos) + (rotate_half(q) * sin) + k_embed = (k * cos) + (rotate_half(k) * sin) + return q_embed, k_embed + + +class Qwen2_5_VLRotaryEmbedding(nn.Module): + def __init__(self, config, device=None): + super().__init__() + # BC: "rope_type" was originally "type" + if hasattr(config, "rope_scaling") and config.rope_scaling is not None: + self.rope_type = config.rope_scaling.get("rope_type", config.rope_scaling.get("type")) + else: + self.rope_type = "default" + self.max_seq_len_cached = config.max_position_embeddings + self.original_max_seq_len = config.max_position_embeddings + + self.config = config + self.rope_init_fn = _compute_default_rope_parameters + + inv_freq, self.attention_scaling = self.rope_init_fn(self.config, device) + self.register_buffer("inv_freq", inv_freq, persistent=False) + self.original_inv_freq = self.inv_freq + + + def _dynamic_frequency_update(self, position_ids, device): + """ + dynamic RoPE layers should recompute `inv_freq` in the following situations: + 1 - growing beyond the cached sequence length (allow scaling) + 2 - the current sequence length is in the original scale (avoid losing precision with small sequences) + """ + seq_len = torch.max(position_ids) + 1 + if seq_len > self.max_seq_len_cached: # growth + inv_freq, self.attention_scaling = self.rope_init_fn( + self.config, device, seq_len=seq_len, **self.rope_kwargs + ) + self.register_buffer("inv_freq", inv_freq, persistent=False) # TODO joao: may break with compilation + self.max_seq_len_cached = seq_len + + if seq_len < self.original_max_seq_len and self.max_seq_len_cached > self.original_max_seq_len: # reset + self.register_buffer("inv_freq", self.original_inv_freq, persistent=False) + self.max_seq_len_cached = self.original_max_seq_len + + + @torch.no_grad() + def forward(self, x, position_ids): + if "dynamic" in self.rope_type: + self._dynamic_frequency_update(position_ids, device=x.device) + + # Core RoPE block. In contrast to other models, Qwen2_5_VL has different position ids for the grids + # So we expand the inv_freq to shape (3, ...) + inv_freq_expanded = self.inv_freq[None, None, :, None].float().expand(3, position_ids.shape[1], -1, 1) + position_ids_expanded = position_ids[:, :, None, :].float() # shape (3, bs, 1, positions) + # Force float32 (see https://github.com/huggingface/transformers/pull/29285) + device_type = x.device.type + device_type = device_type if isinstance(device_type, str) and device_type != "mps" else "cpu" + with torch.autocast(device_type=device_type, enabled=False): + freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(2, 3) + emb = torch.cat((freqs, freqs), dim=-1) + cos = emb.cos() + sin = emb.sin() + + # Advanced RoPE types (e.g. yarn) apply a post-processing scaling factor, equivalent to scaling attention + cos = cos * self.attention_scaling + sin = sin * self.attention_scaling + + return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype) + + +def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor: + """ + This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch, + num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim) + """ + batch, num_key_value_heads, slen, head_dim = hidden_states.shape + if n_rep == 1: + return hidden_states + hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim) + return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim) + + +class Qwen2_5_VLAttention(nn.Module): + def __init__(self, config, layer_idx: Optional[int] = None): + super().__init__() + self.config = config + self.layer_idx = layer_idx + + self.hidden_size = config.hidden_size + self.num_heads = config.num_attention_heads + self.head_dim = self.hidden_size // self.num_heads + self.num_key_value_heads = config.num_key_value_heads + self.num_key_value_groups = self.num_heads // self.num_key_value_heads + self.is_causal = True + self.attention_dropout = config.attention_dropout + self.rope_scaling = config.rope_scaling + + if (self.head_dim * self.num_heads) != self.hidden_size: + raise ValueError( + f"hidden_size must be divisible by num_heads (got `hidden_size`: {self.hidden_size}" + f" and `num_heads`: {self.num_heads})." + ) + self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=True) + self.k_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=True) + self.v_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=True) + self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size, bias=False) + + + def forward( + self, + hidden_states: torch.Tensor, + position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # necessary, but kept here for BC + ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: + bsz, q_len, _ = hidden_states.size() + + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + query_states = query_states.view(bsz, q_len, -1, self.head_dim).transpose(1, 2) + key_states = key_states.view(bsz, q_len, -1, self.head_dim).transpose(1, 2) + value_states = value_states.view(bsz, q_len, -1, self.head_dim).transpose(1, 2) + + cos, sin = position_embeddings + query_states, key_states = apply_multimodal_rotary_pos_emb( + query_states, key_states, cos, sin, self.rope_scaling["mrope_section"] + ) + + # repeat k/v heads if n_kv_heads < n_heads + key_states = repeat_kv(key_states, self.num_key_value_groups) + value_states = repeat_kv(value_states, self.num_key_value_groups) + + attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) / math.sqrt(self.head_dim) + + # Fix precision issues in Qwen2-VL float16 inference + # Replace inf values with zeros in attention weights to prevent NaN propagation + if query_states.dtype == torch.float16: + attn_weights = torch.where(torch.isinf(attn_weights), torch.zeros_like(attn_weights), attn_weights) + + # upcast attention to fp32 + attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype) + attn_weights = nn.functional.dropout(attn_weights, p=self.attention_dropout, training=self.training) + attn_output = torch.matmul(attn_weights, value_states) + + if attn_output.size() != (bsz, self.num_heads, q_len, self.head_dim): + raise ValueError( + f"`attn_output` should be of size {(bsz, self.num_heads, q_len, self.head_dim)}, but is" + f" {attn_output.size()}" + ) + + attn_output = attn_output.transpose(1, 2).contiguous() + attn_output = attn_output.reshape(bsz, q_len, -1) + + attn_output = self.o_proj(attn_output) + + return attn_output + + +class Qwen2MLP(nn.Module): + def __init__(self, config): + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.intermediate_size = config.intermediate_size + self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) + self.act_fn = ACT2FN[config.hidden_act] + + def forward(self, x): + down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x)) + return down_proj + + +class Qwen2RMSNorm(nn.Module): + def __init__(self, hidden_size, eps=1e-6): + """ + Qwen2RMSNorm is equivalent to T5LayerNorm + """ + super().__init__() + self.weight = nn.Parameter(torch.ones(hidden_size)) + self.variance_epsilon = eps + + def forward(self, hidden_states): + input_dtype = hidden_states.dtype + hidden_states = hidden_states.to(torch.float32) + variance = hidden_states.pow(2).mean(-1, keepdim=True) + hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon) + return self.weight * hidden_states.to(input_dtype) + + def extra_repr(self): + return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}" + + +class Qwen2_5_VLDecoderLayer(nn.Module): + def __init__(self, config, layer_idx): + super().__init__() + self.hidden_size = config.hidden_size + + self.self_attn = Qwen2_5_VLAttention(config, layer_idx) + + self.mlp = Qwen2MLP(config) + self.input_layernorm = Qwen2RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = Qwen2RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def forward( + self, + hidden_states: torch.Tensor, + position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # necessary, but kept here for BC + ) -> Tuple[torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]]: + + residual = hidden_states + + hidden_states = self.input_layernorm(hidden_states) + + # Self Attention + hidden_states = self.self_attn( + hidden_states=hidden_states, + position_embeddings=position_embeddings, + ) + hidden_states = residual + hidden_states + + # Fully Connected + residual = hidden_states + hidden_states = self.post_attention_layernorm(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + + return hidden_states + + +class NexusGenImageEmbeddingMerger(nn.Module): + def __init__(self, model_path="models/DiffSynth-Studio/Nexus-GenV2", num_layers=1, out_channel=4096, expand_ratio=4, device='cpu'): + super().__init__() + config = AutoConfig.from_pretrained(model_path) + self.config = config + self.num_layers = num_layers + self.layers = nn.ModuleList([Qwen2_5_VLDecoderLayer(config, layer_idx) for layer_idx in range(num_layers)]) + self.projector = nn.Sequential(Qwen2RMSNorm(config.hidden_size, eps=config.rms_norm_eps), + nn.Linear(config.hidden_size, out_channel * expand_ratio), + Qwen2RMSNorm(out_channel * expand_ratio, eps=config.rms_norm_eps), + ACT2FN[config.hidden_act], nn.Linear(out_channel * expand_ratio, out_channel), + Qwen2RMSNorm(out_channel, eps=config.rms_norm_eps)) + self.base_grid = torch.tensor([[1, 72, 72]], device=device) + self.rotary_emb = Qwen2_5_VLRotaryEmbedding(config=config, device=device) + + def get_position_ids(self, image_grid_thw): + """ + Generates position ids for the input embeddings grid. + modified from the qwen2_vl mrope. + """ + batch_size = image_grid_thw.shape[0] + spatial_merge_size = self.config.vision_config.spatial_merge_size + t, h, w = ( + image_grid_thw[0][0], + image_grid_thw[0][1], + image_grid_thw[0][2], + ) + llm_grid_t, llm_grid_h, llm_grid_w = ( + t.item(), + h.item() // spatial_merge_size, + w.item() // spatial_merge_size, + ) + scale_h = self.base_grid[0][1].item() / h.item() + scale_w = self.base_grid[0][2].item() / w.item() + + range_tensor = torch.arange(llm_grid_t).view(-1, 1) + expanded_range = range_tensor.expand(-1, llm_grid_h * llm_grid_w) + time_tensor = expanded_range * self.config.vision_config.tokens_per_second + t_index = time_tensor.long().flatten().to(image_grid_thw.device) + h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten().to(image_grid_thw.device) * scale_h + w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten().to(image_grid_thw.device) * scale_w + # 3, B, L + position_ids = torch.stack([t_index, h_index, w_index]).unsqueeze(0).repeat(batch_size, 1, 1).permute(1, 0, 2) + return position_ids + + def forward(self, embeds, embeds_grid, ref_embeds=None, ref_embeds_grid=None): + position_ids = self.get_position_ids(embeds_grid) + hidden_states = embeds + if ref_embeds is not None: + position_ids_ref_embeds = self.get_position_ids(ref_embeds_grid) + position_ids = torch.cat((position_ids, position_ids_ref_embeds), dim=-1) + hidden_states = torch.cat((embeds, ref_embeds), dim=1) + + position_embeddings = self.rotary_emb(hidden_states, position_ids) + for layer in self.layers: + hidden_states = layer(hidden_states, position_embeddings) + + hidden_states = self.projector(hidden_states) + return hidden_states + + @staticmethod + def state_dict_converter(): + return NexusGenMergerStateDictConverter() + + +class NexusGenMergerStateDictConverter: + def __init__(self): + pass + + def from_diffusers(self, state_dict): + return state_dict + + def from_civitai(self, state_dict): + merger_state_dict = {key.replace("embedding_merger.", ""): value for key, value in state_dict.items() if key.startswith('embedding_merger.')} + return merger_state_dict + + +class NexusGenAdapter(nn.Module): + """ + Adapter for Nexus-Gen generation decoder. + """ + def __init__(self, input_dim=3584, output_dim=4096): + super(NexusGenAdapter, self).__init__() + self.adapter = nn.Sequential(nn.Linear(input_dim, output_dim), + nn.LayerNorm(output_dim), nn.ReLU(), + nn.Linear(output_dim, output_dim), + nn.LayerNorm(output_dim)) + + def forward(self, x): + return self.adapter(x) + + @staticmethod + def state_dict_converter(): + return NexusGenAdapterStateDictConverter() + + +class NexusGenAdapterStateDictConverter: + def __init__(self): + pass + + def from_diffusers(self, state_dict): + return state_dict + + def from_civitai(self, state_dict): + adapter_state_dict = {key: value for key, value in state_dict.items() if key.startswith('adapter.')} + return adapter_state_dict diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index 2abd16c..36d7922 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -22,6 +22,8 @@ from ..models.flux_value_control import MultiValueEncoder from ..models.flux_infiniteyou import InfiniteYouImageProjector from ..models.flux_lora_encoder import FluxLoRAEncoder, LoRALayerBlock from ..models.tiler import FastTileWorker +from ..models.nexus_gen import NexusGenAutoregressiveModel +from ..models.nexus_gen_projector import NexusGenAdapter, NexusGenImageEmbeddingMerger from ..utils import BasePipeline, ModelConfig, PipelineUnitRunner, PipelineUnit from ..lora.flux_lora import FluxLoRALoader, FluxLoraPatcher, FluxLoRAFuser @@ -94,6 +96,9 @@ class FluxImagePipeline(BasePipeline): self.ipadapter_image_encoder = None self.qwenvl = None self.step1x_connector: Qwen2Connector = None + self.nexus_gen: NexusGenAutoregressiveModel = None + self.nexus_gen_generation_adapter: NexusGenAdapter = None + self.nexus_gen_editing_adapter: NexusGenImageEmbeddingMerger = None self.value_controller: MultiValueEncoder = None self.infinityou_processor: InfinitYou = None self.image_proj_model: InfiniteYouImageProjector = None @@ -113,6 +118,7 @@ class FluxImagePipeline(BasePipeline): FluxImageUnit_ControlNet(), FluxImageUnit_IPAdapter(), FluxImageUnit_EntityControl(), + FluxImageUnit_NexusGen(), FluxImageUnit_TeaCache(), FluxImageUnit_Flex(), FluxImageUnit_Step1x(), @@ -397,6 +403,9 @@ class FluxImagePipeline(BasePipeline): pipe.infinityou_processor = InfinitYou(device=device) pipe.lora_patcher = model_manager.fetch_model("flux_lora_patcher") pipe.lora_encoder = model_manager.fetch_model("flux_lora_encoder") + pipe.nexus_gen = model_manager.fetch_model("nexus_gen_llm") + pipe.nexus_gen_generation_adapter = model_manager.fetch_model("nexus_gen_generation_adapter") + pipe.nexus_gen_editing_adapter = model_manager.fetch_model("nexus_gen_editing_adapter") # ControlNet controlnets = [] @@ -468,6 +477,8 @@ class FluxImagePipeline(BasePipeline): value_controller_inputs: Union[list[float], float] = None, # Step1x step1x_reference_image: Image.Image = None, + # NexusGen + nexus_gen_reference_image: Image.Image = None, # LoRA Encoder lora_encoder_inputs: Union[list[ModelConfig], ModelConfig, str] = None, lora_encoder_scale: float = 1.0, @@ -504,6 +515,7 @@ class FluxImagePipeline(BasePipeline): "flex_inpaint_image": flex_inpaint_image, "flex_inpaint_mask": flex_inpaint_mask, "flex_control_image": flex_control_image, "flex_control_strength": flex_control_strength, "flex_control_stop": flex_control_stop, "value_controller_inputs": value_controller_inputs, "step1x_reference_image": step1x_reference_image, + "nexus_gen_reference_image": nexus_gen_reference_image, "lora_encoder_inputs": lora_encoder_inputs, "lora_encoder_scale": lora_encoder_scale, "tea_cache_l1_thresh": tea_cache_l1_thresh, "tiled": tiled, "tile_size": tile_size, "tile_stride": tile_stride, @@ -764,6 +776,60 @@ class FluxImageUnit_EntityControl(PipelineUnit): return inputs_shared, inputs_posi, inputs_nega +class FluxImageUnit_NexusGen(PipelineUnit): + def __init__(self): + super().__init__( + take_over=True, + onload_model_names=("nexus_gen", "nexus_gen_generation_adapter", "nexus_gen_editing_adapter"), + ) + + def process(self, pipe: FluxImagePipeline, inputs_shared, inputs_posi, inputs_nega): + if pipe.nexus_gen is None: + return inputs_shared, inputs_posi, inputs_nega + pipe.load_models_to_device(self.onload_model_names) + if inputs_shared.get("nexus_gen_reference_image", None) is None: + assert pipe.nexus_gen_generation_adapter is not None, "NexusGen requires a generation adapter to be set." + embed = pipe.nexus_gen(inputs_posi["prompt"])[0].unsqueeze(0) + inputs_posi["prompt_emb"] = pipe.nexus_gen_generation_adapter(embed) + inputs_posi['text_ids'] = torch.zeros(embed.shape[0], embed.shape[1], 3).to(device=pipe.device, dtype=pipe.torch_dtype) + else: + assert pipe.nexus_gen_editing_adapter is not None, "NexusGen requires an editing adapter to be set." + embed, ref_embed, grids = pipe.nexus_gen(inputs_posi["prompt"], inputs_shared["nexus_gen_reference_image"]) + embeds_grid = grids[1:2].to(device=pipe.device, dtype=torch.long) + ref_embeds_grid = grids[0:1].to(device=pipe.device, dtype=torch.long) + + inputs_posi["prompt_emb"] = pipe.nexus_gen_editing_adapter(embed.unsqueeze(0), embeds_grid, ref_embed.unsqueeze(0), ref_embeds_grid) + inputs_posi["text_ids"] = self.get_editing_text_ids( + inputs_shared["latents"], + embeds_grid[0][1].item(), embeds_grid[0][2].item(), + ref_embeds_grid[0][1].item(), ref_embeds_grid[0][2].item(), + ) + return inputs_shared, inputs_posi, inputs_nega + + + def get_editing_text_ids(self, latents, target_embed_height, target_embed_width, ref_embed_height, ref_embed_width): + # prepare text ids for target and reference embeddings + batch_size, height, width = latents.shape[0], target_embed_height, target_embed_width + embed_ids = torch.zeros(height // 2, width // 2, 3) + scale_factor_height, scale_factor_width = latents.shape[-2] / height, latents.shape[-1] / width + embed_ids[..., 1] = embed_ids[..., 1] + torch.arange(height // 2)[:, None] * scale_factor_height + embed_ids[..., 2] = embed_ids[..., 2] + torch.arange(width // 2)[None, :] * scale_factor_width + embed_ids = embed_ids[None, :].repeat(batch_size, 1, 1, 1).reshape(batch_size, height // 2 * width // 2, 3) + embed_text_ids = embed_ids.to(device=latents.device, dtype=latents.dtype) + + batch_size, height, width = latents.shape[0], ref_embed_height, ref_embed_width + ref_embed_ids = torch.zeros(height // 2, width // 2, 3) + scale_factor_height, scale_factor_width = latents.shape[-2] / height, latents.shape[-1] / width + ref_embed_ids[..., 0] = ref_embed_ids[..., 0] + 1.0 + ref_embed_ids[..., 1] = ref_embed_ids[..., 1] + torch.arange(height // 2)[:, None] * scale_factor_height + ref_embed_ids[..., 2] = ref_embed_ids[..., 2] + torch.arange(width // 2)[None, :] * scale_factor_width + ref_embed_ids = ref_embed_ids[None, :].repeat(batch_size, 1, 1, 1).reshape(batch_size, height // 2 * width // 2, 3) + ref_embed_text_ids = ref_embed_ids.to(device=latents.device, dtype=latents.dtype) + + text_ids = torch.cat([embed_text_ids, ref_embed_text_ids], dim=1) + return text_ids + + class FluxImageUnit_Step1x(PipelineUnit): def __init__(self): super().__init__(take_over=True,onload_model_names=("qwenvl","vae_encoder")) diff --git a/examples/flux/model_inference/Nexus-Gen-Editing.py b/examples/flux/model_inference/Nexus-Gen-Editing.py new file mode 100644 index 0000000..603ac33 --- /dev/null +++ b/examples/flux/model_inference/Nexus-Gen-Editing.py @@ -0,0 +1,34 @@ +import importlib +import torch +from PIL import Image +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from modelscope import snapshot_download + +if importlib.util.find_spec("transformers") is None: + raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") +else: + import transformers + assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==4.49.0, please install it with `pip install transformers==4.49.0`." + +snapshot_download("DiffSynth-Studio/Nexus-GenV2", local_dir="models/DiffSynth-Studio/Nexus-GenV2") +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="edit_decoder.bin"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) + +prompt = "给猫加一副太阳镜" +ref_image = Image.open("cat.png").convert("RGB") +image = pipe( + prompt=prompt, negative_prompt="", + seed=0, cfg_scale=1.0, num_inference_steps=50, + nexus_gen_reference_image=ref_image, + height=512, width=512, +) +image.save("cat_glasses.jpg") diff --git a/examples/flux/model_inference/Nexus-Gen-Generation.py b/examples/flux/model_inference/Nexus-Gen-Generation.py index 102b7ef..07ef1d2 100644 --- a/examples/flux/model_inference/Nexus-Gen-Generation.py +++ b/examples/flux/model_inference/Nexus-Gen-Generation.py @@ -1,21 +1,31 @@ import importlib import torch from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from modelscope import snapshot_download if importlib.util.find_spec("transformers") is None: raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") else: import transformers - assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==0.49.0, please install it with `pip install transformers==0.49.0`." + assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==4.49.0, please install it with `pip install transformers==4.49.0`." +snapshot_download("DiffSynth-Studio/Nexus-GenV2", local_dir="models/DiffSynth-Studio/Nexus-GenV2") pipe = FluxImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", model_configs=[ - ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors"), ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="generation_decoder.bin"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), ], ) + +prompt = "一只可爱的猫咪" +image = pipe( + prompt=prompt, negative_prompt="", + seed=0, cfg_scale=3, num_inference_steps=50, + height=1024, width=1024, +) +image.save("cat.jpg") From 29663b25a65d35f129a88dadbe28e74e895c0a49 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Mon, 28 Jul 2025 16:49:28 +0800 Subject: [PATCH 39/51] fix wan2.2 vae --- diffsynth/models/wan_video_vae.py | 8 +++++--- examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/diffsynth/models/wan_video_vae.py b/diffsynth/models/wan_video_vae.py index d737e2f..397a2e7 100644 --- a/diffsynth/models/wan_video_vae.py +++ b/diffsynth/models/wan_video_vae.py @@ -1075,6 +1075,7 @@ class WanVideoVAE(nn.Module): # init model self.model = VideoVAE_(z_dim=z_dim).eval().requires_grad_(False) self.upsampling_factor = 8 + self.z_dim = z_dim def build_1d_mask(self, length, left_bound, right_bound, border_width): @@ -1170,7 +1171,7 @@ class WanVideoVAE(nn.Module): out_T = (T + 3) // 4 weight = torch.zeros((1, 1, out_T, H // self.upsampling_factor, W // self.upsampling_factor), dtype=video.dtype, device=data_device) - values = torch.zeros((1, 16, out_T, H // self.upsampling_factor, W // self.upsampling_factor), dtype=video.dtype, device=data_device) + values = torch.zeros((1, self.z_dim, out_T, H // self.upsampling_factor, W // self.upsampling_factor), dtype=video.dtype, device=data_device) for h, h_, w, w_ in tqdm(tasks, desc="VAE encoding"): hidden_states_batch = video[:, :, :, h:h_, w:w_].to(computation_device) @@ -1221,8 +1222,8 @@ class WanVideoVAE(nn.Module): for video in videos: video = video.unsqueeze(0) if tiled: - tile_size = (tile_size[0] * 8, tile_size[1] * 8) - tile_stride = (tile_stride[0] * 8, tile_stride[1] * 8) + tile_size = (tile_size[0] * self.upsampling_factor, tile_size[1] * self.upsampling_factor) + tile_stride = (tile_stride[0] * self.upsampling_factor, tile_stride[1] * self.upsampling_factor) hidden_state = self.tiled_encode(video, device, tile_size, tile_stride) else: hidden_state = self.single_encode(video, device) @@ -1372,3 +1373,4 @@ class WanVideoVAE38(WanVideoVAE): # init model self.model = VideoVAE38_(z_dim=z_dim, dim=dim).eval().requires_grad_(False) self.upsampling_factor = 16 + self.z_dim = z_dim diff --git a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py index 50d81c2..fa91965 100644 --- a/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py +++ b/examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py @@ -19,7 +19,7 @@ pipe.enable_vram_management() video = pipe( prompt="两只可爱的橘猫戴上拳击手套,站在一个拳击台上搏斗。", negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", - seed=0, tiled=False, + seed=0, tiled=True, height=704, width=1248, num_frames=121, ) @@ -35,7 +35,7 @@ input_image = Image.open("data/examples/wan/cat_fightning.jpg").resize((1248, 70 video = pipe( prompt="两只可爱的橘猫戴上拳击手套,站在一个拳击台上搏斗。", negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", - seed=0, tiled=False, + seed=0, tiled=True, height=704, width=1248, input_image=input_image, num_frames=121, From 68aafab09e4d8a9c7b215c6f35e7a38b2dfe3108 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Mon, 28 Jul 2025 17:02:30 +0800 Subject: [PATCH 40/51] update readme --- README.md | 7 ++++++- README_zh.md | 7 ++++++- diffsynth/pipelines/wan_video_new.py | 9 +++++---- examples/wanvideo/README.md | 7 +++++-- examples/wanvideo/README_zh.md | 7 +++++-- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 11403a5..8c01734 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,9 @@ image.save("image.jpg") |[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py)| @@ -317,7 +320,9 @@ https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-44 ## Update History -- **July 11, 2025** 🔥🔥🔥 We propose Nexus-Gen, a unified model that synergizes the language reasoning capabilities of LLMs with the image synthesis power of diffusion models. This framework enables seamless image understanding, generation, and editing tasks. +- **July 28, 2025** 🔥🔥🔥 With the open-sourcing of Wan 2.2, we immediately provided comprehensive support, including low-GPU-memory layer-by-layer offload, FP8 quantization, sequence parallelism, LoRA training, full training. See [./examples/wanvideo/](./examples/wanvideo/). + +- **July 11, 2025** We propose Nexus-Gen, a unified model that synergizes the language reasoning capabilities of LLMs with the image synthesis power of diffusion models. This framework enables seamless image understanding, generation, and editing tasks. - Paper: [Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) - Github Repo: https://github.com/modelscope/Nexus-Gen - Model: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2), [HuggingFace](https://huggingface.co/modelscope/Nexus-GenV2) diff --git a/README_zh.md b/README_zh.md index 650d2ec..bfcaa46 100644 --- a/README_zh.md +++ b/README_zh.md @@ -167,6 +167,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) |[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-1.3B.py)| |[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-14B.py)| |[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./examples/wanvideo/model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py)| @@ -333,7 +336,9 @@ https://github.com/Artiprocher/DiffSynth-Studio/assets/35051019/59fb2f7b-8de0-44 ## 更新历史 -- **2025年7月11日** 🔥🔥🔥 我们提出 Nexus-Gen,一个将大语言模型(LLM)的语言推理能力与扩散模型的图像生成能力相结合的统一框架。该框架支持无缝的图像理解、生成和编辑任务。 +- **2025年7月28日** 🔥🔥🔥 Wan 2.2 开源,我们第一时间提供了全方位支持,包括低显存逐层 offload、FP8 量化、序列并行、LoRA 训练、全量训练。详细信息请参考 [./examples/wanvideo/](./examples/wanvideo/)。 + +- **2025年7月11日** 我们提出 Nexus-Gen,一个将大语言模型(LLM)的语言推理能力与扩散模型的图像生成能力相结合的统一框架。该框架支持无缝的图像理解、生成和编辑任务。 - 论文: [Nexus-Gen: Unified Image Understanding, Generation, and Editing via Prefilled Autoregression in Shared Embedding Space](https://arxiv.org/pdf/2504.21356) - Github 仓库: https://github.com/modelscope/Nexus-Gen - 模型: [ModelScope](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2), [HuggingFace](https://huggingface.co/modelscope/Nexus-GenV2) diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index 4f62a74..a425da2 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -323,10 +323,11 @@ class WanVideoPipeline(BasePipeline): # Load models pipe.text_encoder = model_manager.fetch_model("wan_video_text_encoder") - pipe.dit = model_manager.fetch_model("wan_video_dit") - num_dits = len([model_name for model_name in model_manager.model_name if model_name == "wan_video_dit"]) - if num_dits == 2: - pipe.dit2 = [model for model, model_name in zip(model_manager.model, model_manager.model_name) if model_name == "wan_video_dit"][-1] + dit = model_manager.fetch_model("wan_video_dit", index=2) + if isinstance(dit, list): + pipe.dit, pipe.dit2 = dit + else: + pipe.dit = dit pipe.vae = model_manager.fetch_model("wan_video_vae") pipe.image_encoder = model_manager.fetch_model("wan_video_image_encoder") pipe.motion_controller = model_manager.fetch_model("wan_video_motion_controller") diff --git a/examples/wanvideo/README.md b/examples/wanvideo/README.md index 6d967c5..8231e29 100644 --- a/examples/wanvideo/README.md +++ b/examples/wanvideo/README.md @@ -1,8 +1,8 @@ -# Wan 2.1 +# Wan [切换到中文](./README_zh.md) -Wan 2.1 is a collection of video synthesis models open-sourced by Alibaba. +Wan is a collection of video synthesis models open-sourced by Alibaba. **DiffSynth-Studio has adopted a new inference and training framework. To use the previous version, please click [here](https://github.com/modelscope/DiffSynth-Studio/tree/3edf3583b1f08944cee837b94d9f84d669c2729c).** @@ -247,6 +247,7 @@ The pipeline accepts the following input parameters during inference: * `num_frames`: Number of frames, default is 81. Must be a multiple of 4 plus 1; if not, it will be rounded up, minimum is 1. * `cfg_scale`: Classifier-free guidance scale, default is 5. Higher values increase adherence to the prompt but may cause visual artifacts. * `cfg_merge`: Whether to merge both sides of classifier-free guidance for unified inference. Default is `False`. This parameter currently only works for basic text-to-video and image-to-video models. +* `switch_DiT_boundary`: The time point for switching between DiT models. Default value is 0.875. This parameter only takes effect for mixed models with multiple DiTs, for example, [Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B). * `num_inference_steps`: Number of inference steps, default is 50. * `sigma_shift`: Parameter from Rectified Flow theory, default is 5. Higher values make the model stay longer at the initial denoising stage. Increasing this may improve video quality but may also cause inconsistency between generated videos and training data due to deviation from training behavior. * `motion_bucket_id`: Motion intensity, range [0, 100], applicable to motion control modules such as [`DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1`](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1). Larger values indicate more intense motion. @@ -282,6 +283,8 @@ The script includes the following parameters: * Models * `--model_paths`: Paths to load models. In JSON format. * `--model_id_with_origin_paths`: Model ID with origin paths, e.g., Wan-AI/Wan2.1-T2V-1.3B:diffusion_pytorch_model*.safetensors. Comma-separated. + * `--max_timestep_boundary`: Maximum value of the timestep interval, ranging from 0 to 1. Default is 1. This needs to be manually set only when training mixed models with multiple DiTs, for example, [Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B). + * `--min_timestep_boundary`: Minimum value of the timestep interval, ranging from 0 to 1. Default is 1. This needs to be manually set only when training mixed models with multiple DiTs, for example, [Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B). * Training * `--learning_rate`: Learning rate. * `--num_epochs`: Number of epochs. diff --git a/examples/wanvideo/README_zh.md b/examples/wanvideo/README_zh.md index 54b07da..9f26858 100644 --- a/examples/wanvideo/README_zh.md +++ b/examples/wanvideo/README_zh.md @@ -1,8 +1,8 @@ -# 通义万相 2.1(Wan 2.1) +# 通义万相(Wan) [Switch to English](./README.md) -Wan 2.1 是由阿里巴巴通义实验室开源的一系列视频生成模型。 +Wan 是由阿里巴巴通义实验室开源的一系列视频生成模型。 **DiffSynth-Studio 启用了新的推理和训练框架,如需使用旧版本,请点击[这里](https://github.com/modelscope/DiffSynth-Studio/tree/3edf3583b1f08944cee837b94d9f84d669c2729c)。** @@ -248,6 +248,7 @@ Pipeline 在推理阶段能够接收以下输入参数: * `num_frames`: 帧数,默认为 81。需设置为 4 的倍数 + 1,不满足时向上取整,最小值为 1。 * `cfg_scale`: Classifier-free guidance 机制的数值,默认为 5。数值越大,提示词的控制效果越强,但画面崩坏的概率越大。 * `cfg_merge`: 是否合并 Classifier-free guidance 的两侧进行统一推理,默认为 `False`。该参数目前仅在基础的文生视频和图生视频模型上生效。 +* `switch_DiT_boundary`: 切换 DiT 模型的时间点,默认值为 0.875,仅对多 DiT 的混合模型生效,例如 [Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)。 * `num_inference_steps`: 推理次数,默认值为 50。 * `sigma_shift`: Rectified Flow 理论中的参数,默认为 5。数值越大,模型在去噪的开始阶段停留的步骤数越多,可适当调大这个参数来提高画面质量,但会因生成过程与训练过程不一致导致生成的视频内容与训练数据存在差异。 * `motion_bucket_id`: 运动幅度,范围为 [0, 100]。适用于速度控制模块,例如 [`DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1`](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1),数值越大,运动幅度越大。 @@ -284,6 +285,8 @@ Wan 系列模型训练通过统一的 [`./model_training/train.py`](./model_trai * 模型 * `--model_paths`: 要加载的模型路径。JSON 格式。 * `--model_id_with_origin_paths`: 带原始路径的模型 ID,例如 Wan-AI/Wan2.1-T2V-1.3B:diffusion_pytorch_model*.safetensors。用逗号分隔。 + * `--max_timestep_boundary`: Timestep 区间最大值,范围为 0~1,默认为 1,仅在多 DiT 的混合模型训练中需要手动设置,例如 [Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)。 + * `--min_timestep_boundary`: Timestep 区间最小值,范围为 0~1,默认为 1,仅在多 DiT 的混合模型训练中需要手动设置,例如 [Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)。 * 训练 * `--learning_rate`: 学习率。 * `--num_epochs`: 轮数(Epoch)。 From 283f35447a129b04e44a87a28a201c2d3f61d33a Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Mon, 28 Jul 2025 18:25:43 +0800 Subject: [PATCH 41/51] refine readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c01734..87bca13 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,6 @@ image.save("image.jpg") |[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| -|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py)| -|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py)| -|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py)| @@ -170,6 +167,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) |[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-1.3B.py)| |[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-14B.py)| |[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./examples/wanvideo/model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py)| From 9e683bfe25886b5de5eaefd865ceae61796cd14e Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Mon, 28 Jul 2025 18:30:04 +0800 Subject: [PATCH 42/51] fix typo --- .../wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py | 2 +- .../wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py | 2 +- .../wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py | 2 +- .../wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py b/examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py index ddcdf5c..3f6d253 100644 --- a/examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py +++ b/examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py @@ -28,6 +28,6 @@ video = pipe( negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", input_image=input_image, num_frames=49, - seed=1, tiled=False, + seed=1, tiled=True, ) save_video(video, "video_Wan2.2-I2V-A14B.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py index 0f0ea5d..8a3d36c 100644 --- a/examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py +++ b/examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py @@ -25,6 +25,6 @@ video = pipe( negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", input_image=input_image, num_frames=49, - seed=1, tiled=False, + seed=1, tiled=True, ) save_video(video, "video_Wan2.2-TI2V-5B.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py b/examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py index 4a6bd9c..f221ef7 100644 --- a/examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py +++ b/examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py @@ -26,6 +26,6 @@ video = pipe( negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", input_image=input_image, num_frames=49, - seed=1, tiled=False, + seed=1, tiled=True, ) save_video(video, "video_Wan2.2-I2V-A14B.mp4", fps=15, quality=5) diff --git a/examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py b/examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py index d5a9229..e5b16c8 100644 --- a/examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py +++ b/examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py @@ -24,6 +24,6 @@ video = pipe( negative_prompt="色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", input_image=input_image, num_frames=49, - seed=1, tiled=False, + seed=1, tiled=True, ) save_video(video, "video_Wan2.2-TI2V-5B.mp4", fps=15, quality=5) From 158567ca2061d9702c30088209fc776a221b4dcf Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 29 Jul 2025 10:16:40 +0800 Subject: [PATCH 43/51] bug fix --- examples/wanvideo/model_training/train.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/wanvideo/model_training/train.py b/examples/wanvideo/model_training/train.py index 93ec0bd..98c737f 100644 --- a/examples/wanvideo/model_training/train.py +++ b/examples/wanvideo/model_training/train.py @@ -83,6 +83,8 @@ class WanTrainingModule(DiffusionTrainingModule): inputs_shared["input_image"] = data["video"][0] elif extra_input == "end_image": inputs_shared["end_image"] = data["video"][-1] + elif extra_input == "reference_image" or extra_input == "vace_reference_image": + inputs_shared[extra_input] = data[extra_input][0] else: inputs_shared[extra_input] = data[extra_input] From c125728ce03bd723e271f28cd8a92c2a0f16aa78 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 29 Jul 2025 11:16:50 +0800 Subject: [PATCH 44/51] bug fix --- diffsynth/pipelines/wan_video_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diffsynth/pipelines/wan_video_new.py b/diffsynth/pipelines/wan_video_new.py index f1a4dfe..80807dd 100644 --- a/diffsynth/pipelines/wan_video_new.py +++ b/diffsynth/pipelines/wan_video_new.py @@ -1012,7 +1012,7 @@ class TemporalTiler_BCTHW: def __init__(self): pass - def build_1d_mask(length, left_bound, right_bound, border_width): + def build_1d_mask(self, length, left_bound, right_bound, border_width): x = torch.ones((length,)) if border_width == 0: return x From 8ef91b36728947e3563af2925a0a6c861a6309a3 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Tue, 29 Jul 2025 13:28:42 +0800 Subject: [PATCH 45/51] support training for eligen and nexusgen --- README.md | 4 +-- README_zh.md | 4 +-- diffsynth/models/nexus_gen.py | 11 +++--- diffsynth/pipelines/flux_image_new.py | 3 +- diffsynth/trainers/utils.py | 9 +++-- .../flux/model_inference/Nexus-Gen-Editing.py | 11 +++--- .../Nexus-Gen-Editing.py | 36 +++++++++++++++++++ .../full/FLUX.1-NexusGen-Edit.sh | 14 ++++++++ .../full/accelerate_config_zero2offload.yaml | 22 ++++++++++++ .../lora/FLUX.1-NexusGen-Edit.sh | 17 +++++++++ .../model_training/lora/FLUX.1-dev-EliGen.sh | 17 +++++++++ .../validate_full/Nexus-Gen-Editing.py | 28 +++++++++++++++ .../validate_lora/FLUX.1-dev-EliGen.py | 33 +++++++++++++++++ .../validate_lora/Nexus-Gen-Editing.py | 26 ++++++++++++++ 14 files changed, 218 insertions(+), 17 deletions(-) create mode 100644 examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py create mode 100644 examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh create mode 100644 examples/flux/model_training/full/accelerate_config_zero2offload.yaml create mode 100644 examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh create mode 100644 examples/flux/model_training/lora/FLUX.1-dev-EliGen.sh create mode 100644 examples/flux/model_training/validate_full/Nexus-Gen-Editing.py create mode 100644 examples/flux/model_training/validate_lora/FLUX.1-dev-EliGen.py create mode 100644 examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py diff --git a/README.md b/README.md index 11403a5..f592abb 100644 --- a/README.md +++ b/README.md @@ -96,12 +96,12 @@ image.save("image.jpg") |[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| |[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| -|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./examples/flux/model_inference/FLUX.1-dev-EliGen.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./examples/flux/model_inference/FLUX.1-dev-EliGen.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-|[code](./examples/flux/model_training/lora/FLUX.1-dev-EliGen.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-EliGen.py)| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| |[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| - +|[Nexus-Gen-Edit](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|`nexus_gen_reference_image`|[code](./examples/flux/model_inference/Nexus-Gen-Editing.py)|[code](./examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh)|[code](./examples/flux/model_training/validate_full/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py)| diff --git a/README_zh.md b/README_zh.md index 650d2ec..dc1b514 100644 --- a/README_zh.md +++ b/README_zh.md @@ -98,12 +98,12 @@ image.save("image.jpg") |[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| |[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./examples/flux/model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./examples/flux/model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| -|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./examples/flux/model_inference/FLUX.1-dev-EliGen.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./examples/flux/model_inference/FLUX.1-dev-EliGen.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-|[code](./examples/flux/model_training/lora/FLUX.1-dev-EliGen.sh)|[code](./examples/flux/model_training/validate_lora/FLUX.1-dev-EliGen.py)| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./examples/flux/model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./examples/flux/model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| |[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| - +|[Nexus-Gen-Edit](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|`nexus_gen_reference_image`|[code](./examples/flux/model_inference/Nexus-Gen-Editing.py)|[code](./examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh)|[code](./examples/flux/model_training/validate_full/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py)| ### Wan 系列 diff --git a/diffsynth/models/nexus_gen.py b/diffsynth/models/nexus_gen.py index f7a771e..31475c7 100644 --- a/diffsynth/models/nexus_gen.py +++ b/diffsynth/models/nexus_gen.py @@ -14,7 +14,7 @@ class NexusGenAutoregressiveModel(torch.nn.Module): self.model = Qwen2_5_VLForConditionalGeneration(model_config) self.processor = Qwen2_5_VLProcessor.from_pretrained(model_path) - + @staticmethod def state_dict_converter(): return NexusGenAutoregressiveModelStateDictConverter() @@ -34,6 +34,7 @@ class NexusGenAutoregressiveModel(torch.nn.Module): return messages def get_generation_msg(self, instruction): + instruction = "Generate an image according to the following description: {}".format(instruction) messages = [{"role":"user", "content":instruction}, {"role":"assistant", "content":"Here is an image based on the description: "}] return messages @@ -80,9 +81,10 @@ class NexusGenAutoregressiveModel(torch.nn.Module): ) input_embeds = input_embeds.masked_scatter(gt_image_mask.unsqueeze(-1).expand_as(input_embeds), image_prefill_embeds) - position_ids, _ = model.get_rope_index(inputs['input_ids'], - inputs['image_grid_thw'], - attention_mask=inputs['attention_mask']) + position_ids, _ = model.get_rope_index( + inputs['input_ids'], + inputs['image_grid_thw'], + attention_mask=inputs['attention_mask']) position_ids = position_ids.contiguous() outputs = model(inputs_embeds=input_embeds, position_ids=position_ids, attention_mask=inputs['attention_mask'], return_dict=True) output_image_embeddings = outputs.image_embeddings[:, :-1, :] @@ -97,4 +99,3 @@ class NexusGenAutoregressiveModelStateDictConverter: def from_civitai(self, state_dict): state_dict = {"model." + key: value for key, value in state_dict.items()} return state_dict - \ No newline at end of file diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index 36d7922..8f9ec61 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -767,9 +767,10 @@ class FluxImageUnit_EntityControl(PipelineUnit): if eligen_entity_prompts is None or eligen_entity_masks is None: return inputs_shared, inputs_posi, inputs_nega pipe.load_models_to_device(self.onload_model_names) + eligen_enable_on_negative = inputs_shared.get("eligen_enable_on_negative", False) eligen_kwargs_posi, eligen_kwargs_nega = self.prepare_eligen(pipe, inputs_nega, eligen_entity_prompts, eligen_entity_masks, inputs_shared["width"], inputs_shared["height"], - inputs_shared["t5_sequence_length"], inputs_shared["eligen_enable_on_negative"], inputs_shared["cfg_scale"]) + inputs_shared["t5_sequence_length"], eligen_enable_on_negative, inputs_shared["cfg_scale"]) inputs_posi.update(eligen_kwargs_posi) if inputs_shared.get("cfg_scale", 1.0) != 1.0: inputs_nega.update(eligen_kwargs_nega) diff --git a/diffsynth/trainers/utils.py b/diffsynth/trainers/utils.py index b171857..07e3664 100644 --- a/diffsynth/trainers/utils.py +++ b/diffsynth/trainers/utils.py @@ -120,8 +120,13 @@ class ImageDataset(torch.utils.data.Dataset): data = self.data[data_id % len(self.data)].copy() for key in self.data_file_keys: if key in data: - path = os.path.join(self.base_path, data[key]) - data[key] = self.load_data(path) + if isinstance(data[key], list): + print(f"Loading multiple files for key '{key}'.") + path = [os.path.join(self.base_path, p) for p in data[key]] + data[key] = [self.load_data(p) for p in path] + else: + path = os.path.join(self.base_path, data[key]) + data[key] = self.load_data(path) if data[key] is None: warnings.warn(f"cannot load file {data[key]}.") return None diff --git a/examples/flux/model_inference/Nexus-Gen-Editing.py b/examples/flux/model_inference/Nexus-Gen-Editing.py index 603ac33..f24f0c0 100644 --- a/examples/flux/model_inference/Nexus-Gen-Editing.py +++ b/examples/flux/model_inference/Nexus-Gen-Editing.py @@ -2,7 +2,7 @@ import importlib import torch from PIL import Image from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig -from modelscope import snapshot_download +from modelscope import snapshot_download, dataset_snapshot_download if importlib.util.find_spec("transformers") is None: raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") @@ -23,12 +23,13 @@ pipe = FluxImagePipeline.from_pretrained( ], ) -prompt = "给猫加一副太阳镜" -ref_image = Image.open("cat.png").convert("RGB") +dataset_snapshot_download(dataset_id="DiffSynth-Studio/examples_in_diffsynth", local_dir="./", allow_file_pattern=f"data/examples/nexusgen/cat.jpg") +ref_image = Image.open("data/examples/nexusgen/cat.jpg").convert("RGB") +prompt = "Add a crown." image = pipe( prompt=prompt, negative_prompt="", - seed=0, cfg_scale=1.0, num_inference_steps=50, + seed=42, cfg_scale=2.0, num_inference_steps=50, nexus_gen_reference_image=ref_image, height=512, width=512, ) -image.save("cat_glasses.jpg") +image.save("cat_crown.jpg") diff --git a/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py b/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py new file mode 100644 index 0000000..70a543f --- /dev/null +++ b/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py @@ -0,0 +1,36 @@ +import importlib +import torch +from PIL import Image +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from modelscope import snapshot_download, dataset_snapshot_download + +if importlib.util.find_spec("transformers") is None: + raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") +else: + import transformers + assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==4.49.0, please install it with `pip install transformers==4.49.0`." + +snapshot_download("DiffSynth-Studio/Nexus-GenV2", local_dir="models/DiffSynth-Studio/Nexus-GenV2") +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="edit_decoder.bin"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) +pipe.enable_vram_management() + +dataset_snapshot_download(dataset_id="DiffSynth-Studio/examples_in_diffsynth", local_dir="./", allow_file_pattern=f"data/examples/nexusgen/cat.jpg") +ref_image = Image.open("data/examples/nexusgen/cat.jpg").convert("RGB") +prompt = "Add a crown." +image = pipe( + prompt=prompt, negative_prompt="", + seed=42, cfg_scale=2.0, num_inference_steps=50, + nexus_gen_reference_image=ref_image, + height=512, width=512, +) +image.save("cat_crown.jpg") diff --git a/examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh b/examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh new file mode 100644 index 0000000..ab1c324 --- /dev/null +++ b/examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh @@ -0,0 +1,14 @@ +accelerate launch --config_file examples/flux/model_training/full/accelerate_config_zero2offload.yaml examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_nexusgen_edit.csv \ + --data_file_keys "image,nexus_gen_reference_image" \ + --max_pixels 262144 \ + --dataset_repeat 400 \ + --model_id_with_origin_paths "DiffSynth-Studio/Nexus-GenV2:model*.safetensors,DiffSynth-Studio/Nexus-GenV2:edit_decoder.bin,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors" \ + --learning_rate 1e-5 \ + --num_epochs 1 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-NexusGen-Edit_full" \ + --trainable_models "dit" \ + --extra_inputs "nexus_gen_reference_image" \ + --use_gradient_checkpointing_offload diff --git a/examples/flux/model_training/full/accelerate_config_zero2offload.yaml b/examples/flux/model_training/full/accelerate_config_zero2offload.yaml new file mode 100644 index 0000000..8a75f3d --- /dev/null +++ b/examples/flux/model_training/full/accelerate_config_zero2offload.yaml @@ -0,0 +1,22 @@ +compute_environment: LOCAL_MACHINE +debug: false +deepspeed_config: + gradient_accumulation_steps: 1 + offload_optimizer_device: 'cpu' + offload_param_device: 'cpu' + zero3_init_flag: false + zero_stage: 2 +distributed_type: DEEPSPEED +downcast_bf16: 'no' +enable_cpu_affinity: false +machine_rank: 0 +main_training_function: main +mixed_precision: bf16 +num_machines: 1 +num_processes: 8 +rdzv_backend: static +same_network: true +tpu_env: [] +tpu_use_cluster: false +tpu_use_sudo: false +use_cpu: false diff --git a/examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh b/examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh new file mode 100644 index 0000000..3e6eac1 --- /dev/null +++ b/examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_nexusgen_edit.csv \ + --data_file_keys "image,nexus_gen_reference_image" \ + --max_pixels 1048576 \ + --dataset_repeat 400 \ + --model_id_with_origin_paths "DiffSynth-Studio/Nexus-GenV2:model*.safetensors,DiffSynth-Studio/Nexus-GenV2:edit_decoder.bin,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-NexusGen-Edit_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --align_to_opensource_format \ + --extra_inputs "nexus_gen_reference_image" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/lora/FLUX.1-dev-EliGen.sh b/examples/flux/model_training/lora/FLUX.1-dev-EliGen.sh new file mode 100644 index 0000000..10a18e0 --- /dev/null +++ b/examples/flux/model_training/lora/FLUX.1-dev-EliGen.sh @@ -0,0 +1,17 @@ +accelerate launch examples/flux/model_training/train.py \ + --dataset_base_path data/example_image_dataset \ + --dataset_metadata_path data/example_image_dataset/metadata_eligen.json \ + --data_file_keys "image,eligen_entity_masks" \ + --max_pixels 1048576 \ + --dataset_repeat 50 \ + --model_id_with_origin_paths "black-forest-labs/FLUX.1-dev:flux1-dev.safetensors,black-forest-labs/FLUX.1-dev:text_encoder/model.safetensors,black-forest-labs/FLUX.1-dev:text_encoder_2/,black-forest-labs/FLUX.1-dev:ae.safetensors" \ + --learning_rate 1e-4 \ + --num_epochs 5 \ + --remove_prefix_in_ckpt "pipe.dit." \ + --output_path "./models/train/FLUX.1-dev-EliGen_lora" \ + --lora_base_model "dit" \ + --lora_target_modules "a_to_qkv,b_to_qkv,ff_a.0,ff_a.2,ff_b.0,ff_b.2,a_to_out,b_to_out,proj_out,norm.linear,norm1_a.linear,norm1_b.linear,to_qkv_mlp" \ + --lora_rank 32 \ + --align_to_opensource_format \ + --extra_inputs "eligen_entity_masks,eligen_entity_prompts" \ + --use_gradient_checkpointing diff --git a/examples/flux/model_training/validate_full/Nexus-Gen-Editing.py b/examples/flux/model_training/validate_full/Nexus-Gen-Editing.py new file mode 100644 index 0000000..5f7a2d2 --- /dev/null +++ b/examples/flux/model_training/validate_full/Nexus-Gen-Editing.py @@ -0,0 +1,28 @@ +import torch +from PIL import Image +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from diffsynth import load_state_dict + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="edit_decoder.bin"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) +state_dict = load_state_dict("models/train/FLUX.1-NexusGen-Edit_full/epoch-0.safetensors") +pipe.dit.load_state_dict(state_dict) + +ref_image = Image.open("data/example_image_dataset/nexus_gen/image_1.png").convert("RGB") +prompt = "Add a pair of sunglasses." +image = pipe( + prompt=prompt, negative_prompt="", + seed=42, cfg_scale=2.0, num_inference_steps=50, + nexus_gen_reference_image=ref_image, + height=512, width=512, +) +image.save("NexusGen-Edit_full.jpg") diff --git a/examples/flux/model_training/validate_lora/FLUX.1-dev-EliGen.py b/examples/flux/model_training/validate_lora/FLUX.1-dev-EliGen.py new file mode 100644 index 0000000..7df3db2 --- /dev/null +++ b/examples/flux/model_training/validate_lora/FLUX.1-dev-EliGen.py @@ -0,0 +1,33 @@ +import torch +from PIL import Image +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="flux1-dev.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) + +pipe.load_lora(pipe.dit, "models/train/FLUX.1-dev-EliGen_lora/epoch-4.safetensors", alpha=1) + +entity_prompts = ["A beautiful girl", "sign 'Entity Control'", "shorts", "shirt"] +global_prompt = "A beautiful girl wearing shirt and shorts in the street, holding a sign 'Entity Control'" +masks = [Image.open(f"data/example_image_dataset/eligen/{i}.png").convert('RGB') for i in range(len(entity_prompts))] +# generate image +image = pipe( + prompt=global_prompt, + cfg_scale=1.0, + num_inference_steps=50, + embedded_guidance=3.5, + seed=42, + height=1024, + width=1024, + eligen_entity_prompts=entity_prompts, + eligen_entity_masks=masks, +) +image.save(f"EliGen_lora.png") diff --git a/examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py b/examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py new file mode 100644 index 0000000..21c376f --- /dev/null +++ b/examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py @@ -0,0 +1,26 @@ +import torch +from PIL import Image +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig + +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="edit_decoder.bin"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) +pipe.load_lora(pipe.dit, "models/train/FLUX.1-NexusGen-Edit_lora/epoch-4.safetensors", alpha=1) + +ref_image = Image.open("data/example_image_dataset/nexus_gen/image_1.png").convert("RGB") +prompt = "Add a pair of sunglasses." +image = pipe( + prompt=prompt, negative_prompt="", + seed=42, cfg_scale=1.0, num_inference_steps=50, + nexus_gen_reference_image=ref_image, + height=512, width=512, +) +image.save("NexusGen-Edit_lora.jpg") From 7df48fc2b56ae6b84fb70fb19e938e8995606030 Mon Sep 17 00:00:00 2001 From: mi804 <1576993271@qq.com> Date: Tue, 29 Jul 2025 13:33:14 +0800 Subject: [PATCH 46/51] remove debug out --- diffsynth/trainers/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/diffsynth/trainers/utils.py b/diffsynth/trainers/utils.py index 07e3664..8e51f18 100644 --- a/diffsynth/trainers/utils.py +++ b/diffsynth/trainers/utils.py @@ -121,7 +121,6 @@ class ImageDataset(torch.utils.data.Dataset): for key in self.data_file_keys: if key in data: if isinstance(data[key], list): - print(f"Loading multiple files for key '{key}'.") path = [os.path.join(self.base_path, p) for p in data[key]] data[key] = [self.load_data(p) for p in path] else: From 79fa8607dcc6f916dcbcba8f3b7e224b733776b5 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 29 Jul 2025 17:05:41 +0800 Subject: [PATCH 47/51] update README --- README.md | 6 +++--- README_zh.md | 6 +++--- examples/wanvideo/README.md | 6 +++--- examples/wanvideo/README_zh.md | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 87bca13..648fc16 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) | Model ID | Extra Parameters | Inference | Full Training | Validate After Full Training | LoRA Training | Validate After LoRA Training | |-|-|-|-|-|-|-| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py)| |[Wan-AI/Wan2.1-T2V-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-1.3B)||[code](./examples/wanvideo/model_inference/Wan2.1-T2V-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-T2V-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-T2V-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-T2V-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-T2V-1.3B.py)| |[Wan-AI/Wan2.1-T2V-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-14B)||[code](./examples/wanvideo/model_inference/Wan2.1-T2V-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-T2V-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-T2V-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-T2V-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-T2V-14B.py)| |[Wan-AI/Wan2.1-I2V-14B-480P](https://modelscope.cn/models/Wan-AI/Wan2.1-I2V-14B-480P)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-I2V-14B-480P.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-I2V-14B-480P.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-I2V-14B-480P.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-I2V-14B-480P.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-I2V-14B-480P.py)| @@ -167,9 +170,6 @@ save_video(video, "video1.mp4", fps=15, quality=5) |[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-1.3B.py)| |[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-14B.py)| |[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./examples/wanvideo/model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| -|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py)| -|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py)| -|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py)| diff --git a/README_zh.md b/README_zh.md index bfcaa46..94b20fa 100644 --- a/README_zh.md +++ b/README_zh.md @@ -148,6 +148,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) |模型 ID|额外参数|推理|全量训练|全量训练后验证|LoRA 训练|LoRA 训练后验证| |-|-|-|-|-|-|-| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py)| |[Wan-AI/Wan2.1-T2V-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-1.3B)||[code](./examples/wanvideo/model_inference/Wan2.1-T2V-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-T2V-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-T2V-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-T2V-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-T2V-1.3B.py)| |[Wan-AI/Wan2.1-T2V-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-14B)||[code](./examples/wanvideo/model_inference/Wan2.1-T2V-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-T2V-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-T2V-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-T2V-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-T2V-14B.py)| |[Wan-AI/Wan2.1-I2V-14B-480P](https://modelscope.cn/models/Wan-AI/Wan2.1-I2V-14B-480P)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.1-I2V-14B-480P.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-I2V-14B-480P.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-I2V-14B-480P.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-I2V-14B-480P.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-I2V-14B-480P.py)| @@ -167,9 +170,6 @@ save_video(video, "video1.mp4", fps=15, quality=5) |[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-1.3B.py)| |[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./examples/wanvideo/model_inference/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-VACE-14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-VACE-14B.py)| |[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./examples/wanvideo/model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./examples/wanvideo/model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| -|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-I2V-A14B.py)| -|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./examples/wanvideo/model_inference/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-T2V-A14B.py)| -|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./examples/wanvideo/model_inference/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/full/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./examples/wanvideo/model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./examples/wanvideo/model_training/validate_lora/Wan2.2-TI2V-5B.py)| diff --git a/examples/wanvideo/README.md b/examples/wanvideo/README.md index 8231e29..71ff60e 100644 --- a/examples/wanvideo/README.md +++ b/examples/wanvideo/README.md @@ -48,6 +48,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) | Model ID | Extra Parameters | Inference | Full Training | Full Training Validation | LoRA Training | LoRA Training Validation | |-|-|-|-|-|-|-| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./model_inference/Wan2.2-I2V-A14B.py)|[code](./model_training/full/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./model_inference/Wan2.2-T2V-A14B.py)|[code](./model_training/full/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./model_inference/Wan2.2-TI2V-5B.py)|[code](./model_training/full/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_lora/Wan2.2-TI2V-5B.py)| |[Wan-AI/Wan2.1-T2V-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-1.3B)||[code](./model_inference/Wan2.1-T2V-1.3B.py)|[code](./model_training/full/Wan2.1-T2V-1.3B.sh)|[code](./model_training/validate_full/Wan2.1-T2V-1.3B.py)|[code](./model_training/lora/Wan2.1-T2V-1.3B.sh)|[code](./model_training/validate_lora/Wan2.1-T2V-1.3B.py)| |[Wan-AI/Wan2.1-T2V-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-14B)||[code](./model_inference/Wan2.1-T2V-14B.py)|[code](./model_training/full/Wan2.1-T2V-14B.sh)|[code](./model_training/validate_full/Wan2.1-T2V-14B.py)|[code](./model_training/lora/Wan2.1-T2V-14B.sh)|[code](./model_training/validate_lora/Wan2.1-T2V-14B.py)| |[Wan-AI/Wan2.1-I2V-14B-480P](https://modelscope.cn/models/Wan-AI/Wan2.1-I2V-14B-480P)|`input_image`|[code](./model_inference/Wan2.1-I2V-14B-480P.py)|[code](./model_training/full/Wan2.1-I2V-14B-480P.sh)|[code](./model_training/validate_full/Wan2.1-I2V-14B-480P.py)|[code](./model_training/lora/Wan2.1-I2V-14B-480P.sh)|[code](./model_training/validate_lora/Wan2.1-I2V-14B-480P.py)| @@ -67,9 +70,6 @@ save_video(video, "video1.mp4", fps=15, quality=5) |[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./model_inference/Wan2.1-VACE-1.3B.py)|[code](./model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./model_training/validate_lora/Wan2.1-VACE-1.3B.py)| |[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./model_inference/Wan2.1-VACE-14B.py)|[code](./model_training/full/Wan2.1-VACE-14B.sh)|[code](./model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./model_training/lora/Wan2.1-VACE-14B.sh)|[code](./model_training/validate_lora/Wan2.1-VACE-14B.py)| |[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| -|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./model_inference/Wan2.2-I2V-A14B.py)|[code](./model_training/full/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-I2V-A14B.py)| -|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./model_inference/Wan2.2-T2V-A14B.py)|[code](./model_training/full/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-T2V-A14B.py)| -|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./model_inference/Wan2.2-TI2V-5B.py)|[code](./model_training/full/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_lora/Wan2.2-TI2V-5B.py)| ## Model Inference diff --git a/examples/wanvideo/README_zh.md b/examples/wanvideo/README_zh.md index 9f26858..461a86f 100644 --- a/examples/wanvideo/README_zh.md +++ b/examples/wanvideo/README_zh.md @@ -48,6 +48,9 @@ save_video(video, "video1.mp4", fps=15, quality=5) |模型 ID|额外参数|推理|全量训练|全量训练后验证|LoRA 训练|LoRA 训练后验证| |-|-|-|-|-|-|-| +|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./model_inference/Wan2.2-I2V-A14B.py)|[code](./model_training/full/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-I2V-A14B.py)| +|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./model_inference/Wan2.2-T2V-A14B.py)|[code](./model_training/full/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-T2V-A14B.py)| +|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./model_inference/Wan2.2-TI2V-5B.py)|[code](./model_training/full/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_lora/Wan2.2-TI2V-5B.py)| |[Wan-AI/Wan2.1-T2V-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-1.3B)||[code](./model_inference/Wan2.1-T2V-1.3B.py)|[code](./model_training/full/Wan2.1-T2V-1.3B.sh)|[code](./model_training/validate_full/Wan2.1-T2V-1.3B.py)|[code](./model_training/lora/Wan2.1-T2V-1.3B.sh)|[code](./model_training/validate_lora/Wan2.1-T2V-1.3B.py)| |[Wan-AI/Wan2.1-T2V-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-T2V-14B)||[code](./model_inference/Wan2.1-T2V-14B.py)|[code](./model_training/full/Wan2.1-T2V-14B.sh)|[code](./model_training/validate_full/Wan2.1-T2V-14B.py)|[code](./model_training/lora/Wan2.1-T2V-14B.sh)|[code](./model_training/validate_lora/Wan2.1-T2V-14B.py)| |[Wan-AI/Wan2.1-I2V-14B-480P](https://modelscope.cn/models/Wan-AI/Wan2.1-I2V-14B-480P)|`input_image`|[code](./model_inference/Wan2.1-I2V-14B-480P.py)|[code](./model_training/full/Wan2.1-I2V-14B-480P.sh)|[code](./model_training/validate_full/Wan2.1-I2V-14B-480P.py)|[code](./model_training/lora/Wan2.1-I2V-14B-480P.sh)|[code](./model_training/validate_lora/Wan2.1-I2V-14B-480P.py)| @@ -67,9 +70,6 @@ save_video(video, "video1.mp4", fps=15, quality=5) |[Wan-AI/Wan2.1-VACE-1.3B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-1.3B)|`vace_control_video`, `vace_reference_image`|[code](./model_inference/Wan2.1-VACE-1.3B.py)|[code](./model_training/full/Wan2.1-VACE-1.3B.sh)|[code](./model_training/validate_full/Wan2.1-VACE-1.3B.py)|[code](./model_training/lora/Wan2.1-VACE-1.3B.sh)|[code](./model_training/validate_lora/Wan2.1-VACE-1.3B.py)| |[Wan-AI/Wan2.1-VACE-14B](https://modelscope.cn/models/Wan-AI/Wan2.1-VACE-14B)|`vace_control_video`, `vace_reference_image`|[code](./model_inference/Wan2.1-VACE-14B.py)|[code](./model_training/full/Wan2.1-VACE-14B.sh)|[code](./model_training/validate_full/Wan2.1-VACE-14B.py)|[code](./model_training/lora/Wan2.1-VACE-14B.sh)|[code](./model_training/validate_lora/Wan2.1-VACE-14B.py)| |[DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1](https://modelscope.cn/models/DiffSynth-Studio/Wan2.1-1.3b-speedcontrol-v1)|`motion_bucket_id`|[code](./model_inference/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./model_training/full/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./model_training/validate_full/Wan2.1-1.3b-speedcontrol-v1.py)|[code](./model_training/lora/Wan2.1-1.3b-speedcontrol-v1.sh)|[code](./model_training/validate_lora/Wan2.1-1.3b-speedcontrol-v1.py)| -|[Wan-AI/Wan2.2-I2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-I2V-A14B)|`input_image`|[code](./model_inference/Wan2.2-I2V-A14B.py)|[code](./model_training/full/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-I2V-A14B.py)|[code](./model_training/lora/Wan2.2-I2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-I2V-A14B.py)| -|[Wan-AI/Wan2.2-T2V-A14B](https://modelscope.cn/models/Wan-AI/Wan2.2-T2V-A14B)||[code](./model_inference/Wan2.2-T2V-A14B.py)|[code](./model_training/full/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_full/Wan2.2-T2V-A14B.py)|[code](./model_training/lora/Wan2.2-T2V-A14B.sh)|[code](./model_training/validate_lora/Wan2.2-T2V-A14B.py)| -|[Wan-AI/Wan2.2-TI2V-5B](https://modelscope.cn/models/Wan-AI/Wan2.2-TI2V-5B)|`input_image`|[code](./model_inference/Wan2.2-TI2V-5B.py)|[code](./model_training/full/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_full/Wan2.2-TI2V-5B.py)|[code](./model_training/lora/Wan2.2-TI2V-5B.sh)|[code](./model_training/validate_lora/Wan2.2-TI2V-5B.py)| ## 模型推理 From 9c51623fc2b653b01cee0ec175a98c719fe6bd5d Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 29 Jul 2025 18:47:16 +0800 Subject: [PATCH 48/51] refine code --- README.md | 3 +- README_zh.md | 3 +- diffsynth/models/nexus_gen.py | 72 +++++++++++++++++-- diffsynth/models/nexus_gen_projector.py | 9 ++- diffsynth/pipelines/flux_image_new.py | 4 ++ examples/flux/README.md | 5 +- examples/flux/README_zh.md | 3 +- .../flux/model_inference/Nexus-Gen-Editing.py | 6 +- .../model_inference/Nexus-Gen-Generation.py | 5 +- .../Nexus-Gen-Generation.py | 32 +++++++++ .../{FLUX.1-NexusGen-Edit.sh => Nexus-Gen.sh} | 0 .../{FLUX.1-NexusGen-Edit.sh => Nexus-Gen.sh} | 0 .../{Nexus-Gen-Editing.py => Nexus-Gen.py} | 0 .../{Nexus-Gen-Editing.py => Nexus-Gen.py} | 0 14 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py rename examples/flux/model_training/full/{FLUX.1-NexusGen-Edit.sh => Nexus-Gen.sh} (100%) rename examples/flux/model_training/lora/{FLUX.1-NexusGen-Edit.sh => Nexus-Gen.sh} (100%) rename examples/flux/model_training/validate_full/{Nexus-Gen-Editing.py => Nexus-Gen.py} (100%) rename examples/flux/model_training/validate_lora/{Nexus-Gen-Editing.py => Nexus-Gen.py} (100%) diff --git a/README.md b/README.md index f592abb..dfea6d1 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,8 @@ image.save("image.jpg") |[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| -|[Nexus-Gen-Edit](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|`nexus_gen_reference_image`|[code](./examples/flux/model_inference/Nexus-Gen-Editing.py)|[code](./examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh)|[code](./examples/flux/model_training/validate_full/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py)| +|[Nexus-Gen](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|`nexus_gen_reference_image`|[code](./examples/flux/model_inference/Nexus-Gen-Editing.py)|[code](./examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/full/Nexus-Gen.sh)|[code](./examples/flux/model_training/validate_full/Nexus-Gen.py)|[code](./examples/flux/model_training/lora/Nexus-Gen.sh)|[code](./examples/flux/model_training/validate_lora/Nexus-Gen.py)| + diff --git a/README_zh.md b/README_zh.md index dc1b514..2aae18a 100644 --- a/README_zh.md +++ b/README_zh.md @@ -103,7 +103,8 @@ image.save("image.jpg") |[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./examples/flux/model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./examples/flux/model_inference/Step1X-Edit.py)|[code](./examples/flux/model_inference_low_vram/Step1X-Edit.py)|[code](./examples/flux/model_training/full/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_full/Step1X-Edit.py)|[code](./examples/flux/model_training/lora/Step1X-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./examples/flux/model_inference/FLEX.2-preview.py)|[code](./examples/flux/model_inference_low_vram/FLEX.2-preview.py)|[code](./examples/flux/model_training/full/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_full/FLEX.2-preview.py)|[code](./examples/flux/model_training/lora/FLEX.2-preview.sh)|[code](./examples/flux/model_training/validate_lora/FLEX.2-preview.py)| -|[Nexus-Gen-Edit](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|`nexus_gen_reference_image`|[code](./examples/flux/model_inference/Nexus-Gen-Editing.py)|[code](./examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh)|[code](./examples/flux/model_training/validate_full/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh)|[code](./examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py)| +|[Nexus-Gen](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|`nexus_gen_reference_image`|[code](./examples/flux/model_inference/Nexus-Gen-Editing.py)|[code](./examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py)|[code](./examples/flux/model_training/full/Nexus-Gen.sh)|[code](./examples/flux/model_training/validate_full/Nexus-Gen.py)|[code](./examples/flux/model_training/lora/Nexus-Gen.sh)|[code](./examples/flux/model_training/validate_lora/Nexus-Gen.py)| + ### Wan 系列 diff --git a/diffsynth/models/nexus_gen.py b/diffsynth/models/nexus_gen.py index 31475c7..0110398 100644 --- a/diffsynth/models/nexus_gen.py +++ b/diffsynth/models/nexus_gen.py @@ -1,18 +1,77 @@ import torch from PIL import Image -from qwen_vl_utils import smart_resize -from transformers import AutoConfig -from .nexus_gen_ar_model import Qwen2_5_VLForConditionalGeneration, Qwen2_5_VLProcessor class NexusGenAutoregressiveModel(torch.nn.Module): - def __init__(self, model_path="models/DiffSynth-Studio/Nexus-GenV2", max_length=1024, max_pixels=262640, dtype=torch.bfloat16, device="cuda"): + def __init__(self, max_length=1024, max_pixels=262640): super(NexusGenAutoregressiveModel, self).__init__() + from .nexus_gen_ar_model import Qwen2_5_VLForConditionalGeneration + from transformers import Qwen2_5_VLConfig self.max_length = max_length self.max_pixels = max_pixels - model_config = AutoConfig.from_pretrained(model_path) + model_config = Qwen2_5_VLConfig(**{ + "_name_or_path": "DiffSynth-Studio/Nexus-GenV2", + "architectures": [ + "Qwen2_5_VLForConditionalGeneration" + ], + "attention_dropout": 0.0, + "auto_map": { + "AutoConfig": "configuration_qwen2_5_vl.Qwen2_5_VLConfig", + "AutoModel": "modeling_qwen2_5_vl.Qwen2_5_VLModel", + "AutoModelForCausalLM": "modeling_qwen2_5_vl.Qwen2_5_VLForConditionalGeneration" + }, + "bos_token_id": 151643, + "eos_token_id": 151645, + "hidden_act": "silu", + "hidden_size": 3584, + "image_token_id": 151655, + "initializer_range": 0.02, + "intermediate_size": 18944, + "max_position_embeddings": 128000, + "max_window_layers": 28, + "model_type": "qwen2_5_vl", + "num_attention_heads": 28, + "num_hidden_layers": 28, + "num_key_value_heads": 4, + "pad_token_id": 151643, + "rms_norm_eps": 1e-06, + "rope_scaling": { + "mrope_section": [ + 16, + 24, + 24 + ], + "rope_type": "default", + "type": "default" + }, + "rope_theta": 1000000.0, + "sliding_window": 32768, + "tie_word_embeddings": False, + "torch_dtype": "bfloat16", + "transformers_version": "4.49.0", + "use_cache": False, + "use_sliding_window": False, + "video_token_id": 151656, + "vision_config": { + "hidden_size": 1280, + "in_chans": 3, + "model_type": "qwen2_5_vl", + "spatial_patch_size": 14, + "tokens_per_second": 2, + "torch_dtype": "bfloat16" + }, + "vision_end_token_id": 151653, + "vision_start_token_id": 151652, + "vision_token_id": 151654, + "vocab_size": 152064 + }) self.model = Qwen2_5_VLForConditionalGeneration(model_config) - self.processor = Qwen2_5_VLProcessor.from_pretrained(model_path) + self.processor = None + + + def load_processor(self, path): + from .nexus_gen_ar_model import Qwen2_5_VLProcessor + self.processor = Qwen2_5_VLProcessor.from_pretrained(path) @staticmethod @@ -20,6 +79,7 @@ class NexusGenAutoregressiveModel(torch.nn.Module): return NexusGenAutoregressiveModelStateDictConverter() def bound_image(self, image, max_pixels=262640): + from qwen_vl_utils import smart_resize resized_height, resized_width = smart_resize( image.height, image.width, diff --git a/diffsynth/models/nexus_gen_projector.py b/diffsynth/models/nexus_gen_projector.py index b35ff3f..0adbafb 100644 --- a/diffsynth/models/nexus_gen_projector.py +++ b/diffsynth/models/nexus_gen_projector.py @@ -2,9 +2,8 @@ import math import torch import torch.nn as nn from typing import Optional, Tuple -from transformers.activations import ACT2FN -from transformers.modeling_rope_utils import _compute_default_rope_parameters -from transformers import AutoConfig + + def rotate_half(x): """Rotates half the hidden dims of the input.""" @@ -39,6 +38,7 @@ class Qwen2_5_VLRotaryEmbedding(nn.Module): self.original_max_seq_len = config.max_position_embeddings self.config = config + from transformers.modeling_rope_utils import _compute_default_rope_parameters self.rope_init_fn = _compute_default_rope_parameters inv_freq, self.attention_scaling = self.rope_init_fn(self.config, device) @@ -181,6 +181,7 @@ class Qwen2_5_VLAttention(nn.Module): class Qwen2MLP(nn.Module): def __init__(self, config): super().__init__() + from transformers.activations import ACT2FN self.config = config self.hidden_size = config.hidden_size self.intermediate_size = config.intermediate_size @@ -254,6 +255,8 @@ class Qwen2_5_VLDecoderLayer(nn.Module): class NexusGenImageEmbeddingMerger(nn.Module): def __init__(self, model_path="models/DiffSynth-Studio/Nexus-GenV2", num_layers=1, out_channel=4096, expand_ratio=4, device='cpu'): super().__init__() + from transformers import AutoConfig + from transformers.activations import ACT2FN config = AutoConfig.from_pretrained(model_path) self.config = config self.num_layers = num_layers diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index 8f9ec61..b750509 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -375,6 +375,7 @@ class FluxImagePipeline(BasePipeline): torch_dtype: torch.dtype = torch.bfloat16, device: Union[str, torch.device] = "cuda", model_configs: list[ModelConfig] = [], + nexus_gen_processor_config: ModelConfig = None, ): # Download and load models model_manager = ModelManager() @@ -406,6 +407,9 @@ class FluxImagePipeline(BasePipeline): pipe.nexus_gen = model_manager.fetch_model("nexus_gen_llm") pipe.nexus_gen_generation_adapter = model_manager.fetch_model("nexus_gen_generation_adapter") pipe.nexus_gen_editing_adapter = model_manager.fetch_model("nexus_gen_editing_adapter") + if nexus_gen_processor_config is not None and pipe.nexus_gen is not None: + nexus_gen_processor_config.download_if_necessary() + pipe.nexus_gen.load_processor(nexus_gen_processor_config.path) # ControlNet controlnets = [] diff --git a/examples/flux/README.md b/examples/flux/README.md index a66e2bc..4ef0947 100644 --- a/examples/flux/README.md +++ b/examples/flux/README.md @@ -43,18 +43,19 @@ image.save("image.jpg") |Model ID|Extra Args|Inference|Low VRAM Inference|Full Training|Validation after Full Training|LoRA Training|Validation after LoRA Training| |-|-|-|-|-|-|-|-| -|[FLUX.1-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-dev )||[code](./model_inference/FLUX.1-dev.py)|[code](./model_inference_low_vram/FLUX.1-dev.py)|[code](./model_training/full/FLUX.1-dev.sh)|[code](./model_training/validate_full/FLUX.1-dev.py)|[code](./model_training/lora/FLUX.1-dev.sh)|[code](./model_training/validate_lora/FLUX.1-dev.py)| +|[FLUX.1-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-dev)||[code](./model_inference/FLUX.1-dev.py)|[code](./model_inference_low_vram/FLUX.1-dev.py)|[code](./model_training/full/FLUX.1-dev.sh)|[code](./model_training/validate_full/FLUX.1-dev.py)|[code](./model_training/lora/FLUX.1-dev.sh)|[code](./model_training/validate_lora/FLUX.1-dev.py)| |[FLUX.1-Kontext-dev](https://www.modelscope.cn/models/black-forest-labs/FLUX.1-Kontext-dev)|`kontext_images`|[code](./model_inference/FLUX.1-Kontext-dev.py)|[code](./model_inference_low_vram/FLUX.1-Kontext-dev.py)|[code](./model_training/full/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_full/FLUX.1-Kontext-dev.py)|[code](./model_training/lora/FLUX.1-Kontext-dev.sh)|[code](./model_training/validate_lora/FLUX.1-Kontext-dev.py)| |[FLUX.1-dev-Controlnet-Inpainting-Beta](https://www.modelscope.cn/models/alimama-creative/FLUX.1-dev-Controlnet-Inpainting-Beta)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Inpainting-Beta.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Inpainting-Beta.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Inpainting-Beta.py)| |[FLUX.1-dev-Controlnet-Union-alpha](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-Controlnet-Union-alpha)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Union-alpha.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Union-alpha.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Union-alpha.py)| |[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| |[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| -|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-|[code](./model_training/lora/FLUX.1-dev-EliGen.sh)|[code](./model_training/validate_lora/FLUX.1-dev-EliGen.py)| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| |[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./model_inference/Step1X-Edit.py)|[code](./model_inference_low_vram/Step1X-Edit.py)|[code](./model_training/full/Step1X-Edit.sh)|[code](./model_training/validate_full/Step1X-Edit.py)|[code](./model_training/lora/Step1X-Edit.sh)|[code](./model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./model_inference/FLEX.2-preview.py)|[code](./model_inference_low_vram/FLEX.2-preview.py)|[code](./model_training/full/FLEX.2-preview.sh)|[code](./model_training/validate_full/FLEX.2-preview.py)|[code](./model_training/lora/FLEX.2-preview.sh)|[code](./model_training/validate_lora/FLEX.2-preview.py)| +|[Nexus-Gen](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|`nexus_gen_reference_image`|[code](./model_inference/Nexus-Gen-Editing.py)|[code](./model_inference_low_vram/Nexus-Gen-Editing.py)|[code](./model_training/full/Nexus-Gen.sh)|[code](./model_training/validate_full/Nexus-Gen.py)|[code](./model_training/lora/Nexus-Gen.sh)|[code](./model_training/validate_lora/Nexus-Gen.py)| ## Model Inference diff --git a/examples/flux/README_zh.md b/examples/flux/README_zh.md index 3d3dc35..2e7b645 100644 --- a/examples/flux/README_zh.md +++ b/examples/flux/README_zh.md @@ -50,11 +50,12 @@ image.save("image.jpg") |[FLUX.1-dev-Controlnet-Upscaler](https://www.modelscope.cn/models/jasperai/Flux.1-dev-Controlnet-Upscaler)|`controlnet_inputs`|[code](./model_inference/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_inference_low_vram/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/full/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_full/FLUX.1-dev-Controlnet-Upscaler.py)|[code](./model_training/lora/FLUX.1-dev-Controlnet-Upscaler.sh)|[code](./model_training/validate_lora/FLUX.1-dev-Controlnet-Upscaler.py)| |[FLUX.1-dev-IP-Adapter](https://www.modelscope.cn/models/InstantX/FLUX.1-dev-IP-Adapter)|`ipadapter_images`, `ipadapter_scale`|[code](./model_inference/FLUX.1-dev-IP-Adapter.py)|[code](./model_inference_low_vram/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/full/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_full/FLUX.1-dev-IP-Adapter.py)|[code](./model_training/lora/FLUX.1-dev-IP-Adapter.sh)|[code](./model_training/validate_lora/FLUX.1-dev-IP-Adapter.py)| |[FLUX.1-dev-InfiniteYou](https://www.modelscope.cn/models/ByteDance/InfiniteYou)|`infinityou_id_image`, `infinityou_guidance`, `controlnet_inputs`|[code](./model_inference/FLUX.1-dev-InfiniteYou.py)|[code](./model_inference_low_vram/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/full/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_full/FLUX.1-dev-InfiniteYou.py)|[code](./model_training/lora/FLUX.1-dev-InfiniteYou.sh)|[code](./model_training/validate_lora/FLUX.1-dev-InfiniteYou.py)| -|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-||| +|[FLUX.1-dev-EliGen](https://www.modelscope.cn/models/DiffSynth-Studio/Eligen)|`eligen_entity_prompts`, `eligen_entity_masks`, `eligen_enable_on_negative`, `eligen_enable_inpaint`|[code](./model_inference/FLUX.1-dev-EliGen.py)|[code](./model_inference_low_vram/FLUX.1-dev-EliGen.py)|-|-|[code](./model_training/lora/FLUX.1-dev-EliGen.sh)|[code](./model_training/validate_lora/FLUX.1-dev-EliGen.py)| |[FLUX.1-dev-LoRA-Encoder](https://www.modelscope.cn/models/DiffSynth-Studio/LoRA-Encoder-FLUX.1-Dev)|`lora_encoder_inputs`, `lora_encoder_scale`|[code](./model_inference/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_inference_low_vram/FLUX.1-dev-LoRA-Encoder.py)|[code](./model_training/full/FLUX.1-dev-LoRA-Encoder.sh)|[code](./model_training/validate_full/FLUX.1-dev-LoRA-Encoder.py)|-|-| |[FLUX.1-dev-LoRA-Fusion-Preview](https://modelscope.cn/models/DiffSynth-Studio/LoRAFusion-preview-FLUX.1-dev)||[code](./model_inference/FLUX.1-dev-LoRA-Fusion.py)|-|-|-|-|-| |[Step1X-Edit](https://www.modelscope.cn/models/stepfun-ai/Step1X-Edit)|`step1x_reference_image`|[code](./model_inference/Step1X-Edit.py)|[code](./model_inference_low_vram/Step1X-Edit.py)|[code](./model_training/full/Step1X-Edit.sh)|[code](./model_training/validate_full/Step1X-Edit.py)|[code](./model_training/lora/Step1X-Edit.sh)|[code](./model_training/validate_lora/Step1X-Edit.py)| |[FLEX.2-preview](https://www.modelscope.cn/models/ostris/Flex.2-preview)|`flex_inpaint_image`, `flex_inpaint_mask`, `flex_control_image`, `flex_control_strength`, `flex_control_stop`|[code](./model_inference/FLEX.2-preview.py)|[code](./model_inference_low_vram/FLEX.2-preview.py)|[code](./model_training/full/FLEX.2-preview.sh)|[code](./model_training/validate_full/FLEX.2-preview.py)|[code](./model_training/lora/FLEX.2-preview.sh)|[code](./model_training/validate_lora/FLEX.2-preview.py)| +|[Nexus-Gen](https://www.modelscope.cn/models/DiffSynth-Studio/Nexus-GenV2)|`nexus_gen_reference_image`|[code](./model_inference/Nexus-Gen-Editing.py)|[code](./model_inference_low_vram/Nexus-Gen-Editing.py)|[code](./model_training/full/Nexus-Gen.sh)|[code](./model_training/validate_full/Nexus-Gen.py)|[code](./model_training/lora/Nexus-Gen.sh)|[code](./model_training/validate_lora/Nexus-Gen.py)| ## 模型推理 diff --git a/examples/flux/model_inference/Nexus-Gen-Editing.py b/examples/flux/model_inference/Nexus-Gen-Editing.py index f24f0c0..c9ab88c 100644 --- a/examples/flux/model_inference/Nexus-Gen-Editing.py +++ b/examples/flux/model_inference/Nexus-Gen-Editing.py @@ -2,7 +2,8 @@ import importlib import torch from PIL import Image from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig -from modelscope import snapshot_download, dataset_snapshot_download +from modelscope import dataset_snapshot_download + if importlib.util.find_spec("transformers") is None: raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") @@ -10,7 +11,7 @@ else: import transformers assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==4.49.0, please install it with `pip install transformers==4.49.0`." -snapshot_download("DiffSynth-Studio/Nexus-GenV2", local_dir="models/DiffSynth-Studio/Nexus-GenV2") + pipe = FluxImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", @@ -21,6 +22,7 @@ pipe = FluxImagePipeline.from_pretrained( ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), ], + nexus_gen_processor_config=ModelConfig("DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor"), ) dataset_snapshot_download(dataset_id="DiffSynth-Studio/examples_in_diffsynth", local_dir="./", allow_file_pattern=f"data/examples/nexusgen/cat.jpg") diff --git a/examples/flux/model_inference/Nexus-Gen-Generation.py b/examples/flux/model_inference/Nexus-Gen-Generation.py index 07ef1d2..dfe6880 100644 --- a/examples/flux/model_inference/Nexus-Gen-Generation.py +++ b/examples/flux/model_inference/Nexus-Gen-Generation.py @@ -1,7 +1,7 @@ import importlib import torch from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig -from modelscope import snapshot_download + if importlib.util.find_spec("transformers") is None: raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") @@ -9,7 +9,7 @@ else: import transformers assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==4.49.0, please install it with `pip install transformers==4.49.0`." -snapshot_download("DiffSynth-Studio/Nexus-GenV2", local_dir="models/DiffSynth-Studio/Nexus-GenV2") + pipe = FluxImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", @@ -20,6 +20,7 @@ pipe = FluxImagePipeline.from_pretrained( ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), ], + nexus_gen_processor_config=ModelConfig("DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor"), ) prompt = "一只可爱的猫咪" diff --git a/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py b/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py new file mode 100644 index 0000000..053b22b --- /dev/null +++ b/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py @@ -0,0 +1,32 @@ +import importlib +import torch +from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig +from modelscope import snapshot_download + +if importlib.util.find_spec("transformers") is None: + raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") +else: + import transformers + assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==4.49.0, please install it with `pip install transformers==4.49.0`." + +snapshot_download("DiffSynth-Studio/Nexus-GenV2", local_dir="models/DiffSynth-Studio/Nexus-GenV2") +pipe = FluxImagePipeline.from_pretrained( + torch_dtype=torch.bfloat16, + device="cuda", + model_configs=[ + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="generation_decoder.bin"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ], +) +pipe.enable_vram_management() + +prompt = "一只可爱的猫咪" +image = pipe( + prompt=prompt, negative_prompt="", + seed=0, cfg_scale=3, num_inference_steps=50, + height=1024, width=1024, +) +image.save("cat.jpg") diff --git a/examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh b/examples/flux/model_training/full/Nexus-Gen.sh similarity index 100% rename from examples/flux/model_training/full/FLUX.1-NexusGen-Edit.sh rename to examples/flux/model_training/full/Nexus-Gen.sh diff --git a/examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh b/examples/flux/model_training/lora/Nexus-Gen.sh similarity index 100% rename from examples/flux/model_training/lora/FLUX.1-NexusGen-Edit.sh rename to examples/flux/model_training/lora/Nexus-Gen.sh diff --git a/examples/flux/model_training/validate_full/Nexus-Gen-Editing.py b/examples/flux/model_training/validate_full/Nexus-Gen.py similarity index 100% rename from examples/flux/model_training/validate_full/Nexus-Gen-Editing.py rename to examples/flux/model_training/validate_full/Nexus-Gen.py diff --git a/examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py b/examples/flux/model_training/validate_lora/Nexus-Gen.py similarity index 100% rename from examples/flux/model_training/validate_lora/Nexus-Gen-Editing.py rename to examples/flux/model_training/validate_lora/Nexus-Gen.py From 03c8fd5e61dbeba7d1788f9388c4a90ae04e8278 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 29 Jul 2025 18:49:18 +0800 Subject: [PATCH 49/51] refine code --- .../Nexus-Gen-Editing.py | 16 +++++++++------- .../Nexus-Gen-Generation.py | 15 ++++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py b/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py index 70a543f..313ce3c 100644 --- a/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py +++ b/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py @@ -2,7 +2,8 @@ import importlib import torch from PIL import Image from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig -from modelscope import snapshot_download, dataset_snapshot_download +from modelscope import dataset_snapshot_download + if importlib.util.find_spec("transformers") is None: raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") @@ -10,17 +11,18 @@ else: import transformers assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==4.49.0, please install it with `pip install transformers==4.49.0`." -snapshot_download("DiffSynth-Studio/Nexus-GenV2", local_dir="models/DiffSynth-Studio/Nexus-GenV2") + pipe = FluxImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", model_configs=[ - ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors"), - ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="edit_decoder.bin"), - ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), - ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), - ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="edit_decoder.bin", offload_device="cpu"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu"), ], + nexus_gen_processor_config=ModelConfig("DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor"), ) pipe.enable_vram_management() diff --git a/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py b/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py index 053b22b..c865271 100644 --- a/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py +++ b/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py @@ -1,7 +1,7 @@ import importlib import torch from diffsynth.pipelines.flux_image_new import FluxImagePipeline, ModelConfig -from modelscope import snapshot_download + if importlib.util.find_spec("transformers") is None: raise ImportError("You are using Nexus-GenV2. It depends on transformers, which is not installed. Please install it with `pip install transformers==4.49.0`.") @@ -9,17 +9,18 @@ else: import transformers assert transformers.__version__ == "4.49.0", "Nexus-GenV2 requires transformers==4.49.0, please install it with `pip install transformers==4.49.0`." -snapshot_download("DiffSynth-Studio/Nexus-GenV2", local_dir="models/DiffSynth-Studio/Nexus-GenV2") + pipe = FluxImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", model_configs=[ - ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors"), - ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="generation_decoder.bin"), - ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors"), - ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), - ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="model*.safetensors", offload_device="cpu"), + ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="generation_decoder.bin", offload_device="cpu"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder/model.safetensors", offload_device="cpu"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu"), + ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu"), ], + nexus_gen_processor_config=ModelConfig("DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor"), ) pipe.enable_vram_management() From 87ab7d020b4f5e8acfe1a60dede2ec7bf4e9dba4 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 29 Jul 2025 20:02:34 +0800 Subject: [PATCH 50/51] refine code --- diffsynth/models/nexus_gen_projector.py | 61 ++++++++++++++++++- diffsynth/pipelines/flux_image_new.py | 2 +- .../flux/model_inference/Nexus-Gen-Editing.py | 2 +- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/diffsynth/models/nexus_gen_projector.py b/diffsynth/models/nexus_gen_projector.py index 0adbafb..d69b3e1 100644 --- a/diffsynth/models/nexus_gen_projector.py +++ b/diffsynth/models/nexus_gen_projector.py @@ -253,11 +253,66 @@ class Qwen2_5_VLDecoderLayer(nn.Module): class NexusGenImageEmbeddingMerger(nn.Module): - def __init__(self, model_path="models/DiffSynth-Studio/Nexus-GenV2", num_layers=1, out_channel=4096, expand_ratio=4, device='cpu'): + def __init__(self, num_layers=1, out_channel=4096, expand_ratio=4, device='cpu'): super().__init__() - from transformers import AutoConfig + from transformers import Qwen2_5_VLConfig from transformers.activations import ACT2FN - config = AutoConfig.from_pretrained(model_path) + config = Qwen2_5_VLConfig(**{ + "_name_or_path": "DiffSynth-Studio/Nexus-GenV2", + "architectures": [ + "Qwen2_5_VLForConditionalGeneration" + ], + "attention_dropout": 0.0, + "auto_map": { + "AutoConfig": "configuration_qwen2_5_vl.Qwen2_5_VLConfig", + "AutoModel": "modeling_qwen2_5_vl.Qwen2_5_VLModel", + "AutoModelForCausalLM": "modeling_qwen2_5_vl.Qwen2_5_VLForConditionalGeneration" + }, + "bos_token_id": 151643, + "eos_token_id": 151645, + "hidden_act": "silu", + "hidden_size": 3584, + "image_token_id": 151655, + "initializer_range": 0.02, + "intermediate_size": 18944, + "max_position_embeddings": 128000, + "max_window_layers": 28, + "model_type": "qwen2_5_vl", + "num_attention_heads": 28, + "num_hidden_layers": 28, + "num_key_value_heads": 4, + "pad_token_id": 151643, + "rms_norm_eps": 1e-06, + "rope_scaling": { + "mrope_section": [ + 16, + 24, + 24 + ], + "rope_type": "default", + "type": "default" + }, + "rope_theta": 1000000.0, + "sliding_window": 32768, + "tie_word_embeddings": False, + "torch_dtype": "bfloat16", + "transformers_version": "4.49.0", + "use_cache": False, + "use_sliding_window": False, + "video_token_id": 151656, + "vision_config": { + "hidden_size": 1280, + "in_chans": 3, + "model_type": "qwen2_5_vl", + "spatial_patch_size": 14, + "tokens_per_second": 2, + "torch_dtype": "bfloat16" + }, + "vision_end_token_id": 151653, + "vision_start_token_id": 151652, + "vision_token_id": 151654, + "vocab_size": 152064 + }) self.config = config self.num_layers = num_layers self.layers = nn.ModuleList([Qwen2_5_VLDecoderLayer(config, layer_idx) for layer_idx in range(num_layers)]) diff --git a/diffsynth/pipelines/flux_image_new.py b/diffsynth/pipelines/flux_image_new.py index b750509..9384624 100644 --- a/diffsynth/pipelines/flux_image_new.py +++ b/diffsynth/pipelines/flux_image_new.py @@ -375,7 +375,7 @@ class FluxImagePipeline(BasePipeline): torch_dtype: torch.dtype = torch.bfloat16, device: Union[str, torch.device] = "cuda", model_configs: list[ModelConfig] = [], - nexus_gen_processor_config: ModelConfig = None, + nexus_gen_processor_config: ModelConfig = ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor/"), ): # Download and load models model_manager = ModelManager() diff --git a/examples/flux/model_inference/Nexus-Gen-Editing.py b/examples/flux/model_inference/Nexus-Gen-Editing.py index c9ab88c..10351d5 100644 --- a/examples/flux/model_inference/Nexus-Gen-Editing.py +++ b/examples/flux/model_inference/Nexus-Gen-Editing.py @@ -22,7 +22,7 @@ pipe = FluxImagePipeline.from_pretrained( ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors"), ], - nexus_gen_processor_config=ModelConfig("DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor"), + nexus_gen_processor_config=ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor/"), ) dataset_snapshot_download(dataset_id="DiffSynth-Studio/examples_in_diffsynth", local_dir="./", allow_file_pattern=f"data/examples/nexusgen/cat.jpg") From 2ed3860085fdf629e8874444c812e18524b21e38 Mon Sep 17 00:00:00 2001 From: Artiprocher Date: Tue, 29 Jul 2025 20:10:08 +0800 Subject: [PATCH 51/51] refine code --- examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py | 2 +- examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py b/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py index 313ce3c..7dd3193 100644 --- a/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py +++ b/examples/flux/model_inference_low_vram/Nexus-Gen-Editing.py @@ -22,7 +22,7 @@ pipe = FluxImagePipeline.from_pretrained( ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu"), ], - nexus_gen_processor_config=ModelConfig("DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor"), + nexus_gen_processor_config=ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor/"), ) pipe.enable_vram_management() diff --git a/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py b/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py index c865271..25feb23 100644 --- a/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py +++ b/examples/flux/model_inference_low_vram/Nexus-Gen-Generation.py @@ -20,7 +20,7 @@ pipe = FluxImagePipeline.from_pretrained( ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="text_encoder_2/", offload_device="cpu"), ModelConfig(model_id="black-forest-labs/FLUX.1-dev", origin_file_pattern="ae.safetensors", offload_device="cpu"), ], - nexus_gen_processor_config=ModelConfig("DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor"), + nexus_gen_processor_config=ModelConfig(model_id="DiffSynth-Studio/Nexus-GenV2", origin_file_pattern="processor/"), ) pipe.enable_vram_management()