diff --git a/CHANGELOG.md b/CHANGELOG.md index 97e85a9c4..dd56f7235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Change Log for SD.Next -## Update for 2025-12-15 +## Update for 2025-12-16 +- **Models** + - [LongCat Image](https://github.com/meituan-longcat/LongCat-Image) in *Image* and *Image Edit* variants + LongCat is a new 8B diffusion base model using Qwen-2.5 as text encoder - **Features** - Google models support for both *Dev* and *Vertex* access methods see [docs](https://vladmandic.github.io/sdnext-docs/Google-GenAI/) for details diff --git a/html/reference.json b/html/reference.json index f9c08e89c..6d2405a09 100644 --- a/html/reference.json +++ b/html/reference.json @@ -314,6 +314,25 @@ "date": "2025 July" }, + "Meituan LongCat Image": { + "path": "meituan-longcat/LongCat-Image", + "preview": "meituan-longcat--LongCat-Image.jpg", + "desc": "Pioneering open-source and bilingual (Chinese-English) foundation model for image generation, designed to address core challenges in multilingual text rendering, photorealism, deployment efficiency, and developer accessibility prevalent in current leading models.", + "skip": true, + "extras": "", + "size": 27.30, + "date": "2025 December" + }, + "Meituan LongCat Image-Edit": { + "path": "meituan-longcat/LongCat-Image-Edit", + "preview": "meituan-longcat--LongCat-Image-Edit.jpg", + "desc": "Pioneering open-source and bilingual (Chinese-English) foundation model for image generation, designed to address core challenges in multilingual text rendering, photorealism, deployment efficiency, and developer accessibility prevalent in current leading models.", + "skip": true, + "extras": "", + "size": 27.30, + "date": "2025 December" + }, + "Ostris Flex.2 Preview": { "path": "ostris/Flex.2-preview", "preview": "ostris--Flex.2-preview.jpg", diff --git a/installer.py b/installer.py index 3503e14a2..dabb2a8af 100644 --- a/installer.py +++ b/installer.py @@ -633,7 +633,7 @@ def check_diffusers(): t_start = time.time() if args.skip_all: return - sha = '3d02cd543ef3101d821cb09c8fcab23c6e7ead33' # diffusers commit hash + sha = 'a748a839add5fe9f45a66e45dd93d8db0b45ce0f' # diffusers commit hash # if args.use_rocm or args.use_zluda or args.use_directml: # sha = '043ab2520f6a19fce78e6e060a68dbc947edb9f9' # lock diffusers versions for now pkg = pkg_resources.working_set.by_key.get('diffusers', None) diff --git a/modules/lora/lora_overrides.py b/modules/lora/lora_overrides.py index 155524ae9..640181977 100644 --- a/modules/lora/lora_overrides.py +++ b/modules/lora/lora_overrides.py @@ -43,6 +43,7 @@ force_models_diffusers = [ # forced always 'chrono', 'z_image', 'f2', + 'longcat', # video models 'hunyuanvideo', 'hunyuanvideo15' diff --git a/modules/modeldata.py b/modules/modeldata.py index 6a7f7451c..1c97530b4 100644 --- a/modules/modeldata.py +++ b/modules/modeldata.py @@ -76,6 +76,8 @@ def get_model_type(pipe): model_type = 'x-omni' elif 'Photoroom' in name: model_type = 'prx' + elif 'LongCat' in name: + model_type = 'longcat' # video models elif "CogVideo" in name: model_type = 'cogvideo' diff --git a/modules/sd_detect.py b/modules/sd_detect.py index f093e9585..5f54505f5 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -139,6 +139,8 @@ def guess_by_name(fn, current_guess): new_guess = 'NanoBanana' elif 'z-image' in fn.lower() or 'z_image' in fn.lower(): new_guess = 'Z-Image' + elif 'longcat-image' in fn.lower(): + new_guess = 'LongCat' if debug_load: shared.log.trace(f'Autodetect: method=name file="{fn}" previous="{current_guess}" current="{new_guess}"') return new_guess or current_guess diff --git a/modules/sd_models.py b/modules/sd_models.py index e63203ecb..93319b90d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -458,6 +458,10 @@ def load_diffuser_force(detected_model_type, checkpoint_info, diffusers_load_con from pipelines.model_z_image import load_z_image sd_model = load_z_image(checkpoint_info, diffusers_load_config) allow_post_quant = False + elif model_type in ['LongCat']: + from pipelines.model_longcat import load_longcat + sd_model = load_longcat(checkpoint_info, diffusers_load_config) + allow_post_quant = False except Exception as e: shared.log.error(f'Load {op}: path="{checkpoint_info.path}" {e}') if debug_load: diff --git a/modules/sd_offload.py b/modules/sd_offload.py index e521cf8c2..6c727fe04 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -14,7 +14,7 @@ from modules.timer import process as process_timer debug = os.environ.get('SD_MOVE_DEBUG', None) is not None verbose = os.environ.get('SD_MOVE_VERBOSE', None) is not None debug_move = log.trace if debug else lambda *args, **kwargs: None -offload_warn = ['sc', 'sd3', 'f1', 'f2', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'omnigen2', 'cogview4', 'cosmos', 'chroma', 'x-omni', 'hunyuanimage', 'hunyuanimage3'] +offload_warn = ['sc', 'sd3', 'f1', 'f2', 'h1', 'hunyuandit', 'auraflow', 'omnigen', 'omnigen2', 'cogview4', 'cosmos', 'chroma', 'x-omni', 'hunyuanimage', 'hunyuanimage3', 'longcat'] offload_post = ['h1'] offload_hook_instance = None balanced_offload_exclude = ['CogView4Pipeline', 'MeissonicPipeline'] diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index dfebbedc9..8089ad8e1 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -9,7 +9,7 @@ from modules import shared, devices, processing, images, sd_vae_approx, sd_vae_t SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options']) approximation_indexes = { "Simple": 0, "Approximate": 1, "TAESD": 2, "Full VAE": 3 } -flow_models = ['f1', 'f2', 'sd3', 'lumina', 'auraflow', 'sana', 'z_image', 'lumina2', 'cogview4', 'h1', 'cosmos', 'chroma', 'omnigen', 'omnigen2'] +flow_models = ['f1', 'f2', 'sd3', 'lumina', 'auraflow', 'sana', 'z_image', 'lumina2', 'cogview4', 'h1', 'cosmos', 'chroma', 'omnigen', 'omnigen2', 'longcat'] warned = False queue_lock = threading.Lock() diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index e599fe70e..1ec5b15cb 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -38,7 +38,7 @@ prev_cls = '' prev_type = '' prev_model = '' lock = threading.Lock() -supported = ['sd', 'sdxl', 'sd3', 'f1', 'h1', 'z_image', 'lumina2', 'hunyuanvideo', 'wanai', 'chrono', 'mochivideo', 'pixartsigma', 'pixartalpha', 'hunyuandit', 'omnigen', 'qwen'] +supported = ['sd', 'sdxl', 'sd3', 'f1', 'h1', 'z_image', 'lumina2', 'hunyuanvideo', 'wanai', 'chrono', 'mochivideo', 'pixartsigma', 'pixartalpha', 'hunyuandit', 'omnigen', 'qwen', 'longcat'] def warn_once(msg, variant=None): @@ -59,7 +59,7 @@ def get_model(model_type = 'decoder', variant = None): model_cls = 'sd' elif model_cls in {'pixartsigma', 'hunyuandit', 'omnigen', 'auraflow'}: model_cls = 'sdxl' - elif model_cls in {'h1', 'z_image', 'lumina2', 'chroma'}: + elif model_cls in {'h1', 'z_image', 'lumina2', 'chroma', 'longcat'}: model_cls = 'f1' elif model_cls in {'wanai', 'qwen', 'chrono'}: variant = variant or 'TAE WanVideo' diff --git a/modules/shared_items.py b/modules/shared_items.py index e78a0ba40..40e3b768f 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -48,6 +48,7 @@ pipelines = { 'Qwen': getattr(diffusers, 'QwenImagePipeline', None), 'HunyuanImage': getattr(diffusers, 'HunyuanImagePipeline', None), 'Z-Image': getattr(diffusers, 'ZImagePipeline', None), + 'LongCat': getattr(diffusers, 'LongCatImagePipeline', None), # dynamically imported and redefined later 'Meissonic': getattr(diffusers, 'DiffusionPipeline', None), 'Monetico': getattr(diffusers, 'DiffusionPipeline', None), diff --git a/pipelines/model_longcat.py b/pipelines/model_longcat.py new file mode 100644 index 000000000..de103c054 --- /dev/null +++ b/pipelines/model_longcat.py @@ -0,0 +1,40 @@ +import transformers +import diffusers +from modules import shared, devices, sd_models, model_quant, sd_hijack_te +from pipelines import generic + + +def load_longcat(checkpoint_info, diffusers_load_config=None): + if diffusers_load_config is None: + diffusers_load_config = {} + repo_id = sd_models.path_to_repo(checkpoint_info) + sd_models.hf_auth_check(checkpoint_info) + + load_args, _quant_args = model_quant.get_dit_args(diffusers_load_config, allow_quant=False) + shared.log.debug(f'Load model: type=LongCat repo="{repo_id}" config={diffusers_load_config} offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype} args={diffusers_load_config}') + + transformer = generic.load_transformer(repo_id, cls_name=diffusers.LongCatImageTransformer2DModel, load_config=diffusers_load_config) + text_encoder = generic.load_text_encoder(repo_id, cls_name=transformers.Qwen2_5_VLForConditionalGeneration, load_config=diffusers_load_config) + text_processor = transformers.Qwen2VLProcessor.from_pretrained(repo_id, subfolder='tokenizer', cache_dir=shared.opts.hfcache_dir) + + if 'edit' in repo_id.lower(): + cls = diffusers.LongCatImageEditPipeline + else: + cls = diffusers.LongCatImagePipeline + + pipe = cls.from_pretrained( + repo_id, + cache_dir=shared.opts.diffusers_dir, + transformer=transformer, + text_encoder=text_encoder, + text_processor=text_processor, + **load_args, + ) + + del transformer + del text_encoder + del text_processor + sd_hijack_te.init_hijack(pipe) + + devices.torch_gc(force=True, reason='load') + return pipe