ColabWan / models /flux /flux_main.py
1ripon1's picture
Upload folder using huggingface_hub
7344bef verified
Raw
History Blame Contribute Delete
28.2 kB
import os
import re
import time
from dataclasses import dataclass
from glob import iglob
from mmgp import offload as offload
import torch
from shared.utils.utils import calculate_new_dimensions
from .sampling import denoise, get_schedule, get_schedule_flux2, get_schedule_piflux2, prepare_kontext, prepare_prompt, prepare_multi_ip, unpack, resizeinput, patches_to_image, build_mask
from .modules.layers import get_linear_split_map
from transformers import SiglipVisionModel, SiglipImageProcessor
import torchvision.transforms.functional as TVF
import math
from shared.utils.utils import convert_image_to_tensor, convert_tensor_to_image
from shared.utils import files_locator as fl
from transformers import Qwen2_5_VLForConditionalGeneration, Qwen2VLProcessor
from .modules.autoencoder_flux2 import AutoencoderKLFlux2, AutoEncoderParamsFlux2
from shared.qtypes import nunchaku_int4 as _nunchaku_int4
from shared.utils.text_encoder_cache import TextEncoderCache
from .util import load_ae, load_clip, load_flow_model, load_t5, preprocess_flux_state_dict
from .flux2_adapter import (
scatter_ids ,
batched_prc_img,
batched_prc_txt,
encode_image_refs,
)
from .modules.autoencoder_flux2 import AutoencoderKLFlux2
from PIL import Image
def preprocess_ref(raw_image: Image.Image, long_size: int = 512):
# 获取原始图像的宽度和高度
image_w, image_h = raw_image.size
# 计算长边和短边
if image_w >= image_h:
new_w = long_size
new_h = int((long_size / image_w) * image_h)
else:
new_h = long_size
new_w = int((long_size / image_h) * image_w)
# 按新的宽高进行等比例缩放
raw_image = raw_image.resize((new_w, new_h), resample=Image.LANCZOS)
target_w = new_w // 16 * 16
target_h = new_h // 16 * 16
# 计算裁剪的起始坐标以实现中心裁剪
left = (new_w - target_w) // 2
top = (new_h - target_h) // 2
right = left + target_w
bottom = top + target_h
# 进行中心裁剪
raw_image = raw_image.crop((left, top, right, bottom))
# 转换为 RGB 模式
raw_image = raw_image.convert("RGB")
return raw_image
def stitch_images(img1, img2):
# Resize img2 to match img1's height
width1, height1 = img1.size
width2, height2 = img2.size
new_width2 = int(width2 * height1 / height2)
img2_resized = img2.resize((new_width2, height1), Image.Resampling.LANCZOS)
stitched = Image.new('RGB', (width1 + new_width2, height1))
stitched.paste(img1, (0, 0))
stitched.paste(img2_resized, (width1, 0))
return stitched
class model_factory:
def __init__(
self,
checkpoint_dir,
model_filename = None,
model_type = None,
model_def = None,
base_model_type = None,
text_encoder_filename = None,
quantizeTransformer = False,
save_quantized = False,
dtype = torch.bfloat16,
VAE_dtype = torch.float32,
mixed_precision_transformer = False
):
self.device = torch.device(f"cuda")
self._interrupt = False
self.VAE_dtype = VAE_dtype
self.dtype = dtype
torch_device = "cpu"
self.model_def = model_def
self.guidance_max_phases = model_def.get("guidance_max_phases", 0)
self.name = model_def.get("flux-model", "flux-dev")
self.is_piflux2 = self.name == "pi-flux2"
self.is_flux2 = self.name.startswith("flux2") or self.is_piflux2
self.text_encoder_cache = TextEncoderCache()
# model_filename = ["c:/temp/flux1-schnell.safetensors"]
source = model_def.get("source", None)
self.clip = self.t5 = self.vision_encoder = self.mistal = None
if self.is_flux2:
self.model = load_flow_model(
self.name,
model_filename if source is None else source,
torch_device,
preprocess_sd=preprocess_flux_state_dict,
)
text_encoder_type = model_def.get("text_encoder_type", "mistral3")
if text_encoder_type == "qwen3":
from .modules.text_encoder_qwen3 import Qwen3Embedder
text_encoder_folder = model_def.get("text_encoder_folder")
tokenizer_path = os.path.dirname(fl.locate_file(os.path.join(text_encoder_folder, "tokenizer_config.json")))
self.mistral = Qwen3Embedder(
model_spec=text_encoder_filename,
tokenizer_path=tokenizer_path,
)
else:
from .modules.text_encoder_mistral import Mistral3SmallEmbedder
self.mistral = Mistral3SmallEmbedder(model_spec=text_encoder_filename)
with torch.device("meta"):
self.vae = AutoencoderKLFlux2(AutoEncoderParamsFlux2())
offload.load_model_data(self.vae, fl.locate_file("flux2_vae.safetensors"), writable_tensors= False, )
self.vae_scale_factor = 8
else:
self.t5 = load_t5(torch_device, text_encoder_filename, max_length=512)
self.clip = load_clip(torch_device)
self.name = model_def.get("flux-model", "flux-dev")
# self.name= "flux-dev-kontext"
# self.name= "flux-dev"
# self.name= "flux-schnell"
source = model_def.get("source", None)
self.model = load_flow_model(
self.name,
model_filename[0] if source is None else source,
torch_device,
preprocess_sd=preprocess_flux_state_dict,
)
self.model_def = model_def
self.vae = None if getattr(self.model, "radiance", False) else load_ae(self.name, device=torch_device)
siglip_processor = siglip_model = feature_embedder = None
if self.name == 'flux-dev-uso':
siglip_path = fl.locate_folder("siglip-so400m-patch14-384")
siglip_processor = SiglipImageProcessor.from_pretrained(siglip_path)
siglip_model = offload.fast_load_transformers_model(
fl.locate_file(os.path.join("siglip-so400m-patch14-384", "model.safetensors")),
modelClass=SiglipVisionModel,
defaultConfigPath=fl.locate_file(os.path.join("siglip-so400m-patch14-384", "vision_config.json")),
writable_tensors=False,
)
siglip_model.eval().to("cpu")
if len(model_filename) > 1:
from .modules.layers import SigLIPMultiFeatProjModel
feature_embedder = SigLIPMultiFeatProjModel(
siglip_token_nums=729,
style_token_nums=64,
siglip_token_dims=1152,
hidden_size=3072, #self.hidden_size,
context_layer_norm=True,
)
offload.load_model_data(feature_embedder, model_filename[1], writable_tensors=False)
self.vision_encoder = siglip_model
self.vision_encoder_processor = siglip_processor
self.feature_embedder = feature_embedder
if self.name in ['flux-dev-kontext-dreamomni2']:
self.processor = Qwen2VLProcessor.from_pretrained(fl.locate_folder("Qwen2.5-VL-7B-DreamOmni2"))
self.vlm_model = offload.fast_load_transformers_model(fl.locate_file( os.path.join("Qwen2.5-VL-7B-DreamOmni2","Qwen2.5-VL-7B-DreamOmni2_quanto_bf16_int8.safetensors")), writable_tensors= True , modelClass=Qwen2_5_VLForConditionalGeneration, defaultConfigPath= fl.locate_file(os.path.join("Qwen2.5-VL-7B-DreamOmni2", "config.json")))
else:
self.processor = None
self.vlm_model = None
# offload.change_dtype(self.model, dtype, True)
# offload.save_model(self.model, "flux-dev.safetensors")
if not source is None:
from wgp import save_model
save_model(self.model, model_type, dtype, None)
if save_quantized:
from wgp import save_quantized_model
save_quantized_model(self.model, model_type, model_filename[0], dtype, None)
split_linear_modules_map = get_linear_split_map(
self.model.hidden_size,
getattr(self.model.params, "mlp_ratio", 4.0),
getattr(self.model.params, "single_linear1_mlp_ratio", None),
getattr(self.model.params, "double_linear1_mlp_ratio", None),
)
self.model.split_linear_modules_map = split_linear_modules_map
split_kwargs = None
for module in self.model.modules():
qtype = getattr(module, "weight_qtype", None)
if getattr(qtype, "name", None) == _nunchaku_int4._NUNCHAKU_INT4_QTYPE_NAME:
split_kwargs = _nunchaku_int4.get_nunchaku_split_kwargs()
break
if split_kwargs:
offload.split_linear_modules(
self.model,
split_linear_modules_map,
split_handlers=split_kwargs.get("split_handlers"),
share_fields=split_kwargs.get("share_fields"),
)
else:
offload.split_linear_modules(self.model, split_linear_modules_map)
def infer_vlm(self, input_img_path,input_instruction,prefix):
tp=[]
for path in input_img_path:
tp.append({"type": "image", "image": path})
tp.append({"type": "text", "text": input_instruction+prefix})
messages = [
{
"role": "user",
"content": tp,
}
]
# Preparation for inference
text = self.processor.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
# from .vprocess import process_vision_info
# image_inputs, video_inputs = process_vision_info(messages)
inputs = self.processor(
text=[text],
images=input_img_path,
# images=image_inputs,
# videos=video_inputs,
padding=True,
return_tensors="pt",
)
inputs = inputs.to("cpu")
# Inference
generated_ids = self.vlm_model.generate(**inputs, do_sample=False, max_new_tokens=4096)
generated_ids_trimmed = [
out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
output_text = self.processor.batch_decode(
generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)
return output_text[0]
def generate(
self,
seed: int | None = None,
input_prompt: str = "replace the logo with the text 'Black Forest Labs'",
n_prompt: str = None,
sampling_steps: int = 20,
input_ref_images = None,
input_frames= None,
input_masks= None,
width= 832,
height=480,
embedded_guidance_scale: float = 2.5,
guide_scale = 2.5,
fit_into_canvas = None,
callback = None,
loras_slists = None,
batch_size = 1,
video_prompt_type = "",
joint_pass = False,
image_refs_relative_size = 100,
denoising_strength = 1.,
masking_strength = 1.,
**bbargs
):
if self._interrupt:
return None
device="cuda"
flux2 = self.is_flux2
model_mode = bbargs.get("model_mode", None)
model_mode_int = None
if model_mode is not None:
try:
model_mode_int = int(model_mode)
except (TypeError, ValueError):
model_mode_int = None
lanpaint_enabled = model_mode_int in (2, 3, 4, 5)
if self.guidance_max_phases < 1: guide_scale = 1
if n_prompt is None or len(n_prompt) == 0: n_prompt = "low quality, ugly, unfinished, out of focus, deformed, disfigure, blurry, smudged, restricted palette, flat colors"
nag_scale = bbargs.get("NAG_scale", 1.0)
nag_tau = bbargs.get("NAG_tau", 3.5)
nag_alpha = bbargs.get("NAG_alpha", 0.5)
NAG = None
if nag_scale > 1 and guide_scale <= 1:
NAG = {"scale": nag_scale, "tau": nag_tau, "alpha": nag_alpha, "prefix_len": 0}
def _align_seq_len(tensor, target_len):
if tensor is None:
return tensor
seq_dim = 0 if tensor.dim() == 2 else 1
cur_len = tensor.shape[seq_dim]
if cur_len == target_len:
return tensor
if cur_len < target_len:
pad_len = target_len - cur_len
if seq_dim == 0:
pad = tensor[-1:].repeat(pad_len, 1)
return torch.cat([tensor, pad], dim=0)
pad = tensor[:, -1:, :].repeat(1, pad_len, 1)
return torch.cat([tensor, pad], dim=1)
return tensor.narrow(seq_dim, 0, target_len)
flux_dev_uso = self.name in ['flux-dev-uso']
flux_dev_umo = self.name in ['flux-dev-umo']
radiance = self.name in ['flux-chroma-radiance']
flux_kontext_dreamomni2 = self.name in ['flux-dev-kontext-dreamomni2']
if flux2:
if input_frames is not None:
input_ref_images = [convert_tensor_to_image(input_frames) ] + (input_ref_images or [])
shape = (batch_size, 128, height // 16, width // 16)
generator = torch.Generator(device="cuda").manual_seed(seed)
randn = torch.randn(shape, generator=generator, dtype=torch.bfloat16, device="cuda")
img, img_ids = batched_prc_img(randn)
encode_fn = lambda prompts: list(zip(*batched_prc_txt(self.mistral(prompts).to(torch.bfloat16))))
txt_embeds, txt_ids = self.text_encoder_cache.encode(encode_fn, [input_prompt], device=self.device)[0]
if NAG is not None:
neg_embeds, neg_ids = self.text_encoder_cache.encode(encode_fn, [n_prompt], device=self.device)[0]
if txt_embeds.dim() == 2:
txt_embeds = txt_embeds.unsqueeze(0)
txt_ids = txt_ids.unsqueeze(0)
if neg_embeds.dim() == 2:
neg_embeds = neg_embeds.unsqueeze(0)
neg_ids = neg_ids.unsqueeze(0)
pos_len = txt_embeds.shape[1]
neg_embeds = _align_seq_len(neg_embeds, pos_len)
neg_ids = _align_seq_len(neg_ids, pos_len)
txt_embeds = torch.cat([txt_embeds, neg_embeds], dim=1)
txt_ids = torch.cat([txt_ids, neg_ids], dim=1)
NAG["cap_embed_len"] = pos_len
if txt_embeds.dim() == 2:
txt_embeds = txt_embeds.unsqueeze(0)
txt_ids = txt_ids.unsqueeze(0)
txt_embeds, txt_ids = txt_embeds.expand(batch_size, -1, -1), txt_ids.expand(batch_size, -1, -1)
vec = torch.zeros(batch_size, 1, device=device, dtype=self.dtype)
inp = { "img": img, "img_ids": img_ids, "txt": txt_embeds.to(device), "txt_ids": txt_ids.to(device), "vec": vec }
if guide_scale != 1:
txt_embeds, txt_ids = self.text_encoder_cache.encode(encode_fn, [n_prompt], device=self.device)[0]
txt_embeds, txt_ids = txt_embeds.expand(batch_size, -1, -1), txt_ids.expand(batch_size, -1, -1)
inp.update({ "neg_txt": txt_embeds.to(device), "neg_txt_ids": txt_ids.to(device), "neg_vec": vec })
if input_masks is not None:
inp.update( build_mask(width, height, convert_tensor_to_image(input_masks, mask_levels= True), device))
inp["original_image_latents"], _ = encode_image_refs(self.vae, [input_ref_images[0].resize((width, height), resample=Image.Resampling.LANCZOS)])
if input_ref_images is not None and len(input_ref_images):
cond_latents, cond_ids = encode_image_refs(self.vae, input_ref_images)
cond_latents, cond_ids = cond_latents.expand(batch_size, -1, -1), cond_ids.expand(batch_size, -1, -1)
inp.update({"img_cond_seq": cond_latents, "img_cond_seq_ids": cond_ids})
noise_patch_size = 2
if self.is_piflux2:
timesteps = get_schedule_piflux2(sampling_steps, inp["img"].shape[1])
else:
timesteps = get_schedule_flux2(sampling_steps, inp["img"].shape[1])
unpack_latent = lambda x : self.vae.pre_decode(torch.cat(scatter_ids(x, inp["img_ids"])).squeeze(2))
ref_style_imgs = []
image_mask = None
else:
latent_stiching = flux_dev_uso or flux_dev_umo or flux_kontext_dreamomni2
lock_dimensions= False
input_ref_images = [] if input_ref_images is None else input_ref_images[:]
if flux_dev_umo:
ref_long_side = 512 if len(input_ref_images) <= 1 else 320
input_ref_images = [preprocess_ref(img, ref_long_side) for img in input_ref_images]
lock_dimensions = True
elif flux_kontext_dreamomni2:
for i, img in enumerate(input_ref_images):
input_ref_images[i] = resizeinput(img)
input_prompt= self.infer_vlm(input_ref_images,input_prompt, " It is editing task." if "K" in video_prompt_type else " It is generation task." )
input_prompt = input_prompt[6:-7]
print(input_prompt)
lock_dimensions = True
ref_style_imgs = []
if "I" in video_prompt_type and len(input_ref_images) > 0:
if flux_dev_uso :
if "J" in video_prompt_type:
ref_style_imgs = input_ref_images
input_ref_images = []
elif len(input_ref_images) > 1 :
ref_style_imgs = input_ref_images[-1:]
input_ref_images = input_ref_images[:-1]
if latent_stiching:
# latents stiching with resize
if not lock_dimensions :
for i in range(len(input_ref_images)):
w, h = input_ref_images[i].size
image_height, image_width = calculate_new_dimensions(int(height*image_refs_relative_size/100), int(width*image_refs_relative_size/100), h, w, 0)
input_ref_images[i] = input_ref_images[i].resize((image_width, image_height), resample=Image.Resampling.LANCZOS)
else:
# image stiching method
stiched = input_ref_images[0]
for new_img in input_ref_images[1:]:
stiched = stitch_images(stiched, new_img)
input_ref_images = [stiched]
elif input_frames is not None:
input_ref_images = [convert_tensor_to_image(input_frames) ]
else:
input_ref_images = None
image_mask = None if input_masks is None else convert_tensor_to_image(input_masks, mask_levels= True)
noise_patch_size = self.model.patch_size if radiance else 2
noise_channels = self.model.out_channels if radiance else 16
if latent_stiching :
inp, height, width = prepare_multi_ip(
ae=self.vae,
img_cond_list=input_ref_images,
target_width=width,
target_height=height,
bs=batch_size,
seed=seed,
device=device,
res_match_output= flux_dev_uso or flux_dev_umo,
pe = 'w' if flux_kontext_dreamomni2 else 'd',
set_cond_index = flux_kontext_dreamomni2,
conditions_zero_start= flux_kontext_dreamomni2
)
else:
inp, height, width = prepare_kontext(
ae=self.vae,
img_cond_list=input_ref_images,
target_width=width,
target_height=height,
bs=batch_size,
seed=seed,
device=device,
img_mask=image_mask,
patch_size=noise_patch_size,
noise_channels=noise_channels,
)
encode_fn = lambda prompts: [prepare_prompt(self.t5, self.clip, 1, prompt, device=device) for prompt in prompts]
prompt_list = [input_prompt] if isinstance(input_prompt, str) else input_prompt
prompt_bs = len(prompt_list) if batch_size == 1 and not isinstance(input_prompt, str) else batch_size
prompt_contexts = self.text_encoder_cache.encode(encode_fn, prompt_list, device=device)
txt = torch.cat([ctx["txt"] for ctx in prompt_contexts], dim=0)
vec = torch.cat([ctx["vec"] for ctx in prompt_contexts], dim=0)
if txt.shape[0] == 1 and prompt_bs > 1:
txt = txt.repeat(prompt_bs, 1, 1)
vec = vec.repeat(prompt_bs, 1)
if NAG is not None:
pos_len = txt.shape[1]
neg_list = [n_prompt] if isinstance(n_prompt, str) else n_prompt
neg_bs = len(neg_list) if batch_size == 1 and not isinstance(n_prompt, str) else batch_size
neg_contexts = self.text_encoder_cache.encode(encode_fn, neg_list, device=device)
neg_txt = torch.cat([ctx["txt"] for ctx in neg_contexts], dim=0)
if neg_txt.shape[0] == 1 and neg_bs > 1:
neg_txt = neg_txt.repeat(neg_bs, 1, 1)
neg_txt = _align_seq_len(neg_txt, pos_len)
if neg_txt.shape[0] == 1 and txt.shape[0] > 1:
neg_txt = neg_txt.repeat(txt.shape[0], 1, 1)
txt = torch.cat([txt, neg_txt], dim=1)
NAG["cap_embed_len"] = pos_len
txt_ids = torch.zeros(txt.shape[0], txt.shape[1], 3, device=device)
inp.update({"txt": txt.to(device), "txt_ids": txt_ids.to(device), "vec": vec.to(device)})
if guide_scale != 1:
neg_list = [n_prompt] if isinstance(n_prompt, str) else n_prompt
neg_bs = len(neg_list) if batch_size == 1 and not isinstance(n_prompt, str) else batch_size
neg_contexts = self.text_encoder_cache.encode(encode_fn, neg_list, device=device)
neg_txt = torch.cat([ctx["txt"] for ctx in neg_contexts], dim=0)
neg_vec = torch.cat([ctx["vec"] for ctx in neg_contexts], dim=0)
if neg_txt.shape[0] == 1 and neg_bs > 1:
neg_txt = neg_txt.repeat(neg_bs, 1, 1)
neg_vec = neg_vec.repeat(neg_bs, 1)
neg_txt_ids = torch.zeros(neg_bs, neg_txt.shape[1], 3, device=device)
inp.update({"neg_txt": neg_txt.to(device), "neg_txt_ids": neg_txt_ids.to(device), "neg_vec": neg_vec.to(device)})
timesteps = get_schedule(sampling_steps, inp["img"].shape[1], shift=(self.name != "flux-schnell"))
ref_style_imgs = [self.vision_encoder_processor(img, return_tensors="pt").to(self.device) for img in ref_style_imgs]
if self.feature_embedder is not None and ref_style_imgs is not None and len(ref_style_imgs) > 0 and self.vision_encoder is not None:
# processing style feat into textural hidden space
siglip_embedding = [self.vision_encoder(**emb, output_hidden_states=True) for emb in ref_style_imgs]
siglip_embedding = torch.cat([self.feature_embedder(emb) for emb in siglip_embedding], dim=1)
siglip_embedding_ids = torch.zeros( siglip_embedding.shape[0], siglip_embedding.shape[1], 3 ).to(device)
inp["siglip_embedding"] = siglip_embedding
inp["siglip_embedding_ids"] = siglip_embedding_ids
if NAG is not None:
NAG["prefix_len"] = siglip_embedding.shape[1]
if radiance:
def unpack_latent(x):
return patches_to_image(x.float(), height, width, noise_patch_size)
else:
def unpack_latent(x):
return unpack(x.float(), height, width)
# denoise initial noise
x = denoise(
self.model,
**inp,
timesteps=timesteps,
guidance=embedded_guidance_scale,
real_guidance_scale=guide_scale,
final_step_size_scale=0.5 if self.is_piflux2 else None,
callback=callback,
pipeline=self,
loras_slists=loras_slists,
unpack_latent=unpack_latent,
joint_pass=joint_pass,
denoising_strength=denoising_strength,
masking_strength=masking_strength,
model_mode=model_mode,
height=height,
width=width,
vae_scale_factor=8,
NAG=NAG,
)
if x==None: return None
# decode latents to pixel space
x = unpack_latent(x)
if self.vae is not None:
with torch.autocast(device_type=device, dtype=torch.bfloat16):
x = self.vae.decode(x)
img_msk_rebuilt = inp.get("img_msk_rebuilt") if isinstance(inp, dict) else None
if img_msk_rebuilt is not None and (lanpaint_enabled or (masking_strength == 1 and not flux2)):
img = None
if input_frames is not None:
img = input_frames.squeeze(1).unsqueeze(0)
elif input_ref_images is not None and len(input_ref_images) > 0:
img = convert_image_to_tensor(
input_ref_images[0].resize((width, height), resample=Image.Resampling.LANCZOS)
).unsqueeze(0)
if img is not None:
x = img * (1 - img_msk_rebuilt) + x.to(img) * img_msk_rebuilt
x = x.clamp_(-1, 1).add_(1).mul_(127.5).round_().clamp_(0, 255).to(torch.uint8)
x = x.transpose(0, 1)
return x
def get_loras_transformer(self, get_model_recursive_prop, model_type, model_mode, video_prompt_type, **kwargs):
def resolve_preload_lora(lora_ref: str) -> str:
resolved = fl.locate_file(lora_ref, error_if_none=False)
if resolved is None:
resolved = fl.locate_file(os.path.basename(lora_ref))
return os.path.abspath(resolved)
preloadURLs = get_model_recursive_prop(model_type, "preload_URLs")
if self.is_piflux2:
if len(preloadURLs) < 1:
return [], []
return [resolve_preload_lora(preloadURLs[0])], [1]
if model_type != "flux_dev_kontext_dreamomni2":
return [], []
if len(preloadURLs) < 2:
return [], []
edit = "K" in video_prompt_type
return [resolve_preload_lora(preloadURLs[0 if edit else 1])], [1]